Compare commits

...

554 Commits

Author SHA1 Message Date
Anthony
2d77a2377d add element id in item render_page items instead 2025-10-09 12:43:24 -04:00
Anthony
9e1f9c74c0 upper case all lsp references in titles and descriptions 2025-10-09 12:33:41 -04:00
Anthony
e0be758308 Fix some settings ui elements having duplicate ids 2025-10-09 12:29:21 -04:00
Anthony
bb31a97e22 Deduplicate terminal settings and fix dropdown toggle bug 2025-10-09 12:02:56 -04:00
Anthony
08b73d0943 Merge remote-tracking branch 'origin/main' into settings-ui-elements 2025-10-09 11:16:43 -04:00
Anthony
f2b1dd262c Merge conflicts 2025-10-09 11:12:09 -04:00
Ben Kunkle
c543709d5f settings_ui: Add terminal settings (#39874)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-09 10:59:08 -04:00
Dino
c58931ac04 git_ui: Fix open diff for untracked files when sorting by path enabled (#39862)
Fixes the `Open Diff` action for untracked files when the `sort_by_path`
setting is enabled. The `ProjectDiff` wasn't correctly moving the
multibuffer's cursor to the untracked file because, when that setting is
enabled, it's sort prefix is changed to the tracked files sort prefix, and that
wasn't accounted for in `move_to_entry`.

Before these changes, the `sort_prefix` field for `PathKey` was called `namespace`, it was renamed to be clearer what its purpose is.

Closes #39529 

Release Notes:

- Fixed 'Open Diff' action for untracked files when `sort_by_path` is
enabled

---------

Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-10-09 14:34:52 +00:00
Ben Brandt
dd5da592f0 Provide codex as an option on remote sessions (#39774)
Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-10-09 16:10:56 +02:00
Ben Brandt
f1d17fcfbe acp: Simplify auth check and allow for custom /logout commands (#39867)
- Prefer agent-specific logout handling to allow state reset 
- Treat any auth method as supported; remove provider-specific filter 
- Avoid prompting auth when issuing /logout and agent supports it

Release Notes:

- N/A
2025-10-09 12:58:59 +00:00
Sunli
ccfc1ce387 gpui: Fix drawing rotated SVGs (#33288)
Fixes: https://github.com/longbridge/gpui-component/issues/994

1. When SVG is rotated, incorrect graphics are drawn.

For example: the original aspect ratio of the SVG is 1:1, if the bounds
used to render the SVG are 400x200 (aspect ratio 2:1),
[here](21f985a018/crates/gpui/src/svg_renderer.rs (L91))
the width is used as the scaling factor, causing the rendered SVG to
only have half the height. This PR ensures the complete SVG image is
always rendered.

2. The clipping region has no transformation applied, I added a function
called `distance_from_clip_rect_transformed` in the shader.

3. Fixed `monochrome_sprite_fragment` in `shader.metal` not applying
clipping region.

### Before:


https://github.com/user-attachments/assets/8f93ac36-281e-4837-96cd-c308bfbf92d1

### After:


https://github.com/user-attachments/assets/f52b67a6-4cb9-4d6c-b759-bbb91b59c1cf

Release Notes:

- N/A

---------

Co-authored-by: Jason Lee <huacnlee@gmail.com>
2025-10-09 14:53:36 +02:00
Dino
3d4f488d46 vim: Update change surrounds to match vim's behavior (#38721)
These changes refactor the whitespace handling logic for Vim's change
surrounds command (`cs`), making its behavior closely match
[tpope/vim-surround](https://github.com/tpope/vim-surround), following
[this
discussion](https://github.com/zed-industries/zed/issues/38169#issuecomment-3304129461).

Zed's current implementation has two main differences when compared to
[tpope/vim-surround](https://github.com/tpope/vim-surround):

- It only considers whether a single space should be added or removed,
instead of all the space that is between the surrounding character and
the content
- It only takes into consideration the new surrounding characters in
order to determine whether to add or remove that space

A review of
[tpope/vim-surround](https://github.com/tpope/vim-surround)'s behavior
reveals these rules for whitespace:

* Quote to Quote
    * Whitespace is never changed
* Quote to Bracket
    * If opening bracket, add one space
    * If closing bracket, do not add space
* Bracket to Bracket
    * If opening to opening, keep only one space
    * If opening to closing, remove all space
    * If closing to opening, add one space
    * If closing to closing, do not change space
* Bracket to Quote
    * If opening, remove all space
    * If closing, preserve all space

Below is a table with examples for each scenario. A new test has also
been added to specifically check the scenarios outlined above,
`vim::surrounds::test::test_change_surrounds_vim`.

| Type              | Before      | Command | After         |
|-------------------|-------------|---------|---------------|
| Quote → Quote     | `'   a   '` | `cs'"`  | `"   a   "`   |
| Quote → Quote     | `"   a   "` | `cs"'`  | `'   a   '`   |
| Quote → Bracket   | `'   a   '` | `cs'{`  | `{    a    }` |
| Quote → Bracket   | `'   a   '` | `cs'}`  | `{   a   }`   |
| Bracket → Bracket | `[   a   ]` | `cs[{`  | `{ a }`       |
| Bracket → Bracket | `[   a   ]` | `cs[}`  | `{a}`         |
| Bracket → Bracket | `[   a   ]` | `cs]{`  | `{    a    }` |
| Bracket → Bracket | `[   a   ]` | `cs]}`  | `{   a   }`   |
| Bracket → Quote   | `[   a   ]` | `cs['`  | `'a'`         |
| Bracket → Quote   | `[   a   ]` | `cs]'`  | `'   a   '`   |

These changes diverge from
[tpope/vim-surround](https://github.com/tpope/vim-surround) when
handling newlines. For example, with the following snippet:

```rust
fn test_surround() {
    if 2 > 1 {
        println!("place cursor here");
    }
};
```

Placing the cursor inside the string and running any combination of
‎`cs{[`, ‎`cs{]`, ‎`cs}[`, or ‎`cs}]` would previously remove newline
characters. With these changes, using commands like ‎`cs}]` will now
preserve newlines.

Related to #38169
Closes #39334

Release Notes:

- Improved Vim’s change surround command to closely match
[tpope/vim-surround](https://github.com/tpope/vim-surround) behavior.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-10-09 12:18:48 +01:00
Piotr Osiewicz
ba2337ffb9 project search: Reduce hangs on main thread (#39857)
This takes the idea that @RemcoSmitsDev started on in
https://github.com/zed-industries/zed/pull/39354. We did away with
grabbing a snapshot of the display map when buffer coordinates were
sufficient.
Closes #37267

Release Notes:

- Reduced micro-stutters in project search with large multi-buffer
contents.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-10-09 13:11:11 +02:00
Merlin04
37d676e2c6 Add support for xonsh shell (#39834)
Closes #39506

Release Notes:

- Fixed environment variable capture when login shell is
[xonsh](https://xon.sh/)

---------

Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
2025-10-09 12:00:22 +02:00
Mikayla Maki
1bb6752e3e gpui: Fix typo in publish script (#39836)
Release Notes:

- N/A
2025-10-09 05:11:11 +00:00
Mikayla Maki
8c9b42dda8 gpui 0.2.0 (#39835)
Release Notes:

- N/A
2025-10-09 04:58:59 +00:00
Mikayla Maki
15c4aadb57 Add bump gpui script (#39833)
Release Notes:

- N/A
2025-10-09 04:15:37 +00:00
Ben Kunkle
3d200a5466 settings_ui: Improve keyboard nav (#39819)
Closes #ISSUE

From notes:

```markdown
  - [x] Clicking on the disclsoure icon button in the root-level tree view item should steal focus and move it to the root item (not the icon button)
  - [x] [@ben] Allow left/right arrow keys to expand/collapse root tree view items in the nav
    - [x] With this, make enter/space work the same as clicking (activate page, don't expand root items, focus moves to the content and leaves nav — becomes consistent with mouse interaction)
  - [x] Smart cmd-shift-e: toggling focus should take you to the selected item
  - [x] [@ben] pageup + pagedown in nav -> jump between root items
  - [x] [@ben] home + end buttons should work
    - in nav:
      - home always goes to first section header
      - end always goes to last _visible_ item (does not expand)
```

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-08 23:22:02 -04:00
Matthijs Kok
e077b63915 settings_ui: Correct "File Icons" description (#39805)
Align with
cd656485c8/crates/settings/src/settings_content/workspace.rs (L490)

By the way, LOVE the settings UI! <3 Great job so far :)


Release Notes:

- N/A
2025-10-08 20:47:55 -03:00
John Tur
ef839cc207 Improve importing font-family settings from VS Code (#39736)
Closes https://github.com/zed-industries/zed/issues/39259

- Fixes import of `editor.fontFamily` (we were looking for the wrong
key)
- Adds basic support for the CSS font-family syntax used by VS Code,
including font fallback

Release Notes:

- N/A
2025-10-08 19:19:48 -04:00
Michael Sloan
3d0312f4c7 zeta2 inspector: Sort by scores and add score components tooltip (#39821)
Release Notes:

- N/A

Co-authored-by: Agus <agus@zed.dev>
2025-10-08 23:14:40 +00:00
Tom Planche
c1e3958c26 editor: Fix duplicate and copy line newlines (#39610)
Closes #34797 and its child #39508.


![zed-#34797-#39508](https://github.com/user-attachments/assets/48a0fe28-8b8a-480d-bffc-6abc7ff310ff)

Release Notes:

- Fixed `editor::DuplicateLineUp` duplicating the last line onto itself
when the line doesn't end with a newline (#39508)
- Fixed line copy not including a newline at end of buffer, causing
paste to occur on the same line (#34797)
2025-10-08 22:56:25 +00:00
Andrew Farkas
ba937d16e7 Onboarding refactor (#39724)
<img width="1648" height="976" alt="Screenshot 2025-10-07 at 6 57 20 PM"
src="https://github.com/user-attachments/assets/ae7289c0-8820-4fdf-ae28-84fb6bd64942"
/>

Fixes #39347

Release Notes:

- Improved onboarding UI by collapsing it to a single page

---------

Co-authored-by: dino <dinojoaocosta@gmail.com>
Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2025-10-08 22:47:25 +00:00
Kirill Bulatov
4dbd186485 Do not deselect in terminal on copy by default (#39814)
Release Notes:

- Flips `terminal.keep_selection_on_copy` default to `true`
2025-10-08 18:01:44 -04:00
Cole Miller
88887fd292 debugger: Add support for remote browser debugging (#39248)
This PR adds support for browser debugging in SSH and WSL projects. We
use the vscode-js-debug-companion extension, repackaged as a standalone
CLI (https://github.com/zed-industries/js-debug-companion-cli).

Closes #38878

Release Notes:

- debugger: Browser debugging is now supported in SSH and WSL projects.

---------

Co-authored-by: Nia <nia@zed.dev>
2025-10-08 21:57:57 +00:00
ozer
31e75b2235 git_ui: Add repository search and alphabetical sorting (#39351)
Closes #38778

Release Notes:

- Added: Search functionality to repository selector
- Improved: Repositories now display in alphabetical order
2025-10-08 17:51:20 -04:00
robert7k
681c19899f Allow adding files to .gitignore (#38089)
This feature allows users to add a new, untracked file to `.gitignore`
by using the context menu in the git panel.

<img width="300" alt="Demo screen shot"
src="https://github.com/user-attachments/assets/3f2402fb-9337-42f8-939f-dac12ca09518"
/>

Release Notes:

- Added feature to add a new file to `.gitignore`
2025-10-08 17:49:06 -04:00
Jakub Konka
439add3d23 terminal: Clear shell after activating (#39798)
Two tweaks were required to ensure we correctly clear the shell after
running an activate script(s):
1. PowerShell upon receiving `\r\n` input, will enter the continuation
mode (>>). To avoid this, we send an "enter" key press instead `\x0d`.
2. In order to clear the terminal _after_ issuing all activation
commands, we need to take into account the asynchronous nature of the
activation process:
   - We write the command to run the script to PTY
- We send "enter" (It is now being processed by the shell) At this point
we need to wait for the shell to finish executing before we clear the
terminal. Otherwise we will create a race where we might clear the
terminal _before_ the shell finished executing the activation script(s).
   - Write `clear`/`cls` command to PTY
- Send "enter" This way we guarantee that we clear the terminal _after_
all scripts were executed.

Closes #38474 

Release Notes:

- N/A
2025-10-08 23:28:11 +02:00
Lev Zakharov
81b98cdd4d go: Add ability to run testable examples (#39390)
See related discussion #39381.

<img width="724" height="488"
src="https://github.com/user-attachments/assets/4a69e13e-783f-45d7-99f4-e23c0415a781"
/>

Release Notes:

- Added ability to run Go Testable Examples
2025-10-08 22:55:26 +02:00
Agus Zubiaga
ca89a40df2 zeta2 inspector: Plan prompt locally (#39811)
Plans and displays the prompt locally before the response arrives.
Helpful while debugging prompt planning.

Release Notes:

- N/A

---------

Co-authored-by: Michael Sloan <mgsloan@gmail.com>
2025-10-08 20:47:35 +00:00
Maksim Bondarenkov
f5884e99d0 audio: Move log::info into a global import (#39810)
I didn't find a commit, but it's now required for all platforms, I got
this compile error with 0.207.3 tag

``` 
  error: cannot find macro `info` in this scope
     --> crates\audio\src\audio.rs:121:13
      |
  121 |             info!("Output stream: {:?}", output_handle);
      |             ^^^^
      |
  help: consider importing this macro
      |
    1 + use log::info;
      |
  
  error: could not compile `audio` (lib) due to 1 previous error
```

Closes #ISSUE

Release Notes:

- N/A
2025-10-08 20:30:27 +00:00
Agus Zubiaga
fce931144e zeta2 inspector: Display prediction request immediately (#39809)
Release Notes:

- N/A

Co-authored-by: Michael Sloan <mgsloan@gmail.com>
2025-10-08 20:23:48 +00:00
Piotr Osiewicz
ef423148fc lsp: Serialize LSP notifications on background threads (#39403)
This should reduce hiccups when opening large files.

Release Notes:

- N/A
2025-10-08 19:48:40 +00:00
Danilo Leal
cd656485c8 settings ui: Fix some layout regressions (#39804)
Release Notes:

- N/A
2025-10-08 15:56:22 -03:00
Alvaro Parker
1e149b755f gpui: Add support for floating windows (#39702)
Closes #ISSUE

This allows new windows like the Rules library or the Settings UI window
to appear floating on window managers like hyprland:


https://github.com/user-attachments/assets/628db7f9-4459-4601-85f1-789923831182

Left is with `WindowKind::Floating` and right is with
`WindowKind::Normal`

Release Notes:

- Added support for floating windows on x11 and wayland
2025-10-08 20:48:17 +02:00
Bartosz Kaszubowski
e0eeda11ed inspector_ui: Align with title bar, other visual tweaks (#39697)
# How

Few tweaks for the GPUI Inspector panel, including toolbar align with
title bar, buffer font for source link, few other layout, spacing and
wording tweaks.

Release Notes:

- N/A

# Preview

### Before

<img width="1286" height="602" alt="Screenshot 2025-10-07 at 19 33 20"
src="https://github.com/user-attachments/assets/515ddcdf-a2c8-4f5f-b37e-b1668df2147f"
/>

### After

<img width="1286" height="542" alt="Screenshot 2025-10-07 at 19 09 24"
src="https://github.com/user-attachments/assets/3a777974-3427-4545-afda-37fabcb012ba"
/>
2025-10-08 12:10:53 -06:00
Michael Sloan
bcef3b5010 zeta2: Parse imports via Tree-sitter queries + improve zeta retrieval-stats (#39735)
Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
Co-authored-by: Agus <agus@zed.dev>
Co-authored-by: Oleksiy <oleksiy@zed.dev>
2025-10-08 12:04:06 -06:00
David
5fd187769d Add Codestral edit predictions provider (#34371)
Release Notes:

- Added Codestral edit predictions provider which can be enabled by adding an API key in the Mistral section of agent settings.

![2025-07-13 11 35
33](https://github.com/user-attachments/assets/8bf599d7-33c7-4556-b878-6c645d69661f)


## Config

Get API key from https://console.mistral.ai/codestral and add it in the Mistral section of the agent settings. 

```
  "features": {
    "edit_prediction_provider": "codestral"
  },
  "edit_predictions": {
    "codestral": {
      "model": "codestral-latest",
      "max_tokens": 150
    }
  },
```

---------

Co-authored-by: Michael Sloan <michael@zed.dev>
2025-10-08 12:02:21 -06:00
Munish Mummadi
096930817b Make FoldAtLevel commands discoverable in command palette (#39422)
## Description
Fixes #39376

Add individual FoldAtLevel1-9 actions so users can find fold commands in
the command palette while keeping existing keybindings.

Migrating user keymaps is necessary to have the keybinds show in the command palette.

Closes #39376 

### Changes
- `crates/editor/src/actions.rs` - Added FoldAtLevel1-9 action structs
- `crates/editor/src/editor.rs` - Implemented fold_at_level_1-9 handler
methods
- `crates/editor/src/element.rs` - Registered new actions
- `assets/keymaps/*.json` - Updated keybindings to use new individual
actions

### Other Approaches considered
- Adding #[serde(default)] to existing FoldAtLevel(u32) - wouldn't make
it discoverable
- Creating a single action with enumerated variants - idk about this
that well.

### Release Notes
Release Notes:
- Added Fold At Level 1-9 actions to the command palette

---------

Co-authored-by: HactarCE <6060305+HactarCE@users.noreply.github.com>
2025-10-08 17:28:46 +00:00
Xiaobo Liu
c7d5afedc5 docs: Add missing docs for CommandInterceptResult fields (#39676)
Document the `string` and `positions` fields to resolve TODO comments.

Release Notes:

- N/A

Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>
2025-10-08 20:20:50 +03:00
Bartosz Kaszubowski
d6b1801fb3 inspector_ui: Split out size from bounds string (#39703)
# How

Tweak the way in which inspected element bounds and size are printed to
improved readability of GPUI Inspector data.

> [!note]
> It looks like the only place in the workspace where bounds are used
within formatted print is GPUI Inspector panel, but I decided to do not
alter [GPUI `geometry.rs` default
format](a7e7f46020/crates/gpui/src/geometry.rs (L1579-L1587)),
since adding multiline output and additional labels in there does not
feel like the beast approach, but maybe I'm wrong?

Release Notes:

- N/A

# Preview

<img width="1168" height="224" alt="Screenshot 2025-10-07 at 20 08 35"
src="https://github.com/user-attachments/assets/97753fc1-68d7-4cf8-ad92-afe85319f3d8"
/>

<img width="1168" height="228" alt="Screenshot 2025-10-07 at 20 09 24"
src="https://github.com/user-attachments/assets/beed2a92-0817-4ed2-bb62-4d7b931e8709"
/>
2025-10-08 11:17:11 -06:00
Conrad Irwin
7c55f7181d Fix configuring shell in project settings (#39795)
I mistakenly broke this when refactoring settings

Closes #39479

Release Notes:

- Fixed a bug where you could no longer configure `terminal.shell` in
project settings
2025-10-08 16:49:44 +00:00
Jakub Konka
4684d6b50e terminal: Fix escaping arguments when using CMD as the shell (#39701)
A couple of caveats:
- We should not auto-escape arguments with Alacritty's `escape_args`
option if using CMD otherwise, the generated command will have way too
many escaped characters for CMD to parse correctly.
- When composing a full command for CMD, we need to put it in double
quotes manually: `cmd /C "activate.bat& pwsh.exe -C do_something"` so
that CMD executes the entire string as a sequence of commands.
- CMD requires `&` as a chaining operator for commands (`;` for other
shells).

Release Notes:

- N/A
2025-10-08 16:44:04 +00:00
Ben Kunkle
578e7e4cbd settings_ui: Focus content controls when opened from nav bar (#39792)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-08 12:42:52 -04:00
Danilo Leal
a960db6a43 keymap editor: Adjust the "edit in keymap.json" button (#39789)
Making its visuals and positioning more consistent with the same button
in the settings UI.

Release Notes:

- N/A
2025-10-08 13:03:15 -03:00
Marshall Bowers
5a0f796a44 agent2: Expand auto-retries for completion errors (#39787)
This PR expands our automatic retry behavior for certain classes of
completion errors (e.g., rate limit errors).

Previously this was only available when using burn mode.

We now auto-retry when:

- Using the Zed provider while on a token-based plan
- Using the Zed provider while on a legacy plan with burn mode enabled
- Using a non-Zed provider

Release Notes:

- Expanded automatic retry behavior for errors in the Agent. Errors
classified as "retryable" (such as rate limit errors) will now
automatically be retried when:
  - Using the Zed provider while on a token-based plan
  - Using the Zed provider while on a legacy plan with burn mode enabled
  - Using a non-Zed provider

---------

Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-10-08 15:52:06 +00:00
Dino
604d56659d file_finder: Fix path matching on starting slash (#39480)
These changes update the way the file finder decides wether to only look
for an absolute path or for a relative path too.

When the provided query started with a slash (`/`) the file finder would
assume this to be an absolute path so would always try to find an
absolute path and return no matches if none was found. This is meant to
support situtations where, for example, a CLI tool might output the
absolute path of a file and the user can copy and paste that in the file
finder.

However, it's should be possible to use slash (`/`) at the start of the
query to specify that only relative files inside a folder should be
matched, which would not work in this scenario.

With these changes, the file finder will first check if the path is
absolute and, if it is and no absolute matches were found, it'll still
try to find relative matches, otherwise it'll simply look for relative
matches.

Closes #39350

Release Notes:

- Fixed project files matches when using slash (`/`) at the start in
order to consider relative paths

---------

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-10-08 16:42:39 +01:00
Conrad Irwin
1d1c799b4b Reland "Remove cx from ThemeSettings" (#39720)
- **Reapply "Remove cx from ThemeSettings (#38836)" (#39691)**
- **Fix theme loading races**

Closes #ISSUE

Release Notes:

- N/A
2025-10-08 17:36:52 +02:00
Danilo Leal
70af11ef2a settings ui: Add a handful of design tweaks (#39784)
Release Notes:

- N/A
2025-10-08 12:27:22 -03:00
Piotr Osiewicz
5fa4b3bfe8 windows: Do not exit from app in dev builds when cli is not found (#39768)
Release Notes:

- N/A
2025-10-08 17:14:58 +02:00
Joseph T. Lyons
93a5dffea1 Bump Zed to v0.209 (#39781)
Release Notes:

- N/A
2025-10-08 15:14:54 +00:00
Finn Evers
9ac010043c settings_ui: Add fallback for agent_ui_font_size (#39782)
Closes https://github.com/zed-industries/zed/issues/39775

Release Notes:

- N/A
2025-10-08 15:08:39 +00:00
Ben Brandt
dd3b65f707 acp: Don't display failed terminal call on display only terminals (#39780)
We don't get an ExitStatus from a remote terminal, so this check was
failing.

Ideally we move all of this to just needing an exit code, but we will
have to revisit that later.

Release Notes:

- N/A
2025-10-08 14:17:37 +00:00
Dino
057b7b1543 vim: Fix % motion edge case (#39620)
Update Vim's `%` motion to first attempt finding the exact matching
bracket/tag under the cursor, then fall back to the previous
nearest-enclosing logic if none is found. This prevents accidentally
jumping to nested pairs in languages like TSX and Svelte where `<>`,
`</>`, and `/>` are also treated as brackets.

Closes #39368 

Release Notes:

- Fixed an edge case with the `%` motion in vim, where the cursor could
end up in a closing HTML tag instead of the matching bracket
2025-10-08 13:49:55 +01:00
Dino
a9455eb947 migrator: Avoid attempting to migrate empty content (#39771)
This commit fixes an issue where opening zed using `--user-data-dir`
with an empty directory would cause the first run to display a "Failed
to migrate settings" error.

This was caused by the migrator attempting to migrate an empty string,
so if that's the case, we'll simply return `Ok(None)` and avoid
attempting to migrate anything at all.

Relates to #39400

Release Notes:

- N/A

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-10-08 13:38:26 +01:00
Finn Evers
db3c186af0 language_model: Add image decoding support for BMP and TIFF image formats (#39767)
Related: #39745

Release Notes:

- Added support for pasting TIFF and BMP images in the agent panel.
2025-10-08 11:53:32 +00:00
Xiaobo Liu
71856706c7 agent2: Fix test_save_load_thread for Windows paths (#39753)
Use path! macro for platform-specific path formatting in test
assertions, fixing hardcoded Unix-style paths that failed on Windows.

Release Notes:

- N/A

Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>
2025-10-08 12:47:43 +02:00
Mikayla Maki
4ec24ebe01 Fix more settings UX problems (#39760)
And remove the feature flag for now.

Release Notes:

- N/A
2025-10-08 10:34:06 +00:00
Remco Smits
4152942a8e markdown: Add support for HTML block quotes (#39755)
This PR adds support for HTML block quotes, that also allows you to have
nested variant of it.

<img width="1441" height="804" alt="Screenshot 2025-10-08 at 10 25 57"
src="https://github.com/user-attachments/assets/4e1da766-fb54-4e87-8654-1ea14330bc97"
/>

Code example used in screenshot:

```html
<blockquote>
    <p>
        Words can be like X-rays, if you use them properly—they’ll go through
        anything. You read and you’re pierced.
    </p>
    <blockquote>
        <p>
            lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor.
        </p>
    </blockquote>
</blockquote>
```

Release Notes:

- Markdown: Added support for `HTML` block quotes
2025-10-08 11:33:42 +02:00
Mikayla Maki
bbf4bfad6f Implement the unimplemented setting (#39747)
Release Notes:

- N/A
2025-10-08 07:15:40 +00:00
Mikayla Maki
989d172cfc Add edit JSON button (#39732)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-08 06:23:43 +00:00
Danilo Leal
1265b229a9 Update doc comments for agent_buffer_font_size (#39743)
Follow up to https://github.com/zed-industries/zed/pull/39468.

Unlike `agent_ui_font_size`, the `agent_buffer_font_size` setting does
have a default value, which means it does not fall back to the regular
UI font size, but rather to its default value.

Release Notes:

- N/A
2025-10-08 06:14:18 +00:00
Danilo Leal
294ca25f44 settings ui: Add another batch of UX fixes and improvements (#39742)
Release Notes:

- N/A
2025-10-08 06:11:34 +00:00
Ben Kunkle
5c7907ad2f settings_ui: Pre preview launch cleanup (#39733)
Closes #ISSUE

Release Notes:

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

---------

Co-authored-by: Anthony <anthony@zed.dev>
2025-10-07 22:41:48 -04:00
Anthony
898af2e75d Merge remote-tracking branch 'origin/main' into settings-ui-elements 2025-10-07 22:08:14 -04:00
Anthony
e34526534f Add Cursor Shape 2025-10-07 22:07:53 -04:00
Ben Kunkle
f652c3a14d settings_ui: Filter to get project settings (#39730)
Closes #ISSUE

Release Notes:

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

---------

Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2025-10-07 21:36:40 -04:00
Mikayla Maki
69ac003bc9 Add escape to settings window (#39699)
Release Notes:

- N/A
2025-10-08 00:36:33 +00:00
Danilo Leal
d615525771 ui: Rename and simplify NumberField component (#39731) 2025-10-07 21:35:51 -03:00
Danilo Leal
8bf37dd130 settings ui: Add more UX improvements (#39700)
Release Notes:

- N/A
2025-10-07 20:01:52 -03:00
Anthony
e3f4fee4d5 Add more terminal settings 2025-10-07 17:37:37 -04:00
Smit Barmase
8cb67ec91c remote: Fix opening a remote terminal failing on certain systems (#39715)
Closes #38538

Release Notes:

- Fixed an issue where opening a remote terminal failed on systems like
BusyBox, Alpine, Amazon Linux 2, some CentOS images, etc., due to an
invalid option 'C'.
2025-10-08 03:04:32 +05:30
Ben Kunkle
cd67941598 settings_ui: Preserve selected nav entry when changing files (#39721)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-07 21:28:47 +00:00
Anthony Eid
669db62e33 settings ui: Move selected nav bar entry on scroll (#39633)
This PR makes selecting a sub-entry in the settings UI nav bar scroll to
that section in the settings page. It also updates the selected
sub-entry when scrolling through a settings page to match what a user is
viewing on the page.

I also added a new helper method to `ScrollHandle` type called
`scroll_to_top_of_item` that scrolls until an item is the top element
visible.

Release Notes:

- N/A
2025-10-07 17:16:39 -04:00
Smit Barmase
41f1835bbe project_panel: Fix clicking away to create file or directory doesn't create it (#39716)
Closes #38919

Now, when unfocusing the filename editor while creating a file or
directory in the project panel, it will create it by default unless the
name is empty or already exists.

Release Notes:

- Improved behavior where unfocusing while creating a new file or
directory in the project panel now creates it instead of discarding it.
2025-10-08 02:23:44 +05:30
Ben Kunkle
791ba9ce4c settings_ui: Soft fail on no default & fix language default loading (#39709)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-07 20:27:42 +00:00
Marshall Bowers
e60a61f7e7 languages: Add comment injections for Rust (#39714)
This PR adds comment injections for Rust.

Release Notes:

- Rust: Added comment injections.
2025-10-07 20:26:05 +00:00
Ben Kunkle
b8a6180b82 settings_ui: Title Case Enums (#39711)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-07 19:44:45 +00:00
Bartosz Kaszubowski
dfce57c7f8 Remove unused blake3 dependency (#39677)
Did not found any code reference or direct dependants of this package in
the workspace.

Release Notes:

- N/A
2025-10-07 15:35:01 -04:00
Antal Szabó
15580a867b windows: Fix handling of AltGr to avoid conflicts (#38925)
The previous modifier detection treated `AltGr` presses as `Ctrl+Alt`,
which broke entering characters produced by AltGr. For example, on a
Hungarian layout `{` is typed with `AltGr+B`; our code saw that as
`Ctrl+Alt+B` and the keybind took precedence, so the character couldn’t
be entered.

On Windows, AltGr isn’t a first-class modifier. It’s emulated as a
combination of `Right Alt (VK_RMENU)` plus a synthetic `Left Ctrl
(VK_LCONTROL)` press. When users press AltGr, `GetKeyState` reports both
Ctrl and Alt as down, which makes AltGr indistinguishable from a real
`Ctrl+Alt` chord if we only look at aggregate modifier state.

Fix: detect the AltGr pattern by checking `VK_RMENU && VK_LCONTROL`.
When that pattern is present, treat it as text-entry intent and suppress
`control` and `alt` in `current_modifiers()`. This prevents
AltGr-produced characters from colliding with `Ctrl+Alt` keybinds while
keeping other modifiers intact.

Limitation: there is no Windows API to tell whether the active layout
actually has AltGr. As a result, on non-AltGr layouts (e.g. US),
pressing `Right Alt + Left Ctrl` will be interpreted as AltGr and will
not trigger `Ctrl+Alt` keybinds. This is an acceptable trade-off to
ensure AltGr layouts can reliably enter characters; users can still
invoke `Ctrl+Alt` keybinds using `Left Alt` or by choosing bindings that
avoid common AltGr pairs.

I based this on https://github.com/zed-industries/zed/pull/36115 after
trying other different approaches, but this one is a bit more specific.

Does this approach make sense, or is slightly breaking US input in favor
of fixing international input a no-go? I think the benefit - being able
to type certain characters _at all_ - outweighs the shortcomings.
Otherwise, there's a way to detect if the keyboard layout uses AltGr or
not, but it's quite hacky, and involves reading the registry to find the
current layout dll's name, opening that dll, manually declaring struct
layouts that it uses, then parsing out the AltGr flag from a function
call result. I don't think that's worth it, but if needed, I can give
that a shot, let me know.


Release Notes:

- windows: Fixed handling of AltGr to avoid keybinds preventing
character input
2025-10-07 21:28:50 +02:00
Anthony Eid
f7bb22fb83 settings ui: Add missing setting elements (#39644)
Added the following settings to the UI

Editor Page - Scrollbar Section (9 settings)
- Show
- Cursors
- Git Diff
- Search Results
- Selected Text
- Selected Symbol
- Diagnostics
- Horizontal Scrollbar
- Vertical Scrollbar

 Editor Page - Minimap Section (6 settings)
- Show
- Display In
- Thumb
- Thumb Border
- Current Line Highlight
- Max Width Columns

Editor Page - Editor Behavior Section (3 settings)
- Expand Excerpt Lines
- Excerpt Context Lines
- Minimum Contrast For Highlights

 Debugger Page (7 settings)
- Stepping Granularity
- Save Breakpoints
- Timeout
- Dock
- Log DAP Communications
- Format DAP Log Messages
- Button

 Panels Page - Git Panel Section (3 settings)
- Button
- Dock
- Default Width

Collaboration Page - Experimental Section (4 settings)
- Auto Microphone Volume
- Auto Speaker Volume
- Denoise
- Legacy Audio Compatible

Release Notes:

- N/A
2025-10-07 19:20:33 +00:00
Lukas Wirth
7db7ad93a2 Revert "gpui: Assert validity of text runs for StyleText" (#39708)
Reverts zed-industries/zed#39581

This has done its job uncovering incorrect constructions of the
highlight ranges pretty fast. Reverting this to prevent this from
spilling into preview until I can fix the call sites next week
2025-10-07 19:08:33 +00:00
Lukas Wirth
642643de01 language: Fix HighlightedText::first_line_preview creating incorrect highlight ranges (#39705)
Fixes ZED-1XW

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-07 18:52:59 +00:00
Ben Kunkle
391e304c9f settings_ui: Keyboard navigation (#39652)
Closes #ISSUE

Release Notes:

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

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2025-10-07 18:23:11 +00:00
Jakub Konka
3106472bf3 terminal: Escape strings with backticks rather than backslashes in PowerShell (#39657)
Closes #39007 

Strings should be escaped with backticks in PowerShell, so the following

```
\"pwsh.exe -C pytest -m \\\"some_test\\\"\"
```

becomes

```
\"pwsh.exe -C pytest -m `\"some_test`\"\"
```

Otherwise PowerShell will misinterpret the invocation resulting in
weirdness all-around such as the issue linked above.

Release Notes:

- N/A
2025-10-07 19:30:09 +02:00
Cole Miller
d04ac864b8 Don't construct an agent panel when disable_ai is set (#39689)
Follow-up to #39649, possible fix for #39669

This implements an alternate strategy for showing/hiding the agent panel
in response to `disable_ai`. We don't load the panel at all if AI is
disabled at startup, and when the value of `disable_ai` changes, we load
the panel or destroy it as needed.

Release Notes:

- N/A
2025-10-07 12:48:37 -04:00
Vinicius da Motta
f9a2724a8b Remove empty line when collapsing diagnostics (#39459)
Closes #39028

Fixed empty lines appearing when collapsing files with diagnostic
messages in the diagnostics panel.

Added a flag to track when processing a `FoldedBuffer` and skip
`Near/Below` blocks (diagnostic messages) that immediately follow it.
This prevents diagnostics from rendering as empty lines when their file
is collapsed.

Before:
<img width="1489" height="429" alt="before"
src="https://github.com/user-attachments/assets/5e233290-1f6e-403c-a6b3-a65107586d01"
/>

After:
<img width="981" height="270" alt="after"
src="https://github.com/user-attachments/assets/a877b651-6b7f-4441-805c-38ea41e73a18"
/>

Release Notes:
- Fixed empty lines when collapsing files with diagnostics in the
diagnostics panel
2025-10-07 16:38:35 +00:00
Finn Evers
ded73c9d56 Fix an issue where scrollbars would capture too many events (#39690)
This PR fixes an issue where scrollbars would overagressively capture
some events, which could lead to clicks being lost in the process. Also
improves how hovering of the parent is detected to lead to less false
positives.

Release Notes:

- Fixed a rare issue where scrollbars would react to and capture events
they should not react to.
2025-10-07 16:10:36 +00:00
Conrad Irwin
41cf114d8a Revert "Remove cx from ThemeSettings (#38836)" (#39691)
This reverts commit a2a7bd139a.

This caused themes to not load correctly on startup, you needed to edit
your settings.

Release Notes:

- N/A
2025-10-07 15:45:20 +00:00
Marshall Bowers
e765818487 agent: Remove some unused code from the Thread (#39688)
This PR removes some unused code from the Agent1 `Thread`.

Release Notes:

- N/A

Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-10-07 15:36:53 +00:00
Danilo Leal
84f488879c settings ui: Review available items ordering & writing (#39682)
Release Notes:

- N/A
2025-10-07 11:54:13 -03:00
Bennet Bo Fenner
85985fe960 git: Fix panic in git panel when sort_by_path is true (#39678)
Fixes ZED-1NX

This panic could occur when an `bulk_staging` was set to `Some(...)` and
`sort_by_path` was set to `true`.
When setting `sort_by_path: true`, we call `update_visible_entries(...)`
which then checks if `bulk_staging ` is `Some(...)` and calls
`entry_by_path`. That function accesses `entries`, which still consists
of both headers and entries. But the code
(`entry.status_entry().unwrap()`) assumes that there are no headers in
the entry list if `sort_by_path: true`.

```rust
if GitPanelSettings::get_global(cx).sort_by_path {
    return self
        .entries
        .binary_search_by(|entry| entry.status_entry().unwrap().repo_path.cmp(path)) //This unwrap() would panic
        .ok();
}
```

This has now been fixed by clearing all the entries when `sort_by_path`
changes, as this is the only case where our assumptions are invalid. I
also added a test which 1) actually tests the sort_by_path logic 2)
ensures that we do not re-introduce this panic in the future.


Release Notes:

- Fixed a panic that could occur when using `sort_by_path: true` in the
git panel
2025-10-07 13:05:13 +00:00
Piotr Osiewicz
3bec885536 relpaths: Fix repeated usages of RelPath::unix on static paths (#39675)
- **paths: Cache away results of static construction of RelPath**
- **agent: Cache away results of converting rules file names into
relpaths**

This PR fixed a regression from relpath PR where we've started doing
more work when working with static (Rel-)Paths.

Release Notes:

- N/A
2025-10-07 12:23:31 +00:00
Lukas Wirth
9a5034ea6d Improve command logging and log_err module paths (#39674)
Prior we only logged the crate in `log_err`, which is not too helpful.
We now assemble the module path from the file system path.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-07 12:11:15 +00:00
Alvaro Parker
64eec67a81 Fix floating file chooser (#39154)
Closes #39117 

Some window managers (example: hyprland
https://github.com/hyprwm/Hyprland/issues/11229) still won't open a
floating file chooser because they don't support the XDG foreign
protocol yet: https://wayland.app/protocols/xdg-foreign-unstable-v2

Release Notes:

- Fixed file chooser not floating

---------

Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-10-07 14:06:48 +02:00
Lukas Wirth
ffff56f7fe Revert "search: Introduce more yield points in project search pending_search task" (#39672)
Reverts zed-industries/zed#39624

This seems to have had the opposite effect
2025-10-07 11:58:58 +00:00
Finn Evers
b02b130b7c extensions_ui: Fix uneven horizontal padding (#39627)
This fixes an issue where the horizontal padding on the extensions page
was uneven and where the padding on the right side would be much larger.

| Before | After |
| --- | --- |
| <img width="2550" height="1694" alt="Bildschirmfoto 2025-10-06 um 19
26 56"
src="https://github.com/user-attachments/assets/cf05b77b-4a9e-4ad9-8fa7-381f9b6b45af"
/> | <img width="2546" height="1694" alt="Bildschirmfoto 2025-10-06 um
19 25 49"
src="https://github.com/user-attachments/assets/493ba188-534a-4e7a-b2c1-2b1380be7150"
/> |

Release Notes:

- Improved the horizontal padding on the extensions tab.
2025-10-07 13:23:02 +02:00
Piotr Osiewicz
41ac6a8764 windows: Use nc-esque ssh askpass auth for remoting (#39646)
This lets us avoid storing user PW in ZED_ASKPASS_PASSWORD env var.
Release Notes:

- N/A
2025-10-07 09:48:03 +02:00
Danilo Leal
963204c99d settings ui: Add new batch of settings (#39650)
Release Notes:

- N/A
2025-10-07 00:27:58 -03:00
Cole Miller
f6f11eb544 Avoid spawning external agent process when AI is disabled at startup (#39649)
Closes #39645 

Release Notes:

- Fixed external agent servers sometimes being spawned when Zed started
even when AI was disabled.
2025-10-07 01:01:17 +00:00
Ben Kunkle
c1e917165d settings_ui: Language settings UI (#39640)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-06 19:56:23 -04:00
Conrad Irwin
a2a7bd139a Remove cx from ThemeSettings (#38836)
Before this change the active theme and icon theme were retrofitted onto
the ThemeSettings.

Now they're in their own new global (GlobalTheme::theme(cx) and
GlobalTheme::icon_theme(cx))

This lets us remove cx from the settings traits, and tidy up a few other
things along the way.

Release Notes:

- N/A
2025-10-06 23:06:50 +00:00
Marco Mihai Condrache
4de13e06ec askpass: Fix cli path when executed in a remote server (#39475)
Closes #39469
Closes #39438
Closes #39458

I'm not able to test it, i would appreciate if somebody could do it. I
think this bug was present also for SSH remote projects

Release Notes:

- Fixed an issue where zed bin was not found in remote servers for
askpass

---------

Signed-off-by: Marco Mihai Condrache <52580954+marcocondrache@users.noreply.github.com>
2025-10-07 00:49:06 +02:00
Cole Miller
e680dfb0a0 git_ui: Update project diff more aggressively (#39642)
This fixes a regression in #39557--for the project diff, we rely on
getting an event when a path inside a git repository changes, even if
the git state of the repository didn't change as a result (e.g. a new
modification to a file that already had the "modified" status).

I've also changed this code to send the `UpdateRepository` proto message
even when the git state didn't change, since otherwise we have the same
problem in SSH and collab projects.

Release Notes:

- N/A
2025-10-06 17:52:23 -04:00
Cole Miller
31544d294d ci: Show output of failed tests at the end too (#39643)
This makes it a bit easier to read GHA logs of failed CI runs.

Release Notes:

- N/A
2025-10-06 17:40:45 -04:00
Anthony Eid
4e932297a4 settings ui: Fix panic from reading BufferLineHeight custom variant (#39631)
The panic happened when a user had a settings file with a buffer line
height custom variant, because the drop-down renderer only took into
account the two named variants.

The fix for this will be creating a custom element that allows a user to
manually input a line height greater than one or select either
Comfortable or Standard.

Release Notes:

- N/A
2025-10-06 20:39:05 +00:00
John Tur
b2f0b1b168 Fix Ctrl+C not working when Zed is launched from CLI (#39482)
Closes https://github.com/zed-industries/zed/issues/38383
Closes https://github.com/zed-industries/zed/issues/39330

Release Notes:

- N/A
2025-10-06 20:35:44 +00:00
Ben Kunkle
94f1faffa7 settings_ui: Make unimplemented helper (#39639)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-06 19:35:40 +00:00
Anthony Eid
075104a529 settings ui: Move settings data out of settings window (#39638)
Moved `user_settings_data` and `project_settings_data` into their own
module because those functions just represent static data.

Release Notes:

- N/A
2025-10-06 19:29:36 +00:00
Andrew Farkas
c80d213227 Fix infinite loop when worktree is deleted (#39637)
Closes #39442

Release Notes:

- Fixed infinite loop when worktree is deleted

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-10-06 19:20:01 +00:00
Cole Miller
fe9895d112 node_runtime: Bump minimum version for system node to match copilot's requirement (#39632)
Copilot now requires 22.x. See the last min node version bump:
https://github.com/zed-industries/zed/pull/27912

Closes #39461

<img width="1040" height="97" alt="image"
src="https://github.com/user-attachments/assets/8f0490e3-b9b5-45fd-b7f1-321691b862f0"
/>

Release Notes:

- Zed will no longer use `node` from your `$PATH` if it's older than
22.x (previously, the minimum version was 20.x). Instead, it will fall
back to its bundled `node`. This fixes being unable to use Copilot if an
older `node` was installed system-wide.
2025-10-06 18:38:30 +00:00
Conrad Irwin
24bc52a15a Remove chat from docs (#39623)
Updates #37789

Release Notes:

- N/A
2025-10-06 18:33:54 +00:00
David Kleingeld
a65a8bea43 Revert YankEndOfLine default (part of PR #39143) (#39626)
Release Notes:

- N/A
2025-10-06 17:06:35 +00:00
Anthony Eid
ea60a7b172 settings ui: Use font picker element from onboarding instead of editor for font components (#39593)
The font picker from onboarding is a lot friendlier to interact with and
makes it impossible for a user to select an invalid font from the
settings ui.

I also moved the font picker from the onboarding crate to the ui_input
crate

## New Look
<img width="1136" height="812" alt="image"
src="https://github.com/user-attachments/assets/7436682c-6a41-4860-a18b-13e15b8f3f31"
/>

Release Notes:

- N/A
2025-10-06 13:04:43 -04:00
warrenjokinen
a67a55d81a docs: Fix Tree-sitter casing in vim.md (#39527)
The AI here in GitHub helped me find the creative ways that Tree-sitter
was incorrectly typed in the document vim.md

<img width="704" height="196" alt="Tree-sitter-bg"
src="https://github.com/user-attachments/assets/90924405-0961-4436-b6b8-2066de527ddc"
/>

Release Notes:

- N/A
2025-10-06 19:01:48 +02:00
Hexorg
1a9f9ccc29 Add note about inode/directory to Zed desktop entry (#39076)
Release Notes:

- N/A
2025-10-06 18:26:11 +02:00
localcc
6da5945cd2 Optimize fs_watcher to use less RAM by doing less work (#39602)
mac_watcher already does this so it would make more sense to also do
this on Windows and it saves ~500-600mb of ram on the chromium project.

This does not improve memory usage on linux because inotify cannot do
recursive directory monitoring

Release Notes:

- N/A
2025-10-06 18:24:28 +02:00
Lukas Wirth
354cc65daa search: Introduce more yield points in project search pending_search task (#39624)
This should help with project search lagging I believe

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-06 16:09:00 +00:00
Lukas Wirth
2c6a8634cc remote: Fix wsl failing to start on some setups (#39612)
Closes https://github.com/zed-industries/zed/issues/39433

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-06 17:40:05 +02:00
Danilo Leal
84ec865c44 agent: Fix gradient overlay in file list within the activity bar (#39619)
Release Notes:

- N/A
2025-10-06 15:32:08 +00:00
Smit Barmase
80727a03bf editor: Limit snippet query range instead of collecting from buffer start (#39617)
Fixes hang when computing query for snippet completions when working
with really large buffers.

Release Notes:

- N/A
2025-10-06 20:54:42 +05:30
ozer
e7339fbd42 project_panel: Focus project panel when clicking empty space (#39489)
Closes #39486

Release Notes:

- Fixed: Project Panel now properly focuses when clicking empty space,
allowing keyboard shortcuts to work as expected
2025-10-06 20:50:56 +05:30
Danilo Leal
db5b1a31b5 settings ui: Add some UX adjustments (#39615)
Release Notes:

- N/A
2025-10-06 12:12:35 -03:00
Ratazzi
bc39ed2575 editor: Preserve font features for vim block cursor (#39474)
## Summary

Fixes an issue where font features (like ligatures) were not applied to
text under the vim block cursor. The cursor would inherit the font
family from the character at the cursor
position, but would use default font features instead of the editor's
configured font features.

## Changes

- Make the font mutable when rendering the vim block cursor
- Apply the editor's text style font features to the cursor font

This ensures that text under the block cursor renders with the same
visual appearance as the rest of the editor content.

Closes #39471

Release Notes:

- Fixed vim block cursor not respecting font features (like ligatures)
2025-10-06 08:58:46 -06:00
Danilo Leal
1764337a5d agent: Fix plan summary text overflow in Claude Code threads (#39603)
| Before | After |
|--------|--------|
| <img width="700" height="764" alt="Screenshot 2025-10-06 at 9  43@2x"
src="https://github.com/user-attachments/assets/faf7e93f-f0d8-4bea-9f8d-272c83b41b18"
/> | <img width="700" height="394" alt="Screenshot 2025-10-06 at 9  43
2@2x"
src="https://github.com/user-attachments/assets/3f404e69-de3a-44c2-8111-0212d5d91199"
/> |

Release Notes:

- agent: Fixed a bug in Claude Code threads where the plan summary text
would overflow beyond its container.
2025-10-06 11:40:01 -03:00
Lev Zakharov
3707102702 title_bar: Show git status indicator icon in the title bar (#38029)
See related discussion #37046.

<details>

<summary>Screenshots</summary>

**No Changes**
<img
src="https://github.com/user-attachments/assets/e814da6e-bc9b-4edd-b37a-6bb4680d5bb3"
/>

**Added**
<img
src="https://github.com/user-attachments/assets/07ffdf90-08cb-43f4-b2bd-9966a21e08de"
/>

**Changed**
<img
src="https://github.com/user-attachments/assets/7e13b999-83b3-41ea-b2ab-baaa1541b169"
/>

**Deleted**
<img
src="https://github.com/user-attachments/assets/a77fc7e3-a026-419a-87bd-7146c3ca46a9"
/>

**Conflicts**
<img
src="https://github.com/user-attachments/assets/17e7e35c-d81b-4660-808d-08e12107ea2d"
/>

</details>

Release Notes:

- Show git status indicator icon in the title bar
2025-10-06 09:43:22 -04:00
Marco Mihai Condrache
5263f51432 terminal: Fix terminal cloning on WSL (#39552)
Should close #39428

The working directory of the `wsl.exe` program is set to a Linux path,
which is invalid on the Windows side, causing the terminal to crash. The
first spawn works because there is no active terminal view, allowing a
new shell (which checks for the remote) to be created. I cannot explain
why it works on SSH remote clients, but I may be missing something in
the remote connection implementation.

I don't have a Windows machine to test this, so I would appreciate
someone testing it. 🙏🏼

Release Notes:

- Fixed an issue where WSL terminals could not be splitted

---------

Signed-off-by: Marco Mihai Condrache <52580954+marcocondrache@users.noreply.github.com>
2025-10-06 13:18:27 +00:00
Lukas Wirth
93cd10aaa8 terminal: Re-enable activation scripts on windows (#39604)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-06 13:07:15 +00:00
Smit Barmase
2c1cc01b81 linux: Fix enter key triggering newline instead of commiting input (#39599)
Closes #31337 #35537

Release Notes:

- Fixed an issue on Linux X11 where pressing Enter added a new line
instead of confirming English input.
2025-10-06 18:22:02 +05:30
Lukas Wirth
81ada92306 editor: Fix clangd switch source header action failing on wsl (#39598)
Closes https://github.com/zed-industries/zed/issues/39180

Release Notes:

- Fixed clangd switch source header action failing on wsl
2025-10-06 12:36:12 +00:00
Lukas Wirth
4bd7ef8bad acp_thread: If available, use git bash over powershell in terminal tool (#39466)
Release Notes:

- When git bash is installed, agents will now use that over powershell
when invoking terminal commands
2025-10-06 13:39:19 +02:00
Lukas Wirth
d1e2a1f20c gpui: Assert validity of text runs for StyleText (#39581)
Should help with figuring out the char boundary panic in text shaping on
windows

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-06 13:04:45 +02:00
Danilo Leal
79a8986cb7 settings ui: Add scrollbar and other design details (#39504)
Release Notes:

- N/A
2025-10-06 08:00:47 -03:00
Anthony Eid
d2b91eb2bc settings ui: Add numeric steppers to settings UI (#39491)
This PR adds the numeric stepper component to the settings ui and
implements some settings that rely on this component as well.

I also switched {buffer/ui}_font_weight to the `gpui::FontWeight` type
and added a manual implementation of the Schemars trait. This allows Zed
to send min, max, and default information to the JSON LSP when a user is
manually editing the settings file.

The numeric stepper elements added to the settings ui are below:
- ui font size
- ui font weight
- Buffer font size
- Buffer font weight 
- Scroll sensitivity
- Fast scroll sensitivity
- Vertical scroll margin
- Horizontal scroll margin
- Inline blame padding 
- Inline blame delay
- Inline blame min column
- Unnecessary code fade
- Tab Size
- Hover popover delay

Release Notes:

- N/A
2025-10-06 10:06:33 +00:00
Bartosz Kaszubowski
c26937a848 zed: Show GPUI Inspector item in Dev build menus (#39287)
# Why

I have find out that this tool exists by browsing Keymap Editor. I think
it would be nice for its discoverability to show it in the app menus in
Dev builds.

# How

Add "GPUI Inspector" app menu item conditionally for Dev builds only.

Release Notes:

- N/A

# Preview

<img width="1014" height="948" alt="Screenshot 2025-10-01 at 14 36 48"
src="https://github.com/user-attachments/assets/c0409e67-1f4d-44f3-90b3-293ad4fe5c73"
/>
2025-10-06 12:21:48 +03:00
Lukas Wirth
da82eec4cb editor: Fix utf8 boundary panic in process_completion_for_edit (#39561)
Fixes ZED-1WH

Release Notes:

- Fixed panic when requesting completions after a multibyte character
2025-10-06 08:39:51 +00:00
Lukas Wirth
2bfcd60b88 editor: Shrink DisplayMapSnapshot from 824 to 256 bytes (#39568)
We have unnecessary clones for the fields here as most of the snapshots
contain the others hierarchically.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-06 08:08:49 +00:00
Ratazzi
9c7369f54d terminal: Fix rendering of zero-width combining characters (#39526)
Add support for rendering Unicode combining characters (diacritics) in
the terminal's batched text runs.

- Add append_zero_width_chars() to handle combining marks
- Integrate zero-width chars into all batching code paths
- Update cell extras tracking logic
- Add test for combining character rendering

Fixes display of é, ñ, ô and other diacritics.

Closes #39525

Release Notes:

- Fixed: NFD/NFKD normalized text (e.g., é as e + ◌́) not rendering in
integrated terminal

Before:

<img width="874" height="688" alt="SCR-20251004-udnj"
src="https://github.com/user-attachments/assets/8d9f9c9f-dac4-4382-92c2-8b6c1d817abd"
/>

After:

<img width="873" height="686" alt="SCR-20251004-ulsw"
src="https://github.com/user-attachments/assets/fbd5cdc7-fdd6-44dc-8b05-cc425644f1a0"
/>
2025-10-06 10:02:27 +02:00
Anthony Eid
5160510ed0 chore: Remove unused settings ui module (#39580)
The editor settings control module was the first prototype of what a
settings UI could look like in Zed, but the code is outdated now and is
no longer used. So this PR removes it for cleanup.

Release Notes:

- N/A
2025-10-06 07:32:08 +00:00
Mikayla Maki
ee557fb7ea Add window close keybindings for Settings UI (#39578)
Closes #ISSUE

Release Notes:

- N/A
2025-10-06 07:27:54 +00:00
Mikayla Maki
f9919f9214 Swap the start building and login buttons (#39576)
New onboarding screen:

<img width="1027" height="700" alt="Screenshot 2025-10-05 at 10 38
57 PM"
src="https://github.com/user-attachments/assets/5dc49e53-68e7-4559-8ce0-1bada629781d"
/>


This PR also adds a new telemetry event: `Welcome Start Building
Clicked`

Release Notes:

- N/A
2025-10-06 05:55:57 +00:00
Mikayla Maki
0f0974f105 Add script to bump GPUI version (#39573)
This script successfully published the [0.2.0-test.4 GPUI
prerelease](https://crates.io/crates/gpui/0.2.0-test.4).

Release Notes:

- N/A
2025-10-06 01:42:17 +00:00
Mikayla Maki
e317d98915 Prep crates for GPUI on crates.io (#39543)
Release Notes:

- N/A
2025-10-05 13:44:31 -07:00
Kirill Bulatov
dada318be7 Remove iterations from the slow FS tests (#39564)
Follow-up to https://github.com/zed-industries/zed/pull/39557

Release Notes:

- N/A
2025-10-05 19:47:34 +00:00
Lukas Wirth
b53f9c8863 editor: Fix panic in delete_line with multibyte characters (#39560)
Fixes ZED-1TG

Release Notes:

- Fixed panic in `delete line` when following line contains multibyte
characters
2025-10-05 19:26:27 +00:00
Martin Pool
5b0a2f1ab6 Add more unit tests for Rope (#39426)
I was looking at the rope implementation and some of the existing bugs
that crash in there, and I ran cargo-mutants to inspect test coverage. I
was motivated by bugs like
https://github.com/zed-industries/zed/issues/38556 but this doesn't fix
it and the bug may well be at a higher layer.

This PR adds coverage for a few functions that aren't tested today. I
didn't find any actual bugs yet.

I can see this tree is pretty sparse on docstrings so if you think these
are too verbose I can take them out or drop the whole PR.

Release Notes:

- N/A
2025-10-05 21:42:27 +03:00
Lukas Wirth
d5a4890142 remote: Keep full shell path on wsl (#39555)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-05 20:29:58 +02:00
Be
cd61bfbd42 docs: Fix path of language extensions on Linux (#39425)
Release Notes:

- N/A
2025-10-05 18:21:48 +00:00
Kirill Bulatov
469ecfbe13 Emit less update events for odd FS events (#39557)
When running flycheck, I've noticed that scrolling starts to lag:


https://github.com/user-attachments/assets/b0bef0a3-ccbd-479d-a385-273398086d38

When checking the trace, it is notable that project panel updates its
entire tree multiple times during flycheck:

<img width="2032" height="1136" alt="image"
src="https://github.com/user-attachments/assets/d1935e77-3b00-4be5-a12a-8a17a9d64202"
/>


[scrolling.trace.zip](https://github.com/user-attachments/files/22710852/scrolling.trace.zip)

Turns out, `target/debug` directory is loaded by Zed (presumably,
reported by langserver as there are sources generated by bindgen and
proto that need to be loaded), and `target/debug/build` directory
received multiple events of a `None` kind for Zed, which trigger the
rescans.

Rework the logic to omit the `None`-kind events in Zed, and to avoid
excessive repo updates if not needed.


Release Notes:

- Improved worktree FS event emits in gitignored directories

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-10-05 17:34:55 +00:00
Remco Smits
46b6adadf9 markdown: Add HTML table element support (#38605)
Follow-up: https://github.com/zed-industries/zed/pull/38590

**Note**: this PR contains changes from the [previous
PR](https://github.com/zed-industries/zed/pull/38590), when that PR gets
merged we should see the real changes.
This PR fixes 4 things in order to make:

1. Add html/markdown minifier to remove all the **\t** and **\n**
characters. This is needed as you cannot create new lines with markdown
by just adding an enter to the source file.
2. The event Event::HTML only contained a chunk of the real html for
multiline HTML code. I fixed this by storing the currently watched HTML
inside a buffer and at the end we parse it into the right elements.
Instead of trying to parse a chunck into multiple elements which would
always fail before.
3. Add support for html tables.
4. Fixed panic that occured when table does not have an header.

I also decided to keep the html minifier inside Zed, because making it a
dependency for just a few 100 lines seems to be an overkill. The
original crate had a few cve in their dependencies, so figured this
would be the best.

**Html table support**
<img width="1439" height="801" alt="Screenshot 2025-09-27 at 12 19 07"
src="https://github.com/user-attachments/assets/a884cc6f-cf47-45a2-81fa-91300c7bbf3f"
/>

**Before & after Zed's README (no changes)**
<img width="3440" height="1378" alt="Screenshot 2025-09-27 at 12 34 47"
src="https://github.com/user-attachments/assets/1273b094-fb24-4abd-bffa-56ef3b44670c"
/>

Release Notes:

- Markdown: Added support for html tables
2025-10-05 13:31:17 +02:00
Lukas Wirth
1a9e9c5faa workspace: Add Close Multibuffers pane context menu entry (#39199)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-05 10:50:36 +02:00
Lukas Wirth
eb64ca8758 askpass: Don't log error when user cancels askpass prompt (#39544)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-05 07:50:34 +00:00
Ngonidzashe Mangudya
68e6d55596 terminal: Fix terminal split pane opening in wrong directory (#39537)
## Problem
When splitting a terminal pane, the new pane opens in the root directory
(`/`) instead of preserving the current working directory of the
original terminal.

For example, when working in `/Users/modestnerd/Developer/Projects/zed`
(my pc) and splitting the terminal pane, the new pane would open in `/`
instead of staying in the current directory.

## Solution
Restructured the fallback logic in
`new_pane_with_cloned_active_terminal` (terminal_panel.rs:452-456) to
ensure `default_working_directory(workspace, cx)` is called as a
fallback even when a terminal view exists but its `working_directory()`
returns `None`.

The fix changes the nested `and_then` to use `or_else` for the fallback,
ensuring the working directory is always properly resolved before
entering the async block.

Release Notes:

- Fixed terminal split pane opening in wrong directory instead of
preserving the current working directory
2025-10-05 07:08:17 +00:00
Richard Feldman
bcd2d269e2 Fix CRLF handling in display-only terminals (#39538)
## Before

<img width="558" height="739" alt="Screenshot 2025-10-03 at 11 08 43 PM"
src="https://github.com/user-attachments/assets/5dae7f9d-03b6-48eb-826d-e2be60320546"
/>

## After

<img width="551" height="843" alt="Screenshot 2025-10-04 at 8 29 51 PM"
src="https://github.com/user-attachments/assets/2b06dcec-7758-42ad-acf0-c32a7f50f1b1"
/>

No release notes because we aren't using display-only terminals anywhere
yet (`codex-acp` will be the first to use them, and it's still
feature-flagged right now).

Release Notes:

- N/A
2025-10-05 02:43:46 +00:00
Richard Feldman
b32075cdcb Decouple agent reregistration from settings changes (#39528)
Fixes a `--release`-only bug in feature-flagged agents where the feature
flag isn't picked up in some situations (unless there was a settings
change to go with it - due to an early return when settings didn't
change).

Release Notes:

- N/A
2025-10-04 17:19:27 +00:00
Richard Feldman
21e75b8221 Pass through cwd from ACP extension (#39511)
If we get a `cwd` from ACP (because e.g. `codex-acp` is driving the
terminal rather than our own PTY) then use that to display the `cwd` of
the terminal process.

Release Notes:

- N/A
2025-10-04 00:50:14 -04:00
Richard Feldman
978951b79a Don't use PTY in the display-only terminal (#39510)
This only affects `codex-acp` for now.

Not using the PTY in display-only terminals means they don't display the
login prompt (or spurious `%`s) at the end of terminal output
renderings.

Release Notes:

- N/A
2025-10-04 04:49:33 +00:00
Ben Kunkle
6b980ecad3 settings_ui: Dynamic navbar filtering (#39494)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-04 03:29:28 +00:00
Mansoor Ahmed
d9c7f44b0b Add ability to hide status bar (#39430)
This pull request adds the ability to configure the setting to hide or
show the status bar, as described in discussion:
https://github.com/zed-industries/zed/discussions/38591

The original [PR
#38974](https://github.com/zed-industries/zed/pull/38974#issuecomment-3362020879)
was merged but reverted due to hidden conflicts. As per @ConradIrwin 's
[request](https://github.com/zed-industries/zed/pull/38974#issuecomment-3362020879),
I am recreating the PR on top of updated main branch.

Release Notes:

- Added an experimental setting `"status_bar": { "experimental.show":
false}` to hide the status bars.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-10-03 20:11:21 -06:00
John Tur
55e68553a4 Fix caption buttons going off-screen (#39502)
https://github.com/user-attachments/assets/27bf58df-b8c4-4730-856b-d62ec639a552

Previously the caption buttons (minimize, maximize, close) would
disappear off the right side of the title bar.

Release Notes:

- N/A

Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
2025-10-03 23:25:54 +00:00
John Tur
9fe46dc8d2 Fix double-clicking on non-empty title bar area (#39500)
Closes #38685 



Release Notes:

- N/A

---------

Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
2025-10-03 19:25:15 -04:00
Cole Miller
aced13bc9f Fix ordering of multibuffer excerpts (#39476)
The ordering of path-based excerpts in multibuffers regressed with
#38744, because we changed the `path` field of `PathKey` to be a string
(from `std::path::Path`) and used the derived `Ord` implementation,
which doesn't agree with the path-based order of worktree traversals.
This PR fixes that by using `RelPath` for `PathKey`. Instead of using
`File::full_path`, which can be absolute, we always use `File::path` and
distinguish different worktrees using their ID.

Release Notes:

- N/A

---------

Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
2025-10-03 22:17:31 +00:00
Lukas Wirth
2859cbdba9 Make ShellBuilder::new not branch on a remote shell (#39493)
Release Notes:

- Fixed claude code agent login on remotes

Co-authored-by: Max Brunsfeld <max@zed.dev>
Co-authored-by: Cole Miller <cole@zed.dev>
2025-10-03 23:23:09 +02:00
Marshall Bowers
4443f61c16 x_ai: Add support for Grok 4 Fast (#39492)
This PR adds support for Grok 4 Fast.

Release Notes:

- Added support for Grok 4 Fast models.

Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-10-03 16:00:09 -04:00
Ben Kunkle
f0f0beb42f settings_ui: Implement sub pages (#39484)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-03 19:59:46 +00:00
Finn Evers
6707ff3b50 Make outline modal work in channel notes (#39481)
This fixes an issue where the outline modal would not work in editors
that had no explicit workspace attached to them.

Release Notes:

- Enabled the outline modal to work in channel notes.
2025-10-03 19:20:51 +00:00
Finn Evers
93770e8314 Bring CI back up (#39485)
Release Notes:

- N/A
2025-10-03 19:01:30 +00:00
Max Brunsfeld
f8c617303a Build Windows installer for all releases (#39414)
Release Notes:

- N/A
2025-10-03 10:57:39 -07:00
Anthony Eid
e5f05a21ce settings ui: Improve numeric stepper component interface (#36513)
This is the first step to allowing users to type into a numeric stepper
to set its value. This PR makes the numeric stepper take in a generic
type `T` where T: `NumericStepperType`

```rust
pub trait NumericStepperType:
    Display
    + Add<Output = Self>
    + Sub<Output = Self>
    + Copy
    + Clone
    + Sized
    + PartialOrd
    + FromStr
    + 'static
{
    fn default_format(value: &Self) -> String {
        format!("{}", value)
    }
    fn default_step() -> Self;
    fn large_step() -> Self;
    fn small_step() -> Self;
    fn min_value() -> Self;
    fn max_value() -> Self;
}
```

This allows setting of step sizes and min/max values as well as making
the component easier to use.

cc @danilo-leal 

Release Notes:

- N/A

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Gaauwe Rombouts <mail@grombouts.nl>
2025-10-03 17:35:30 +00:00
Danilo Leal
f499504b13 agent: Introduce agent_buffer_font_size setting (#39468)
Closes https://github.com/zed-industries/zed/issues/39406
Follow up to https://github.com/zed-industries/zed/pull/38726

This PR introduces the `agent_buffer_font_size` setting and renames
`agent_font_size` to `agent_ui_font_size`. This allows whoever wants
`buffer_font_size` and `agent_buffer_font_size` to match, as well as
folks who want a slightly smaller size only in the agent panel (which...
also looks just better by default!).

Release Notes:

- agent: Introduced the `agent_buffer_font_size` setting and renamed
`agent_font_size` to `agent_ui_font_size`, allowing for granular buffer
font size control in the agent panel vs. regular editors.
2025-10-03 14:23:23 -03:00
Marshall Bowers
504216cbbf settings: Fix JSON schema for ExtensionCapabilityContent (#39478)
This PR fixes the JSON schema for the `ExtensionCapabilityContent`.

Having the nested structs in the variants caused the `kind` property to
not be generated properly. Inlining the fields into the variants fixes
this.

Release Notes:

- N/A
2025-10-03 16:58:47 +00:00
Marshall Bowers
3bf71c690f extension_host: Load granted extension capabilities from settings (#39472)
This PR adds the ability to control the capabilities granted to
extensions by the extension host via the new
`granted_extension_capabilities` setting.

This setting is a list of the capabilities granted to any extension
running in Zed.

The currently available capabilities are:

- `process:exec` - Grants extensions the ability to invoke commands
using
[`zed_extension_api::process::Command`](https://docs.rs/zed_extension_api/latest/zed_extension_api/process/struct.Command.html)
- `download_file` - Grants extensions the ability to download files
using
[`zed_extension_api::download_file`](https://docs.rs/zed_extension_api/latest/zed_extension_api/fn.download_file.html)
- `npm:install` - Grants extensions the ability to install npm packages
using
[`zed_extension_api::npm_install_package`](https://docs.rs/zed_extension_api/latest/zed_extension_api/fn.npm_install_package.html)

Each of these capabilities has parameters that can be used to customize
the permissions.

For instance, to only allow downloads from GitHub, the `download_file`
capability can specify an allowed `host`:

```json
[
  { "kind": "download_file", "host": "github.com", "path": ["**"] }
]
```

The same capability can also be granted multiple times with different
parameters to build up an allowlist:

```json
[
  { "kind": "download_file", "host": "github.com", "path": ["**"] },
  { "kind": "download_file", "host": "gitlab.com", "path": ["**"] }
]
```

When an extension is not granted a capability, the associated extension
APIs protected by that capability will fail.

For instance, trying to use `zed_extension_api::download_file` when the
`download_file` capability is not granted will result in an error that
will be surfaced by the extension:

```
Language server phpactor:

from extension "PHP" version 0.4.3: failed to download file: capability for download_file https://github.com/phpactor/phpactor/releases/download/2025.07.25.0/phpactor.phar is not granted by the extension host
```

Release Notes:

- Added a `granted_extension_capabilities` setting to control the
capabilities granted to extensions.
2025-10-03 15:55:01 +00:00
Smit Barmase
456ba32ea7 macOS: Fix keyboards shortcuts does not work until mouse clicked inside Zed (#39467)
Closes #38258

Regressed in https://github.com/zed-industries/zed/pull/33334
 
Release Notes:

- Fixed an issue on macOS where keyboard shortcuts wouldn’t work until
you clicked inside Zed.
2025-10-03 20:38:29 +05:30
Andrew Farkas
9aeb617a89 Keep folds at cursor open for "fold at level" (#39396)
Closes #39308

Also fixes a possible bug in `apply_selected_diff_hunks()` caused by
reversed selections.

Release Notes:

- Fixed "editor: fold at level" closing regions containing selections

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-10-03 15:06:46 +00:00
Ben Kunkle
fd8bae9b72 docs: Document ctrl-b to toggle left dock not working in Vim mode on Linux and Windows (#39464)
Closes #39370

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-03 10:26:04 -04:00
Ben Kunkle
f71c9122ca settings_ui: Write local settings files (#39408)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-03 10:05:44 -04:00
Dino
8441aa49b2 vim: Fix visual block handling of wrapped lines (#39355)
These changes fix an issue with vim's visual block mode when soft
wrapping is enabled. In this situation, if one was to move the cursor
either up or down, the selection would be updated to include visual
(wrapped) rows, instead of only the buffer rows. For example, take the
following contents:

```
1 | And here's a very long line that is wrapping
    at this exact point.
2 | And another very long line that is will also
    wrap at this exact point.
```

If one was to place the cursor at the start of the first line, character
`A`, trigger visual block mode with `ctrl-v` and then move down one line
with `j`, the selection would end up as (with [X] representing the
selected characters):

```
1 | [A]nd here's a very long line that is wrapping
    [a]t this exact point.
2 | [A]nd another very long line that is will also
    wrap at this exact point.
```

Instead of the expected:

```
1 | [A]nd here's a very long line that is wrapping
    at this exact point.
2 | [A]nd another very long line that is will also
    wrap at this exact point.
```

With the changes in this commit, `Vim.visual_block_motion` will now
leverage buffer rows in order to navigate to the next or previous row.

Release Notes:

- Fixed handling of soft wrapped lines in vim's visual block mode
2025-10-03 15:58:34 +02:00
Danilo Leal
7b96e1cf1a agent: Add profile description in docs aside (#39412)
This improves the design of the profile picker a bit by making every
item on it have the same height; it also makes it more consistent with
the model selector.

Release Notes:

- N/A
2025-10-03 10:16:33 -03:00
Lukas Wirth
86322a186f worktree: Prevent background scanner from trying to scan file worktrees (#39277)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-03 13:12:24 +00:00
Joseph T. Lyons
1b94d74dc3 Clarify extension license detection in docs (#39456)
Release Notes:

- N/A
2025-10-03 12:32:24 +00:00
Lukas Wirth
db825c1141 remote: Do not allocate pseudo terminal for ssh commands (#39451)
Closes https://github.com/zed-industries/zed/issues/25382

Release Notes:

- Fixed ssh remote not working if the default shell profile prints to
stdout
2025-10-03 11:33:44 +00:00
Tim Vermeulen
f3abd1dab5 Fix rust-analyzer startup issue in single-file worktrees (#39441)
I'm not sure about the exact conditions for reproducing this issue, but
whenever I build Zed locally and have it open a single-file worktree on
launch, the rust-analyzer language server fails to start up because Zed
attempts to run `rust-analyzer --help` on a path that is not a
directory. This fixes that by running the command on the parent path in
the case of a single-file worktree.

Release Notes:

- Fixed rust-analyzer startup issue in single-file worktrees
2025-10-03 12:42:46 +02:00
Richard Feldman
662ec9977f Detect new releases of codex-acp (#39388)
Now we use GitHub Releases to detect when there's a new version of
codex-acp out, and we notify the user in the same way we do for the
other external agents.

This also moves `github_download.rs` out of the `languages` crate and
into `http_client`, because now we're not just using it for language
servers anymore, we're also using it for external agents.

Release Notes:

- N/A

---------

Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
2025-10-03 12:10:40 +02:00
Lukas Wirth
3ab5103de1 multi_buffer: Fix ExcerptId::max() handling in summaries_for_anchors (#39436)
Closes https://github.com/zed-industries/zed/issues/39333

Release Notes:

- Fixed IME inputs breaking when typing at the end of an editor

Co-authored-by: Smit Barmase <smit@zed.dev>
2025-10-03 09:48:26 +00:00
Jacob
39bd03b92d file_icons: Add support for multiple file extensions (#36342)
Currently most icon theme extensions already support file types like
stories.tsx and stories.svelte. However within Zed itself these file
type overrides are not supported yet. This change adds support for those

Release Notes:

- Added support for icons on file extensions such as stories.tsx and
stories.svelte
2025-10-03 11:41:59 +02:00
Be
1fffcb99ba docs: Remove outdated mention about Vulkan on Asahi Linux (#39423)
Vulkan is now supported running Linux on ARM Macs
https://asahilinux.org/2024/10/aaa-gaming-on-asahi-linux/

Release Notes:

- N/A
2025-10-03 07:17:21 +00:00
Conrad Irwin
e4f90b5da2 Fix race-condition in autosave (#39409)
This removes a long-standing thing we've done, which is send a `DidSave`
notification to the language server for the clean parts of a
multi-buffer. However, it seems like the intent of that notification is
to tell the language server to reload the file from disk.

As we didn't actually write those files to disk, it seems clearer to not
send this notification; and just remove this whole code-path.

Release Notes:

- Fixed a race where autosave in a multibuffer could cause unsaved
buffers to appear saved
2025-10-02 22:14:12 -06:00
Richard Feldman
dc6fad9659 Display-only ACP terminals (#39419)
Codex needs (and future projects are anticipated to need as well) a
concept of display-only terminals. This refactors terminals to decouple
the PTY part from the display part, so that we can render terminal
changes based on a series of events - regardless of whether they're
being driven from a PTY inside Zed or from an outside source (e.g.
`codex-acp`).

Release Notes:

- N/A
2025-10-03 02:50:32 +00:00
Richard Feldman
64c289a9a2 Fix Claude Code login regression (#39413)
This was added for Codex, but had undesirable consequences for Claude
Code (on Nightly, never made it to Preview). We're going to address this
in `codex-acp` instead.

Release Notes:

- N/A
2025-10-03 00:26:22 +00:00
Marshall Bowers
a08897ff30 collab: Add token_spend_in_cents column to billing_subscriptions table (#39404)
This PR adds a `token_spend_in_cents` and associated
`token_spend_in_cents_updated_at` column to the `billing_subscriptions`
table.

Release Notes:

- N/A
2025-10-02 22:04:57 +00:00
Piotr Osiewicz
d359a814f8 editor: Represent scroll offset with more precision (#39367)
Closes #5355

Release Notes:

- Fixed rendering glitches with files with more than 16 million lines
(that occured due to floating number rounding errors).

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-10-02 23:04:31 +02:00
Ben Kunkle
4c35274b6e Don't allow formatters in format on save (#39400)
Closes #ISSUE



Release Notes:

- settings: Removed support for having format steps in both the
`format_on_save` and `formatter` settings for languages.
`format_on_save` is now restricted to the values of `"on"` and `"off"`,
and all format steps should be set under the `formatter` key. If you
were using `format_on_save` but not `formatter` this will be migrated
for you, otherwise it will require a manual migration.

---------

Co-authored-by: Smit <smit@zed.dev>
2025-10-02 20:34:31 +00:00
Lukas Wirth
bf48a95344 acp_thread: Respect terminal settings shell for terminal tool environment (#39349)
When sourcing the project environment for the terminal tool, we will now
do so by spawning the shell specified by the users `terminal.shell`
setting (or as usual fall back to the login shell).

Closes #37687 

Release Notes:

- N/A
2025-10-02 22:10:55 +02:00
Ben Kunkle
7c3a21f732 JSON based migrations (#39398)
Closes #ISSUE

Adds the ability to create settings and keymap migrations by mutating
`serde_json::Value`s instead of using tree-sitter queries. This
(hopefully) will make complicated migrations far simpler to implement.

Release Notes:

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

---------

Co-authored-by: Smit <heysmitbarmase@gmail.com>
Co-authored-by: Smit <smit@zed.dev>
2025-10-02 16:07:26 -04:00
Cole Miller
af630be7ca git: Use environment from login shell to search for system git binary, and prefer it to the bundled binary (#39302)
Closes #38571

Release Notes:

- git: Fixed git features not working when git was installed in an
unusual location.

---------

Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
2025-10-02 14:22:10 -04:00
Finn Evers
dbd8efe129 ui: Implement graceful autohiding for scrollbars (#39225)
How it looks:


https://github.com/user-attachments/assets/9a355807-5461-4e8d-b7a8-9efb98cea67a

Idea behind this is to reduce flickering in areas where nothing is
happening - whenever these hide, the user is specifically not
interacting with them, hence it can be distracting to have something
flicker in the side of your eye. This PR tackles this.


Release Notes:

- Added graceful autohiding to scrollbars outside of the editor

---------

Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
2025-10-02 17:11:46 +00:00
Marco Mihai Condrache
3afbe836a1 file_finder: Fix history items not using worktree path (#39304)
Closes #39283

Release Notes:

- Fixed: In multi-repo workspaces, files with the same name are no
longer hidden in the file picker after one is opened

---------

Signed-off-by: Marco Mihai Condrache <52580954+marcocondrache@users.noreply.github.com>
2025-10-02 18:36:09 +02:00
Finn Evers
d8709f2107 docs: Re-add context for lsp_highlight_debounce (#39391)
Release Notes:

- N/A
2025-10-02 16:30:54 +00:00
Finn Evers
df7bc8200d docs: Add coverage for named directory icon support (#39387)
Also updates the link to the new schema version which now includes named
directory icons.

Release Notes:

- N/A
2025-10-02 18:21:21 +02:00
David Kleingeld
8575972a07 Show display name in collab panel (#39384)
Release Notes:

- Improved Collab panel by showing display names and github handles

Co-authored-by: Cole Miller <cole@zed.dev>
2025-10-02 16:17:27 +00:00
Richard Feldman
40c417f9c3 Subscribe to CodexAcpFeatureFlag (#39380)
Otherwise Codex doesn't work on first launch.

Release Notes:

- N/A
2025-10-02 12:16:51 -04:00
Conrad Irwin
7c2cf86dd9 Revert "Add ability to hide status bar (#38974)"
This reverts commit 126ed6fbdd.
2025-10-02 10:08:54 -06:00
Mansoor Ahmed
126ed6fbdd Add ability to hide status bar (#38974)
This pull request adds the ability to configure the setting to hide or
show the status bar, as described in discussion:
https://github.com/zed-industries/zed/discussions/38591

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-10-02 10:02:57 -06:00
Lukas Wirth
6f4381b39d remote(wsl): Execute commands on wsl without spawning a shell (#39357)
Closes https://github.com/zed-industries/zed/issues/39091

Release Notes:

- Fixed wsl connection failing if user's shell prints to stdout on
startup
2025-10-02 14:49:05 +00:00
Ben Kunkle
6fbbdb3512 settings: Flatten code actions formatters object (#39375)
Closes #ISSUE

Release Notes:

- settings: Changed code action format in `formatter` and
`format_on_save` settings.

**Previous format:**
```
{
  "code_actions": {
    "source.organizeImports": true,
    "source.fixAll": true
  }
}
```

**New format:**
```
[
  {"code_action": "source.organizeImports"},
  {"code_action": "source.fixAll"}
]
```

After #39246, code actions run sequentially in order. The structure now
reflects this and aligns with other formatter options (e.g., language
servers).

Both the `formatter` and `format_on_save` settings will be
auto-migrated.
2025-10-02 14:48:15 +00:00
Nomad
179fb21778 git_ui: Expand commit editor hitbox by setting min_lines = max_lines (#38587)
Closes #26527

The commit editor hitbox was too small since min_lines < max_lines,
making it grow only when typing more lines.

Release Notes:

- N/A


https://github.com/user-attachments/assets/e026d688-594f-40b6-a971-6c92e3fdb496
2025-10-02 14:44:18 +00:00
Joseph T. Lyons
6584fb23e3 Add extension licensing documentation (#39373)
Release Notes:

- N/A
2025-10-02 14:43:19 +00:00
rufevean
d8698dffe3 project: Change Git repo automatically with change in file buffer (#36796)
### Summary

* Auto-activates the active repository when opening a buffer.
* Prepares branching for future support of a user choice (e.g.,
`auto_activate_repo_on_open` flag).

### Release Notes

* **Improved**: Opening a buffer now automatically updates the active
repository.
2025-10-02 10:42:08 -04:00
Junseong Park
bf44dc5ff5 Add missing GEMINI.md rule file for gemini-cli (#38885)
This pull request adds the missing **`GEMINI.md`** file, which will
serve as the rule/configuration file for **`gemini-cli`**.

Currently, the repository includes several rule files such as
**`.clinerules`**, **`.cursorrules`**, **`.rules`**, and
**`.windsurfrules`**. Adding **`GEMINI.md`** standardizes the
configuration structure and ensures that the specific rules for the
`gemini-cli` are properly documented alongside the others.


Release Notes:

- N/A
2025-10-02 09:47:29 -04:00
Bennet Bo Fenner
d85b6a1544 zeta2: Fix panic when running Zed without any worktrees (#39365)
Release Notes:

- N/A
2025-10-02 13:34:13 +00:00
localcc
702e618bba Fix local to WSL path conversion (#39301)
Release Notes:

- N/A
2025-10-02 15:20:48 +02:00
Ben Brandt
1029d3c301 acp: Alphabetize the external agents list (#39363)
Makes it a bit easier to find what you are looking for.
Also makes sure all of them are available in the settings bar.

Release Notes:

- N/A
2025-10-02 13:16:30 +00:00
Cole Miller
97f552876c agent: Fix Claude Code terminal login on Windows (#39325)
Remove the ad-hoc quoting we were doing before, which only works for
POSIX shells, in favor of using `Shell::WithArguments`.

Release Notes:

- N/A

---------

Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
2025-10-02 12:58:40 +00:00
Jason Lee
63c081d456 editor: Improve inlay color border (#39353)
Release Notes:

- Improved inlay color border to more clearly.

---

It was used `border_color`, that variable is often gray, which makes the
border look blurred when mixed with other inlay color backgrounds.

## Before

<img width="590" height="516" alt="SCR-20251002-qrkt"
src="https://github.com/user-attachments/assets/733a9a49-55ac-49aa-83fa-ebcfeece8129"
/>
<img width="590" height="516" alt="SCR-20251002-qrlt"
src="https://github.com/user-attachments/assets/34fa92bb-c754-4587-9e02-f3901dbc2fd6"
/>
<img width="590" height="516" alt="SCR-20251002-qrmw"
src="https://github.com/user-attachments/assets/b7f7abd8-e2c9-415d-9522-0801575b41c7"
/>
<img width="590" height="516" alt="SCR-20251002-qroa"
src="https://github.com/user-attachments/assets/8106d4c5-9bcd-4997-9644-ba680feadbce"
/>
<img width="590" height="516" alt="SCR-20251002-qrsf"
src="https://github.com/user-attachments/assets/6c9f5e58-e3a5-4363-a2d3-d6e5c4f40d17"
/>
<img width="590" height="516" alt="SCR-20251002-qsaw"
src="https://github.com/user-attachments/assets/706171be-af4f-4f19-ba97-ca2dab6ca15e"
/>

## After

<img width="663" height="541" alt="SCR-20251002-qqci"
src="https://github.com/user-attachments/assets/d586b5c3-2a10-4c8d-8403-2707e1e6c8bd"
/>
<img width="663" height="541" alt="SCR-20251002-qqdl"
src="https://github.com/user-attachments/assets/4adbc2a1-3763-4c6f-b1ef-61ef30652079"
/>
<img width="663" height="541" alt="SCR-20251002-qqev"
src="https://github.com/user-attachments/assets/d7d9dcfa-82db-4e3d-ae99-add493b3ebc2"
/>
<img width="663" height="541" alt="SCR-20251002-qqfs"
src="https://github.com/user-attachments/assets/4e910140-9de1-4a10-b2ca-aa0a8b335fad"
/>
<img width="663" height="541" alt="SCR-20251002-qqhb"
src="https://github.com/user-attachments/assets/ea16baee-3015-4899-af99-afed2a5b1dd3"
/>

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-10-02 12:49:41 +00:00
Bartosz Kaszubowski
6970ab2040 markdown_preview: Stylize links using accented text color (#39149)
# How

Emphasize links in Markdown Preview text using accented text color. 

> [!note]
> I have chosen the accent color for links since it was looking fine
with all bundled by default themes, but I'm happy to alter the color to
use different theme value, if you have better candidates.

Release Notes:

- Stylize links using accented text color in Markdown Preview

# Preview

### Before

<img width="1606" height="1066" alt="Screenshot 2025-09-29 at 22 19 38"
src="https://github.com/user-attachments/assets/59b6ee72-4523-42fb-a468-9c694d30b5df"
/>

### After
<img width="1652" height="1066" alt="Screenshot 2025-09-29 at 22 18 20"
src="https://github.com/user-attachments/assets/e00e3742-6435-4c1d-aaaa-e6332719db17"
/>
<img width="1652" height="1066" alt="Screenshot 2025-09-29 at 22 18 47"
src="https://github.com/user-attachments/assets/a1b76f4a-c4d2-4ca8-ae3c-fc4dc5d55e01"
/>

**Release notes**

<img width="2090" height="582" alt="Screenshot 2025-09-29 at 22 36 33"
src="https://github.com/user-attachments/assets/81d6df12-83bd-4794-b71e-5a1fd40f0140"
/>
<img width="2090" height="582" alt="Screenshot 2025-09-29 at 22 40 41"
src="https://github.com/user-attachments/assets/aa820767-b82b-42a5-aa5b-b0d3d22ac5e3"
/>
2025-10-02 09:39:18 -03:00
Enger Jimenez
e42dfb4387 Add more selection options to app menus (#39262)
## Summary

The purpose of this pull request is to add new menu items for the menu
bar as mentioned on this [discussion or feature
request](https://github.com/zed-industries/zed/discussions/28153#discussion-8169826).

The actions are already supported by the command palette, but not
available on the `MenuBar`.

## Screenshot

<img width="498" height="392" alt="image"
src="https://github.com/user-attachments/assets/8ad0e836-8295-4b46-a67a-0edf1408ad59"
/>

Release Notes:

- Added `SelectPrevious` and `SelectAllMatches` items to the `Selection`
app menu.
2025-10-02 11:21:25 +02:00
Anthony Eid
ec202a26c8 settings ui: Add basic setting page fields to UI (#39343)
This PR starts the process of adding each setting field manually to
their respective page in the UI and organizes user/project fields as
well. The next major step is implementing a numeric stepper component,
and handling discriminate union enums as well.

I also did some minor polish in this PR as well
- Switches now use accent color
- Fixed text input rendering with zero width 
- Made setting pages scrollable 
- Set drop down context menu style to outline

Release Notes:

- N/A

---------

Co-authored-by: dino <dinojoaocosta@gmail.com>
2025-10-02 09:04:02 +00:00
Ben Brandt
f17096879c agent: Update shell path in system prompt to match the terminal we give it (#39344)
In the ACP changes, we changed how terminals are created for the agent,
and so the system prompt was putting in the system shell instead of the
default one, potentially causing confusion for the model.

These are now in sync, so this will hopefully alleviate issues people
were seeing, as well as use a more standard shell to increase the
likelihood of successful model tool calls.

Release Notes:

- agent: Align default shell path in system prompt with the actual path
it is given
2025-10-02 09:01:47 +00:00
Mario Kozjak
fb343a7743 Add support for macOS' "Do Nothing" window setting (#39311)
Fixes titlebar double-click behavior to properly handle the macOS system
setting when "Do Nothing" is selected in System Settings > Desktop &
Dock > "Double-click a window's title bar to".

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

Release Notes:

- Fixed macOS Do Nothing window double click setting not being
respected.
2025-10-02 08:51:10 +02:00
Piotr Osiewicz
a49b2d5bf8 project panel: Make updates asynchronous (#38881)
Closes #ISSUE

Release Notes:

- project panel: Revamped how project panel entries are refreshed, which
should lead to a significantly smoother experience when working in large
projects.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-10-02 11:40:09 +05:30
Michael Sloan
b5d57598b6 Add an action for that runs a sequence of actions (#39261)
Thanks to @Zertsov for #37932 which caused me to consider this
implementation approach.

One known issue with this is that it will not wait for actions that do
async work to complete. Supporting this would require quite a lot of
code change. It also doesn't affect the main usecase of sequencing
editor actions, since few are async.

Another caveat is that this is implemented as an action handler on
workspace and so won't work in other types of windows. This seems fine
for now, since action sequences don't seem useful in other window types.
The command palette isn't accessible in non-workspace windows.

Alternatives considered:

* Add `cx: &App` to `Action::build`. This would allow removal of the
special case in keymap parsing. Decided not to do this, since ideally
`build` is a pure function of the input json.

* Build it more directly into GPUI. The main advantage of this would be
the potential to handle non-workspace windows. Since it's possible to do
outside of GPUI, seems better to do so. While some aspects of the GPUI
action system are pretty directly informed by the specifics of Zed's
keymap files, it seems to avoid this as much as possible.

* Bake it more directly into keymap syntax like in #37932. While I think
it would be good for this to be a primitive in the JSON syntax, it seems
like it would better fit in a more comprehensive change to provide
better JSON structure. So in the meantime it seems better to keep the
structure the same and just add a new action.

- Another reason to not bake it in yet is that this provides a place to
document the caveat about async actions.

Closes #17710

Release Notes:

- Added support for action sequences in keymaps. Example:
`["action::Sequence", [ ["editor::SelectLargerSyntaxNode",
"editor::Copy", "editor::UndoSelection"]`

---------

Co-authored-by: Mitchel Vostrez <mitch@voz.dev>
2025-10-02 00:06:53 -06:00
Richard Feldman
b9d9602074 Add codex acp (#39327)
Behind a feature flag for now.

<img width="576" height="234" alt="Screenshot 2025-10-01 at 9 34 16 PM"
src="https://github.com/user-attachments/assets/f4e717cf-3fba-4256-af69-e3ffb5174717"
/>

Release Notes:

- N/A
2025-10-02 03:52:06 +00:00
Alvaro Parker
cc19f66ee1 Fix background on rules library panel (#39319)
Closes #39318 

The rules panel on the rules library window was rendering a black
background when the `panel.background` property on the active theme had
some level of transparency (for example `1917264D` on `nightfox` theme).

<img width="1650" height="889" alt="image"
src="https://github.com/user-attachments/assets/6a8d124a-38da-4d01-817a-c289926bd39c"
/>

Left is before, right is after. The bug can be replicated by using
`theme_overrides` on settings:

```json
  "experimental.theme_overrides": {
    "panel.background": "#00000000",
    "background": "#ffffff"
  },
```

Release Notes:

- Fix "secondary" background on rules panel
2025-10-02 00:40:14 -03:00
Danilo Leal
62f90fec77 settings ui: Use the tree view item component and other design tweaks (#39329)
An initial pass at some foundational styles.

Release Notes:

- N/A
2025-10-02 03:35:10 +00:00
Danilo Leal
86ebb1890d ui: Add a TreeViewItem component (#39253)
A new (and very simple, for now) `TreeViewItem` component in the set.

<img width="500" height="1712" alt="Screenshot 2025-10-01 at 8  59@2x"
src="https://github.com/user-attachments/assets/c2de1585-7b42-4d20-a749-30d93898ae37"
/>

Release Notes:

- N/A
2025-10-02 01:19:11 +00:00
Jakub Konka
dd5099ac28 terminal: Log selected shell (#39295)
It is useful to double check in the logs which shell program is used by
Zed's terminal.

Release Notes:

- N/A
2025-10-02 02:48:58 +02:00
morgankrey
c95b88d546 Trial notes (#39321)
Closes #ISSUE

Release Notes:

- N/A
2025-10-01 16:10:47 -05:00
Joseph T. Lyons
c217f6bd36 Disable automation sending release notes to Kit (#39320)
These are now being crafted by hand, using the social media content we
do each Wednesday. I'm keeping the action around because we may want to
use this to automate publishing the hand-crafted emails in the future.

Release Notes:

- N/A
2025-10-01 20:41:17 +00:00
Anthony Eid
3314de8175 settings ui: Fix panic that occurred when changing the selected settings file (#39293)
The panic happened because navbar index wasn't updated when changing
files.

Release Notes:

- N/A

Co-authored-by: dino <dinojoaocosta@gmail.com>
2025-10-01 14:54:06 -04:00
Danilo Leal
6b907bd102 docs: Improve description on some agent settings (#39306)
Just a small wording refinement.

Release Notes:

- N/A
2025-10-01 14:44:36 -03:00
Danilo Leal
3cb933ddb1 docs: Update agent settings content (#39303)
Removes the preview note of the `buffer_font_size` used for agent panel
buffers, now that's available in stable as of 206.6. Also ended up
removing the "available in agent settings UI" thing because... that will
very soon not be needed to be called out.

Release Notes:

- N/A
2025-10-01 14:11:36 -03:00
Joseph T. Lyons
cf5362ffd1 Bump Zed to v0.208 (#39298)
Release Notes:

- N/A
2025-10-01 16:15:20 +00:00
Nia
74ac5ece6a perf: Functionality for CI integration (#39297)
Release Notes:

- N/A
2025-10-01 15:44:45 +00:00
Smit Barmase
f107708de3 title_bar: Show app menu even when signed out (#39296)
Partially closes #39271

Regressed in https://github.com/zed-industries/zed/pull/35375

<img width="282" height="188" alt="image"
src="https://github.com/user-attachments/assets/7e39d819-458a-47a1-96ca-e29797602e73"
/>

Release Notes:

- Fixed the top-right dropdown not showing when you're not signed in.
2025-10-01 21:13:00 +05:30
Max Brunsfeld
4940e53d23 Remove obsolete extensions and avoid loading or downloading them (#39254)
Release Notes:

- N/A
2025-10-01 08:42:51 -07:00
Nia
ab79fa440d gpui: Add a doc module with use examples (#39282)
cc @dvdsk 

Release Notes:

- N/A

---------

Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-10-01 17:03:08 +02:00
Smit Barmase
c9b7df4113 Revert "gpui: Respect macOS 'Do Nothing' window double-click setting" (#39291)
Reverts zed-industries/zed#39235

This broke double-click to zoom, even though it is configured in
settings.
2025-10-01 14:25:18 +00:00
localcc
f2df49764e Fix remote ping timing out (#39114)
Closes #38899 

Release Notes:

- N/A
2025-10-01 14:15:34 +02:00
Kirill Bulatov
77cc55656e Make test_terminal_eof less flaky and faster (#39281)
Release Notes:

- N/A
2025-10-01 12:14:06 +00:00
Alvaro Parker
1c85995ed7 Enable vim mode within the rules editor (#39244)
Release Notes:

- Enable vim mode on rule editor
2025-10-01 12:45:38 +02:00
Andreas Johansson
d1543f75b6 prompts: Improve inline assist prompt to reduce garbage from smaller models (#38278)
Closes #24412 and #19471

I tested both insertion and replacing with o3-mini and it failed with
the current prompt. With the updated prompt it does no longer return
`<document><rewrite_this>` or `{{REWRITTEN_CODE}}`

I have ensured the LLM Worker works with these prompt changes.

Release Notes:

- Improved prompting for the inline assistant
2025-10-01 09:07:57 +00:00
Lukas Wirth
fc0b249136 multi_buffer: Fix handling of ExcerptId::max() (#38887)
This removes a hack from `MultiBuffer::anchor_at` that works around
missing logic for handling `ExcerptId::max()` by implementing that said
missing logic.

Generally, `ExcerptId::min()` is already being handled correctly due to
how `Cursor` seeking works, we tend to seek to or beyond a seek target,
meaning `min` will always match the first excerpt as expected. `max` on
the other hand will always seek beyond the last excerpt resulting in no
excerpt being found, so any code path dealing with the excerpt sumtree
will have to specially check for this special excerpt ID to work
correctly.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-01 07:43:22 +00:00
Miao
01dbc68f82 editor: Preserve grapheme identity during rewrap (#39223)
Closes #39207

Release Notes:

- N/A
2025-10-01 09:37:23 +02:00
Mario Kozjak
e111acad33 gpui: Respect macOS 'Do Nothing' window double-click setting (#39235)
Fixes titlebar double-click behavior to properly handle the macOS system
setting when "Do Nothing" is selected in System Settings > Desktop &
Dock > "Double-click a window's title bar to".

Closes #39102

Release Notes:

- Fixed macOS `Do Nothing` window double click setting not be respected
2025-10-01 07:02:33 +00:00
Michael Sloan
c61409e577 zeta_cli: Avoid unnecessary rechecks in retrieval-stats (#39267)
Before this change, it would save every buffer and wait for diagnostics.
For rust analyzer this would cause a lot of rechecking and greatly slow
down the analysis

Release Notes:

- N/A

Co-authored-by: Agus <agus@zed.dev>
2025-10-01 06:31:27 +00:00
Conrad Irwin
1659fb81e7 Remove panic/crash reporting from collab (#39249)
Crashes have been going to Sentry since v0.201.x

Release Notes:

- N/A
2025-09-30 23:10:13 -06:00
Cole Miller
dd6c653fe9 agent: Fix terminal tool on Windows (#39260)
Seems like we don't want to escape the dollar sign in `$null`.

Release Notes:

- N/A
2025-09-30 23:19:32 -04:00
Ben Kunkle
a13e84a108 Fix bug in code action formatter handling (#39246)
Closes #39112

Release Notes:

- Fixed an issue when using code actions on format where specifying
multiple code actions in the same code actions block that resolved to
code actions from different language servers could result in conflicting
edits being applied and mangled buffer text.
2025-09-30 19:13:20 -04:00
Danilo Leal
1cac3e3e40 agent: Only show profile manage list item selection keybinding on the focused item (#39242)
Small update here that makes the UI simpler; there's no need to see the
keybinding in all the items you're not focused in.

| Before | After |
|--------|--------|
| <img width="1112" height="720" alt="Screenshot 2025-09-30 at 5  25@2x"
src="https://github.com/user-attachments/assets/e0362f98-889a-4007-a50d-8006dfb91787"
/> | <img width="1112" height="732" alt="Screenshot 2025-09-30 at 5  25
2@2x"
src="https://github.com/user-attachments/assets/b536b6ba-ef61-4891-8b2f-c27c40c70e4e"
/> |

Release Notes:

- N/A
2025-09-30 19:39:12 -03:00
David
9abe5811a5 agent: Make the profile switcher a picker (#39218)
Split off from https://github.com/zed-industries/zed/pull/39175

Adds a search bar to the 'profile' panel, so that we can switch profiles
without having to use the mouse or `tab` a few times

![2025-09-30 13 32
55](https://github.com/user-attachments/assets/2fc1f32b-9e25-4059-aae1-d195334a5fdb)

Release Notes:

- agent: Added the ability to search profiles in the agent panel's
profile picker.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-09-30 19:39:02 -03:00
Jakub Konka
97bd2846e9 windows: Fix breakpoints in WSL (#39196)
Release Notes:

- Fixed breakpoints not being hit in the debugger in WSL (or any
POSIX-target from WIndows host)
2025-10-01 00:17:45 +02:00
versecafe
e9244d50a7 docs: Remove macOS Tahoe runtime shaders callout (#39241)
@ConradIrwin No longer needed the issue appears to be fully resolved
after moving to MacOS Tahoe as the latest instead of only in dev beta

Release Notes:

- N/A
2025-09-30 15:47:01 -06:00
Conrad Irwin
83e5a3033e Don't run MCP servers for remote projects (#39243)
Closes #39213

Release Notes:

- Fixed a bug where we tried to run MCP servers in the remote project's
working directory on the local machine
2025-09-30 21:34:42 +00:00
Anthony Eid
94a4c0c352 settings ui: Fix bug with navbar index to page index translation (#39245)
This happened when search results completely filtered out a page above
the selected page index.

The old index was calculated based on the nav bar entry's position and
the count of root entries above it, this was wrong because root entries
could be filtered out with a search. Now the page index is saved when
building the navbar

Release Notes:

- N/A
2025-09-30 17:25:49 -04:00
Mikayla Maki
0f8693386a Update blade dependencies to the newest versions (#39233)
Release Notes:

- N/A
2025-09-30 13:51:09 -07:00
warrenjokinen
ed269b4467 Correct button label on basics_page.rs (Jetbrains to JetBrains) (#39240)
Correct typo, Jetbrains to JetBrains

Thanks for the opportunity to participate!

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-30 20:41:30 +00:00
Cole Miller
34ddf5466f agent: Remove stray separator in edited files UI (#39237)
Release Notes:

- N/A
2025-09-30 20:12:45 +00:00
Anthony Eid
a701388cb7 settings ui: Implement settings search (#38989)
Get a basic search implementation working in the settings ui and fix nav
bar toggling bugs.

Search functionality works by passing in each page and its items into
our fuzzy search crate and filtering out any non-matches. A page is a
match if any of its items are a match and an item is a match if its
title or description has a fuzzy score greater than zero.

In the future, a page section header will be filtered out if none of its
children has a match or it will show all its children on a match. The
team still has to decide what to do in that edge case, but that's the
last step until search is fully implemented for our initial launch.

Finally, I found some bugs in our nav bar toggling that occurred because
we weren't taking into account the index change that occurred when
toggling an element with children that is above the selected nav bar
entry. I added tests to cover those edge cases as well.

Release Notes:

- N/A

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-09-30 16:12:13 -04:00
Bennet Bo Fenner
29afc0412e worktree: Remove unwrap in BackgroundScanner::update_ignore_status (#39191)
We've seen this panic come up in the last two weeks, which might be
caused by #33592. However, we are not sure what paths can cause this
`unwrap()` to fail. Therefore adding some logging around this, so that
the next time someone opens a bug report we can further diagnose the
issue.

Fixes ZED-1F6

Release Notes:

- Fixed an issue where Zed could crash when including specific paths in
a global `.gitignore` files
2025-09-30 22:01:45 +02:00
Kirill Bulatov
e65a9291ef Add basic shell tests (#39232)
Release Notes:

- N/A

---------

Co-authored-by: Lukas Wirth <lukas@zed.dev>
2025-09-30 22:45:36 +03:00
Lukas Wirth
a53faff412 terminals: Remove (now) incorrect alacritty workaround for task spawning (#39230)
Closes #39228

Release Notes:

- Fixed venv activation failing with powershell
2025-09-30 18:36:20 +00:00
Lukas Wirth
074cb88036 acp_thread: Skip git pagination on windows (#39229)
Release Notes:

- Fixed agents running git commands with pagination enabled

Co-authored-by: Cole Miller <cole@zed.dev>
2025-09-30 18:10:04 +00:00
Lukas Wirth
67ebb1f795 task: Fix ShellBuilder::redirect_stdin_to_dev_null constructing invalid commands on windows (#39227)
Release Notes:

- Fixed agents not being able to use the terminal tool with powershell

Co-authored-by: Cole Miller <cole@zed.dev>
2025-09-30 17:55:46 +00:00
Anthony Eid
ace617037f debugger: Fix python debug scenario not showing up in code actions (#39224)
The bug happened because the Python locator was checking for a quote
before the ZED task variable. Removing that part of the check fixed the
issue.

Closes #39179 

Release Notes:

- Fix Python debug tasks not showing up in code actions or debug picker
2025-09-30 13:38:01 -04:00
Mikayla Maki
43061b6b16 Add SettingsFile APIs to SettingsStore (#39129)
Closes #ISSUE

Adds a couple functions to the `SettingsStore`:
- `get_value_from_file`: Gets a value from a given settings file
(`Local`, `User`, etc) and if the value isn't found in the requested
file, walks the known settings files in the order in which they are
merged to find the settings value in lower precedence settings files
(i.e. if value not set anywhere will always return default value)
- `get_overrides_for_field`: Returns a list of settings files where a
given setting is set that have higher precedence than the passed in
file. e.g. passing in user will result in project settings files where
the value is set being returned.

Additionally changes the default for the `project_name` setting to
uphold the rules we are attempting to enforce on the settings, namely:
- All settings fields should be of the form `Option<T>`
- `None` (or `null` in JSON) should never be a meaningful value

Follow up PRs will handle implementing a function to write to an
arbitrary settings file, and passing through metadata to the above
functions to control how overrides are determined for more complicated
cases like `SaturatingBool` (`disable_ai`) and `ExtendingVec`

Release Notes:

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

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Anthony <anthony@zed.dev>
2025-09-30 17:08:06 +00:00
Ben Brandt
e23e976e58 acp: Bump minimum Claude Code version (#39217)
There was an issue with login after the migration to the new anthropic
package. This makes sure folks are migrated to a known working version
(though the latest version also now works on old versions)

Release Notes:

- N/A

Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2025-09-30 15:37:22 +00:00
Tim Vermeulen
0266a995aa Use the alt modifier when going to a definition with cmd-click (#38148)
I don't totally follow how the `cmd_click_reveal_task` function works,
but it branches on whether `self.hovered_link_state` exists and contains
any links, and in case it doesn't, it doesn't use `modifiers.alt` for
deciding where to navigate. This PR addresses that.

The problem I've been having is that cmd-alt-click sometimes behaves as
cmd-click, i.e. it navigates to the definition in the current pane. This
appears to happen whenever I cmd-alt-click while the symbol I'm hovering
over isn't underlined, possibly when I click too quickly?

An alternative way to reliably reproduce this is to cmd-alt-click on a
symbol without letting go of cmd and alt and without moving the cursor.
Now the symbol is no longer underlined (and the hover preview has
disappeared as well), so clicking again (while still holding cmd and
alt) goes to the definition in the current pane:


https://github.com/user-attachments/assets/34003e01-fd95-4741-8a7d-6240d1c5a495

Release notes:

- Fixed a bug that caused cmd-alt-click to sometimes go to the
definition in the current pane

Co-authored-by: Joseph T. Lyons <JosephTLyons@gmail.com>
2025-09-30 11:35:18 -04:00
Danilo Leal
9741e9ab8b rules library: Improve delineation of default and non-default rules (#39209)
Closes https://github.com/zed-industries/zed/issues/39183

This PR adds UI improvements to clarify the concept of "default rules"
and how they separate from regular rules. This is mostly motivated by
the issue linked above, where it clarified that the star icon was
communicating a "favoriting" affordance, which is not correct with how
rules work in Zed. When you tag/attach a rule as default, it will always
be included in every prompt, together with the agent's system prompt and
project rules (if they exist).

Hopefully, this will make understanding better. Here's how it looks like
now?


https://github.com/user-attachments/assets/435d3af7-e8a6-4646-8f00-94a409bd5f42

Release Notes:

- Improve rules library UI to better communicate the concept of default
rules vs. regular rules.
2025-09-30 12:27:23 -03:00
Danilo Leal
3f31fc2874 agent: Fix keybinding to deny running a command (#39214)
Despite how great `cmd-d` as a keybinding is, that was not working as it
was conflicting with an editor keybinding:


https://github.com/user-attachments/assets/2ea8665b-7008-4f0a-9426-8d31d379ee1c

This PR changes it to `cmd-alt-z`, which is the best "remove/fix"-type
of keybinding I could find that doesn't conflict with anything else.
Ideally, we'd use either the D, N, or R letters for "deny", "no", and
"reject", but unfortunately, none of them are nicely available in this
context...


Release Notes:

- agent: Fix keybinding to deny running a command
2025-09-30 12:27:09 -03:00
Conrad Irwin
6c50fd6de9 Remove "integer" from font size docs (#39215)
Fixes #38765

Release Notes:

- N/A
2025-09-30 15:15:40 +00:00
Agus Zubiaga
df43a2d3b1 zeta2 cli: Include section ranges in new full output format (#39203)
Release Notes:

- N/A

---------

Co-authored-by: Oleksiy Syvokon <oleksiy.syvokon@gmail.com>
2025-09-30 14:30:13 +00:00
Ben Brandt
35749e99e5 acp: Notify of latest agent version only after successful download (#39201)
Before we would notify the user even if the download failed. We also
we're overwriting the directory, which means a user could be stuck in a
loop if a previous download failed

Release Notes:

- acp: Fix user seeing update prompt in a loop because of a previous
failed download
2025-09-30 13:46:09 +00:00
Joseph T. Lyons
e965c43703 Remove issue response action (#39200)
This action has consistently failed to run for many months on end, so we
haven't been relying on it.

Release Notes:

- N/A
2025-09-30 13:14:55 +00:00
张小白
14fc726cae windows: Fix ssh reporting wrong password even it's actually correct (#38263)
Closes #34393

Currently, we’re using `zed.exe --askpass` kind of like an `nc`
substitute, it prints out the SSH password to stdout with something like
`println!("user-pwd")`. `ssh.exe` then reads the password from stdout so
it can establish the connection.

The problem is that in release builds we set `subsystem=windows` to
avoid Windows spawning a black console window by default. The side
effect is that `zed.exe` no longer has a stdout, so `ssh.exe` can’t read
the password.

Through testing, I confirmed that neither allocating a new console for
`zed.exe` nor attaching it to the parent process’s stdout resolves the
issue. As a result, this PR updates the implementation to use `cli.exe
--askpass` instead.

TODO:

- [ ] Check that the `cli` path is correct on macOS
- [ ] Check that the `cli` path is correct on Linux

Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-09-30 21:03:06 +08:00
张小白
4f95186b53 windows: Fix auto-update for conpty.dll (#39178)
This PR is a follow-up to #39090 and addresses two issues:

* Moves `conpty.dll` and `OpenConsole.exe` out of the `bin` folder to
prevent other programs from using them.
* Updates these files only after Zed exits, avoiding update failures due
to file locks.


Release Notes:

- N/A

---------

Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
2025-09-30 21:02:46 +08:00
Sergei Zharinov
33f44009de gpui: Respect font smoothing on macOS (#39197)
- Closes #38847
- See also: #37622 and #38467

Release Notes:

- Fonts are now rendered in accordance with the `AppleFontSmoothing`
setting.
2025-09-30 13:01:25 +00:00
Lukas Wirth
9d895c5ea7 git_ui: Fix blame avatars using wrong config (#39195)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-30 12:32:48 +00:00
Lukas Wirth
0811d48a7a diagnostics: Reduce cloning of DiagnosticEntry (#39193)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-30 11:41:49 +00:00
Remco Smits
d8cafdf937 markdown: Add support for HTML heading elements (#38590)
This PR adds support for HTML heading (h1, h2, h3, h4, h5, h6) elements.

**Before**
<img width="1440" height="556" alt="Screenshot 2025-09-21 at 11 05 18"
src="https://github.com/user-attachments/assets/6e7241a5-be1c-4018-ba04-f29058f97941"
/>

**After**
<img width="1436" height="598" alt="Screenshot 2025-09-21 at 10 58 12"
src="https://github.com/user-attachments/assets/3f74b5f7-6c35-41db-989b-fcaaede264b5"
/>

cc @SomeoneToIgnore

Release Notes:

- Markdown: Added support for HTML `heading` elements
2025-09-30 12:39:22 +02:00
Kirill Bulatov
95190a2034 Add a test on a with_timeout util function (#39187)
Release Notes:

- N/A

Co-authored-by: Lukas Wirth <lukas@zed.dev>
2025-09-30 10:07:23 +00:00
hrou0003
49335d54be Pane tabs: Scroll entire new tab into view (#36827)
The state of the child bounds is not up-to-date when `scroll_to_item`
gets triggered, causing the new tab to not scroll completely into view.

Closes #36317 

Release Notes:

- Fix an issue where a new tab is only partially visible on creation.
2025-09-30 11:04:34 +02:00
Kirill Bulatov
624e448492 Remove bold inlay hints style from all other theme variants (#39177)
Follow-up of https://github.com/zed-industries/zed/pull/39105

Release Notes:

- N/A

Co-authored-by: Lukas Wirth <lukas@zed.dev>
2025-09-30 08:27:24 +00:00
Piotr Osiewicz
bf9dd6bbef python: Fix user settings not getting passed on for Ty (#39174)
Closes #39144

Release Notes:

- python: Fixed user settings not being respected with Ty language
server.
2025-09-30 08:19:23 +00:00
Michael Sloan
6af385235d zeta_cli: Add retrieval-stats command for comparing with language server symbol resolution (#39164)
Release Notes:

- N/A

---------

Co-authored-by: Agus <agus@zed.dev>
2025-09-30 08:06:31 +00:00
Lukas Wirth
cc19387853 git_ui: Render avatars in git blame gutter (#39168)
Release Notes:

- Added setting to render avatar in blame gutter
2025-09-30 06:55:09 +00:00
Dmitry Nefedov
5922f4adce themes: Fix Ayu theme comment colors (#39131)
Closes https://github.com/zed-industries/zed/issues/39122

Currently comment colors in Ayu theme do not work as expected and hard
to differentiate. In my understanding something is really wrong how zed
interprets rgba hex color codes, for example:

|  #5c677300 | #5c6773ff |
| ------------- | ---------- |
| <img width="134" height="38" alt="image"
src="https://github.com/user-attachments/assets/c9f1f618-958e-4fe9-a44a-636681d2f418"
/> | <img width="117" height="32" alt="image"
src="https://github.com/user-attachments/assets/78eac6b3-aecd-4be1-83d4-42590604c3a6"
/> |

This PR works around this by using comment color codes from
[ayu-vim](https://github.com/ayu-theme/ayu-vim). Maybe I am not
understanding how RGBA works, but in my opinion underlying issue should
be solved.

Release Notes:

- N/A
2025-09-29 20:50:03 -03:00
Dino
cac920d992 vim: Add support for ignorecase and noignorecase options (#37459)
Update the list of supported options in vim mode so that the following
are now available:

- `:set ignorecase`
- `:set noignorecase`
- `:set ic`
- `:set noic`

This controls whether the case-sensitive search option is disabled or
enabled when using the buffer and project searches, with `ignorecase`
disabling the search option and `noignorecase` enabling it.

Release Notes:

- Added support for `:set ignorecase` and `:set noignorecase` in vim
mode

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-09-29 22:43:05 +00:00
Michael Sloan
773850f477 zeta2: Use bounded parallelism for tree-sitter indexing + await completion in zeta_cli (#39147)
Also skips indexing files that don't have a suffix that indicates a
known language, and skips when the language doesn't have an outline
grammar.

Release Notes:

- N/A

---------

Co-authored-by: Agus <agus@zed.dev>
2025-09-29 22:15:00 +00:00
AidanV
9c60bc3837 vim: Add vim counts and vim shortcuts to project_panel (#36653)
Closes #10930 
Closes #11353

Release Notes:

- Adds commands to project_panel
  - `ctrl-u` scrolls the project_panel up half of the visible entries
  - `ctrl-d` scrolls the project_panel down half of the visible entries
  - `z z` scrolls current selection to center of window
  - `z t`  scrolls current selection to top of window
  - `z b` scrolls current selection to bottom of window
  - `{num} j` and `{num} k` now move up and  down with a count
2025-09-29 15:53:59 -06:00
warrenjokinen
fbb4dcf2b1 Update a Help menu item in app_menus.rs with "Locally" (#39151)
Add the single word "Locally" to clarify where the info is coming from,
(and that you don't need to be online.)

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-29 21:21:53 +00:00
Nia
2ccadc7f65 perf: Doc fixes (#39150)
Release Notes:

- N/A
2025-09-29 21:16:00 +00:00
Nia
80989d6767 treesitter: Bump to 0.25.10 and fix Go tests (#39138)
Closes #29827

Release Notes:

- Fixed tree-sitter possibly crashing on certain grammars
2025-09-29 20:58:05 +00:00
David Kleingeld
719013dae6 Add YankEndOfLine action (#39143)
Since 2021 Neovim remaps Y to $y (1). DO the same in zed through a new action `YankToEndOfLine`. 

1: https://github.com/neovim/neovim/pull/13268

Release Notes:

- Added vim::YankToEndOfLine action which copies from the cursor to the end of the line excluding the newline. We bind it to Y by default in the vim keymap.
2025-09-29 20:32:57 +00:00
Jakub Konka
8af3f583c2 Better conpty (#39090)
Closes #22657
Closes #37863

# Background

Several users have noted that the terminal shipped with Zed on Windows
is either misbehaving or missing several features including lack of
consistent clearing behaviour. After some investigation which included
digging into the Microsoft Terminal project and VSCode editor, it turns
out that the pseudoconsole provided by Windows OS is severely outdated
which manifests itself in problems such as lack of clearing behaviour,
etc. Interestingly however, neither MS Terminal nor VSCode exhibit this
limitation so the question was why. Enter custom `conpty.dll` and
`OpenConsole.exe` runtime. These are updated, developed in MS Terminal
tree subprojects that aim to replace native Windows API as well as
augment the `conhost.exe` process that runs by default in Windows. They
also fix all the woes we had with the terminal on Windows (there is a
chance that ctrl-c behaviour is also fixed with these, but still need to
double check that this is indeed the case). This PR ensures that Zed
also benefits from the update pseudoconsole API.

# Proposed approach

It is possible to fork MS Terminal and instrument the necessary
subprojects for Rust-awareness (using `cc-rs` or otherwise to compile
the C++ code and then embed it in Rust-produced binaries for easier
inclusion in projects) but it comes at a cost of added complexity,
maintenance burden, etc. An alternative approach was proposed by
@reflectronic to download the binary from the official Nuget repo and
bundle it for release/local use. This PR aims to do just that.

There are two bits to this PR:
1. ~~when building Zed locally, and more specifically, when the `zed`
crate is being built, we will strive to download and unpack the binaries
into `OUT_DIR` provided by `cargo`. We will then set
`ZED_CONPTY_INSTALL_PATH=${OUT_DIR}/conpty` and use it at runtime in Zed
binary to tweak the loader's search path with that additional path. This
effectively ensures that Zed built from source on Windows has full
terminal support.~~ EDIT: after several discussions offline, we've
decided that keeping it minimal will serve us best, meaning: when
developing locally it is up to the developer of Zed to install
`conpty.dll` and put it in the loader's search path.
2. when bundling Windows release, we will download and unpack the nuget
package into Zed's bundle which will ensure it is installed in the same
directory as Zed by the installer.

**Note** I realise that 1. may actually not be needed - instead we could
leave that bit for the user who wants to run Zed from source to ensure
that they have `conpty.dll` in the loader's search path. I'd love to
hear opinions on this!

Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-09-29 22:08:35 +02:00
Conrad Irwin
f1d80b715a Fix panic in UnwrapSyntaxNode (#39139)
Closes #39139
Fixes ZED-1HY

Release Notes:

- Fixed a panic in UnwrapSyntaxNode in multi-buffers
2025-09-29 14:01:34 -06:00
Tim Vermeulen
42ef3e5d3d editor: Make cmd-alt-click behavior more consistent (#38733)
Fixes two inconsistencies around the behavior of cmd-alt-click that mess
with my VSCode muscle memory:
- The definition is opened in a pane to the right of the current pane,
unless there exists an adjacent pane to the left and not to the right,
in which case it's opened in the pane on the left
- In case Go to Definition needs to open a multibuffer, cmd-alt-click
does not open it in an existing pane to the right of the current pane,
it always creates a new pane directly to the right of the current pane

This PR irons out this behavior by always going to the definition in the
pane directly to the right of the current one, creating one only if one
doesn't yet exist.

If changing `Workspace::adjacent_pane` to not consider an existing pane
to the left is undesirable then that logic could be moved somewhere
else, or we can make it user configurable if necessary. Also happy to
split this PR up if either of these changes is controversial 🙂

Before:


https://github.com/user-attachments/assets/395754cd-6ecb-40bf-ae61-ee8903eed4ae

After:


https://github.com/user-attachments/assets/002797b1-51a7-48e5-a8d0-100d3a5049eb

Release Notes:

- Made the behavior of cmd-alt-click more consistent

---------

Co-authored-by: Joseph T. Lyons <JosephTLyons@gmail.com>
2025-09-29 19:30:06 +00:00
Miao
90ea252c82 vim: Disregard non-text content on system clipboard for yanking (#39118)
Closes #39086

Release Notes:

- Fixed the vim problem that image clipboard content overrides the
unnamed register and produces an empty paste.
2025-09-29 13:25:45 -06:00
warrenjokinen
6e5ff6d091 Update onboarding_modal.rs with https protocol (#39136)
Update onboarding_modal.rs with https protocol

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-29 19:18:57 +00:00
warrenjokinen
04216a88f3 Update http link to https in onboarding_modal.rs (#39135)
Use https protocol

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-29 19:15:22 +00:00
Richard Feldman
3ae65153db Default to Sonnet 4.5 in BYOK (#39132)
<img width="381" height="204" alt="Screenshot 2025-09-29 at 2 29 58 PM"
src="https://github.com/user-attachments/assets/c7aaf0b0-b09b-4ed9-8113-8d7b18eefc2f"
/>


Release Notes:

- Claude Sonnet 4.5 and 4.5 Thinking are now the recommended Anthropic
models
2025-09-29 18:56:03 +00:00
mgabor
ffc9060607 Fix file path quoting in Deno test task configuration (#39134)
Closes https://github.com/zed-extensions/deno/issues/14

Release Notes:

- N/A
2025-09-29 20:44:49 +02:00
Richard Feldman
4fc4707cfc Add Sonnet 4.5 support (#39127)
Release Notes:

- Added support for Claude Sonnet 4.5 for Bring-Your-Own-Key (BYOK)
2025-09-29 14:21:58 -04:00
morgankrey
8662025d12 Add Sonnet 4.5 to docs (#39125)
Closes #ISSUE

Release Notes:

- N/A
2025-09-29 12:17:49 -05:00
Finn Evers
ceddd5752a docs: Remove debugger cal.com link (#39124)
Closes #39094

Release Notes:

- N/A
2025-09-29 19:08:20 +02:00
David Kleingeld
20166727a6 Revert "Replace linear resampler with fft based one" (#39120)
Reverts zed-industries/zed#39098

robot voices all over
2025-09-29 16:50:17 +00:00
George Waters
6e80fca0d5 Order venvs by distance to worktree root (#39067)
This is a follow up to #37510 and is also related to #38910.

Release Notes:

- Improved ordering of virtual environments, sort by distance to
worktree root.
2025-09-29 16:25:41 +00:00
George Waters
778ca84f85 Fix selecting and deleting user toolchains (#39068)
I was trying to use the new user toolchains but every time I clicked on
one I had added, it would delete it from the picker. Ironically, it
wouldn't delete it permanently when I tried to by clicking on the trash
can icon. Every time I reopened the workspace all user toolchains were
there.

Release Notes:

- Fixed selecting and deleting user toolchains.
2025-09-29 18:08:47 +02:00
Lukas Wirth
ebdc0572c6 zed: Add binary type to sentry crash tags (#39107)
This allows to filter by main zed binary or remote server crashes, as
well as easily tell whether a crash happened in a remote-server binary
or not.

Release Notes:

- N/A
2025-09-29 09:03:00 -07:00
Bennet Bo Fenner
cda48a3a1c zeta2: Allow provider to suggest edits in different files (#39110)
Release Notes:

- N/A

---------

Co-authored-by: Agus Zubiaga <agus@zed.dev>
2025-09-29 15:48:58 +00:00
Bennet Bo Fenner
b7f9fd7d74 zeta2: Do not include empty edit events (#39116)
Release Notes:

- N/A
2025-09-29 15:45:23 +00:00
Lukas Wirth
98ab118526 git: Work around windows command length limit message fetching (#39115)
Release Notes:

- Fix git blame failing on windows for files with lots of blame entries
2025-09-29 15:29:42 +00:00
tsjason
1e70a1a4ce Improve recent projects search result ordering (#38795)
Previously, search results were sorted solely by candidate_id
(preserving original order from the database), which could result in
less relevant matches appearing before better ones.

This change sorts results primarily by fuzzy match score (descending),
with candidate_id as a tiebreaker for equal scores. This ensures that
better matches appear first while preserving recency order among items
with identical scores.

Example improvement:
- Searching for 'pica' will now rank 'picabo' higher than scattered
matches like 'project-api, project-chat'
- Consecutive character matches are prioritized over scattered matches
across multiple path segments

Release Notes:
- Improved project search relevance by ranking results using match score
instead of insertion order.
2025-09-29 17:15:47 +02:00
AidanV
163219af35 editor: Make kill ring cut at EOF a no-op (#39069)
Release Notes:

- Emacs's kill ring cut at the end of the last line of the file will now
no-op instead of cutting the entire line
2025-09-29 08:55:38 -06:00
Miao
f96fd928d7 git: Fix git modal and panel amend tooltip (#39008)
Closes #38783

Release Notes:

- Fixed the amend button tooltip shortcut in Git panel and modal.
2025-09-29 19:58:14 +05:30
Ben Kunkle
9aa5817b85 Fix panic due to ThemeRegistry::global call in remote server (#39111)
Fixes ZED-1PV

Note: Nightly only panic

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-29 14:24:17 +00:00
David Kleingeld
28cc39ad56 Replace linear resampler with fft based one (#39098)
Replaces the use of Rodio's basic linear resampler with an fft based
resampler from the rubato crate. As we are down-sampling to the minimal
(transparent) sample rate for human speech (16kHz) any down-sampling
artifact will be noticeable.

This also refactors the rodio_ext module into sub-models as it was
getting quite long.

Release Notes:

- N/A
2025-09-29 16:22:45 +02:00
warrenjokinen
0da3f9ffda docs_preprocessor: Update deprecated actions message (#39062)
Minor correction to label (string) used when generating big table of
actions.

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-29 10:11:48 -04:00
Lukas Wirth
f2efe78feb editor: Shrink size of Inlay slightly (#39089)
And some other smaller cleanup things I noticed while reading through
some stuff

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-29 15:33:21 +02:00
Danilo Leal
ed7217ff46 ui prompt: Adjust UI and focus visibility (#39106)
Closes https://github.com/zed-industries/zed/issues/38643

This PR adds some UI improvements to the Zed replacement of the system
dialog/prompt, including better visibility of which button is currently
focused.

One little design note, though: because of a current (and somewhat
annoying) constraint of button component, where we're only drawing a
border when its style is outlined, if I kept them horizontally stacked,
there'd be a little layout shift now that I'm toggling styles for better
focus visibility. So, for this reason, I changed them to be vertically
stacked, which matches the macOS design and avoids this problem. Maybe
in the future, we'll revert it back to being `flex_row` because that
ultimately consumes less space.


https://github.com/user-attachments/assets/500c840b-6b56-4c0c-b56a-535939398a7b

Release Notes:

- Improve focus visibility of the actions within Zed's UI system prompt.
2025-09-29 10:09:31 -03:00
Lukas Wirth
f9fb389f86 themes: Set font_weight to null for syntax.hint (#39105)
Since https://github.com/zed-industries/zed/pull/36219 we now render
inlay hints as bold due to this.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-29 12:54:12 +00:00
Bartosz Kaszubowski
632e569c5f markdown_preview: Improve table elements appearance (#39101)
# How

Eliminate double borders between Markdown rows and cells, restyle
headers relying on background color alteration instead of thicker pixel
border.

Release Notes:

- Improved table elements appearance in Markdown Preview

# Preview

### Before

<img width="1206" height="594" alt="Screenshot 2025-09-29 at 13 28 23"
src="https://github.com/user-attachments/assets/9fe2b8a8-13e1-4052-9e97-34559b44f2d0"
/>

### After

<img width="1206" height="578" alt="Screenshot 2025-09-29 at 13 28 40"
src="https://github.com/user-attachments/assets/0b627ada-f287-436b-9448-92900d4bff59"
/>
2025-09-29 09:41:41 -03:00
Smit Barmase
0c71aa9f01 Bump tree-sitter-python to 0.25.0 (#39103)
- The fork with the patch is now included in 0.25.0
(7ff26dacd7).
- We no longer need `except*` as a keyword, which was added in
https://github.com/zed-industries/zed/pull/21389. It now highlights
correctly without explicitly mentioning it after
1b1ca93298.

Release Notes:

- N/A
2025-09-29 17:57:11 +05:30
Jowell Young
92a09ecf25 x_ai: Add support for tools and images with custom models (#38792)
After the change, we can add "supports_images", "supports_tools" and
"parallel_tool_calls" properties to set up new models. Our
`settings.json` will be as follows:
```json
  "language_models": {
     "x_ai": {
       "api_url": "https://api.x.ai/v1",
       "available_models": [
         {
           "name": "grok-4-fast-reasoning",
           "display_name": "Grok 4 Fast Reasoning",
           "max_tokens": 2000000,
           "max_output_tokens": 64000,
           "supports_tools": true,
           "parallel_tool_calls": true,
         },
         {
           "name": "grok-4-fast-non-reasoning",
           "display_name": "Grok 4 Fast Non-Reasoning",
           "max_tokens": 2000000,
           "max_output_tokens": 64000,
           "supports_images": true,
         }
       ]
     }
   }

```

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

Release Notes:

- xAI: Added support for for configuring tool and image support for
custom model configurations
2025-09-29 11:38:55 +00:00
Ben Brandt
bad96776cd acp: Add NO_PROXY if not set otherwise to not proxy localhost urls (#39100)
Since we might run MCP servers locally for an agent, we don't want to
use the proxy for those.
We set this if the user has set a proxy, but not a custom NO_PROXY env
var.

Closes #38839

Release Notes:

- acp: Don't run local mcp servers through proxy, if set
2025-09-29 11:34:52 +00:00
Kirill Bulatov
aa14980523 Mention pure style changes in the contributing docs (#39096)
Release Notes:

- N/A
2025-09-29 11:02:47 +00:00
warrenjokinen
12aba6193e docs: Fix minor typos in configuring-zed.md (#39048)
Fixed numbering under heading  Bottom Dock Layout

Closes #ISSUE

Release Notes:

- N/A
2025-09-29 13:12:07 +03:00
Bartosz Kaszubowski
720971e47b git_ui: Fix last commit UI glitching on panel resize (#39059)
# Why

Spotted that on Git Panel resize last commit UI part could glitch due to
commit message being wrapped into second line in certain situations.

# How

Force only one line for the last commit message in Git Panel via
`line_clamp`.

I have also remove manual `max-width` setting since it is controlled by
flex layout and gap setting no matter if there is an additional element
on the right or not.

Release Notes:

- Fixed last commit UI glitching on panel resize

# Preview

### Before


https://github.com/user-attachments/assets/9ce74f6f-d33c-4787-b7e4-010de8f0ffff

<img width="852" height="502" alt="Screenshot 2025-09-28 at 18 16 35"
src="https://github.com/user-attachments/assets/1131c73f-fe06-4d8e-adbb-5ce84ecf31e0"
/>

### After


https://github.com/user-attachments/assets/279b8c37-7ec9-4038-8761-197cba26aa83
2025-09-29 13:06:15 +03:00
Lukas Wirth
0a10e3e264 acp_thread: Fix terminal tool incorrectly redirecting stdin to /dev/null (#39092)
Closes https://github.com/zed-industries/zed/issues/38462

Release Notes:

- Fixed AI terminal tool incorrectly redirecting stdin to `/dev/null`
2025-09-29 09:43:50 +00:00
Xiaobo Liu
77854f4627 windows: Refactor shell environment capture to use new_smol_command (#39055)
Using `crate::command::new_smol_command` on the Windows platform will
not display the PowerShell window.

Closes #39052

Release Notes:

- N/A

Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>
2025-09-28 18:54:26 +02:00
Lukas Wirth
5ce7eda8d2 ui: Fix panic in highlight_ranges when given an oob index (#39051)
Fixes ZED-1QW

Release Notes:

- Fixed a panic when highlighting labels
2025-09-28 11:54:18 +00:00
Lukas Wirth
6d7a4c441b search: Fix panic in project search due to workspace double lease (#39049)
Fixes ZED-1K1

Release Notes:

- Fixed panic when spawning a new project search with include file only
filtering
2025-09-28 11:35:36 +00:00
Lukas Wirth
cc85a48de5 editor: Fix panic when syncing empty selections (#39047)
Fixes ZED-1KF

Release Notes:

- Fixed commit modal panicking in specific scenario
2025-09-28 11:01:16 +00:00
warrenjokinen
4cd839e352 Fix typo in search.rs (#39045)
Fixed confusing word

Release Notes:

- Fixed a typo in the tooltip for search case sensitivity.
2025-09-28 11:17:08 +02:00
Yang Gang
78098f6809 windows: Update Windows keymap (#38767)
Pickup the changes from #36550

Release Notes:

- N/A

---------

Signed-off-by: Yang Gang <yanggang.uefi@gmail.com>
Co-authored-by: 张小白 <364772080@qq.com>
2025-09-28 02:09:44 +08:00
warrenjokinen
4d2ff6c899 markup: Update yara.md (#39027)
Minor fixes / clarifications for two links in one markdown file

Release Notes:

- N/A
2025-09-27 19:43:19 +02:00
Lukas Wirth
6f5d1522cb git_ui: Allow splitting commit_view pane (#39025)
Release Notes:

- Allow splitting git commit view pane
2025-09-27 15:28:37 +00:00
Xiaobo Liu
682cf023ca windows: Implement shell environment loading for git operations (#39019)
Fixes the "failed to get working directory environment for repository"
error on Windows by implementing proper shell environment variable
capture.

Release Notes:

- Fixed failed to get working directory environment for repository

---------

Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>
2025-09-27 17:10:06 +02:00
Lukas Wirth
72948e14ee Use into_owned over to_string for Cow<str> (#39024)
This removes unnecessary allocations when the `Cow` is already owned


Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-27 14:50:10 +00:00
Cole Miller
a063a70cfb call: Play a different sound when a guest joins (#38987)
Release Notes:

- collab: A distinct sound effect is now used for when a guest joins a
call.
- collab: Fixed the "joined" sound being excessively loud when joining a
call that already has many participants.

---------

Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-09-27 09:20:55 -04:00
Cole Miller
687e22b4c3 extension_host: Use the more permissive RelPath constructor for paths from extensions (#38965)
Closes #38922 

Release Notes:

- N/A

---------

Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
2025-09-27 09:20:42 -04:00
loczek
e13b88e4bd snippets: Fix configure snippets not opening on remote workspaces (#38790)
Release Notes:

- Fixed `snippets: configure snippets` action not working on remote
workspaces
2025-09-27 11:01:04 +02:00
Bedis Nbiba
e1e9f78dc3 docs: Document config completion for Deno (#38993)
Closes #ISSUE

Release Notes:

- doc: document config completion for deno
2025-09-26 22:17:46 -04:00
Max Brunsfeld
0fe696bc7c Bump html extension version to 0.2.3 (#38997)
Release Notes:

- N/A
2025-09-26 23:23:00 +00:00
Lukas Wirth
ead38fd1be fsevent: Check CFURLCreateFromFileSystemRepresentation return value (#38996)
Fixes ZED-1T

Release Notes:

- Fixed a segmentation fault on macOS fervent stream creation
2025-09-26 22:28:52 +00:00
Lukas Wirth
fbdf5d4df4 editor: Do not panic on tab_size > 16, cap it at 128 (#38994)
Fixes ZED-1PT
Fixes ZED-1PW
Fixes ZED-1G2

Release Notes:

- Fixed Zed panicking when the `tab_size` is set higher than 16
2025-09-27 00:13:16 +02:00
Max Brunsfeld
837f282f1e html: Remove Windows workaround (#38069)
⚠️ Don't merge until Zed 0.205.x is on stable ⚠️ 

See https://github.com/zed-industries/zed/pull/37811

This PR updates the HTML extension, bumping the zed extension API to the
latest version, which removes the need to work around a bug where
`current_dir()` returned an invalid path on windows.

Release Notes:

- N/A
2025-09-26 12:14:54 -07:00
Xiaobo Liu
bd3cccea15 edit_prediction_button: Fix Copilot menu not updating after sign out (#38854)
The edit prediction button menu was displaying stale authentication
status due to capturing the Copilot status in a closure. After signing
out, the menu would still show "Sign Out" instead of "Sign In to
Copilot".

This change fixes the issue by reading the current Copilot status each
time the menu is displayed, ensuring the menu options are always
accurate.

Release Notes:

- Fixed Copilot AI menu not updating after sign out

Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>
2025-09-26 12:42:06 -06:00
justin talbott
d437bbaa0a Don't let ctrl-g clobber git panel keybindings in Emacs keymap (#37732)
i'm testing out zed, coming from emacs, and so i'm trying out the base
keymap for it. i noticed though that zed's default git keybindings don't
work when the gitpanel is open though, because of the top-level binding
of `ctrl-g` to cancel. my expectation is that the emacs-like keybindings
would work insofar as they don't clobber zed's defaults (which would
take precedence), but obviously i'll defer to others on this!

another option could be to use the `C-x v` keymap prefix that the emacs
built-in `vc` package uses, but it doesn't contain the same set of
bindings for git commands that zed has.
2025-09-26 12:13:35 -06:00
Conrad Irwin
114791e1a8 Revert "Fix arrow function detection in TypeScript/JavaScript outline (#38411)" (#38982)
This reverts commit 1bbf98aea6.

We found that #38411 caused problems where anonymous functions are
included too many times in the outline. We'd like to figure out a better
fix before shipping this to stable.

Fixes #38956

Release Notes:

- (preview only) revert changes to outline view
2025-09-26 13:54:52 -04:00
Martin Pool
d6fcd404af Show config messages from install-wild, install-mold (#38979)
Follows on from
https://github.com/zed-industries/zed/pull/37717#discussion_r2376739687

@dvdsk suggested this but I didn't get to it in the previous PR.

# Tested

```
; sudo rm /usr/local/bin/wild
; ./script/install-wild
Downloading from https://github.com/davidlattimore/wild/releases/download/0.6.0/wild-linker-0.6.0-x86_64-unknown-linux-gnu.tar.gz
Wild is installed to /usr/local/bin/wild

To make it your default, add or merge these lines into your ~/.cargo/config.toml:

[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=--ld-path=wild"]

[target.aarch64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=--ld-path=wild"]

```

```
; sudo rm /usr/local/bin/mold
; ./script/install-mold 2.34.0
Downloading from https://github.com/rui314/mold/releases/download/v2.34.0/mold-2.34.0-x86_64-linux.tar.gz
Mold is installed to /usr/local/bin/mold

To make it your default, add or merge these lines into your ~/.cargo/config.toml:

[target.'cfg(target_os = "linux")']
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
```

Release Notes:

- N/A
2025-09-26 16:47:38 +00:00
Bartosz Kaszubowski
7ad9ca9bcc editor: Replace hardcoded keystroke in Excerpt Fold Toggle tooltip (#38978)
# Why

I have recently corrected this tooltip content for macOS, but recently
have learnt that keystroke to text helpers already exist in the
codebase.

# How

Replace hardcoded keystroke for Excerpt Fold Toggle in Uncommitted
Changes tab.

> [!important]
> Should be merged after #38969 and #38971, otherwise it would be a
regression on macOS.

Release Notes:

- N/A

# Preview (stacked on mentioned above PRs)

<img width="618" height="248" alt="Screenshot 2025-09-26 at 17 43 53"
src="https://github.com/user-attachments/assets/cdc7fb74-e1d8-4a59-b847-8a8d2edd4641"
/>
2025-09-26 10:44:46 -06:00
Bartosz Kaszubowski
a55dff7834 ui: Fix Vim mode detection in keybinding to text helpers (#38971)
# Why

Refs:
* #38969

When working on the PR above I have spotted that keybinding to text
helpers incorrectly detects if Vim mode is enabled.

# How

Replace inline check with an existing `KeyBinding::is_vim_mode` method
in keybinding text helpers.

Release Notes:

- Fixed incorrect Vim mode detection in UI keybinding to text helpers.

# Test plan

Made sure that when Vim mode is not specified in settings file it
resolves to `false`, and correct keybindings are displayed, than I have
added the `"vim_mode": true,` line to my settings file and made sure
that keybindings text have changed accordingly.

### Before

<img width="712" height="264" alt="Screenshot 2025-09-26 at 16 57 08"
src="https://github.com/user-attachments/assets/62bc24bd-c335-420f-9c2e-3690031518c1"
/>

### After

<img width="712" height="264" alt="Screenshot 2025-09-26 at 17 13 50"
src="https://github.com/user-attachments/assets/e0088897-eb6b-4d7b-855a-931adcc15fe8"
/>
2025-09-26 10:18:49 -06:00
Bartosz Kaszubowski
6db621a1ed ui: Display option in lowercase in Vim mode keybindings (#38969)
# Why

Spotted that some tooltips include `alt` keystroke combination on macOS.

# How

Add missing `vim_mode` version definition of `Option` key to the
`keystroke_text` helper.

Release Notes:

- Fixed keystroke to text helper output for macOS `Option` key in Vim
mode

# Preview

### Before

<img width="712" height="264" alt="Screenshot 2025-09-26 at 16 57 08"
src="https://github.com/user-attachments/assets/d5daa37f-0da7-4430-91ea-4a750c025472"
/>

### After

<img width="712" height="264" alt="Screenshot 2025-09-26 at 16 56 21"
src="https://github.com/user-attachments/assets/5804ed39-9b1b-4028-a9c9-32c066042f4a"
/>
2025-09-26 10:18:31 -06:00
Kirill Bulatov
948b4379df Stop using linear color space on Linux Blade renderer (#38967)
Part of https://github.com/zed-industries/zed/issues/7992
Closes https://github.com/zed-industries/zed/issues/22711

Left is main, right is patched.

* default font

<img width="3862" height="2152" alt="image"
src="https://github.com/user-attachments/assets/c4e3d18a-a0dd-48b8-a1f0-182407655efb"
/>
<img width="3862" height="2152" alt="image"
src="https://github.com/user-attachments/assets/6eea07e7-1676-422c-961f-05bc72677fad"
/>


<img width="3862" height="2152" alt="image"
src="https://github.com/user-attachments/assets/4d9e30dc-6905-48ad-849d-48eac6ebed03"
/>
<img width="3862" height="2152" alt="image"
src="https://github.com/user-attachments/assets/ef20986e-c29c-4fe0-9f20-56da4fb0ac29"
/>


* font size 7

<img width="3862" height="2152" alt="image"
src="https://github.com/user-attachments/assets/8b277e92-9ae4-4415-8903-68566b580f5a"
/>
<img width="3862" height="2152" alt="image"
src="https://github.com/user-attachments/assets/b9140e73-81af-430b-b07f-af118c7e3dae"
/>

<img width="3862" height="2152" alt="image"
src="https://github.com/user-attachments/assets/185f526a-241e-4573-af1d-f27aedeac48e"
/>
<img width="3862" height="2152" alt="image"
src="https://github.com/user-attachments/assets/7a239121-ae13-4db9-99d9-785ec26cd98e"
/>


Release Notes:

- Improved color rendering on Linux

Co-authored-by: Kate <kate@zed.dev>
Co-authored-by: John <john-tur@outlook.com>
Co-authored-by: apricotbucket28 <71973804+apricotbucket28@users.noreply.github.com>
2025-09-26 16:09:30 +00:00
Danilo Leal
8db24dd8ad docs: Update wording around configuring MCP servers (#38973)
Felt like this could be clarified a bit.

Release Notes:

- N/A
2025-09-26 12:47:26 -03:00
Ben Kunkle
4aac5642c1 JSON Schema URIs (#38916)
Closes #ISSUE

Improves the efficiency of our interactions with the Zed language
server. Previously, on startup and after every workspace configuration
changed notification, we would send >1MB of JSON Schemas to the JSON
LSP. The only reason this had to happen was due to the case where an
extension was installed that would result in a change to the JSON schema
for settings (i.e. added language, theme, etc).

This PR changes the behavior to use the URI LSP extensions of
`vscode-json-language-server` in order to send the server URI's that it
can then use to fetch the schemas as needed (i.e. the settings schema is
only generated and sent when `settings.json` is opened. This brings the
JSON we send to on startup and after every workspace configuration
changed notification down to a couple of KB.

Additionally, using another LSP extension request we can notify the
server when a schema has changed using the URI as a key, so we no longer
have to send a workspace configuration changed notification, and the
schema contents will only be re-requested and regenerated if the schema
is in use.

Release Notes:

- Improved the efficiency of communication with the builtin JSON LSP.
JSON Schemas are no longer sent to the JSON language server in their
full form. If you wish to view a builtin JSON schema in the language
server info tab of the language server logs (`dev: open language server
logs`), you must now use the `editor: open url` action with your cursor
over the URL that is sent to the server.
- Made it so that Zed urls (`zed://...`) are resolved locally when
opened within the editor instead of being resolved through the OS. Users
who could not previously open `zed://*` URLs in the editor can now do so
by pasting the link into a buffer and using the `editor: open url`
action (please open an issue if this is the case for you!).

---------

Co-authored-by: Michael <michael@zed.dev>
2025-09-26 11:41:26 -04:00
Nia
30b49cfbf5 perf: Fixup ordering, fix pathing, docs (#38970)
Release Notes:

- N/A
2025-09-26 15:28:48 +00:00
Lukas Wirth
c69912c76a Forbid std::process::Command spawning, replace with smol where appropriate (#38894)
std commands can block for an arbitrary duration and so runs risk of
blocking tasks for too long. This replaces all such uses where sensible
with async processes.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-26 15:17:36 +00:00
Smit Barmase
7f14ab26dd copilot: Ensure minimum Node version (#38945)
Closes #38918

Release Notes:

- N/A
2025-09-26 20:08:21 +05:30
Marshall Bowers
5ee73d3e3c Move settings_macros to Cargo workspace (#38962)
Release Notes:

- N/A
2025-09-26 14:20:36 +00:00
Martin Pool
d5aa81a5b2 Fix up Wild package name and decompression (#38961)
Wild changed in 0.6.0 to using gzip rather than xz, and changed the
format of the package name.

Follows on from and fixes
https://github.com/zed-industries/zed/pull/37717

cc @dvdsk @mati865 

Release Notes:

- N/A
2025-09-26 16:20:01 +02:00
Kirill Bulatov
21855c15e4 Disable subpixel shifting for y axis on Linux (#38959)
Part of https://github.com/zed-industries/zed/issues/7992
Port of #38440

<img width="3836" height="2142" alt="zed_nightly_vs_zed_dev_2"
src="https://github.com/user-attachments/assets/66bcbb9a-2159-4790-8a9a-d4814058d966"
/>

Does not change the rendering on Linux, but prepares us for the times
without cosmic-text where this will be needed.

Release Notes:

- N/A

Co-authored-by: Kate <kate@zed.dev>
Co-authored-by: John <john@zed.dev>
2025-09-26 13:31:59 +00:00
Kirill Bulatov
1f9279a56f linux: Add missing linear to sRGB transform in mono sprite rendering (#38944)
Part of https://github.com/zed-industries/zed/issues/7992
Takes
https://github.com/zed-industries/zed/issues/7992#issuecomment-3083871615
and applies its adjusted version on the current state of things

Screenshots (left is main, right is the patch): 

* default font size

<img width="3840" height="2160" alt="image"
src="https://github.com/user-attachments/assets/26fdc42c-12e6-447f-ad3d-74808e4b2562"
/>

<img width="3840" height="2160" alt="image"
src="https://github.com/user-attachments/assets/29829c61-c998-4e77-97c3-0e66e14b236d"
/>


* buffer and ui font size 7 

<img width="3840" height="2160" alt="image"
src="https://github.com/user-attachments/assets/5d0f1d94-b7ed-488d-ab22-c25eb01e6b4a"
/>

<img width="3840" height="2160" alt="image"
src="https://github.com/user-attachments/assets/7020d62e-de65-4b86-a64b-d3eea798c217"
/>


Release Notes:

- Added missing linear to sRGB transform in mono sprite rendering on
Linux

Co-authored-by: Thomas Dagenais <exrok@i64.dev>
Co-authored-by: Kate <work@localcc.cc>
2025-09-26 09:54:46 +00:00
Michael Sloan
da71465437 edit_prediction_context: Minor optimization of text similarity + some renames (#38941)
Release Notes:

- N/A
2025-09-26 07:57:28 +00:00
Nia
bcc8149263 perf: Fixes (#38935)
Release Notes:

- N/A
2025-09-26 05:41:06 +00:00
Danilo Leal
b1528601cc settings ui: Add some light design tweaks (#38934)
Release Notes:

- N/A
2025-09-26 05:22:57 +00:00
Marshall Bowers
ee357e8987 language_models: Send a header indicating that the client supports xAI models (#38931)
This PR adds an `x-zed-client-supports-x-ai` header to the `GET /models`
request sent to Cloud to indicate that the client supports xAI models.

Release Notes:

- N/A
2025-09-26 04:11:48 +00:00
Floyd Wang
0891a7142d gpui: Fix incorrect colors comment (#38929)
| Before | After |
| - | - |
| <img width="466" height="207" alt="SCR-20250926-khst"
src="https://github.com/user-attachments/assets/c28a9ea8-3d22-458c-a683-b2fabe275a04"
/> | <img width="480" height="215" alt="SCR-20250926-kgru"
src="https://github.com/user-attachments/assets/cfee6392-804c-46e2-a55a-f72071264d10"
/> |

Release Notes:

- N/A
2025-09-25 21:53:04 -06:00
Marshall Bowers
94fe862fb6 x_ai: Fix Model::from_id for Grok 4 (#38930)
This PR fixes `x_ai::Model::from_id`, which was not properly handling
`grok-4`.

Release Notes:

- N/A
2025-09-26 03:49:14 +00:00
Marshall Bowers
4f91fab190 language_models: Add xAI support to Zed Cloud provider (#38928)
This PR adds xAI support to the Zed Cloud provider.

Release Notes:

- N/A
2025-09-26 03:19:12 +00:00
Mikayla Maki
0e0f48d8e1 Introduce SettingsField type to the settings UI (#38921)
Release Notes:

- N/A

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-09-26 01:08:55 +00:00
Max Brunsfeld
7980dbdaea Add API docs for RelPath (#38923)
Also reduce the use of `unsafe` in that module.

Release Notes:

- N/A
2025-09-26 00:49:10 +00:00
Michael Sloan
a5683f3541 zeta_cli: Add --output-format both and --prompt-format only-snippets (#38920)
These are options are probably temporary, added for use in some
experimental code

Release Notes:

- N/A

Co-authored-by: Oleksiy <oleksiy@zed.dev>
2025-09-25 22:49:36 +00:00
Michael Sloan
67984d5e49 provider configuration: Use SingleLineInput instead of Editor (#38814)
Release Notes:

- N/A
2025-09-25 22:38:27 +00:00
Cole Miller
d83d7d35cb windows: Fix inconsistent separators in buffer headers and breadcrumbs (#38898)
Make `resolve_full_path` use the appropriate separators, and return a
`String`.

As part of fixing the fallout from that type change, this also fixes a
bunch of places in the agent code that were using `std::path::Path`
operations on paths that could be non-local, by changing them to operate
instead on strings and use the project's `PathStyle`.

This clears the way a bit for making `full_path` also return a string
instead of a `PathBuf`, but I've left that for a follow-up.

Release Notes:

- N/A
2025-09-25 22:24:32 +00:00
Derek Nguyen
6470443271 python: Fix ty archive extraction on Linux (#38917)
Closes #38553 
Release Notes:

- Fixed wrong AssetKind specified on linux for ty 


As discussed in the linked issue. All of the non windows assets for ty
are `tar.gz` files. This change applies that fix.
2025-09-25 22:17:49 +00:00
Jakub Konka
5b72dfff87 helix: Streamline mode naming in the UI and in settings (#38870)
Release Notes:

- When `helix_mode = true`, modes are called without the `HELIX_` prefix
in the UI:
  `HELIX_NORMAL` becomes `NORMAL`
  `HELIX_SELECT` becomes `SELECT`
- (breaking change) Helix users should remove `"default_mode":
"helix_normal"` from their settings. This is now the default when
`"helix_mode": true`.
2025-09-25 23:57:01 +02:00
Max Brunsfeld
495a7b0a84 Clean up RelPath API (#38912)
Consolidate constructors and accessors.

Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-09-25 14:42:32 -07:00
Lauren Hinchcliffe
301e976465 Fix inlay hints using status theming instead of syntax theming (#36219)
Release Notes:

- Fixed editor inlay hints incorrectly using status theming when syntax
theming is available

Previously, a theme's `style.syntax.hint` object is completely ignored,
and `style.hint` `style.hint.background` are used instead. However,
these seem to be related to status hints, such as the inline git blame
integration.

For syntax hints (as given by an LSP), the reasonable assumption would
be that the `style.syntax.hint` object is used instead, but it isn't.
This means that defining other style characteristics (`font_style`, for
example) does nothing.

I've fixed the issue in a backward-compatible way, by using the theme
`syntax` `HighlightStyle` as the base for inlay hint styling, and
falling back to the original `status` colors should the syntax object
not contain the color definitions.

 With the following theme settings:
```jsonc
{
  "hint": "#ff00ff",                    // Status hints (git blame, etc.)
  "hint.background": "#ff00ff10",
  "syntax": {
    "hint": {
      "color": "#ffffff",               // LSP inlay hints
      "background_color": "#ffffff10",
      "font_style": "italic",           // Now properly applied
      "font_weight": 700
    }
  }
}
```


Current behavior:
<img width="896" height="201" alt="image"
src="https://github.com/user-attachments/assets/e89d212f-ed7e-4d27-94e4-96d716e229d2"
/>

Italics and font weight are ignored. Uses status colors instead.

Fixed behavior:
<img width="896" height="202" alt="image"
src="https://github.com/user-attachments/assets/f14ed2c3-bb60-4b74-886d-6b409d338714"
/>

Italics and font weight are used properly. Status color is preserved for
the git blame status, but correct syntax colors are used for the inlay
hints.
2025-09-25 16:39:12 -05:00
Anthony Eid
daebc4052d settings ui: Implement dynamic navbar based on pages section headers (#38915)
Release Notes:

- N/A

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-09-25 21:19:18 +00:00
Cole Miller
ecc35fcd9a acp: Fix @mentions when remoting from Windows to Linux (#38882)
Closes #38620

`Url::from_file_path` and `Url::from_directory_path` assume the path
style of the target they were compiled for, so we can't use them in
general. So, switch from `file://` to encoding the absolute path (for
mentions that have one) as a query parameter, which works no matter the
platforms. We'll still parse the old `file://` mention URIs for
compatibility with thread history.

Release Notes:

- windows: Fixed a crash when using `@mentions` in agent threads when
remoting from Windows to Linux or WSL.
2025-09-25 16:23:45 -04:00
Ben Kunkle
236006b6b3 settings_ui: Small UI improvements (#38911)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-25 20:23:02 +00:00
Jakub Konka
39c4480841 terminal: Trace terminal events (#38896)
Tracing terminal events can now be enabled using typical `RUST_LOG`
invocation:

```
RUST_LOG=info,terminal=trace,alacritty_terminal=trace cargo run
```

Release Notes:

- N/A
2025-09-25 22:21:33 +02:00
Ben Kunkle
48aac2a746 settings_ui: Add dropdown component + other fixes (#38909)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-25 15:59:34 -04:00
Nathan Sobo
ae036f8ead Read env vars in TestScheduler::many (#38897)
This allows ITERATIONS and SEED environment variables to override the
hard coded values during testing.

cc @ConradIrwin @as-cii 

Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-09-25 13:03:56 -06:00
Bartosz Kaszubowski
de1de25712 keymap_editor: Fix filter input element alignment (#38895)
# Why

I have spotted that Keymap Editor filter input (editor) is misaligned
vertically.

# How

Switch the input wrapper to flex layout, use `items_center` to align
editor vertically in center of the wrapper.

Release Notes:

- Fixed Keymap Editor filter input alignment

# Test plan

I have tested the change locally and compared the UI before and after,
to make sure that change does not affect the size of the wrapper
element.

### Before

<img width="1622" height="428" alt="Screenshot 2025-09-25 at 18 18 59"
src="https://github.com/user-attachments/assets/7d09be5c-6caf-4873-8ecf-2542851cb40a"
/>

### After

<img width="1622" height="428" alt="Screenshot 2025-09-25 at 18 07 18"
src="https://github.com/user-attachments/assets/540fcb3e-691d-4fb7-8130-2ed45ddc0adc"
/>
2025-09-25 15:29:02 -03:00
Cole Miller
18fc951135 Fix flaky test_remote_resolve_path_in_buffer test (#38903)
Release Notes:

- N/A
2025-09-25 18:11:45 +00:00
Cole Miller
40138e12a4 windows: Make ctrl-n open a new terminal when in a terminal (#38900)
This is how `ctrl-n` works on macOS. Right now `ctrl-n` on Windows with
the default keymap usually causes a new buffer to open, which is
inconvenient.

Release Notes:

- N/A
2025-09-25 17:51:19 +00:00
Joseph T. Lyons
e7a5c81b07 Improve media-creation flow in release process (#38902)
Release Notes:

- N/A
2025-09-25 17:49:12 +00:00
Joseph T. Lyons
d98175c0a6 Update release process docs to reflect new process (#38892)
Release Notes:

- N/A
2025-09-25 11:57:00 -04:00
313838373473747564656e74766775
bc7d804a42 remote: Don’t pass --method=GET to wget (#38771)
BusyBox's off brand `wget` does not have support for the `--method`
argument, which makes `zed` incapable of downloading the remote server
unless the _☙authentic❧_ one is installed. Removing this should fix the
issue. Couldn't find much about guidelines on how the code is supposed
to be formatted, so I opted for commenting the line out with an
explanation.

Closes #38712

Release Notes:

- Fixed remote development on BusyBox
2025-09-25 14:59:04 +00:00
Ben Kunkle
50bb8a4ae6 gpui: Add tab group (#38531)
Closes #ISSUE

Co-Authored-By: Mikayla <mikayla@zed.dev>
Co-Authored-By: Anthony <anthony@zed.dev>
Co-Authored-By: Kate <kate@zed.dev>

Release Notes:

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

---------

Co-authored-by: Kate <kate@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-09-25 14:41:29 +00:00
Agus Zubiaga
b2b90b003d zeta2: Add prompt format option to inspector (#38884)
Adds the new prompt format option to the inspector view


Release Notes:

- N/A

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-09-25 14:23:58 +00:00
Agus Zubiaga
c0f56f500e zeta2: Test prediction request (#38794)
Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-09-25 13:49:56 +00:00
Lukas Wirth
a9fe18f4cb Revert "gpui: Flash menu in menubar on macOS when action is triggered (#38588)" (#38880)
This reverts commit ed7bd5a8ed.

We noticed this PR causes the editor to hang if you hold down any of the
menu item actions like ctrl+z, ctrl+x, etc


Release Notes:

- Fixed macOS menu item actions hanging the editor when their key
combination is held down
2025-09-25 13:36:19 +00:00
David Kleingeld
3c5e683fbe Fix experimental audio, add denoise, auto volume.Prep migration (#38874)
Uses the previously merged denoising crate (and fixes a bug in it that
snug in during refactoring) to add denoising to the microphone input. 

Adds automatic volume control for microphone and output.

Prepares for migrating to 16kHz SR mono:
The experimental audio path now picks the samplerate and channel count depending on a setting. It can handle incoming streams with both the current (future legacy) and new samplerate & channel count. These are url-encoded into the livekit track name

Release Notes:

- N/A
2025-09-25 15:11:12 +02:00
Cole Miller
783ba389f7 Fix script/zed-local on Windows (#38832)
There's a mismatch between the URL used here and the one that's referred
to in `build_zed_cloud_url`, which prevents using the script on Windows.

A previous PR changed the script to use `127.0.0.1` instead of
`localhost` because of supposed URL parsing issues, but we were unable
to reproduce those.

Release Notes:

- N/A
2025-09-25 09:03:27 -04:00
Kirill Bulatov
e72021a26b Implement perceptual gamma / contrast correction for Linux font rendering (#38862)
Part of https://github.com/zed-industries/zed/issues/7992
Port of https://github.com/zed-industries/zed/pull/37167 to Linux

When using Blade rendering (Linux platforms and self-compiled builds
with the Blade renderer enabled), Zed reads `ZED_FONTS_GAMMA` and
`ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST` environment variables for the
values to use for font rendering.

`ZED_FONTS_GAMMA` corresponds to
[getgamma](https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwriterenderingparams-getgamma)
values.
Allowed range [1.0, 2.2], other values are clipped.
Default: 1.8

`ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST` corresponds to
[getgrayscaleenhancedcontrast](https://learn.microsoft.com/en-us/windows/win32/api/dwrite_1/nf-dwrite_1-idwriterenderingparams1-getgrayscaleenhancedcontrast)
values.
Allowed range: [0.0, ..), other values are clipped.
Default: 1.0

Screenshots (left is Nightly, right is the new code):

* Non-lodpi display

With the defaults:

<img width="2560" height="1600" alt="image"
src="https://github.com/user-attachments/assets/987168b4-3f5f-45a0-a740-9c0e49efbb9c"
/>


With `env ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST=7777`: 

<img width="2560" height="1600" alt="image"
src="https://github.com/user-attachments/assets/893bc2c7-9db4-4874-8ef6-3425d079db63"
/>


Lodpi, default settings:
<img width="3830" height="2160" alt="image"
src="https://github.com/user-attachments/assets/ec009e00-69b3-4c01-a18c-8286e2015e74"
/>

Lodpi, font size 7:
<img width="3830" height="2160" alt="image"
src="https://github.com/user-attachments/assets/f33e3df6-971b-4e18-b425-53d3404b19be"
/>


Release Notes:

- Implement perceptual gamma / contrast correction for Linux font
rendering

---------

Co-authored-by: localcc <work@localcc.cc>
2025-09-25 16:02:27 +03:00
Agus Zubiaga
f25ace6be0 zeta2 cli: Output raw request (#38876)
Release Notes:

- N/A

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
Co-authored-by: Oleksiy Syvokon <oleksiy.syvokon@gmail.com>
2025-09-25 12:59:17 +00:00
Umesh Yadav
c627543b46 assistant_context: Fix thread_summary_model not getting used in Text Threads (#38859)
Closes https://github.com/zed-industries/zed/issues/37472

Release Notes:

- Fixed an issue in Text Threads where it was using `default_model` even
in case `thread_summary_model` was set.

---------

Signed-off-by: Umesh Yadav <git@umesh.dev>
2025-09-25 14:41:49 +02:00
Ben Brandt
f303a461c4 acp: Use ACP error types in read_text_file (#38863)
- Map path lookup and internal failures to acp::Error 
- Return INVALID_PARAMS for reads beyond EOF

Release Notes:

- acp: Return more informative error types from `read_text_file` to
agents
2025-09-25 11:53:36 +00:00
localcc
a9def8128f Adjust keymap to not conflict with the french keyboard layout (#38868)
Closes #38382 

Release Notes:

- N/A
2025-09-25 11:25:22 +00:00
Lukas Wirth
6580eac077 auto_update: Unmount update disk image in the background (#38867)
Release Notes:

- Fixed potentially temporarily hanging on macOS when updating the app
2025-09-25 11:13:40 +00:00
localcc
5c3c79d667 Fix file association icons on Windows (#38713)
This now uses the default zed icon for file associations as our own icon
svgs are black/white shapes which are not suitable to set as an icon in
a file explorer.

Closes #36286

Release Notes:

- N/A
2025-09-25 12:58:58 +02:00
Lukas Wirth
16fccb5c76 editor: Assert ordering in selections of resolve_selections (#38861)
Inspired by the recent anchor assertions, this asserts that the produced
selections are always ordered at various resolutions stages, this is an
invariant within `SelectionsCollection` but something breaks it
somewhere causing us to seek cursors backwards which panics.

Related to ZED-13X

Release Notes:

- N/A
2025-09-25 10:06:47 +00:00
Driftcell
a25504edaf file_finder: Leverage or-patterns and bindings to deduplicate prefix handling (#38860)
Just small code changes, to deduplicate prefix handling.

Release Notes:

- N/A
2025-09-25 10:01:28 +00:00
Ben Brandt
bc11844b2e acp: Fix read_text_file erroring on empty files (#38856)
The previous validation was too strict and didn't permit reading empty
files.

Addresses: https://github.com/google-gemini/gemini-cli/issues/9280

Release Notes:

- acp: Fix `read_text_file` returning errors for empty files
2025-09-25 09:15:50 +00:00
Martin Pool
10b99c6f55 RFC: Recommend and enable using Wild rather than Mold on Linux for local builds (#37717)
# Summary 

Today, Zed uses Mold on Linux, but Wild can be significantly faster. 

On my machine, Wild is 14% faster at a whole-tree clean build, 20%
faster on an incremental build with a minimal change, and makes no
measurable effect on runtime performance of tests.

However, Wild's page says it's not yet ready for production, so it seems
to early to switch for production and CI builds.

This PR keeps using Mold in CI and lets developers choose in their own
config what linker to use. (The downside of this is that after landing
this change, developers will have to do some local config or it will
fall back to the default linker which may be slower.)

[Wild 0.6 is out, and their announcement has some
benchmarks](https://davidlattimore.github.io/posts/2025/09/23/wild-update-0.6.0.html).

cc @davidlattimore from Wild, just fyi

# Tasks

- [x] Measure Wild build, incremental build, and runtime performance in
different scenarios
- [x] Remove the Linux linker config from `.cargo/config.toml` in the
tree
- [x] Test rope benchmarks etc
- [x] Set the linker to Mold in CI 
- [x] Add instructions to use Wild or Mold into `linux.md`
- [x] Add a script to download Wild
- [x] Measure binary size
- [x] Recommend Wild from `scripts/linux`

# Benchmarks 

| | wild 0.6 (rust 1.89) | mold 2.37.1 (1.89) | lld (rust 1.90) | wild
advantage |
| -- | -- | -- | -- | -- |
| clean workspace build | 176s | 184s | 182s | 5% faster than mold |
| nextest run workspace after build | 137s | 142s | 137s | in the noise?
|
| incremental rebuild | 3.9s | 5.0s | 6.6s | 22% faster than mold | 

I didn't observe any apparent significant change in runtime performance
or binary size, or in the in-tree microbenchmarks.

Release Notes:

- N/A

---------

Co-authored-by: Mateusz Mikuła <oss@mateuszmikula.dev>
2025-09-25 10:35:13 +02:00
Kirill Bulatov
17dea24533 Disable terminal breadcrumbs by default (#38806)
<img width="1211" height="238" alt="image"
src="https://github.com/user-attachments/assets/d847fabe-0e00-474c-ad79-cb4da221b319"
/>

At least on Windows, "git terminal" and PowerShell set the header, which
is not very useful but occupies space and sometimes confuses users:


![telegram-cloud-photo-size-2-5377720447174575846-x](https://github.com/user-attachments/assets/a889fa44-e879-4b3d-956b-0af959113e1e)

Release Notes:

- Disable terminal breadcrumbs by default. Set
`terminal.toolbar.breadcrumbs` to `true` to re-enable.

Co-authored-by: Finn Evers <finn@zed.dev>
2025-09-25 10:25:37 +02:00
Marshall Bowers
17e55daf6f Remove billing-v2 feature flag (#38843)
This PR removes the `billing-v2` feature flag, now that the new pricing
is launched.

Release Notes:

- N/A
2025-09-25 02:11:48 +00:00
Remy Suen
6b968e0118 Remove the duplicated Global LSP Settings section (#38811)
This section [shows up
twice](https://zed.dev/docs/configuring-zed#global-lsp-settings) in the
documentation.

<img width="701" height="1269" alt="image"
src="https://github.com/user-attachments/assets/4d930676-5cae-43c8-83d4-6406c27d149c"
/>

Release Notes:

- N/A

Signed-off-by: Remy Suen <remy.suen@docker.com>
2025-09-24 18:41:58 -06:00
Bartosz Kaszubowski
0f66310192 git_ui: Tweak appearance of repo and branch separator (#38447)
# Why

In Git Panel, it felt to me that repo and branch separator can be
slightly demphasized (since it is not-interactable) and separated a bit
more from the repo and branch popover triggers.

# How

Use `icon_muted` color for the separator (happy to know if this is an
abuse of the UI styleguide 😄), add one pixel horizontal spacing around
the `/` character.

Release Notes:

- Improved appearance of repo and branch separator in Git Commit Panel

# Test plan

I have tested the change locally and compared the UI before and after to
make sure it feels right.

### Before

<img width="466" height="196" alt="Screenshot 2025-09-18 at 20 25 46"
src="https://github.com/user-attachments/assets/7bfcd1a4-8d16-4e75-8660-9cbfa3952848"
/>

### After

<img width="466" height="196" alt="Screenshot 2025-09-18 at 20 25 12"
src="https://github.com/user-attachments/assets/100d3599-ecc6-473f-b270-a71005b41494"
/>

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-09-25 00:39:29 +00:00
warrenjokinen
26adc70ae6 docs: Update glossary (#38820)
Added blank line in front of 2 image tags so markdown renders correctly
in zed. (Previously, images were skipped. They are also skipped in zed
if there are leading spaces in front of img tag.)

Updated text in 3 alt tags.

Fixed 1 typo.

Release Notes:

- N/A
2025-09-24 20:27:17 -04:00
Danilo Leal
a5fb290252 docs: Add stray design tweaks (#38835)
Tiny little improvements opportunities I noticed today while browsing
the docs.

Release Notes:

- N/A
2025-09-25 00:10:42 +00:00
Michael Sloan
8fc7bd9ae8 zeta2: Add labeled sections prompt format (#38828)
Release Notes:

- N/A

Co-authored-by: Agus <agus@zed.dev>
2025-09-25 00:07:43 +00:00
Smit Barmase
7167be5889 editor: Fix predict edit at cursor action when show_edit_predictions is false (#38821)
Closes #37601 

Regressed in https://github.com/zed-industries/zed/pull/36469. 

Edit: Original issue https://github.com/zed-industries/zed/issues/25744
is fixed for Zeta in this PR. For Copilot, it will be covered in a
follow-up. In the case of Copilot, even after discarding, we still get a
prediction on suggest, which is a bug.

Release Notes:

- Fixed issue where predict edit at cursor didn't work when
`show_edit_predictions` is `false`.
2025-09-25 05:28:32 +05:30
Cole Miller
d321cf93ba Fix semantic merge conflict from RelPath refactor (#38829)
Release Notes:

- N/A

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2025-09-24 23:27:11 +00:00
Conrad Irwin
ce7b02e3a1 Whitespace map more (#38827)
Release Notes:

- N/A
2025-09-24 23:11:40 +00:00
Max Brunsfeld
03f9cf4414 Represent relative paths using a dedicated, separator-agnostic type (#38744)
Closes https://github.com/zed-industries/zed/issues/38690
Closes #37353

### Background

On Windows, paths are normally separated by `\`, unlike mac and linux
where they are separated by `/`. When editing code in a project that
uses a different path style than your local system (e.g. remoting from
Windows to Linux, using WSL, and collaboration between windows and unix
users), the correct separator for a path may differ from the "native"
separator.

Previously, to work around this, Zed converted paths' separators in
numerous places. This was applied to both absolute and relative paths,
leading to incorrect conversions in some cases.

### Solution

Many code paths in Zed use paths that are *relative* to either a
worktree root or a git repository. This PR introduces a dedicated type
for these paths called `RelPath`, which stores the path in the same way
regardless of host platform, and offers `Path`-like manipulation APIs.
RelPath supports *displaying* the path using either separator, so that
we can display paths in a style that is determined at runtime based on
the current project.

The representation of absolute paths is left untouched, for now.
Absolute paths are different from relative paths because (except in
contexts where we know that the path refers to the local filesystem)
they should generally be treated as opaque strings. Currently we use a
mix of types for these paths (std::path::Path, String, SanitizedPath).

Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Peter Tripp <petertripp@gmail.com>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
2025-09-24 18:57:33 -04:00
Conrad Irwin
3c626f3758 Only allow single chars for whitespace map (#38825)
Release Notes:

- Only allow single characters in the whitespace map
2025-09-24 16:18:00 -06:00
Joseph T. Lyons
4a1bab52f3 Update release process docs to include storing feature media (#38824)
Release Notes:

- N/A
2025-09-24 21:52:02 +00:00
Conrad Irwin
91b0f42382 Fix panic when hovering string ending with unicode (#38818)
Release Notes:

- Fixed a panic when hovering a string literal ending with an emoji
2025-09-24 15:33:31 -06:00
Ben Kunkle
523c042930 settings_ui: Collect all settings files (#38816)
Closes #ISSUE

Updates the settings editor to collect all known settings files from the
settings store, in order to show them in the UI. Additionally adds a
fake worktree instantiation in the settings UI example binary in order
to have more than one file available when testing.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-24 21:16:06 +00:00
Victor Tran
ed7bd5a8ed gpui: Flash menu in menubar on macOS when action is triggered (#38588)
On macOS, traditionally when a keyboard shortcut is activated, the menu
in the menu bar flashes to indicate that the action was recognised.

<img width="289" height="172" alt="image"
src="https://github.com/user-attachments/assets/a03ecd2f-f159-4f82-b4fd-227f34393703"
/>

This PR adds this functionality to GPUI, where when a keybind is pressed
that triggers an action in the menu, the menu flashes.

Release Notes:

- N/A
2025-09-24 12:09:03 -07:00
Lukas Wirth
8ebe4fa149 gpui_macros: Hide inner test function from project symbols (#38809)
This makes rust-analyzer not consider the function for project symbols,
meaning searching for tests wont show two entries.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-24 18:07:34 +00:00
Bennet Bo Fenner
6b646e3a14 zeta2: Support edit prediction: clear history (#38808)
Release Notes:

- N/A

Co-authored-by: Agus Zubiaga <agus@zed.dev>
2025-09-24 17:44:03 +00:00
Ben Kunkle
e653cc90c5 Clean up last remnants of Settings UI v1 (#38803)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-24 17:02:32 +00:00
Danilo Leal
0794de71e3 docs: Update note about agent message editor setting (#38805)
As of stable 206.0, the `agent.message_editor_min_lines` setting is
fully available, so removing the docs note that said it was only for
Preview.

Release Notes:

- N/A
2025-09-24 13:57:30 -03:00
Anthony Eid
2b283e7c53 Revert "Fix UTF-8 character boundary panic in DirectWrite text ... (#37767)" (#38800)
This reverts commit 9e7302520e.

I run into an infinite hang in Zed nightly and used instruments and
activity monitor to sample what was going on. The root cause seemed to
be the unwrap_unchecked introduced in reverted PR.

Release Notes:

- N/A
2025-09-24 16:44:39 +00:00
Joseph T. Lyons
45a4277026 Add community champion auto labeler (#38802)
Release Notes:

- N/A
2025-09-24 16:42:01 +00:00
Kirill Bulatov
fa76b6ce06 Switch to "standard" as a default line height in the terminal (#38798)
Closes https://github.com/zed-industries/zed/issues/38686

Release Notes:

- Switched to "standard" as a default line height in the terminal
2025-09-24 16:36:35 +00:00
morgankrey
a13e3a8af3 Docs updates September (#38796)
Closes #ISSUE

Release Notes:

- N/A

---------

Co-authored-by: Katie Geer <katie@zed.dev>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-09-24 11:10:58 -05:00
Nia
39370bceb2 perf: Bugfixes (#38725)
Release Notes:

- N/A
2025-09-24 16:03:08 +00:00
Mikayla Maki
53885c00d3 Start up settings UI 2 (#38673)
Release Notes:

- N/A

---------

Co-authored-by: Anthony <hello@anthonyeid.me>
Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Ben Kunkle <ben.kunkle@gmail.com>
2025-09-24 15:45:14 +00:00
Danilo Leal
6f3e66d027 Adjust stash picker design (#38789)
Just making it more consistent with other pickers—button actions
justified to the right and timestamp directly in the list item to avoid
as much as possible relevant information tucked away in a tooltip where
using the keyboard will mostly be the main mean of interaction.

<img width="500" height="310" alt="Screenshot 2025-09-24 at 10  41@2x"
src="https://github.com/user-attachments/assets/0bd478da-d1a6-48fe-ade7-a4759d175c60"
/>


Release Notes:

- N/A
2025-09-24 13:59:42 +00:00
Agus Zubiaga
b3f9be6e9c zeta2: Split up crate into modules (#38788)
Split up provider, prediction, and global into modules.

Release Notes:

- N/A
2025-09-24 13:40:29 +00:00
Agus Zubiaga
4353b61155 zeta2: Compute smaller edits (#38786)
The new cloud endpoint returns structured edits, but they may include
more of the input excerpt than what we want to display in the preview,
so we compute a smaller diff on the client side against the snapshot.

Release Notes:

- N/A
2025-09-24 13:10:52 +00:00
Lukas Wirth
e1b57f00a0 sum_tree: Reduce Cursor size for contextless summary types (#38776)
This reduces the size of cursor by a usize when the summary does not
require a context making Cursor usages and constructions slightly more
efficient.

This change is a bit annoying though, as Rust has no means of
specializing, so this uses a `ContextlessSummary` trait with a blanket
impl while turning the `Context` into a GAT `Context<'a>`. This means
`Summary` implies are a bit more verbose now while contextless ones are
slimmer. It does come with the downside that the lifetime in the GAT is
always considered invariant, so some lifetime splitting occurred due to
that.


 ```
push/4096               time:   [352.65 µs 360.87 µs 367.80 µs]
                        thrpt:  [10.621 MiB/s 10.825 MiB/s 11.077 MiB/s]
                 change:
time: [-2.6633% -1.3640% -0.0561%] (p = 0.05 < 0.05)
                        thrpt:  [+0.0561% +1.3828% +2.7361%]
                        Change within noise threshold.
Found 16 outliers among 100 measurements (16.00%)
  7 (7.00%) low severe
  3 (3.00%) low mild
  2 (2.00%) high mild
  4 (4.00%) high severe
push/65536              time:   [1.2917 ms 1.2949 ms 1.2979 ms]
                        thrpt:  [48.156 MiB/s 48.267 MiB/s 48.387 MiB/s]
                 change:
time: [+1.4428% +1.9844% +2.5299%] (p = 0.00 < 0.05)
                        thrpt:  [-2.4675% -1.9458% -1.4223%]
                        Performance has regressed.
Found 3 outliers among 100 measurements (3.00%)
  1 (1.00%) low severe
  1 (1.00%) low mild
  1 (1.00%) high severe

append/4096             time:   [677.87 ns 678.87 ns 679.83 ns]
                        thrpt:  [5.6112 GiB/s 5.6192 GiB/s 5.6274 GiB/s]
                 change:
time: [-0.8924% -0.5017% -0.1705%] (p = 0.00 < 0.05)
                        thrpt:  [+0.1708% +0.5043% +0.9004%]
                        Change within noise threshold.
Found 2 outliers among 100 measurements (2.00%)
  1 (1.00%) low mild
  1 (1.00%) high mild
append/65536            time:   [9.3275 µs 9.3406 µs 9.3536 µs]
                        thrpt:  [6.5253 GiB/s 6.5344 GiB/s 6.5435 GiB/s]
                 change:
time: [+0.5409% +0.7215% +0.9054%] (p = 0.00 < 0.05)
                        thrpt:  [-0.8973% -0.7163% -0.5380%]
                        Change within noise threshold.

slice/4096              time:   [27.673 µs 27.791 µs 27.907 µs]
                        thrpt:  [139.97 MiB/s 140.56 MiB/s 141.16 MiB/s]
                 change:
time: [-1.1065% -0.6725% -0.2429%] (p = 0.00 < 0.05)
                        thrpt:  [+0.2435% +0.6770% +1.1189%]
                        Change within noise threshold.
Found 5 outliers among 100 measurements (5.00%)
  4 (4.00%) low mild
  1 (1.00%) high mild
slice/65536             time:   [507.55 µs 517.40 µs 535.60 µs]
                        thrpt:  [116.69 MiB/s 120.80 MiB/s 123.14 MiB/s]
                 change:
time: [-1.3489% +0.0599% +2.2591%] (p = 0.96 > 0.05)
                        thrpt:  [-2.2092% -0.0598% +1.3674%]
                        No change in performance detected.
Found 8 outliers among 100 measurements (8.00%)
  5 (5.00%) low mild
  2 (2.00%) high mild
  1 (1.00%) high severe

bytes_in_range/4096     time:   [3.3917 µs 3.4108 µs 3.4313 µs]
                        thrpt:  [1.1117 GiB/s 1.1184 GiB/s 1.1247 GiB/s]
                 change:
time: [-5.3466% -4.7193% -4.1262%] (p = 0.00 < 0.05)
                        thrpt:  [+4.3038% +4.9531% +5.6487%]
                        Performance has improved.
Found 6 outliers among 100 measurements (6.00%)
  1 (1.00%) low mild
  5 (5.00%) high mild
bytes_in_range/65536    time:   [88.175 µs 88.613 µs 89.111 µs]
                        thrpt:  [701.37 MiB/s 705.31 MiB/s 708.82 MiB/s]
                 change:
time: [-0.6935% +0.3769% +1.4655%] (p = 0.50 > 0.05)
                        thrpt:  [-1.4443% -0.3755% +0.6984%]
                        No change in performance detected.
Found 2 outliers among 100 measurements (2.00%)
  2 (2.00%) high mild

chars/4096              time:   [678.70 ns 680.38 ns 682.08 ns]
                        thrpt:  [5.5927 GiB/s 5.6067 GiB/s 5.6206 GiB/s]
                 change:
time: [-0.6969% -0.2755% +0.1485%] (p = 0.20 > 0.05)
                        thrpt:  [-0.1483% +0.2763% +0.7018%]
                        No change in performance detected.
Found 9 outliers among 100 measurements (9.00%)
  5 (5.00%) low mild
  4 (4.00%) high mild
chars/65536             time:   [12.720 µs 12.775 µs 12.830 µs]
                        thrpt:  [4.7573 GiB/s 4.7778 GiB/s 4.7983 GiB/s]
                 change:
time: [-0.6172% -0.1110% +0.4179%] (p = 0.68 > 0.05)
                        thrpt:  [-0.4162% +0.1112% +0.6211%]
                        No change in performance detected.
Found 2 outliers among 100 measurements (2.00%)
  1 (1.00%) low mild
  1 (1.00%) high mild

clip_point/4096         time:   [33.240 µs 33.310 µs 33.394 µs]
                        thrpt:  [116.98 MiB/s 117.27 MiB/s 117.52 MiB/s]
                 change:
time: [-2.8892% -2.6305% -2.3438%] (p = 0.00 < 0.05)
                        thrpt:  [+2.4000% +2.7015% +2.9751%]
                        Performance has improved.
Found 12 outliers among 100 measurements (12.00%)
  1 (1.00%) low mild
  4 (4.00%) high mild
  7 (7.00%) high severe
clip_point/65536        time:   [1.6531 ms 1.6586 ms 1.6640 ms]
                        thrpt:  [37.560 MiB/s 37.683 MiB/s 37.808 MiB/s]
                 change:
time: [-6.6381% -5.9395% -5.2680%] (p = 0.00 < 0.05)
                        thrpt:  [+5.5610% +6.3146% +7.1100%]
                        Performance has improved.
Found 7 outliers among 100 measurements (7.00%)
  1 (1.00%) low mild
  2 (2.00%) high mild
  4 (4.00%) high severe

point_to_offset/4096    time:   [11.586 µs 11.603 µs 11.621 µs]
                        thrpt:  [336.15 MiB/s 336.67 MiB/s 337.16 MiB/s]
                 change:
time: [-14.289% -14.111% -13.939%] (p = 0.00 < 0.05)
                        thrpt:  [+16.197% +16.429% +16.672%]
                        Performance has improved.
Found 12 outliers among 100 measurements (12.00%)
  3 (3.00%) low severe
  5 (5.00%) low mild
  4 (4.00%) high mild
point_to_offset/65536   time:   [527.74 µs 532.08 µs 536.51 µs]
                        thrpt:  [116.49 MiB/s 117.46 MiB/s 118.43 MiB/s]
                 change:
time: [-6.7825% -4.6235% -2.3533%] (p = 0.00 < 0.05)
                        thrpt:  [+2.4100% +4.8477% +7.2760%]
                        Performance has improved.
Found 8 outliers among 100 measurements (8.00%)
  4 (4.00%) high mild
  4 (4.00%) high severe

cursor/4096             time:   [16.154 µs 16.192 µs 16.232 µs]
                        thrpt:  [240.66 MiB/s 241.24 MiB/s 241.81 MiB/s]
                 change:
time: [-3.2536% -2.9145% -2.5526%] (p = 0.00 < 0.05)
                        thrpt:  [+2.6194% +3.0019% +3.3630%]
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  1 (1.00%) low mild
  2 (2.00%) high mild
  2 (2.00%) high severe
cursor/65536            time:   [509.60 µs 511.24 µs 512.93 µs]
                        thrpt:  [121.85 MiB/s 122.25 MiB/s 122.65 MiB/s]
                 change:
time: [-7.3677% -6.6017% -5.7840%] (p = 0.00 < 0.05)
                        thrpt:  [+6.1391% +7.0683% +7.9537%]
                        Performance has improved.
Found 6 outliers among 100 measurements (6.00%)
  3 (3.00%) high mild
  3 (3.00%) high severe
```
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-24 14:35:38 +02:00
Oleksiy Syvokon
c5219e8fd2 agent: Clean up git exclusions after emergency (#38775)
In some rare cases, the auto-generated block gets stuck in
`.git/info/exclude`. We now auto-clean it.

Closes #38374

Release Notes:

- Remove auto-generated block from git excludes if it gets stuck there.
2025-09-24 10:58:39 +00:00
Piotr Osiewicz
5612a961b0 windows: Do not attempt to encrypt empty encrypted strings (#38774)
Related to #38427

Release Notes:

* N/A
2025-09-24 10:27:45 +00:00
Lukas Wirth
c53e5ba397 editor: Fix invalid anchors in hover_links::surrounding_filename (#38766)
Fixes ZED-1K3

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-24 08:30:11 +00:00
tidely
d5a99d079e ollama: Remove dead code (#38550)
The `Duration` argument in `get_models` has been unused for over a year.

The `complete` function is also unused and it has fallen behind in new
feature additions such as Authorization support. This used to exist
because ollama didn't support tools in streaming mode, `with_tools` also
existed because of that. Now however there is no reason to keep this
around.

`ChatResponseDelta ` had unnecessary `#[allow(unused)]` macros since the
fields are marked `pub`. Using `#[expect(unused)]` would've caught this.

Release Notes:

- N/A
2025-09-24 02:19:52 -06:00
Lukas Wirth
9418a2f4bc editor: Prevent panics in BlockChunks if the block spans more than 128 lines (#38763)
Not an ideal fix, but a proper one will require restructuring the
iterator state (which would be easier if Rust had first class
generators)
Fixes ZED-1MB

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-24 08:10:56 +00:00
Santiago Bernhardt
880fff471c ollama: Add support for qwen3-coder (#38608)
Release Notes:

- N/A
2025-09-24 02:09:40 -06:00
Michael Sloan
5f6ae2361f Delete unused types for Mistral non-streaming requests (#38758)
Confusing to have these interspersed with the streaming request types

Release Notes:

- N/A
2025-09-24 04:31:06 +00:00
Conrad Irwin
5d89b2ea26 Revert "Add setting to show/hide title bar (#37428)" (#38756)
Closes https://github.com/zed-industries/zed/issues/38547

Release Notes:

- Reverted the ability to show/hide the titlebar. This caused rendering
bugs on
macOS, and we're preparing for the redesign which requires the toolbar
being present.

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-09-24 07:15:30 +03:00
Smit Barmase
0f7dbf57f5 editor: Fix APCA contrast split text runs offset (#38751)
Closes #38576

In case of inline element rendering, we can have multiple text runs on
the same display row. There was a bug in
https://github.com/zed-industries/zed/pull/37165 which doesn't consider
this multiple text runs case. This PR fixes that and adds a test for it.

Before:

<img width="600" alt="image"
src="https://github.com/user-attachments/assets/3bdf5f14-988b-45dc-bc8e-c5d61ab35a93"
/>

After:

<img width="600" alt="image"
src="https://github.com/user-attachments/assets/0e1a45ff-c521-4994-b259-3a054d89c4df"
/>

Release Notes:

- Fixed an issue where text could be incorrectly highlighted during
search when a line contained an inline color preview.
2025-09-24 04:34:35 +05:30
Danilo Leal
b60f19f71e agent: Allow to see the whole command before running it (#38747)
Closes https://github.com/zed-industries/zed/issues/38528

In the agent panel's `thread_view.rs` file, we have a `render_tool_call`
function that controls what we show in the UI for most types of tools.
However, for some of them—for example, terminal/execute and edit
tools—we have a special rendering so we can tailor the UI for their
specific needs. But... before the specific rendering function is called,
all tools still go through the `render_tool_call`.

Problem is that, in the case of the terminal tool, you couldn't see the
full command the agent wants to run when the tool is still in its
`render_tool_call` state. That's mostly because of the treatment we give
to labels while in that state. A particularly bad scenario because
well... seeing the _full_ command _before_ you choose to accept or
reject is rather important.

This PR fixes that by essentially special-casing the terminal tool
display when in the `render_tool_call` rendering state, so to speak.
There's still a slight UI misalignment I want to fix but it shouldn't
block this fix to go out.

Here's our final result:

<img width="400" height="1172" alt="Screenshot 2025-09-23 at 6  19@2x"
src="https://github.com/user-attachments/assets/71c79e45-ab66-4102-b046-950f137fa3ea"
/>

Release Notes:

- agent: Fixed terminal command not being fully displayed while in the
"waiting for confirmation" state.
2025-09-23 18:57:28 -03:00
Jonathan Hart
0a261ad8d0 Implement regex_select action for Helix (#38736)
Closes #31561

Release Notes:

- Implemented the select_regex Helix keymap

Prior: The keymap `s` defaulted to `vim::Substitute`

After:
<img width="1387" height="376" alt="image"
src="https://github.com/user-attachments/assets/4d3181d9-9d3f-40d2-890f-022655c77577"
/>

Thank you to @ConradIrwin for pairing to work on this
2025-09-23 15:44:40 -06:00
Marshall Bowers
28ed08340c Remove experimental jj UI, for now (#38743)
This PR removes the experimental jj bookmark picker that was added in
#30883.

This was just an exploratory prototype and while I would like to have
native jj UI at some point, I don't know when we'll get back to it.

Release Notes:

- N/A
2025-09-23 21:40:22 +00:00
Michael Sloan
74fe3b17f7 Delete edit_prediction_tools.rs (was moved to zeta2_tools.rs) (#38745)
Move happened in #38718

Release Notes:

- N/A
2025-09-23 21:18:04 +00:00
Kirill Bulatov
9112554262 Clear buffer colors on empty LSP response (#38742)
Follow-up of https://github.com/zed-industries/zed/pull/32816
Closes https://github.com/zed-industries/zed/issues/38602


https://github.com/user-attachments/assets/26058c91-4ffd-4c6f-a41d-17da0c3d7220

Release Notes:

- Fixed buffer colors not cleared on empty LSP responses
2025-09-24 00:00:55 +03:00
Joseph T. Lyons
3b79490e8f Bump Zed to v0.207 (#38741)
Release Notes:

- N/A
2025-09-23 20:49:51 +00:00
Chris Ewald
52c467ea3a Document task filtering based on variables (#38642)
Closes: https://github.com/zed-industries/zed/issues/38525
Documentation follow up for #38614

Task filtering behavior is currently undocumented.

Release Notes:

- N/A

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
2025-09-23 23:26:33 +03:00
Agus Zubiaga
831de8e48f zeta2: Include edits in prompt and add max_prompt_bytes param (#38737)
Release Notes:

- N/A

Co-authored-by: Michael Sloan <mgsloan@gmail.com>
2025-09-23 19:50:07 +00:00
ImFeH2
bc528411df Preserve trailing newline in TerminalOutput::full_text (#38061)
Closes #30678

This is caused by `TerminalOutput::full_text` triming trailing newline
when creating the "REPL Output" buffer.

Release Notes:

- fix: Preserve trailing newline in `TerminalOutput::full_text`
2025-09-23 12:11:35 -07:00
Michael Sloan
9ac511e47c zeta2: Collect nearby diagnostics (#38732)
Release Notes:

- N/A

Co-authored-by: Bennet <bennet@zed.dev>
2025-09-23 12:32:17 -06:00
Peter Tripp
afaed3af62 Windows: Fix keybinds for onboarding dialog (#38730)
Closes: https://github.com/zed-industries/zed/issues/38482

- Previously fixed by: https://github.com/zed-industries/zed/pull/36712
- Regressed in: https://github.com/zed-industries/zed/pull/36572

Release Notes:

- N/A
2025-09-23 17:47:32 +00:00
Marshall Bowers
f78699eb71 Update plan text (#38731)
Release Notes:

- N/A

---------

Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-09-23 17:44:43 +00:00
Umesh Yadav
3646aa6bba language_models: Actually override Ollama model from settings (#38628)
The current problem is that if I specify model parameters, like
`max_tokens`, in `settings.json` for an Ollama model, they do not
override the values coming from the Ollama API. Instead, the parameters
from the API are used. For example, in the settings below, even though I
have overridden `max_tokens`, Zed will still use the API's default
`context_length` of 4k.

```
  "language_models": {
    "ollama": {
      "available_models": [
        {
          "name": "qwen3-coder:latest",
          "display_name": "Qwen 3 Coder",
          "max_tokens": 64000,
          "supports_tools": true,
          "keep_alive": "15m",
          "supports_thinking": false,
          "supports_images": false
        }
      ]
    }
  },
```

Release Notes:

- Fixed an issue where Ollama model parameters were not being correctly
overridden by user settings.
2025-09-23 13:16:52 -04:00
Piotr Osiewicz
dc20a41e0d windows: Encrypt SSH passwords stored in memory (#38427)
Release Notes:

- N/A

---------

Co-authored-by: Julia <julia@zed.dev>
2025-09-23 18:58:46 +02:00
Kirill Bulatov
6a24ad7d39 Fix the markdown table (#38729)
Closes https://github.com/zed-industries/zed/issues/38597

Release Notes:

- N/A
2025-09-23 16:49:45 +00:00
Bennet Bo Fenner
8fefd793f0 zeta2: Include edit events in cloud request (#38724)
Release Notes:

- N/A

Co-authored-by: Agus Zubiaga <agus@zed.dev>
Co-authored-by: Michael Sloan <mgsloan@gmail.com>
2025-09-23 18:45:37 +02:00
Danilo Leal
f6e2a2a808 docs: Tweak the toolchains page (#38728)
Mostly just breaking a massive wall of text in small paragraphs for ease
of reading/parsing.

Release Notes:

- N/A
2025-09-23 13:31:56 -03:00
Danilo Leal
3cf6fa8f61 agent: Make the panel's textarea font size be controlled by buffer_font_size (#38726)
Closes https://github.com/zed-industries/zed/issues/37882

Previously, every piece of text in the agent panel was controlled by
`agent_font_size`. Although it is nice to only have one setting to tweak
that, it could be a bit misleading particularly because we use
monospaced and sans-serif fonts for different elements in the panel. Any
editor/textarea in the panel, whehter it is the main message editor or
the previous message editor, uses the buffer font. Therefore, I think it
is reasonable to expect that tweaking `buffer_font_size` would also
change the agent panel's usage of buffer fonts.

With this change, regular buffers and the agent panel's message editor
will always have the same size.

Release Notes:

- agent: Made the agent panel's textarea font size follow the font size
of regular buffers. They're now both controlled by the
`buffer_font_size` setting.
2025-09-23 13:26:45 -03:00
Dino
2759f541da vim: Fix cursor position being set to end of line in normal mode (#38161)
Address an issue where, in Vim mode, clicking past the end of a line
after selecting the entire line would place the cursor on the newline
character instead of the last character of the line, which is
inconsistent with Vim's normal mode expectations.

I believe the root cause was that the cursor’s position was updated to
the end of the line before the mode switch from Visual to Normal, at
which point `DisplayMap.clip_at_line_ends` was still set to `false`. As
a result, the cursor could end up in an invalid position for Normal
mode. The fix ensures that when switching between these two modes, and
if the selection is empty, the selection point is properly clipped,
preventing the cursor from being placed past the end of the line.

Related #38049 

Release Notes:

- Fixed issue in Vim mode where switching from any mode to normal mode
could end up with the cursor in the newline character

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-09-23 09:39:12 -06:00
Agus Zubiaga
809d3bfe00 acp: Include only path to @mentioned directory in user message (#37942)
Nowadays, people don't expect @-mentioning a directory to include the
contents of all files within it. Doing so makes it very likely to
consume an undesirable amount of tokens.

By default, we'll now only include the path of the directory and let the
model decide how much to read via tools. We'll still include the
contents if no tools are available (e.g. "Minimal" profile is selected).

Release Notes:

- Agent Panel: Do not include the content of @-mentioned directories
when tools are available
2025-09-23 12:33:31 -03:00
Agus Zubiaga
0aad47493e zeta2: Use global zeta in Inspector (#38718)
The edit prediction debug tools has been renamed to zeta2 inspector
because it's now zeta specific. It will now always display the last
prediction request context, prompt, and model response.

Release Notes:

- N/A

---------

Co-authored-by: Bennet <bennet@zed.dev>
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-09-23 12:32:36 -03:00
Alvaro Parker
271d67f7ad git: Fix git amend on panel (#38681)
Closes #38651 

`git_panel.set_amend_pending(false, cx);` was being called before
`git_panel.commit_changes(...)` which was causing the commit buffer to
be cleared/reset before actually sending the commit request to git.

Introduced by #35268 which added clear buffer functionality to the
`set_amend_pending` function.

Release Notes:

- Fix git amend on panel sending "Update ..." instead of the original
commit message
- FIx git amend button not working
2025-09-23 09:20:49 -06:00
localcc
2e87387e53 Change emulated GPU message on Windows (#38710)
Release Notes:

- N/A
2025-09-23 15:57:26 +02:00
Lex Berezhny
15e75bdf04 Add show_summary & show_command to the initial_tasks.json (#38660)
Release Notes:

- Added "show_summary" & "show_command" settings to the initial
tasks.json file.


This makes the initial task template match the docs here:
https://zed.dev/docs/tasks
2025-09-23 12:32:14 +00:00
Ben Brandt
3ac14e15bb agent: Fix Gemini refusing all requests with file-based tool calls (#38705)
Solves an issue where Google APIs refuse all requests with file-based
tool calls attached.
This seems to get triggered in the case where:

- copy_path + another file-based tool call is enabled
- default terminal is `/bin/bash` or something similar

It is unclear why this is happening, but removing the terminal commands
in those tool calls seems to have solved the issue.

Closes #37180 and #37414

Release Notes:

- agent: Fix Gemini refusing requests with certain profiles/systems.
2025-09-23 12:14:03 +00:00
邻二氮杂菲
9e7302520e Fix UTF-8 character boundary panic in DirectWrite text layout (#37767)
## Problem

Zed was crashing with a UTF-8 character boundary error when rendering
text containing multi-byte characters (like emojis or CJK characters):

```
Thread "main" panicked with "byte index 49 is not a char boundary; it is inside '…' (bytes 48..51)"
```

## Root Cause Analysis

The PR reviewer correctly identified that the issue was not in the
DirectWrite boundary handling, but rather in the text run length
calculation in the text system. When text runs are split across lines in
`text_system.rs:426`, the calculation:

```rust
let run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start;
```

This could result in `run_len_within_line` values that don't respect
UTF-8 character boundaries, especially when multi-byte characters (like
'…' which is 3 bytes) get split across lines. The resulting `FontRun`
objects would have lengths that don't align with character boundaries,
causing the panic when DirectWrite tries to slice the string.

## Solution

Fixed the issue by adding UTF-8 character boundary validation in the
text system where run lengths are calculated. The fix ensures that when
text runs are split across lines, the split always occurs at valid UTF-8
character boundaries:

```rust
// Ensure the run length respects UTF-8 character boundaries
if run_len_within_line > 0 {
    let text_slice = &line_text[run_start - line_start..];
    if run_len_within_line < text_slice.len() && !text_slice.is_char_boundary(run_len_within_line) {
        // Find the previous character boundary using efficient bit-level checking
        // UTF-8 characters are at most 4 bytes, so we only need to check up to 3 bytes back
        let lower_bound = run_len_within_line.saturating_sub(3);
        let search_range = &text_slice.as_bytes()[lower_bound..=run_len_within_line];
        
        // SAFETY: A valid character boundary must exist in this range because:
        // 1. run_len_within_line is a valid position in the string slice
        // 2. UTF-8 characters are at most 4 bytes, so some boundary exists in [run_len_within_line-3..=run_len_within_line]
        let pos_from_lower = unsafe {
            search_range
                .iter()
                .rposition(|&b| (b as i8) >= -0x40)
                .unwrap_unchecked()
        };
        
        run_len_within_line = lower_bound + pos_from_lower;
    }
}
```

## Testing

-  Builds successfully on all platforms
-  Eliminates UTF-8 character boundary panics
-  Maintains existing functionality for all text types
-  Handles edge cases like very long multi-byte characters

## Benefits

1. **Root cause fix**: Addresses the issue at the source rather than
treating symptoms
2. **Performance optimal**: Uses the same efficient algorithm as the
standard library
3. **Minimal changes**: Only modifies the specific problematic code path
4. **Future compatible**: Can be easily replaced with
`str::floor_char_boundary()` when stabilized

## Alternative Approaches Considered

1. **DirectWrite boundary fixing**: Initially tried to fix in
DirectWrite, but this was treating symptoms rather than the root cause
2. **Helper function approach**: Considered extracting to a helper
function, but inlined implementation is more appropriate for this
specific use case
3. **Standard library methods**: `floor_char_boundary()` is not yet
stable, so implemented equivalent logic

The chosen approach provides the best balance of performance, safety,
and code maintainability.
---
Release Notes:

- N/A
2025-09-23 14:03:29 +02:00
Lukas Wirth
1bf8332333 editor: Deduplicate locations in navigate_to_hover_links (#38707)
Closes
https://github.com/zed-industries/zed/issues/6730#issuecomment-3320933701

That way if multiple servers are running while reporting the same
results we prevent opening multi buffers for single entries.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-23 11:39:48 +00:00
Kirill Bulatov
d8048f46ee Test task shell commands (#38706)
Add tests on task commands, to ensure things like
https://github.com/zed-industries/zed/issues/38343 do not come so easily
unnoticed and to provide a base to create more tests in the future, if
needed.

Release Notes:

- N/A

---------

Co-authored-by: Lukas Wirth <lukas@zed.dev>
2025-09-23 11:12:39 +00:00
Kaikai
edb804de5a go: Stop running ghost tests, fix broken go test -run for suites (#38167)
Closed #33759
Closed #38166

### Summary

This PR fixes the way `go test` commands are generated for **testify
suite test methods**.
Previously, only the method name was included in the `-run` flag, which
caused Go’s test runner to fail to find suite test cases.

---

### Problem


https://github.com/user-attachments/assets/e6f80a77-bcf3-457c-8bfb-a7286d44ff71

1. **Incorrect command** was generated for suite tests:

   ```bash
   go test -run TestSomething_Success
   ```

   This results in:

   ```
   testing: warning: no tests to run
   ```

2. The correct format requires the **suite name + method name**:

   ```bash
   go test -run ^TestFooSuite$/TestSomething_Success$
   ```

Without the suite prefix (`TestFooSuite`), Go cannot locate test methods
defined on a suite struct.

---

### Changes Made

* **Updated `runnables.scm`**:

  * Added a new query rule for suite methods (`.*Suite` receiver types).
* Ensures only methods on suite structs (e.g., `FooSuite`) are matched.
  * Tagged these with `go-testify-suite` in addition to `go-test`.

* **Extended task template generation**:

  * Introduced `GO_SUITE_NAME_TASK_VARIABLE` to capture the suite name.
  * Create a `TaskTemplate` for the testify suite.

* **Improved labeling**:

* Labels now show the full path (`go test ./pkg -v -run
TestFooSuite/TestSomething_Success`) for clarity.

* **Added a test** `test_testify_suite_detection`:

* Covered testify suite cases to ensure correct detection and command
generation.

---

### Impact


https://github.com/user-attachments/assets/ef509183-534a-4aa4-9dc7-01402ac32260

* **Before**: Running a suite test method produced “no tests to run.”
* **After**: Suite test methods are runnable individually with the
correct `-run` command, and full suites can still be executed as before.

### Release Notes

* Fixed generation of `go test` commands for **testify suite test
methods**.
Suite methods now include both the suite name and the method name in the
`-run` flag (e.g., `^TestFooSuite$/TestSomething_Success$`), ensuring
they are properly detected and runnable individually.
2025-09-23 12:47:18 +02:00
tidely
691bfe71db search: Remove noisy buffer search logs (#38679)
Buffer search initiates a new search every time a key is pressed in the
buffer search bar. This would cancel the task associated with any
pending searches. Whenever one of these searches was canceled Zed would
log `[search]: oneshot canceled`. This log would trigger almost on every
keypress when typing moderately fast. This PR silences these logs by not
treating canceled searches as errors.

Release Notes:

- N/A
2025-09-23 11:59:37 +02:00
张小白
1d5da68560 windows: Show alt-= for pane::GoForward (#38696)
Reorder the shortcuts for `pane::GoForward` so the menu now shows
`Alt-=` instead of `forward`

Release Notes:

- N/A
2025-09-23 08:13:29 +00:00
Jakub Konka
f07bc12aed helix: Further cleanups to helix paste in line mode (#38694)
I noticed that after we paste in line mode, the cursor position is
positioned at the beginning of the next logical line which is somewhat
undesirable since then inserting/appending will position the cursor
after the selection. This does not match helix behaviour which we should
further investigate.

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

Release Notes:

- N/A
2025-09-23 07:44:50 +00:00
Michael Sloan
4532765ae8 zeta2: Add prompt planner and provide access via zeta_cli (#38691)
Release Notes:

- N/A
2025-09-23 06:20:26 +00:00
Conrad Irwin
25a1827456 Ensure we have the targets needed for bundling (#38688)
Closes #ISSUE

Release Notes:

- N/A
2025-09-23 03:51:03 +00:00
Conrad Irwin
98865a3ff2 Fix invalid anchors in breadcrumbs (#38687)
Release Notes:

- (nightly only) Fix panic when your cursor abuts a multibyte character
2025-09-23 02:38:12 +00:00
Michael Sloan
681a4adc42 Remove OutlineItem::signature_range as it is no longer used (#38680)
Use in edit predictions was removed in #38676

Release Notes:

- N/A
2025-09-22 22:57:41 +00:00
Julia Ryan
5e502a32fb Fix remote server crash with JSON files (#38678)
Closes #38594

Release Notes:

- N/A
2025-09-22 22:30:27 +00:00
Conrad Irwin
e9fbcf5abf Allow zed filename.rs: (#38677)
iTerm's editor configuration dialog allows you to set your editor to
`zed \1:\2`, but not (as far as I know) to leave off the : when there's
no line number

This fixes clicking on bare filenames in iTerm for me.

Release Notes:

- Fixed line number parsing so that `zed filename.rs:` will now act as
though you did `zed filename.rs`
2025-09-22 22:26:47 +00:00
Agus Zubiaga
c9e3b32366 zeta2: Provider setup (#38676)
Creates a new `EditPredictionProvider` for zeta2, that requests
completions from a new cloud endpoint including context from the new
`edit_prediction_context` crate. This is not ready for use, but it
allows us to iterate.

Release Notes:

- N/A

---------

Co-authored-by: Michael Sloan <michael@zed.dev>
Co-authored-by: Bennet <bennet@zed.dev>
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-09-22 22:18:38 +00:00
Jens Kouros
e9abd5b28b docs: Mention required matching configs when developing language server extensions (#38674)
This took me quite a while to find out when I developed my first
language extension. I had non-matching entries in the `languages` array
and in the name field of `config.toml`, and it was especially tricky
because the zed extension would start up, but not the language server. I
sure which this had been in the docs, so I am contributing it now!
2025-09-22 21:40:50 +00:00
Piotr Osiewicz
a90abb1009 Bump Rust to 1.90 (#38436)
Release Notes:

- N/A

---------

Co-authored-by: Nia Espera <nia@zed.dev>
Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
2025-09-22 14:36:10 -07:00
Jakub Konka
46d19d8a47 helix: Fix helix-paste mode in line mode (#38663)
In particular,
* if the selection ends at the beginning of the next line, and the
current line under the cursor is empty, we paste at the selection's end.
* if however the current line under the cursor is empty, we need to move
to the beginning of the next line to avoid pasting above the end of
current selection

In addition, in line mode, we always move the cursor to the end of the
inserted text. Otherwise, while it looks fine visually,
inserting/appending ends up in the next logical line which is not
desirable.

Release Notes:

- N/A
2025-09-22 23:03:37 +02:00
Marshall Bowers
e484f49ee8 language_models: Treat a block_reason from Gemini as a refusal (#38670)
This PR updates the Gemini provider to treat a
`prompt_feedback.block_reason` as a refusal, as Gemini does not seem to
return a `stop_reason` to use in this case.

<img width="639" height="162" alt="Screenshot 2025-09-22 at 4 23 15 PM"
src="https://github.com/user-attachments/assets/7a86d67e-06c1-49ea-b58f-fa80666f0f8c"
/>

Previously this would just result in no feedback to the user.

Release Notes:

- Added an error message when a Gemini response contains a
`block_reason`.
2025-09-22 20:40:56 +00:00
Nia
80dcabe95c perf: Better docs, internal refactors (#38664)
Release Notes:

- N/A
2025-09-22 22:37:51 +02:00
Joseph T. Lyons
e602cfadd3 Restore user-defined ordering of profiles (#38665)
This PR fixes a regression where settings profiles were no longer
ordered in the same order that the user defined in their settings.

Release Notes:

- N/A
2025-09-22 19:35:44 +00:00
Peter Tripp
d4adb51553 languages: Update package.json and tsconfig.json schemas (#38655)
Closes: https://github.com/zed-industries/zed/issues/34382

- Add support for `tsconfig.*.json` not just `tsconfig.json`. 
- Updated JSON schemas to
[SchemaStore/schemastore@281aa4a](281aa4aa4a)
(2025-09-21)
-
[tsconfig.json](https://github.com/SchemaStore/schemastore/commits/master/src/schemas/json/tsconfig.json)
@
[281aa4a](https://raw.githubusercontent.com/SchemaStore/schemastore/281aa4aa4ac21385814423f86a54d1b8ccfc17a1/src/schemas/json/tsconfig.json)
-
[package.json](https://github.com/SchemaStore/schemastore/commits/master/src/schemas/json/package.json)
@
[281aa4a](https://raw.githubusercontent.com/SchemaStore/schemastore/281aa4aa4ac21385814423f86a54d1b8ccfc17a1/src/schemas/json/package.json)

See also: 
- [discord
thread](https://discord.com/channels/869392257814519848/1419298937290096760)
-
https://github.com/zed-industries/zed/issues/21994#issuecomment-3319321308

Release Notes:

- Updated package.json and tsconfig.json schemas to newest release
(2025-09-21). Match `tsconfig.*.json` too.
2025-09-22 14:59:24 -04:00
Miao
a0514af589 editor: Make buffer search bar capture CopyPath & CopyRelativePath actions (#38645)
Closes #38495

Cause:

- When the Find input is focused, CopyPath/CopyRelativePath were handled
by the editor and stopped during the bubble phase, preventing
BufferSearchBar from relaying to the file-backed editor.

Release Notes:

- Fixes “Workspace: Copy Relative Path” not copying while the Find bar
is focused.
2025-09-22 19:56:40 +03:00
Joseph T. Lyons
c88fdaf02d Implement Markdown link embedding on paste (#38639)
This PR adds automatic markdown URL embedding on paste when you are in
text associated with the Markdown language and you have a valid URL in
your clipboard. This the default behavior in VS Code and GitHub, when
pasting a URL in Markdown. It works in both singleton buffers and multi
buffers.

One thing that is a bit unfortunate is that, previously, `do_paste` use
to simply call `Editor::insert()`, in the case of pasting content that
was copied from an external application, and now, we are duplicating
some of `insert()`'s logic in place, in order to have control over
transforming the edits before they are inserted.

Release Notes:

- Added automatic Markdown URL embedding on paste.

---------

Co-authored-by: Cole Miller <53574922+cole-miller@users.noreply.github.com>
2025-09-22 12:33:12 -04:00
Conrad Irwin
003163eb4f Move my keybinding fixes to the right platform (#38654)
In cffb883108 I put the fixed keybindings
on the wrong platform

Release Notes:

- Fix syntax node shortcuts
2025-09-22 10:22:37 -06:00
Jakub Konka
9e64b7b911 terminal: Escape args in alacritty on Windows (#38650)
Release Notes:

- N/A
2025-09-22 18:12:35 +02:00
Ran Benita
d4fd59f0a2 vim: Add support for <count>gt and <count>gT (#38570)
Vim mode currently supports `gt` (go to next tab) and `gT` (go to
previous tab) but not with count. Implement the expected behavior as
defined by vim:

- `<count>gt` moves to tab `<count>`
- `<count>gT` moves to previous tab `<count>` times (with wraparound)

Release Notes:

- Improved vim `gt` and `gT` to support count, e.g. `5gt` - go to tab 5,
`8gT` - go to 8th previous tab with wraparound.
2025-09-22 10:07:16 -06:00
Ben Brandt
4e6e424fd7 acp: Support model selection for ACP agents (#38652)
It requires the agent to implement the (still unstable) model selection
API. Will allow us to test it out before stabilizing.

Release Notes:

- N/A
2025-09-22 15:07:40 +00:00
Conrad Irwin
dccbb47fbc Use a consistent default for window scaling (#38527)
(And make it 2, because most macs have retina screens)

Release Notes:

- N/A
2025-09-22 08:56:15 -06:00
Ilija Tovilo
b97843ea02 Add quick "Edit debug.json" button to debugger control strip (#38600)
This button already exists in the main menu, as well as the "New
Session" view in the debugger panel. However, this view disappears after
starting the debugging session. This PR adds the same button to the
debugger control strip that remains accessible. This is convenient for
people editing their debug.json frequently.

Site-node: I feel like the `Cog` icon would be more appropriate, but I
picked `Code` to stay consistent with the "New Session" view.

Before:

<img width="194" height="118" alt="image"
src="https://github.com/user-attachments/assets/5b42a8a4-f48f-4145-a425-53365dd785ca"
/>

After:

<img width="194" height="118" alt="image"
src="https://github.com/user-attachments/assets/12f56ea1-150b-4564-8e6a-da4671f52079"
/>

Release Notes:

- Added "Edit debug.json" button to debugger control strip
2025-09-22 16:52:33 +02:00
Xiaobo Liu
fbe06238e4 cli: Refactor URL prefix checks (#38375)
use slice apply to prefix.

Release Notes:

- N/A

---------

Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>
2025-09-22 13:12:19 +00:00
Bartosz Kaszubowski
e0028fbef2 git_ui: Remove duplicated/unused tooltips (#38439)
Release Notes:

- N/A
2025-09-22 12:56:37 +00:00
strygwyr
1bbf98aea6 Fix arrow function detection in TypeScript/JavaScript outline (#38411)
Closes #35102 



https://github.com/user-attachments/assets/3c946d6c-0acd-4cfe-8cb3-61eb6d20f808


Release Notes:

- TypeScript/JavaScript: symbol outline now includes closures nested
within functions.
2025-09-22 14:35:43 +02:00
localcc
8bac1bee7a Disable subpixel shifting for y axis on Windows (#38440)
Release Notes:

- N/A

---------

Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
2025-09-22 13:46:29 +02:00
Lukas Wirth
55dc9ff7ca text: Implement Rope::clip_offset in terms of the new utf8 boundary methods (#38630)
Release Notes:

- N/A
2025-09-22 11:45:23 +00:00
Justin Su
50bd8bc255 docs: Add instructions for setting up fish_indent for fish (#38414)
Release Notes:

- N/A
2025-09-22 14:29:46 +03:00
Lukas Wirth
a2c71d3d20 text: Assert text anchor offset validity on construction (#38441)
Attempt to aid debugging some utf8 indexing issues

Release Notes:

- N/A

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2025-09-22 11:20:46 +00:00
Matheus
79620454d0 Docs: change format_on_save value from false to "off" (#38615)
Found this outdated piece of information in the docs while trying to
disable it myself, this PR simply changes `false` to `"off"`.

Release Notes:

- N/A
2025-09-22 10:14:04 +00:00
Miao
271771c742 editor: Prevent non‑boundary highlight indices in UTF‑8 (#38510)
Closes #38359

Release Notes:

- Use byte offsets for highlights; fix UTF‑8 crash
2025-09-22 11:06:54 +02:00
Remy Suen
891a06c294 docs: Small grammar fix to use a possessive pronoun (#38610)
> Your extension can define it's own debug locators
> Your extension can define it is own debug locators

The sentence above does not make sense after expanding "it's". We should
instead be using the possessive "its" in this scenario.

Release Notes:

- N/A

Signed-off-by: Remy Suen <remy.suen@docker.com>
2025-09-21 20:18:17 -04:00
Nia
11041ef3b0 perf: Greatly expand profiler (#38584)
Expands on #38543 (notably allows setting importance categories and
weights on tests, and a lot of internal refactoring) because I couldn't
help myself. Also allows exporting runs to json and comparing across them. See code for docs.

Release Notes:

- N/A
2025-09-21 13:54:59 +02:00
Jakub Konka
839c216620 terminal: Re-add sanitizing trailing periods in URL detection (#38569)
I accidentally regressed this when bumping alacritty in
https://github.com/zed-industries/zed/pull/38505

cc @davewa 

Release Notes:

- N/A
2025-09-20 22:10:47 +02:00
Cole Miller
18df6a81b4 acp: Fix spawning login task (#38567)
Reverts #38175, which is not correct, since in fact we do need to
pre-quote the command and arguments for the shell when using
`SpawnInTerminal` (although we should probably change the API so that
this isn't necessary). Then, applies the same fix as #38565 to fix the
root cause of being unable to spawn the login task on macOS, or in any
case where the command/args contain spaces.

Release Notes:

- Fixed being unable to login with Claude Code or Gemini using the
terminal.
2025-09-20 14:14:55 -04:00
CharlesChen0823
f5c2e4b49e vim: Remove duplicate bracket pair (#38560)
remove depulicate code, this same with line: 556-562

Release Notes:

- N/A
2025-09-20 20:01:55 +02:00
Vitaly Slobodin
1d1bbf01a9 docs: Mention herb LSP for Ruby language (#38351)
Hi! This pull request mentions [the `herb` LSP](https://herb-tools.dev)
for `HTML/ERB` language that the Ruby extension supports. Thanks!

Release Notes:

- N/A

---------

Co-authored-by: Finn Evers <finn.evers@outlook.de>
2025-09-20 17:29:12 +00:00
Marshall Bowers
ffa23d25e3 Fix formatting in workspace Cargo.toml (#38563)
This PR fixes some formatting issues in the workspace `Cargo.toml`.

Release Notes:

- N/A
2025-09-20 15:23:02 +00:00
Nia
782058647d tests: Add an automatic perf profiler (#38543)
Add an auto-profiler for our tests, to hopefully allow better triage of
performance impacts resulting from code changes. Comprehensive usage
docs are in the code.

Currently, it uses hyperfine under the hood and prints markdown to the
command line for all crates with relevant tests enabled. We may want to
expand this to allow outputting json in the future to allow e.g.
automatically comparing the difference between two runs on different
commits, and in general a lot of functionality could be added (maybe
measuring memory usage?).

It's enabled (mostly as an example) on two tests inside `gpui` and a
bunch of those inside `vim`. I'd have happily used `cargo bench`, but that's nightly-only.

Release Notes:

- N/A
2025-09-20 09:04:32 +02:00
Smit Barmase
be77682a3f editor: Fix adding extraneous closing tags within TSX (#38534) 2025-09-20 04:40:22 +05:30
Mikayla Maki
8df616e28b Suppress the 'Agent Thread Started' event when initializing the panel (#38535)
Release Notes:

- N/A
2025-09-19 22:55:32 +00:00
Jakub Konka
89520ea221 chore: Bump alacritty_terminal to 0.25.1-rc1 (#38505)
Release Notes:

- N/A

---------

Co-authored-by: Dave Waggoner <waggoner.dave@gmail.com>
2025-09-20 00:15:01 +02:00
Marshall Bowers
de75e2d9f6 extension_host: Expand supported extension API range to include v0.7.0 (#38529)
This PR updates the version range for v0.6.0 of the extension API to
include v0.7.0.

Since we bumped the `zed_extension_api` crate's version to v0.7.0, we
need to expand this range in order for Zed clients to be able to install
extensions built against v0.7.0 of `zed_extension_api`.

Currently no extensions that target `zed_extension_api@0.7.0` can be
installed.

Release Notes:

- N/A
2025-09-19 20:48:52 +00:00
Ben Kunkle
4e316c683b macos: Fix panic when NSWindow::screen returns nil (#38524)
Closes #ISSUE

Release Notes:

- mac: Fixed an issue where Zed would panic if the workspace window was
previously off screen
2025-09-19 13:07:02 -06:00
Conrad Irwin
1afbfcb832 git: Docs-based workaround for GitHub/git auth confusion (#38479)
Closes #ISSUE

Release Notes:

- git: Added a link to Github's authentication help if you end up in Zed
trying to type a password in for https auth
2025-09-19 11:14:31 -06:00
Conrad Irwin
be7575536e Fix theme overrides (#38512)
Release Notes:

- N/A
2025-09-19 10:51:21 -06:00
Conrad Irwin
30a29ab34e Fix server settings (#38477)
In the settings refactor I'd assumed server settings were like project
settings. This is not the case, they are in fact the normal user
settings;
but just read from the server.

Release Notes:

- N/A
2025-09-19 10:38:39 -06:00
Piotr Osiewicz
b9188e0fd3 collab: Fix screen share aspect ratio on non-Mac platforms (#38517)
It was just a bunch of finnickery around UI layout. It affected Linux
too.



Release Notes:

* Fixed aspect ratio of peer screen share when using Linux/Windows
builds.
2025-09-19 18:38:22 +02:00
Finn Evers
df6f0bc2a7 Fix markdown list in bump-zed-minor-versions (#38515)
This fixes a small markdown issue in the `bump-zed-minor-versions`
script that bugged me for too long 😅

Release Notes:

- N/A
2025-09-19 16:11:19 +00:00
Dino
4743fe8415 vim: Fix regression in surround behavior (#38344)
Fix an issue introduced in
https://github.com/zed-industries/zed/pull/37321 where vim's surround
wouldn't work as expected when replacing quotes with non-quotes, with
whitespace always being added, regardless of whether the opening or
closing bracket was used. This is not the intended, or previous,
behavior, where only the opening bracket would trigger whitespace to be
added.

Closes #38169 

Release Notes:

- Fixed regression in vim's surround plugin that ignored whether the
opening or closing bracket was being used when replacing quotes, so
space would always be added
2025-09-19 09:50:33 -06:00
Jan Češpivo
0f4bdca9e9 Update icon theme fallback to use default theme (#38485)
https://github.com/zed-industries/zed/pull/38367 introduced panic:

```
thread 'main' panicked at crates/theme/src/settings.rs:812:18:
called `Option::unwrap()` on a `None` value
```

In this PR I restored fallback logic from the original code - before
settings refactor.

Release Notes:

- N/A
2025-09-19 15:17:35 +00:00
Finn Evers
154b01c5fe Dismiss agent panel when disable_ai is toggled to true (#38461)
Closes https://github.com/zed-industries/zed/issues/38331

This fixes an issue where we would not dismiss the panel once the user
toggled the setting, leaving them in an awkward state where closing the
panel would become hard.

Also takes care of one more check for the `Fix with assistant` action
and consolidates some of the `AgentSettings` and `DisableAiSetting`
checks into one method to make the code more readable.

Release Notes:

- N/A
2025-09-19 17:05:39 +02:00
逃生舱
b6944d0bae docs: Fix duplicate postgresql package and punctuation error (#38478)
Found duplicate `postgresql` package in installation command. Uncertain
whether it should be `postgresql-contrib` or `postgresql-client`, but
neither appears necessary.


Release Notes:

- N/A
2025-09-19 16:43:25 +02:00
Dimas Ari
94fcbb400b docs: Update invalid property in a configuration example (#38466)
Just install Zed for the first time and got a warning from the first
config example i copied from docs.
Great design btw, immediately able to see that this is a well thought
out app. seems like i'll stick with zed and make it my new dev
'sanctuary'.

Release Notes:

- N/A
2025-09-19 16:36:36 +02:00
David Kleingeld
2e97ef32c4 Revert "Audio fixes and mic denoise" (#38509)
Reverts zed-industries/zed#38493

Release Notes:

- N/A
2025-09-19 10:33:38 -04:00
David Kleingeld
aa5b99dc11 Fully qualify images in Docker Compose (#38496)
This enables podman-compose (easier to install and run on linux) as drop
in replacement for docker-compose

Release Notes:

- N/A
2025-09-19 10:12:49 -04:00
Peter Tripp
3217bcb83e docs: Add Kotlin JAVA_HOME example (#38507)
Closes: https://github.com/zed-extensions/kotlin/issues/46

Release Notes:

- N/A
2025-09-19 13:59:13 +00:00
Bartosz Kaszubowski
a3da66cec0 editor: Correct "Toggle Excerpt Fold" tip on macOS (#38487)
Show `"Option+click to toggle all"` instead of `"Alt+click to toggle
all" on macOS.

<img width="546" height="212" alt="Screenshot 2025-09-19 at 10 16 11"
src="https://github.com/user-attachments/assets/b1052b7c-349f-4a11-892b-988cfd2ff365"
/>

Release Notes:

- N/A
2025-09-19 09:41:52 -04:00
Derek Nguyen
9e6f1d5a6e python: Fix ty binary path and required args (#38458)
Closes #38347

Release Notes:

- Fixed path and args to ty lsp binary


When attempting to use the new ty lsp integration in the preview, I
noticed issues related to accessing the binary. After deleting the
downloaded archive and adding the following changes that:

- downloads the archive with the correct `AssetKind::TarGz`
- uses the correct path to the extracted binary
- adds the `server` argument to initialize the lsp (like ruff)

After the above changes the LSP starts correctly
```bash
2025-09-18T16:17:03-05:00 INFO  [lsp] starting language server process. binary path: "/Users/dereknguyen/Library/Application Support/Zed/languages/ty/ty-0.0.1-alpha.20/ty-aarch64-apple-darwin/ty", working directory: "/Users/dereknguyen/projects/test-project", args: ["server"]
```
<img width="206" height="98" alt="image"
src="https://github.com/user-attachments/assets/8fcf423f-40a0-4cd9-a79e-e09666323fe2"
/>

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-09-19 13:29:40 +00:00
Cole Miller
430ac5175f python: Install basedpyright with npm instead of pip (#38471)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-19 13:14:52 +00:00
Bennet Bo Fenner
5f728efccf agent: Show custom MCP servers in agent configuration (#38500)
Fixes a regression introduced in #38419

Release Notes:

- N/A
2025-09-19 12:21:28 +00:00
David Kleingeld
194a13ffb5 Add denoising & prepare for migrating to new samplerate & channel count (#38493)
Uses the previously merged denoising crate (and fixes a bug in it that snug in during refactoring) in the microphone input. The experimental audio path now picks the samplerate and channel count depending on a setting. It can handle incoming streams with both the current (future legacy) and new samplerate & channel count. These are url-encoded into the livekit track name.
2025-09-19 10:31:54 +00:00
jneem
66f2fda625 helix: Initial support for helix-mode paste (#37963)
This is a redo of #29776. I went for a separate function -- instead of
adding a bunch of conditions to `vim::Paste` -- because there were quite
a few differences.

Release Notes:

- Added a `vim::HelixPaste` command that imitates Helix's paste behavior

---------

Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
2025-09-19 09:42:04 +00:00
Conrad Irwin
e62dd2a0e5 Tighten up MergeFrom trait (#38473)
Release Notes:

- N/A
2025-09-18 22:28:17 -06:00
Nia
c826ce6fc6 markdown: Use the faster hasher (#38469)
Micro-optimisation in the markdown crate to use the faster hasher.

Release Notes:

- N/A
2025-09-19 01:51:41 +00:00
Nia
e5e308ba78 fuzzy: Fixup atomic ordering (#38468)
Hopefully partially addresses some crashes that can be triggered in this
code.

Release Notes:

- N/A
2025-09-19 00:45:59 +00:00
Julia Ryan
166b2352f3 Respect user's font-smoothing setting (#38467)
#37622 was incorrectly forcing font smoothing to be enabled on macos
even when the user had disabled that setting at the OS level. See [this
comment](https://github.com/zed-industries/zed/pull/37622#issuecomment-3310030659)
for an example of the difference that font smoothing makes.

Release Notes:

- N/A
2025-09-18 17:21:42 -07:00
tidely
f18b19a73e http_client: Relax lifetime bounds and add fluent builder methods (#38448)
`HttpClient`: Relaxes the lifetime bound to `&self` in `get`/`post`
by returning the `self.send` future directly. This makes both
methods return `'static` futures without extra boxing.

`HttpRequestExt`: Added fluent builder methods to `HttpRequestExt`
inspired by the `gpui::FluentBuilder` trait.

Release Notes:

- N/A
2025-09-19 01:39:26 +02:00
Conrad Irwin
b09764c54a settings: Use a derive macro for refine (#38451)
When we refactored settings to not pass JSON blobs around, we ended up
needing
to write *a lot* of code that just merged things (like json merge used
to do).

Use a derive macro to prevent typos in this logic.

Release Notes:

- N/A
2025-09-18 21:13:49 +00:00
Conrad Irwin
5f4f0a873e Fix wierd rust-analyzer error (#38431)
Release Notes:

- N/A
2025-09-18 14:58:15 -06:00
Conrad Irwin
82e1e5b7ac Fix panic in vim mode (#38437)
Release Notes:

- vim: Fixed a rare panic in search
2025-09-18 14:58:07 -06:00
Peter Tripp
530225a06a python: Remove a redundant pip install call (#38449)
I confirmed that the pip packages match for:
```sh
pip install python-lsp-server && pip install 'python-lsp-server[all]'
pip install 'python-lsp-server[all]'
```

Originally introduced here:
- https://github.com/zed-industries/zed/pull/20358 

Release Notes:

- N/A
2025-09-18 16:40:06 -04:00
Peter Tripp
11212b80f9 docs: Improve Elixir HEEX language server documentation (#38363)
Closes: https://github.com/zed-industries/zed/issues/38009

Release Notes:

- N/A
2025-09-18 16:39:50 -04:00
Anthony Eid
e3e0522e32 debugger: Fix debug scenario picker showing history in reverse order (#38452)
Closes #37859

Release Notes:

- debugger: Fix sort order of pasted launched debug sessions in debugger
launch modal
2025-09-18 20:15:25 +00:00
Matt
fc0eb882f7 debugger_ui: Update new process modal to include more context about its source (#36650)
Closes #36280

Release Notes:
  - Added additional context to debug task selection

Adding additional context when selecting a debug task to help with
projects that have multiple config files with similar names for tasks.

I think there is room for improvement, especially adding context for a
LanguageTask type. I started but it looked like it would need to add a
path value to that and wanted to make sure this was a good idea before
working on that.

Also any thoughts on the wording if you do like this format? 

---

<img width="1246" height="696" alt="image"
src="https://github.com/user-attachments/assets/b42e3f45-cfdb-4cb1-8a7a-3c37f33f5ee2"
/>

---------

Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Anthony <hello@anthonyeid.me>
2025-09-18 16:05:55 -04:00
885 changed files with 152318 additions and 28075 deletions

View File

@@ -10,3 +10,15 @@
# Here, we opted to use `[target.'cfg(all())']` instead of `[build]` because `[target.'**']` is guaranteed to be cumulative.
[target.'cfg(all())']
rustflags = ["-D", "warnings"]
# Use Mold on Linux, because it's faster than GNU ld and LLD.
#
# We no longer set this in the default `config.toml` so that developers can opt in to Wild, which
# is faster than Mold, in their own ~/.cargo/config.toml.
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
[target.aarch64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

View File

@@ -4,14 +4,9 @@ rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"]
[alias]
xtask = "run --package xtask --"
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
[target.aarch64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
perf-test = ["test", "--profile", "release-fast", "--lib", "--bins", "--tests", "--all-features", "--config", "target.'cfg(true)'.runner='cargo run -p perf --release'", "--config", "target.'cfg(true)'.rustflags=[\"--cfg\", \"perf_enabled\"]"]
# Keep similar flags here to share some ccache
perf-compare = ["run", "--profile", "release-fast", "-p", "perf", "--config", "target.'cfg(true)'.rustflags=[\"--cfg\", \"perf_enabled\"]", "--", "compare"]
[target.'cfg(target_os = "windows")']
rustflags = [

View File

@@ -24,9 +24,9 @@ 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 = "808aa5c45b41e8f44729d02e38fd00a2fe2722e7" },
{ name = "zed-scap", git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197176fb19cd0f9b180dd36eaac", version = "0.0.8-zed" },
# build of remote_server should not need to include on libalsa through rodio
{ name = "rodio", git = "https://github.com/RustAudio/rodio", branch = "better_wav_output"},
{ name = "rodio", git = "https://github.com/RustAudio/rodio" },
]
[final-excludes]
@@ -37,8 +37,6 @@ workspace-members = [
"zed_glsl",
"zed_html",
"zed_proto",
"zed_ruff",
"slash_commands_example",
"zed_snippets",
"zed_test_extension",
]

View File

@@ -20,4 +20,4 @@ runs:
- name: Run tests
shell: bash -euxo pipefail {0}
run: cargo nextest run --workspace --no-fail-fast
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final

View File

@@ -24,4 +24,4 @@ runs:
shell: powershell
working-directory: ${{ inputs.working-directory }}
run: |
cargo nextest run --workspace --no-fail-fast
cargo nextest run --workspace --no-fail-fast --failure-output immediate-final

View File

@@ -826,8 +826,9 @@ jobs:
timeout-minutes: 120
name: Create a Windows installer
runs-on: [self-32vcpu-windows-2022]
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'))
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 }}
@@ -870,8 +871,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: ${{ !(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')) }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}

View File

@@ -0,0 +1,48 @@
name: Community Champion Auto Labeler
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
jobs:
label_community_champion:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
- name: Check if author is a community champion and apply label
uses: actions/github-script@v7
with:
script: |
const communityChampionBody = `${{ secrets.COMMUNITY_CHAMPIONS }}`;
const communityChampions = communityChampionBody
.split('\n')
.map(handle => handle.trim().toLowerCase());
let author;
if (context.eventName === 'issues') {
author = context.payload.issue.user.login;
} else if (context.eventName === 'pull_request_target') {
author = context.payload.pull_request.user.login;
}
if (!author || !communityChampions.includes(author.toLowerCase())) {
return;
}
const issueNumber = context.payload.issue?.number || context.payload.pull_request?.number;
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: ['community champion']
});
console.log(`Applied 'community champion' label to #${issueNumber} by ${author}`);
} catch (error) {
console.error(`Failed to apply label: ${error.message}`);
}

View File

@@ -39,7 +39,7 @@ jobs:
content: ${{ steps.get-content.outputs.string }}
send_release_notes_email:
if: github.repository_owner == 'zed-industries' && !github.event.release.prerelease
if: false && github.repository_owner == 'zed-industries' && !github.event.release.prerelease
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

View File

@@ -1,33 +0,0 @@
name: Issue Response
on:
schedule:
- cron: "0 12 * * 2"
workflow_dispatch:
jobs:
issue-response:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
with:
version: 9
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "20"
cache: "pnpm"
cache-dependency-path: "script/issue_response/pnpm-lock.yaml"
- run: pnpm install --dir script/issue_response
- name: Run Issue Response
run: pnpm run --dir script/issue_response start
env:
ISSUE_RESPONSE_GITHUB_TOKEN: ${{ secrets.ISSUE_RESPONSE_GITHUB_TOKEN }}
SLACK_ISSUE_RESPONSE_WEBHOOK_URL: ${{ secrets.SLACK_ISSUE_RESPONSE_WEBHOOK_URL }}

1
.gitignore vendored
View File

@@ -20,6 +20,7 @@
.venv
.vscode
.wrangler
.perf-runs
/assets/*licenses.*
/crates/collab/seed.json
/crates/theme/schemas/theme.json

View File

@@ -63,6 +63,7 @@ Although there are few hard and fast rules, typically we don't merge:
- New file icons. Zed's default icon theme consists of icons that are hand-designed to fit together in a cohesive manner, please don't submit PRs with off-the-shelf SVGs.
- Giant refactorings.
- Non-trivial changes with no tests.
- Stylistic code changes that do not alter any app logic. Reducing allocations, removing `.unwrap()`s, fixing typos is great; making code "more readable" — maybe not so much.
- Features where (in our subjective opinion) the extra complexity isn't worth it for the number of people who will benefit.
- Anything that seems completely AI generated.

2606
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,7 @@ members = [
"crates/cloud_api_client",
"crates/cloud_api_types",
"crates/cloud_llm_client",
"crates/cloud_zeta2_prompt",
"crates/collab",
"crates/collab_ui",
"crates/collections",
@@ -58,7 +59,7 @@ members = [
"crates/edit_prediction",
"crates/edit_prediction_button",
"crates/edit_prediction_context",
"crates/edit_prediction_tools",
"crates/zeta2_tools",
"crates/editor",
"crates/eval",
"crates/explorer_command_injector",
@@ -89,9 +90,8 @@ members = [
"crates/image_viewer",
"crates/inspector_ui",
"crates/install_cli",
"crates/jj",
"crates/jj_ui",
"crates/journal",
"crates/json_schema_store",
"crates/keymap_editor",
"crates/language",
"crates/language_extension",
@@ -150,8 +150,9 @@ members = [
"crates/semantic_version",
"crates/session",
"crates/settings",
"crates/settings_macros",
"crates/settings_profile_selector",
"crates/settings_ui_macros",
"crates/settings_ui",
"crates/snippet",
"crates/snippet_provider",
"crates/snippets_ui",
@@ -163,6 +164,7 @@ members = [
"crates/sum_tree",
"crates/supermaven",
"crates/supermaven_api",
"crates/codestral",
"crates/svg_preview",
"crates/system_specs",
"crates/tab_switcher",
@@ -199,6 +201,7 @@ members = [
"crates/zed_actions",
"crates/zed_env_vars",
"crates/zeta",
"crates/zeta2",
"crates/zeta_cli",
"crates/zlog",
"crates/zlog_settings",
@@ -210,15 +213,14 @@ members = [
"extensions/glsl",
"extensions/html",
"extensions/proto",
"extensions/ruff",
"extensions/slash-commands-example",
"extensions/snippets",
"extensions/test-extension",
#
# Tooling
#
"tooling/perf",
"tooling/workspace-hack",
"tooling/xtask",
]
@@ -269,9 +271,10 @@ 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" }
cloud_zeta2_prompt = { path = "crates/cloud_zeta2_prompt" }
collab = { path = "crates/collab" }
collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections" }
collections = { path = "crates/collections", package = "zed-collections", version = "0.1.0" }
command_palette = { path = "crates/command_palette" }
command_palette_hooks = { path = "crates/command_palette_hooks" }
component = { path = "crates/component" }
@@ -287,6 +290,7 @@ debug_adapter_extension = { path = "crates/debug_adapter_extension" }
debugger_tools = { path = "crates/debugger_tools" }
debugger_ui = { path = "crates/debugger_ui" }
deepseek = { path = "crates/deepseek" }
derive_refineable = { path = "crates/refineable/derive_refineable", package = "zed-derive-refineable", version = "0.1.0" }
diagnostics = { path = "crates/diagnostics" }
editor = { path = "crates/editor" }
extension = { path = "crates/extension" }
@@ -305,22 +309,21 @@ git_ui = { path = "crates/git_ui" }
go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" }
gpui = { path = "crates/gpui", default-features = false }
gpui_macros = { path = "crates/gpui_macros" }
gpui_macros = { path = "crates/gpui_macros", package = "gpui-macros", version = "0.1.0" }
gpui_tokio = { path = "crates/gpui_tokio" }
html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client" }
http_client = { path = "crates/http_client", package = "zed-http-client", version = "0.1.0" }
http_client_tls = { path = "crates/http_client_tls" }
icons = { path = "crates/icons" }
image_viewer = { path = "crates/image_viewer" }
edit_prediction = { path = "crates/edit_prediction" }
edit_prediction_button = { path = "crates/edit_prediction_button" }
edit_prediction_context = { path = "crates/edit_prediction_context" }
edit_prediction_tools = { path = "crates/edit_prediction_tools" }
zeta2_tools = { path = "crates/zeta2_tools" }
inspector_ui = { path = "crates/inspector_ui" }
install_cli = { path = "crates/install_cli" }
jj = { path = "crates/jj" }
jj_ui = { path = "crates/jj_ui" }
journal = { path = "crates/journal" }
json_schema_store = { path = "crates/json_schema_store" }
keymap_editor = { path = "crates/keymap_editor" }
language = { path = "crates/language" }
language_extension = { path = "crates/language_extension" }
@@ -338,7 +341,7 @@ lsp = { path = "crates/lsp" }
markdown = { path = "crates/markdown" }
markdown_preview = { path = "crates/markdown_preview" }
svg_preview = { path = "crates/svg_preview" }
media = { path = "crates/media" }
media = { path = "crates/media", package = "zed-media", version = "0.1.0" }
menu = { path = "crates/menu" }
migrator = { path = "crates/migrator" }
mistral = { path = "crates/mistral" }
@@ -355,6 +358,7 @@ outline = { path = "crates/outline" }
outline_panel = { path = "crates/outline_panel" }
panel = { path = "crates/panel" }
paths = { path = "crates/paths" }
perf = { path = "tooling/perf", package = "zed-perf", version = "0.1.0" }
picker = { path = "crates/picker" }
plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
@@ -366,7 +370,7 @@ project_symbols = { path = "crates/project_symbols" }
prompt_store = { path = "crates/prompt_store" }
proto = { path = "crates/proto" }
recent_projects = { path = "crates/recent_projects" }
refineable = { path = "crates/refineable" }
refineable = { path = "crates/refineable", package = "zed-refineable", version = "0.1.0" }
release_channel = { path = "crates/release_channel" }
scheduler = { path = "crates/scheduler" }
remote = { path = "crates/remote" }
@@ -374,16 +378,16 @@ remote_server = { path = "crates/remote_server" }
repl = { path = "crates/repl" }
reqwest_client = { path = "crates/reqwest_client" }
rich_text = { path = "crates/rich_text" }
rodio = { git = "https://github.com/RustAudio/rodio", branch = "better_wav_output"}
rodio = { git = "https://github.com/RustAudio/rodio" }
rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" }
rules_library = { path = "crates/rules_library" }
search = { path = "crates/search" }
semantic_version = { path = "crates/semantic_version" }
semantic_version = { path = "crates/semantic_version", package = "zed-semantic-version", version = "0.1.0" }
session = { path = "crates/session" }
settings = { path = "crates/settings" }
settings_macros = { path = "crates/settings_macros" }
settings_ui = { path = "crates/settings_ui" }
settings_ui_macros = { path = "crates/settings_ui_macros" }
snippet = { path = "crates/snippet" }
snippet_provider = { path = "crates/snippet_provider" }
snippets_ui = { path = "crates/snippets_ui" }
@@ -392,9 +396,10 @@ sqlez_macros = { path = "crates/sqlez_macros" }
story = { path = "crates/story" }
storybook = { path = "crates/storybook" }
streaming_diff = { path = "crates/streaming_diff" }
sum_tree = { path = "crates/sum_tree" }
sum_tree = { path = "crates/sum_tree", package = "zed-sum-tree", version = "0.1.0" }
supermaven = { path = "crates/supermaven" }
supermaven_api = { path = "crates/supermaven_api" }
codestral = { path = "crates/codestral" }
system_specs = { path = "crates/system_specs" }
tab_switcher = { path = "crates/tab_switcher" }
task = { path = "crates/task" }
@@ -415,8 +420,8 @@ ui = { path = "crates/ui" }
ui_input = { path = "crates/ui_input" }
ui_macros = { path = "crates/ui_macros" }
ui_prompt = { path = "crates/ui_prompt" }
util = { path = "crates/util" }
util_macros = { path = "crates/util_macros" }
util = { path = "crates/util", package = "zed-util", version = "0.1.0" }
util_macros = { path = "crates/util_macros", package = "zed-util-macros", version = "0.1.0" }
vercel = { path = "crates/vercel" }
vim = { path = "crates/vim" }
vim_mode_setting = { path = "crates/vim_mode_setting" }
@@ -431,6 +436,7 @@ zed = { path = "crates/zed" }
zed_actions = { path = "crates/zed_actions" }
zed_env_vars = { path = "crates/zed_env_vars" }
zeta = { path = "crates/zeta" }
zeta2 = { path = "crates/zeta2" }
zlog = { path = "crates/zlog" }
zlog_settings = { path = "crates/zlog_settings" }
@@ -438,9 +444,9 @@ zlog_settings = { path = "crates/zlog_settings" }
# External crates
#
agent-client-protocol = { version = "0.4.0", features = ["unstable"] }
agent-client-protocol = { version = "0.4.3", features = ["unstable"] }
aho-corasick = "1.1"
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
alacritty_terminal = "0.25.1-rc1"
any_vec = "0.14"
anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] }
@@ -469,10 +475,9 @@ backtrace = "0.3"
base64 = "0.22"
bincode = "1.2.1"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "bfa594ea697d4b6326ea29f747525c85ecf933b9" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "bfa594ea697d4b6326ea29f747525c85ecf933b9" }
blade-util = { git = "https://github.com/kvark/blade", rev = "bfa594ea697d4b6326ea29f747525c85ecf933b9" }
blake3 = "1.5.3"
blade-graphics = { version = "0.7.0" }
blade-macros = { version = "0.3.0" }
blade-util = { version = "0.3.0" }
bytes = "1.0"
cargo_metadata = "0.19"
cargo_toml = "0.21"
@@ -509,6 +514,7 @@ futures-lite = "1.13"
git2 = { version = "0.20.1", default-features = false }
globset = "0.4"
handlebars = "4.3"
hashbrown = "0.15.3"
heck = "0.5"
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
hex = "0.4.3"
@@ -524,7 +530,6 @@ indexmap = { version = "2.7.0", features = ["serde"] }
indoc = "2"
inventory = "0.3.19"
itertools = "0.14.0"
jj-lib = { git = "https://github.com/jj-vcs/jj", rev = "e18eb8e05efaa153fad5ef46576af145bba1807f" }
json_dotpath = "1.1"
jsonschema = "0.30.0"
jsonwebtoken = "9.3"
@@ -545,6 +550,7 @@ nanoid = "0.4"
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
nix = "0.29"
num-format = "0.4.4"
num-traits = "0.2"
objc = "0.2"
objc2-foundation = { version = "0.3", default-features = false, features = [
"NSArray",
@@ -601,7 +607,8 @@ rand = "0.9"
rayon = "1.8"
ref-cast = "1.0.24"
regex = "1.5"
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c770a32f1998d6e999cef3e59e0013e6c4415", default-features = false, features = [
# WARNING: If you change this, you must also publish a new version of zed-reqwest to crates.io
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "c15662463bda39148ba154100dd44d3fba5873a4", default-features = false, features = [
"charset",
"http2",
"macos-system-configuration",
@@ -609,17 +616,17 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c77
"rustls-tls-native-roots",
"socks",
"stream",
] }
], package = "zed-reqwest", version = "0.12.15-zed" }
rsa = "0.9.6"
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
"async-dispatcher-runtime",
] }
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustc-demangle = "0.1.23"
rustc-hash = "2.1.0"
rustls = { version = "0.23.26" }
rustls-platform-verifier = "0.5.0"
scap = { git = "https://github.com/zed-industries/scap", rev = "808aa5c45b41e8f44729d02e38fd00a2fe2722e7", default-features = false }
# WARNING: If you change this, you must also publish a new version of zed-scap to crates.io
scap = { git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197176fb19cd0f9b180dd36eaac", default-features = false, package = "zed-scap", version = "0.0.8-zed" }
schemars = { version = "1.0", features = ["indexmap2"] }
semver = "1.0"
serde = { version = "1.0.221", features = ["derive", "rc"] }
@@ -645,7 +652,7 @@ streaming-iterator = "0.1"
strsim = "0.11"
strum = { version = "0.27.0", features = ["derive"] }
subtle = "2.5.0"
syn = { version = "2.0.101", features = ["full", "extra-traits"] }
syn = { version = "2.0.101", features = ["full", "extra-traits", "visit-mut"] }
sys-locale = "0.3.1"
sysinfo = "0.31.0"
take-until = "0.2.0"
@@ -663,8 +670,9 @@ tiny_http = "0.8"
tokio = { version = "1" }
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
toml = "0.8"
toml_edit = { version = "0.22", default-features = false, features = ["display", "parse", "serde"] }
tower-http = "0.4.4"
tree-sitter = { version = "0.25.6", features = ["wasm"] }
tree-sitter = { version = "0.25.10", features = ["wasm"] }
tree-sitter-bash = "0.25.0"
tree-sitter-c = "0.23"
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "5cb9b693cfd7bfacab1d9ff4acac1a4150700609" }
@@ -681,11 +689,11 @@ tree-sitter-html = "0.23"
tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.24"
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
tree-sitter-python = { git = "https://github.com/zed-industries/tree-sitter-python", rev = "218fcbf3fda3d029225f3dec005cb497d111b35e" }
tree-sitter-python = "0.25"
tree-sitter-regex = "0.24"
tree-sitter-ruby = "0.23"
tree-sitter-rust = "0.24"
tree-sitter-typescript = "0.23"
tree-sitter-typescript = { git = "https://github.com/zed-industries/tree-sitter-typescript", rev = "e2c53597d6a5d9cf7bbe8dccde576fe1e46c5899" } # https://github.com/tree-sitter/tree-sitter-typescript/pull/347
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
unicase = "2.6"
unicode-script = "0.5.7"
@@ -712,6 +720,7 @@ windows-core = "0.61"
wit-component = "0.221"
workspace-hack = "0.1.0"
yawc = "0.2.5"
zeroize = "1.8"
zstd = "0.11"
[workspace.dependencies.windows]
@@ -738,6 +747,7 @@ features = [
"Win32_Networking_WinSock",
"Win32_Security",
"Win32_Security_Credentials",
"Win32_Security_Cryptography",
"Win32_Storage_FileSystem",
"Win32_System_Com",
"Win32_System_Com_StructuredStorage",
@@ -795,7 +805,7 @@ wasmtime = { opt-level = 3 }
activity_indicator = { codegen-units = 1 }
assets = { codegen-units = 1 }
breadcrumbs = { codegen-units = 1 }
collections = { codegen-units = 1 }
zed-collections = { codegen-units = 1 }
command_palette = { codegen-units = 1 }
command_palette_hooks = { codegen-units = 1 }
extension_cli = { codegen-units = 1 }
@@ -806,6 +816,7 @@ image_viewer = { codegen-units = 1 }
edit_prediction_button = { codegen-units = 1 }
install_cli = { codegen-units = 1 }
journal = { codegen-units = 1 }
json_schema_store = { codegen-units = 1 }
lmstudio = { codegen-units = 1 }
menu = { codegen-units = 1 }
notifications = { codegen-units = 1 }
@@ -814,11 +825,11 @@ outline = { codegen-units = 1 }
paths = { codegen-units = 1 }
prettier = { codegen-units = 1 }
project_symbols = { codegen-units = 1 }
refineable = { codegen-units = 1 }
zed-refineable = { codegen-units = 1 }
release_channel = { codegen-units = 1 }
reqwest_client = { codegen-units = 1 }
rich_text = { codegen-units = 1 }
semantic_version = { codegen-units = 1 }
zed-semantic-version = { codegen-units = 1 }
session = { codegen-units = 1 }
snippet = { codegen-units = 1 }
snippets_ui = { codegen-units = 1 }
@@ -857,6 +868,7 @@ todo = "deny"
declare_interior_mutable_const = "deny"
redundant_clone = "deny"
disallowed_methods = "deny"
# We currently do not restrict any style rules
# as it slows down shipping code to Zed.

View File

@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.2
FROM rust:1.89-bookworm as builder
FROM rust:1.90-bookworm as builder
WORKDIR app
COPY . .

1
GEMINI.md Symbolic link
View File

@@ -0,0 +1 @@
.rules

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="M10.1645 4.45825L5.20344 9.52074C4.98225 9.74193 4.85798 10.0419 4.85798 10.3548C4.85798 10.6676 4.98225 10.9676 5.20344 11.1888C5.42464 11.41 5.72464 11.5342 6.03746 11.5342C6.35028 11.5342 6.65028 11.41 6.87148 11.1888L11.8326 6.12629C12.2749 5.68397 12.5234 5.08407 12.5234 4.45854C12.5234 3.83302 12.2749 3.23311 11.8326 2.7908C11.3902 2.34849 10.7903 2.1 10.1648 2.1C9.53928 2.1 8.93938 2.34849 8.49707 2.7908L3.55663 7.83265C3.22373 8.16017 2.95897 8.55037 2.77762 8.98072C2.59628 9.41108 2.50193 9.87308 2.50003 10.3401C2.49813 10.8071 2.58871 11.2698 2.76654 11.7017C2.94438 12.1335 3.20595 12.5258 3.53618 12.856C3.8664 13.1863 4.25873 13.4478 4.69055 13.6257C5.12237 13.8035 5.58513 13.8941 6.05213 13.8922C6.51913 13.8903 6.98114 13.7959 7.41149 13.6146C7.84185 13.4332 8.23204 13.1685 8.55957 12.8356L13.5 7.79373" stroke="#C4CAD4" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -30,7 +30,8 @@
"ctrl-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
"ctrl-,": "zed::OpenSettings",
"ctrl-,": "zed::OpenSettingsEditor",
"ctrl-alt-,": "zed::OpenSettings",
"ctrl-q": "zed::Quit",
"f4": "debugger::Start",
"shift-f5": "debugger::Stop",
@@ -250,7 +251,7 @@
"alt-enter": "agent::ContinueWithBurnMode",
"ctrl-y": "agent::AllowOnce",
"ctrl-alt-y": "agent::AllowAlways",
"ctrl-d": "agent::RejectOnce"
"ctrl-alt-z": "agent::RejectOnce"
}
},
{
@@ -369,7 +370,8 @@
"bindings": {
"new": "rules_library::NewRule",
"ctrl-n": "rules_library::NewRule",
"ctrl-shift-s": "rules_library::ToggleDefaultRule"
"ctrl-shift-s": "rules_library::ToggleDefaultRule",
"ctrl-w": "workspace::CloseWindow"
}
},
{
@@ -525,15 +527,15 @@
"ctrl-k ctrl-l": "editor::ToggleFold",
"ctrl-k ctrl-[": "editor::FoldRecursive",
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
"ctrl-k ctrl-1": ["editor::FoldAtLevel", 1],
"ctrl-k ctrl-2": ["editor::FoldAtLevel", 2],
"ctrl-k ctrl-3": ["editor::FoldAtLevel", 3],
"ctrl-k ctrl-4": ["editor::FoldAtLevel", 4],
"ctrl-k ctrl-5": ["editor::FoldAtLevel", 5],
"ctrl-k ctrl-6": ["editor::FoldAtLevel", 6],
"ctrl-k ctrl-7": ["editor::FoldAtLevel", 7],
"ctrl-k ctrl-8": ["editor::FoldAtLevel", 8],
"ctrl-k ctrl-9": ["editor::FoldAtLevel", 9],
"ctrl-k ctrl-1": "editor::FoldAtLevel_1",
"ctrl-k ctrl-2": "editor::FoldAtLevel_2",
"ctrl-k ctrl-3": "editor::FoldAtLevel_3",
"ctrl-k ctrl-4": "editor::FoldAtLevel_4",
"ctrl-k ctrl-5": "editor::FoldAtLevel_5",
"ctrl-k ctrl-6": "editor::FoldAtLevel_6",
"ctrl-k ctrl-7": "editor::FoldAtLevel_7",
"ctrl-k ctrl-8": "editor::FoldAtLevel_8",
"ctrl-k ctrl-9": "editor::FoldAtLevel_9",
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
@@ -1227,9 +1229,6 @@
"context": "Onboarding",
"use_key_equivalents": true,
"bindings": {
"ctrl-1": "onboarding::ActivateBasicsPage",
"ctrl-2": "onboarding::ActivateEditingPage",
"ctrl-3": "onboarding::ActivateAISetupPage",
"ctrl-enter": "onboarding::Finish",
"alt-shift-l": "onboarding::SignIn",
"alt-shift-a": "onboarding::OpenAccount"
@@ -1241,5 +1240,41 @@
"bindings": {
"ctrl-shift-enter": "workspace::OpenWithSystem"
}
},
{
"context": "SettingsWindow",
"use_key_equivalents": true,
"bindings": {
"ctrl-w": "workspace::CloseWindow",
"escape": "workspace::CloseWindow",
"ctrl-m": "settings_editor::Minimize",
"ctrl-f": "search::FocusSearch",
"ctrl-shift-e": "settings_editor::ToggleFocusNav",
// todo(settings_ui): cut this down based on the max files and overflow UI
"ctrl-1": ["settings_editor::FocusFile", 0],
"ctrl-2": ["settings_editor::FocusFile", 1],
"ctrl-3": ["settings_editor::FocusFile", 2],
"ctrl-4": ["settings_editor::FocusFile", 3],
"ctrl-5": ["settings_editor::FocusFile", 4],
"ctrl-6": ["settings_editor::FocusFile", 5],
"ctrl-7": ["settings_editor::FocusFile", 6],
"ctrl-8": ["settings_editor::FocusFile", 7],
"ctrl-9": ["settings_editor::FocusFile", 8],
"ctrl-0": ["settings_editor::FocusFile", 9],
"ctrl-pageup": "settings_editor::FocusPreviousFile",
"ctrl-pagedown": "settings_editor::FocusNextFile"
}
},
{
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",
"pagedown": "settings_editor::FocusNextRootNavEntry",
"home": "settings_editor::FocusFirstNavEntry",
"end": "settings_editor::FocusLastNavEntry"
}
}
]

View File

@@ -39,7 +39,8 @@
"cmd-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
"cmd--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"cmd-0": ["zed::ResetBufferFontSize", { "persist": false }],
"cmd-,": "zed::OpenSettings",
"cmd-,": "zed::OpenSettingsEditor",
"cmd-alt-,": "zed::OpenSettings",
"cmd-q": "zed::Quit",
"cmd-h": "zed::Hide",
"alt-cmd-h": "zed::HideOthers",
@@ -289,7 +290,7 @@
"alt-enter": "agent::ContinueWithBurnMode",
"cmd-y": "agent::AllowOnce",
"cmd-alt-y": "agent::AllowAlways",
"cmd-d": "agent::RejectOnce"
"cmd-alt-z": "agent::RejectOnce"
}
},
{
@@ -550,6 +551,8 @@
"cmd-ctrl-left": "editor::SelectSmallerSyntaxNode", // Shrink selection
"cmd-ctrl-right": "editor::SelectLargerSyntaxNode", // Expand selection
"cmd-ctrl-up": "editor::SelectPreviousSyntaxNode", // Move selection up
"ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand selection (VSCode version)
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink selection (VSCode version)
"cmd-ctrl-down": "editor::SelectNextSyntaxNode", // Move selection down
"cmd-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand
"cmd-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
@@ -579,15 +582,15 @@
"cmd-k cmd-l": "editor::ToggleFold",
"cmd-k cmd-[": "editor::FoldRecursive",
"cmd-k cmd-]": "editor::UnfoldRecursive",
"cmd-k cmd-1": ["editor::FoldAtLevel", 1],
"cmd-k cmd-2": ["editor::FoldAtLevel", 2],
"cmd-k cmd-3": ["editor::FoldAtLevel", 3],
"cmd-k cmd-4": ["editor::FoldAtLevel", 4],
"cmd-k cmd-5": ["editor::FoldAtLevel", 5],
"cmd-k cmd-6": ["editor::FoldAtLevel", 6],
"cmd-k cmd-7": ["editor::FoldAtLevel", 7],
"cmd-k cmd-8": ["editor::FoldAtLevel", 8],
"cmd-k cmd-9": ["editor::FoldAtLevel", 9],
"cmd-k cmd-1": "editor::FoldAtLevel_1",
"cmd-k cmd-2": "editor::FoldAtLevel_2",
"cmd-k cmd-3": "editor::FoldAtLevel_3",
"cmd-k cmd-4": "editor::FoldAtLevel_4",
"cmd-k cmd-5": "editor::FoldAtLevel_5",
"cmd-k cmd-6": "editor::FoldAtLevel_6",
"cmd-k cmd-7": "editor::FoldAtLevel_7",
"cmd-k cmd-8": "editor::FoldAtLevel_8",
"cmd-k cmd-9": "editor::FoldAtLevel_9",
"cmd-k cmd-0": "editor::FoldAll",
"cmd-k cmd-j": "editor::UnfoldAll",
// Using `ctrl-space` / `ctrl-shift-space` in Zed requires disabling the macOS global shortcut.
@@ -1331,10 +1334,7 @@
"context": "Onboarding",
"use_key_equivalents": true,
"bindings": {
"cmd-1": "onboarding::ActivateBasicsPage",
"cmd-2": "onboarding::ActivateEditingPage",
"cmd-3": "onboarding::ActivateAISetupPage",
"cmd-escape": "onboarding::Finish",
"cmd-enter": "onboarding::Finish",
"alt-tab": "onboarding::SignIn",
"alt-shift-a": "onboarding::OpenAccount"
}
@@ -1345,5 +1345,41 @@
"bindings": {
"ctrl-shift-enter": "workspace::OpenWithSystem"
}
},
{
"context": "SettingsWindow",
"use_key_equivalents": true,
"bindings": {
"cmd-w": "workspace::CloseWindow",
"escape": "workspace::CloseWindow",
"cmd-m": "settings_editor::Minimize",
"cmd-f": "search::FocusSearch",
"cmd-shift-e": "settings_editor::ToggleFocusNav",
// todo(settings_ui): cut this down based on the max files and overflow UI
"ctrl-1": ["settings_editor::FocusFile", 0],
"ctrl-2": ["settings_editor::FocusFile", 1],
"ctrl-3": ["settings_editor::FocusFile", 2],
"ctrl-4": ["settings_editor::FocusFile", 3],
"ctrl-5": ["settings_editor::FocusFile", 4],
"ctrl-6": ["settings_editor::FocusFile", 5],
"ctrl-7": ["settings_editor::FocusFile", 6],
"ctrl-8": ["settings_editor::FocusFile", 7],
"ctrl-9": ["settings_editor::FocusFile", 8],
"ctrl-0": ["settings_editor::FocusFile", 9],
"cmd-{": "settings_editor::FocusPreviousFile",
"cmd-}": "settings_editor::FocusNextFile"
}
},
{
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",
"pagedown": "settings_editor::FocusNextRootNavEntry",
"home": "settings_editor::FocusFirstNavEntry",
"end": "settings_editor::FocusLastNavEntry"
}
}
]

View File

@@ -17,7 +17,6 @@
"up": "menu::SelectPrevious",
"enter": "menu::Confirm",
"ctrl-enter": "menu::SecondaryConfirm",
"ctrl-escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
"escape": "menu::Cancel",
"shift-alt-enter": "menu::Restart",
@@ -30,7 +29,8 @@
"ctrl-shift-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
"ctrl-,": "zed::OpenSettings",
"ctrl-,": "zed::OpenSettingsEditor",
"ctrl-alt-,": "zed::OpenSettings",
"ctrl-q": "zed::Quit",
"f4": "debugger::Start",
"shift-f5": "debugger::Stop",
@@ -252,7 +252,7 @@
"alt-enter": "agent::ContinueWithBurnMode",
"ctrl-y": "agent::AllowOnce",
"ctrl-alt-y": "agent::AllowAlways",
"ctrl-d": "agent::RejectOnce"
"ctrl-alt-z": "agent::RejectOnce"
}
},
{
@@ -346,7 +346,7 @@
}
},
{
"context": "AcpThread > Editor",
"context": "AcpThread > Editor && !use_modifier_to_send",
"use_key_equivalents": true,
"bindings": {
"enter": "agent::Chat",
@@ -356,6 +356,17 @@
"shift-tab": "agent::CycleModeSelector"
}
},
{
"context": "AcpThread > Editor && use_modifier_to_send",
"use_key_equivalents": true,
"bindings": {
"ctrl-enter": "agent::Chat",
"ctrl-shift-r": "agent::OpenAgentDiff",
"ctrl-shift-y": "agent::KeepAll",
"ctrl-shift-n": "agent::RejectAll",
"shift-tab": "agent::CycleModeSelector"
}
},
{
"context": "ThreadHistory",
"use_key_equivalents": true,
@@ -368,7 +379,8 @@
"use_key_equivalents": true,
"bindings": {
"ctrl-n": "rules_library::NewRule",
"ctrl-shift-s": "rules_library::ToggleDefaultRule"
"ctrl-shift-s": "rules_library::ToggleDefaultRule",
"ctrl-w": "workspace::CloseWindow"
}
},
{
@@ -465,8 +477,8 @@
"ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes",
"back": "pane::GoBack",
"alt--": "pane::GoBack",
"alt-=": "pane::GoForward",
"forward": "pane::GoForward",
"alt-=": "pane::GoForward",
"f3": "search::SelectNextMatch",
"shift-f3": "search::SelectPreviousMatch",
"ctrl-shift-f": "project_search::ToggleFocus",
@@ -497,8 +509,6 @@
"shift-alt-down": "editor::DuplicateLineDown",
"shift-alt-right": "editor::SelectLargerSyntaxNode", // Expand selection
"shift-alt-left": "editor::SelectSmallerSyntaxNode", // Shrink selection
"ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand selection (VSCode version)
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink selection (VSCode version)
"ctrl-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
"ctrl-f2": "editor::SelectAllMatches", // Select all occurrences of current word
"ctrl-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand
@@ -526,15 +536,15 @@
"ctrl-k ctrl-l": "editor::ToggleFold",
"ctrl-k ctrl-[": "editor::FoldRecursive",
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
"ctrl-k ctrl-1": ["editor::FoldAtLevel", 1],
"ctrl-k ctrl-2": ["editor::FoldAtLevel", 2],
"ctrl-k ctrl-3": ["editor::FoldAtLevel", 3],
"ctrl-k ctrl-4": ["editor::FoldAtLevel", 4],
"ctrl-k ctrl-5": ["editor::FoldAtLevel", 5],
"ctrl-k ctrl-6": ["editor::FoldAtLevel", 6],
"ctrl-k ctrl-7": ["editor::FoldAtLevel", 7],
"ctrl-k ctrl-8": ["editor::FoldAtLevel", 8],
"ctrl-k ctrl-9": ["editor::FoldAtLevel", 9],
"ctrl-k ctrl-1": "editor::FoldAtLevel_1",
"ctrl-k ctrl-2": "editor::FoldAtLevel_2",
"ctrl-k ctrl-3": "editor::FoldAtLevel_3",
"ctrl-k ctrl-4": "editor::FoldAtLevel_4",
"ctrl-k ctrl-5": "editor::FoldAtLevel_5",
"ctrl-k ctrl-6": "editor::FoldAtLevel_6",
"ctrl-k ctrl-7": "editor::FoldAtLevel_7",
"ctrl-k ctrl-8": "editor::FoldAtLevel_8",
"ctrl-k ctrl-9": "editor::FoldAtLevel_9",
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
@@ -610,8 +620,6 @@
"shift-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }],
"shift-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }],
"shift-alt-0": "workspace::ResetOpenDocksSize",
"ctrl-shift-alt--": ["workspace::DecreaseOpenDocksSize", { "px": 0 }],
"ctrl-shift-alt-=": ["workspace::IncreaseOpenDocksSize", { "px": 0 }],
"ctrl-shift-f": "pane::DeploySearch",
"ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
"ctrl-shift-t": "pane::ReopenClosedItem",
@@ -1115,6 +1123,7 @@
"alt-f": ["terminal::SendText", "\u001bf"],
"alt-.": ["terminal::SendText", "\u001b."],
"ctrl-delete": ["terminal::SendText", "\u001bd"],
"ctrl-n": "workspace::NewTerminal",
// Overrides for conflicting keybindings
"ctrl-b": ["terminal::SendKeystroke", "ctrl-b"],
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
@@ -1248,12 +1257,45 @@
"context": "Onboarding",
"use_key_equivalents": true,
"bindings": {
"ctrl-1": "onboarding::ActivateBasicsPage",
"ctrl-2": "onboarding::ActivateEditingPage",
"ctrl-3": "onboarding::ActivateAISetupPage",
"ctrl-escape": "onboarding::Finish",
"alt-tab": "onboarding::SignIn",
"ctrl-enter": "onboarding::Finish",
"alt-shift-l": "onboarding::SignIn",
"shift-alt-a": "onboarding::OpenAccount"
}
},
{
"context": "SettingsWindow",
"use_key_equivalents": true,
"bindings": {
"ctrl-w": "workspace::CloseWindow",
"escape": "workspace::CloseWindow",
"ctrl-m": "settings_editor::Minimize",
"ctrl-f": "search::FocusSearch",
"ctrl-shift-e": "settings_editor::ToggleFocusNav",
// todo(settings_ui): cut this down based on the max files and overflow UI
"ctrl-1": ["settings_editor::FocusFile", 0],
"ctrl-2": ["settings_editor::FocusFile", 1],
"ctrl-3": ["settings_editor::FocusFile", 2],
"ctrl-4": ["settings_editor::FocusFile", 3],
"ctrl-5": ["settings_editor::FocusFile", 4],
"ctrl-6": ["settings_editor::FocusFile", 5],
"ctrl-7": ["settings_editor::FocusFile", 6],
"ctrl-8": ["settings_editor::FocusFile", 7],
"ctrl-9": ["settings_editor::FocusFile", 8],
"ctrl-0": ["settings_editor::FocusFile", 9],
"ctrl-pageup": "settings_editor::FocusPreviousFile",
"ctrl-pagedown": "settings_editor::FocusNextFile"
}
},
{
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",
"pagedown": "settings_editor::FocusNextRootNavEntry",
"home": "settings_editor::FocusFirstNavEntry",
"end": "settings_editor::FocusLastNavEntry"
}
}
]

View File

@@ -4,6 +4,7 @@
// from the command palette.
[
{
"context": "!GitPanel",
"bindings": {
"ctrl-g": "menu::Cancel"
}

View File

@@ -95,8 +95,8 @@
"g g": "vim::StartOfDocument",
"g h": "editor::Hover",
"g B": "editor::BlameHover",
"g t": "pane::ActivateNextItem",
"g shift-t": "pane::ActivatePreviousItem",
"g t": "vim::GoToTab",
"g shift-t": "vim::GoToPreviousTab",
"g d": "editor::GoToDefinition",
"g shift-d": "editor::GoToDeclaration",
"g y": "editor::GoToTypeDefinition",
@@ -240,6 +240,7 @@
"delete": "vim::DeleteRight",
"g shift-j": "vim::JoinLinesNoWhitespace",
"y": "vim::PushYank",
"shift-y": "vim::YankLine",
"x": "vim::DeleteRight",
"shift-x": "vim::DeleteLeft",
"ctrl-a": "vim::Increment",
@@ -426,6 +427,7 @@
";": "vim::HelixCollapseSelection",
":": "command_palette::Toggle",
"m": "vim::PushHelixMatch",
"s": "vim::HelixSelectRegex",
"]": ["vim::PushHelixNext", { "around": true }],
"[": ["vim::PushHelixPrevious", { "around": true }],
"left": "vim::WrappingLeft",
@@ -433,6 +435,8 @@
"h": "vim::WrappingLeft",
"l": "vim::WrappingRight",
"y": "vim::HelixYank",
"p": "vim::HelixPaste",
"shift-p": ["vim::HelixPaste", { "before": true }],
"alt-;": "vim::OtherEnd",
"ctrl-r": "vim::Redo",
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
@@ -576,18 +580,18 @@
// "q": "vim::AnyQuotes",
"q": "vim::MiniQuotes",
"|": "vim::VerticalBars",
"(": "vim::Parentheses",
"(": ["vim::Parentheses", { "opening": true }],
")": "vim::Parentheses",
"b": "vim::Parentheses",
// "b": "vim::AnyBrackets",
// "b": "vim::MiniBrackets",
"[": "vim::SquareBrackets",
"[": ["vim::SquareBrackets", { "opening": true }],
"]": "vim::SquareBrackets",
"r": "vim::SquareBrackets",
"{": "vim::CurlyBrackets",
"{": ["vim::CurlyBrackets", { "opening": true }],
"}": "vim::CurlyBrackets",
"shift-b": "vim::CurlyBrackets",
"<": "vim::AngleBrackets",
"<": ["vim::AngleBrackets", { "opening": true }],
">": "vim::AngleBrackets",
"a": "vim::Argument",
"i": "vim::IndentObj",
@@ -880,10 +884,12 @@
"/": "project_panel::NewSearchInDirectory",
"d": "project_panel::NewDirectory",
"enter": "project_panel::OpenPermanent",
"escape": "project_panel::ToggleFocus",
"escape": "vim::ToggleProjectPanelFocus",
"h": "project_panel::CollapseSelectedEntry",
"j": "menu::SelectNext",
"k": "menu::SelectPrevious",
"j": "vim::MenuSelectNext",
"k": "vim::MenuSelectPrevious",
"down": "vim::MenuSelectNext",
"up": "vim::MenuSelectPrevious",
"l": "project_panel::ExpandSelectedEntry",
"shift-d": "project_panel::Delete",
"shift-r": "project_panel::Rename",
@@ -902,7 +908,22 @@
"{": "project_panel::SelectPrevDirectory",
"shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst",
"-": "project_panel::SelectParent"
"-": "project_panel::SelectParent",
"ctrl-u": "project_panel::ScrollUp",
"ctrl-d": "project_panel::ScrollDown",
"z t": "project_panel::ScrollCursorTop",
"z z": "project_panel::ScrollCursorCenter",
"z b": "project_panel::ScrollCursorBottom",
"0": ["vim::Number", 0],
"1": ["vim::Number", 1],
"2": ["vim::Number", 2],
"3": ["vim::Number", 3],
"4": ["vim::Number", 4],
"5": ["vim::Number", 5],
"6": ["vim::Number", 6],
"7": ["vim::Number", 7],
"8": ["vim::Number", 8],
"9": ["vim::Number", 9]
}
},
{

View File

@@ -29,7 +29,9 @@ Generate {{content_type}} based on the following prompt:
Match the indentation in the original file in the inserted {{content_type}}, don't include any indentation on blank lines.
Immediately start with the following format with no remarks:
Return ONLY the {{content_type}} to insert. Do NOT include any XML tags like <document>, <insert_here>, or any surrounding markup from the input.
Respond with a code block containing the {{content_type}} to insert. Replace \{{INSERTED_CODE}} with your actual {{content_type}}:
```
\{{INSERTED_CODE}}
@@ -66,7 +68,9 @@ Only make changes that are necessary to fulfill the prompt, leave everything els
Start at the indentation level in the original file in the rewritten {{content_type}}. Don't stop until you've rewritten the entire section, even if you have no more changes to make, always write out the whole section with no unnecessary elisions.
Immediately start with the following format with no remarks:
Return ONLY the rewritten {{content_type}}. Do NOT include any XML tags like <document>, <rewrite_this>, or any surrounding markup from the input.
Respond with a code block containing the rewritten {{content_type}}. Replace \{{REWRITTEN_CODE}} with your actual rewritten {{content_type}}:
```
\{{REWRITTEN_CODE}}

View File

@@ -1,5 +1,7 @@
{
"project_name": null,
/// The displayed name of this project. If not set or empty, the root directory name
/// will be displayed.
"project_name": "",
// The name of the Zed theme to use for the UI.
//
// `mode` is one of:
@@ -72,8 +74,10 @@
"ui_font_weight": 400,
// The default font size for text in the UI
"ui_font_size": 16,
// The default font size for text in the agent panel. Falls back to the UI font size if unset.
"agent_font_size": null,
// The default font size for agent responses in the agent panel. Falls back to the UI font size if unset.
"agent_ui_font_size": null,
// The default font size for user messages in the agent panel.
"agent_buffer_font_size": 12,
// How much to fade out unused code.
"unnecessary_code_fade": 0.3,
// Active pane styling settings.
@@ -115,6 +119,7 @@
// Whether to enable vim modes and key bindings.
"vim_mode": false,
// Whether to enable helix mode and key bindings.
// Enabling this mode will automatically enable vim mode.
"helix_mode": false,
// Whether to show the informational hover box when moving the mouse
// over symbols in the editor.
@@ -391,8 +396,6 @@
"use_system_window_tabs": false,
// Titlebar related settings
"title_bar": {
// When to show the title bar: "always" | "never" | "hide_in_full_screen".
"show": "always",
// Whether to show the branch icon beside branch switcher in the titlebar.
"show_branch_icon": false,
// Whether to show the branch name button in the titlebar.
@@ -413,15 +416,33 @@
"experimental.rodio_audio": false,
// Requires 'rodio_audio: true'
//
// Use the new audio systems automatic gain control for your microphone.
// This affects how loud you sound to others.
"experimental.control_input_volume": false,
// Automatically increase or decrease you microphone's volume. This affects how
// loud you sound to others.
//
// Recommended: off (default)
// Microphones are too quite in zed, until everyone is on experimental
// audio and has auto speaker volume on this will make you very loud
// compared to other speakers.
"experimental.auto_microphone_volume": false,
// Requires 'rodio_audio: true'
//
// Use the new audio systems automatic gain control on everyone in the
// call. This makes call members who are too quite louder and those who are
// too loud quieter. This only affects how things sound for you.
"experimental.control_output_volume": false
// Automatically increate or decrease the volume of other call members.
// This only affects how things sound for you.
"experimental.auto_speaker_volume": true,
// Requires 'rodio_audio: true'
//
// Remove background noises. Works great for typing, cars, dogs, AC. Does
// not work well on music.
"experimental.denoise": true,
// Requires 'rodio_audio: true'
//
// Use audio parameters compatible with the previous versions of
// experimental audio and non-experimental audio. When this is false you
// will sound strange to anyone not on the latest experimental audio. In
// the future we will migrate by setting this to false
//
// You need to rejoin a call for this setting to apply
"experimental.legacy_audio_compatible": true
},
// Scrollbar related settings
"scrollbar": {
@@ -1210,6 +1231,10 @@
// 2. Hide the gutter
// "git_gutter": "hide"
"git_gutter": "tracked_files",
/// Sets the debounce threshold (in milliseconds) after which changes are reflected in the git gutter.
///
/// Default: 0
"gutter_debounce": 0,
// Control whether the git blame information is shown inline,
// in the currently focused line.
"inline_blame": {
@@ -1225,6 +1250,9 @@
// The minimum column number to show the inline blame information at
"min_column": 0
},
"blame": {
"show_avatar": true
},
// Control which information is shown in the branch picker.
"branch_picker": {
"show_author_name": true
@@ -1283,15 +1311,18 @@
// "proxy": "",
// "proxy_no_verify": false
// },
// Whether edit predictions are enabled when editing text threads.
// This setting has no effect if globally disabled.
"enabled_in_text_threads": true,
"copilot": {
"enterprise_uri": null,
"proxy": null,
"proxy_no_verify": null
}
},
"codestral": {
"model": null,
"max_tokens": null
},
// Whether edit predictions are enabled when editing text threads.
// This setting has no effect if globally disabled.
"enabled_in_text_threads": true
},
// Settings specific to journaling
"journal": {
@@ -1305,6 +1336,8 @@
},
// Status bar-related settings.
"status_bar": {
// Whether to show the status bar.
"experimental.show": true,
// Whether to show the active language button in the status bar.
"active_language_button": true,
// Whether to show the cursor position button in the status bar.
@@ -1371,8 +1404,8 @@
// 4. A box drawn around the following character
// "hollow"
//
// Default: not set, defaults to "block"
"cursor_shape": null,
// Default: "block"
"cursor_shape": "block",
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
// Alternate Scroll mode converts mouse scroll events into up / down key
// presses when in the alternate screen (e.g. when running applications
@@ -1394,8 +1427,8 @@
// Whether or not selecting text in the terminal will automatically
// copy to the system clipboard.
"copy_on_select": false,
// Whether to keep the text selection after copying it to the clipboard
"keep_selection_on_copy": false,
// Whether to keep the text selection after copying it to the clipboard.
"keep_selection_on_copy": true,
// Whether to show the terminal button in the status bar
"button": true,
// Any key-value pairs added to this list will be added to the terminal's
@@ -1414,7 +1447,7 @@
// "line_height": {
// "custom": 2
// },
"line_height": "comfortable",
"line_height": "standard",
// Activate the python virtual environment, if one is found, in the
// terminal's working directory (as resolved by the working_directory
// setting). Set this to "off" to disable this behavior.
@@ -1434,7 +1467,7 @@
//
// The shell running in the terminal needs to be configured to emit the title.
// Example: `echo -e "\e]2;New Title\007";`
"breadcrumbs": true
"breadcrumbs": false
},
// Scrollbar-related settings
"scrollbar": {
@@ -1514,7 +1547,7 @@
// }
//
"file_types": {
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json", "tsconfig*.json"],
"Shell Script": [".env.*"]
},
// Settings for which version of Node.js and NPM to use when installing
@@ -1540,6 +1573,14 @@
"auto_install_extensions": {
"html": true
},
// The capabilities granted to extensions.
//
// This list can be customized to restrict what extensions are able to do.
"granted_extension_capabilities": [
{ "kind": "process:exec", "command": "*", "args": ["**"] },
{ "kind": "download_file", "host": "*", "path": ["**"] },
{ "kind": "npm:install", "package": "*" }
],
// Controls how completions are processed for this language.
"completions": {
// Controls how words are completed.
@@ -1838,21 +1879,19 @@
// Allows to enable/disable formatting with Prettier
// and configure default Prettier, used when no project-level Prettier installation is found.
"prettier": {
// // Whether to consider prettier formatter or not when attempting to format a file.
"allowed": false
//
// // Use regular Prettier json configuration.
// // If Prettier is allowed, Zed will use this for its Prettier instance for any applicable file, if
// // the project has no other Prettier installed.
// "plugins": [],
//
// // Use regular Prettier json configuration.
// // If Prettier is allowed, Zed will use this for its Prettier instance for any applicable file, if
// // the project has no other Prettier installed.
// Enables or disables formatting with Prettier for any given language.
"allowed": false,
// Forces Prettier integration to use a specific parser name when formatting files with the language.
"plugins": [],
// Default Prettier options, in the format as in package.json section for Prettier.
// If project installs Prettier via its package.json, these options will be ignored.
// "trailingComma": "es5",
// "tabWidth": 4,
// "semi": false,
// "singleQuote": true
// Forces Prettier integration to use a specific parser name when formatting files with the language
// when set to a non-empty string.
"parser": ""
},
// Settings for auto-closing of JSX tags.
"jsx_tag_auto_close": {
@@ -2002,7 +2041,7 @@
// Examples:
// "profiles": {
// "Presenting": {
// "agent_font_size": 20.0,
// "agent_ui_font_size": 20.0,
// "buffer_font_size": 20.0,
// "theme": "One Light",
// "ui_font_size": 20.0

View File

@@ -43,7 +43,11 @@
// "args": ["--login"]
// }
// }
"shell": "system"
"shell": "system",
// Whether to show the task line in the output of the spawned task, defaults to `true`.
"show_summary": true,
// Whether to show the command line in the output of the spawned task, defaults to `true`.
"show_command": true
// Represents the tags for inline runnable indicators, or spawning multiple tasks at once.
// "tags": []
}

Binary file not shown.

View File

@@ -192,7 +192,7 @@
"font_weight": null
},
"comment": {
"color": "#abb5be8c",
"color": "#5c6773ff",
"font_style": null,
"font_weight": null
},
@@ -239,7 +239,7 @@
"hint": {
"color": "#628b80ff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#ff8f3fff",
@@ -583,7 +583,7 @@
"font_weight": null
},
"comment": {
"color": "#787b8099",
"color": "#abb0b6ff",
"font_style": null,
"font_weight": null
},
@@ -630,7 +630,7 @@
"hint": {
"color": "#8ca7c2ff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#fa8d3eff",
@@ -974,7 +974,7 @@
"font_weight": null
},
"comment": {
"color": "#b8cfe680",
"color": "#5c6773ff",
"font_style": null,
"font_weight": null
},
@@ -1021,7 +1021,7 @@
"hint": {
"color": "#7399a3ff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#ffad65ff",

View File

@@ -248,7 +248,7 @@
"hint": {
"color": "#8c957dff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#fb4833ff",
@@ -653,7 +653,7 @@
"hint": {
"color": "#8c957dff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#fb4833ff",
@@ -1058,7 +1058,7 @@
"hint": {
"color": "#8c957dff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#fb4833ff",
@@ -1463,7 +1463,7 @@
"hint": {
"color": "#677562ff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#9d0006ff",
@@ -1868,7 +1868,7 @@
"hint": {
"color": "#677562ff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#9d0006ff",
@@ -2273,7 +2273,7 @@
"hint": {
"color": "#677562ff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#9d0006ff",

View File

@@ -244,7 +244,7 @@
"hint": {
"color": "#788ca6ff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#b477cfff",
@@ -643,7 +643,7 @@
"hint": {
"color": "#7274a7ff",
"font_style": null,
"font_weight": 700
"font_weight": null
},
"keyword": {
"color": "#a449abff",

View File

@@ -5,3 +5,14 @@ ignore-interior-mutability = [
# and Hash impls do not use fields with interior mutability.
"agent::context::AgentContextKey"
]
disallowed-methods = [
{ path = "std::process::Command::spawn", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::spawn" },
{ path = "std::process::Command::output", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::output" },
{ path = "std::process::Command::status", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::status" },
]
disallowed-types = [
# { path = "std::collections::HashMap", replacement = "collections::HashMap" },
# { path = "std::collections::HashSet", replacement = "collections::HashSet" },
# { path = "indexmap::IndexSet", replacement = "collections::IndexSet" },
# { path = "indexmap::IndexMap", replacement = "collections::IndexMap" },
]

View File

@@ -1,6 +1,6 @@
services:
postgres:
image: postgres:15
image: docker.io/library/postgres:15
container_name: zed_postgres
ports:
- 5432:5432
@@ -23,7 +23,7 @@ services:
- ./.blob_store:/data
livekit_server:
image: livekit/livekit-server
image: docker.io/livekit/livekit-server
container_name: livekit_server
entrypoint: /livekit-server --config /livekit.yaml
ports:
@@ -34,7 +34,7 @@ services:
- ./livekit.yaml:/livekit.yaml
postgrest_app:
image: postgrest/postgrest
image: docker.io/postgrest/postgrest
container_name: postgrest_app
ports:
- 8081:8081
@@ -47,7 +47,7 @@ services:
- postgres
postgrest_llm:
image: postgrest/postgrest
image: docker.io/postgrest/postgrest
container_name: postgrest_llm
ports:
- 8082:8082
@@ -60,7 +60,7 @@ services:
- postgres
stripe-mock:
image: stripe/stripe-mock:v0.178.0
image: docker.io/stripe/stripe-mock:v0.178.0
ports:
- 12111:12111
- 12112:12112

View File

@@ -3,6 +3,7 @@ mod diff;
mod mention;
mod terminal;
use ::terminal::terminal_settings::TerminalSettings;
use agent_settings::AgentSettings;
use collections::HashSet;
pub use connection::*;
@@ -11,7 +12,7 @@ use language::language_settings::FormatOnSave;
pub use mention::*;
use project::lsp_store::{FormatTrigger, LspFormatTarget};
use serde::{Deserialize, Serialize};
use settings::Settings as _;
use settings::{Settings as _, SettingsLocation};
use task::{Shell, ShellBuilder};
pub use terminal::*;
@@ -34,7 +35,7 @@ use std::rc::Rc;
use std::time::{Duration, Instant};
use std::{fmt::Display, mem, path::PathBuf, sync::Arc};
use ui::App;
use util::{ResultExt, get_default_system_shell};
use util::{ResultExt, get_default_system_shell_preferring_bash};
use uuid::Uuid;
#[derive(Debug)]
@@ -573,7 +574,7 @@ impl ToolCallContent {
))),
acp::ToolCallContent::Diff { diff } => Ok(Self::Diff(cx.new(|cx| {
Diff::finalized(
diff.path,
diff.path.to_string_lossy().into_owned(),
diff.old_text,
diff.new_text,
language_registry,
@@ -787,6 +788,8 @@ pub struct AcpThread {
prompt_capabilities: acp::PromptCapabilities,
_observe_prompt_capabilities: Task<anyhow::Result<()>>,
terminals: HashMap<acp::TerminalId, Entity<Terminal>>,
pending_terminal_output: HashMap<acp::TerminalId, Vec<Vec<u8>>>,
pending_terminal_exit: HashMap<acp::TerminalId, acp::TerminalExitStatus>,
}
#[derive(Debug)]
@@ -809,6 +812,126 @@ pub enum AcpThreadEvent {
impl EventEmitter<AcpThreadEvent> for AcpThread {}
#[derive(Debug, Clone)]
pub enum TerminalProviderEvent {
Created {
terminal_id: acp::TerminalId,
label: String,
cwd: Option<PathBuf>,
output_byte_limit: Option<u64>,
terminal: Entity<::terminal::Terminal>,
},
Output {
terminal_id: acp::TerminalId,
data: Vec<u8>,
},
TitleChanged {
terminal_id: acp::TerminalId,
title: String,
},
Exit {
terminal_id: acp::TerminalId,
status: acp::TerminalExitStatus,
},
}
#[derive(Debug, Clone)]
pub enum TerminalProviderCommand {
WriteInput {
terminal_id: acp::TerminalId,
bytes: Vec<u8>,
},
Resize {
terminal_id: acp::TerminalId,
cols: u16,
rows: u16,
},
Close {
terminal_id: acp::TerminalId,
},
}
impl AcpThread {
pub fn on_terminal_provider_event(
&mut self,
event: TerminalProviderEvent,
cx: &mut Context<Self>,
) {
match event {
TerminalProviderEvent::Created {
terminal_id,
label,
cwd,
output_byte_limit,
terminal,
} => {
let entity = self.register_terminal_created(
terminal_id.clone(),
label,
cwd,
output_byte_limit,
terminal,
cx,
);
if let Some(mut chunks) = self.pending_terminal_output.remove(&terminal_id) {
for data in chunks.drain(..) {
entity.update(cx, |term, cx| {
term.inner().update(cx, |inner, cx| {
inner.write_output(&data, cx);
})
});
}
}
if let Some(_status) = self.pending_terminal_exit.remove(&terminal_id) {
entity.update(cx, |_term, cx| {
cx.notify();
});
}
cx.notify();
}
TerminalProviderEvent::Output { terminal_id, data } => {
if let Some(entity) = self.terminals.get(&terminal_id) {
entity.update(cx, |term, cx| {
term.inner().update(cx, |inner, cx| {
inner.write_output(&data, cx);
})
});
} else {
self.pending_terminal_output
.entry(terminal_id)
.or_default()
.push(data);
}
}
TerminalProviderEvent::TitleChanged { terminal_id, title } => {
if let Some(entity) = self.terminals.get(&terminal_id) {
entity.update(cx, |term, cx| {
term.inner().update(cx, |inner, cx| {
inner.breadcrumb_text = title;
cx.emit(::terminal::Event::BreadcrumbsChanged);
})
});
}
}
TerminalProviderEvent::Exit {
terminal_id,
status,
} => {
if let Some(entity) = self.terminals.get(&terminal_id) {
entity.update(cx, |_term, cx| {
cx.notify();
});
} else {
self.pending_terminal_exit.insert(terminal_id, status);
}
}
}
}
}
#[derive(PartialEq, Eq, Debug)]
pub enum ThreadStatus {
Idle,
@@ -886,6 +1009,8 @@ impl AcpThread {
prompt_capabilities,
_observe_prompt_capabilities: task,
terminals: HashMap::default(),
pending_terminal_output: HashMap::default(),
pending_terminal_exit: HashMap::default(),
}
}
@@ -1780,20 +1905,26 @@ impl AcpThread {
limit: Option<u32>,
reuse_shared_snapshot: bool,
cx: &mut Context<Self>,
) -> Task<Result<String>> {
) -> Task<Result<String, acp::Error>> {
// Args are 1-based, move to 0-based
let line = line.unwrap_or_default().saturating_sub(1);
let limit = limit.unwrap_or(u32::MAX);
let project = self.project.clone();
let action_log = self.action_log.clone();
cx.spawn(async move |this, cx| {
let load = project.update(cx, |project, cx| {
let path = project
.project_path_for_absolute_path(&path, cx)
.context("invalid path")?;
anyhow::Ok(project.open_buffer(path, cx))
});
let buffer = load??.await?;
let load = project
.update(cx, |project, cx| {
let path = project
.project_path_for_absolute_path(&path, cx)
.ok_or_else(|| {
acp::Error::resource_not_found(Some(path.display().to_string()))
})?;
Ok(project.open_buffer(path, cx))
})
.map_err(|e| acp::Error::internal_error().with_data(e.to_string()))
.flatten()?;
let buffer = load.await?;
let snapshot = if reuse_shared_snapshot {
this.read_with(cx, |this, _| {
@@ -1820,15 +1951,17 @@ impl AcpThread {
};
let max_point = snapshot.max_point();
if line >= max_point.row {
anyhow::bail!(
let start_position = Point::new(line, 0);
if start_position > max_point {
return Err(acp::Error::invalid_params().with_data(format!(
"Attempting to read beyond the end of the file, line {}:{}",
max_point.row + 1,
max_point.column
);
)));
}
let start = snapshot.anchor_before(Point::new(line, 0));
let start = snapshot.anchor_before(start_position);
let end = snapshot.anchor_before(Point::new(line.saturating_add(limit), 0));
project.update(cx, |project, cx| {
@@ -1953,16 +2086,24 @@ impl AcpThread {
) -> Task<Result<Entity<Terminal>>> {
let env = match &cwd {
Some(dir) => self.project.update(cx, |project, cx| {
project.directory_environment(dir.as_path().into(), cx)
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
project.directory_environment(&shell, dir.as_path().into(), cx)
}),
None => Task::ready(None).shared(),
};
let env = cx.spawn(async move |_, _| {
let mut env = env.await.unwrap_or_default();
if cfg!(unix) {
env.insert("PAGER".into(), "cat".into());
}
// Disables paging for `git` and hopefully other commands
env.insert("PAGER".into(), "".into());
for var in extra_env {
env.insert(var.name, var.value);
}
@@ -1977,24 +2118,22 @@ impl AcpThread {
let terminal_id = terminal_id.clone();
async move |_this, cx| {
let env = env.await;
let (command, args) = ShellBuilder::new(
project
.update(cx, |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
})?
.as_deref(),
&Shell::Program(get_default_system_shell()),
)
.redirect_stdin_to_dev_null()
.build(Some(command), &args);
let shell = project
.update(cx, |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
})?
.unwrap_or_else(|| get_default_system_shell_preferring_bash());
let (task_command, task_args) = ShellBuilder::new(&Shell::Program(shell))
.redirect_stdin_to_dev_null()
.build(Some(command.clone()), &args);
let terminal = project
.update(cx, |project, cx| {
project.create_terminal_task(
task::SpawnInTerminal {
command: Some(command.clone()),
args: args.clone(),
command: Some(task_command),
args: task_args,
cwd: cwd.clone(),
env,
..Default::default()
@@ -2071,6 +2210,32 @@ impl AcpThread {
pub fn emit_load_error(&mut self, error: LoadError, cx: &mut Context<Self>) {
cx.emit(AcpThreadEvent::LoadError(error));
}
pub fn register_terminal_created(
&mut self,
terminal_id: acp::TerminalId,
command_label: String,
working_dir: Option<PathBuf>,
output_byte_limit: Option<u64>,
terminal: Entity<::terminal::Terminal>,
cx: &mut Context<Self>,
) -> Entity<Terminal> {
let language_registry = self.project.read(cx).languages().clone();
let entity = cx.new(|cx| {
Terminal::new(
terminal_id.clone(),
&command_label,
working_dir.clone(),
output_byte_limit.map(|l| l as usize),
terminal,
language_registry,
cx,
)
});
self.terminals.insert(terminal_id.clone(), entity.clone());
entity
}
}
fn markdown_for_raw_output(
@@ -2147,6 +2312,145 @@ mod tests {
});
}
#[gpui::test]
async fn test_terminal_output_buffered_before_created_renders(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, [], cx).await;
let connection = Rc::new(FakeAgentConnection::new());
let thread = cx
.update(|cx| connection.new_thread(project, std::path::Path::new(path!("/test")), cx))
.await
.unwrap();
let terminal_id = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
// Send Output BEFORE Created - should be buffered by acp_thread
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Output {
terminal_id: terminal_id.clone(),
data: b"hello buffered".to_vec(),
},
cx,
);
});
// Create a display-only terminal and then send Created
let lower = cx.new(|cx| {
let builder = ::terminal::TerminalBuilder::new_display_only(
::terminal::terminal_settings::CursorShape::default(),
::terminal::terminal_settings::AlternateScroll::On,
None,
0,
)
.unwrap();
builder.subscribe(cx)
});
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Created {
terminal_id: terminal_id.clone(),
label: "Buffered Test".to_string(),
cwd: None,
output_byte_limit: None,
terminal: lower.clone(),
},
cx,
);
});
// After Created, buffered Output should have been flushed into the renderer
let content = thread.read_with(cx, |thread, cx| {
let term = thread.terminal(terminal_id.clone()).unwrap();
term.read_with(cx, |t, cx| t.inner().read(cx).get_content())
});
assert!(
content.contains("hello buffered"),
"expected buffered output to render, got: {content}"
);
}
#[gpui::test]
async fn test_terminal_output_and_exit_buffered_before_created(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, [], cx).await;
let connection = Rc::new(FakeAgentConnection::new());
let thread = cx
.update(|cx| connection.new_thread(project, std::path::Path::new(path!("/test")), cx))
.await
.unwrap();
let terminal_id = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
// Send Output BEFORE Created
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Output {
terminal_id: terminal_id.clone(),
data: b"pre-exit data".to_vec(),
},
cx,
);
});
// Send Exit BEFORE Created
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Exit {
terminal_id: terminal_id.clone(),
status: acp::TerminalExitStatus {
exit_code: Some(0),
signal: None,
meta: None,
},
},
cx,
);
});
// Now create a display-only lower-level terminal and send Created
let lower = cx.new(|cx| {
let builder = ::terminal::TerminalBuilder::new_display_only(
::terminal::terminal_settings::CursorShape::default(),
::terminal::terminal_settings::AlternateScroll::On,
None,
0,
)
.unwrap();
builder.subscribe(cx)
});
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Created {
terminal_id: terminal_id.clone(),
label: "Buffered Exit Test".to_string(),
cwd: None,
output_byte_limit: None,
terminal: lower.clone(),
},
cx,
);
});
// Output should be present after Created (flushed from buffer)
let content = thread.read_with(cx, |thread, cx| {
let term = thread.terminal(terminal_id.clone()).unwrap();
term.read_with(cx, |t, cx| t.inner().read(cx).get_content())
});
assert!(
content.contains("pre-exit data"),
"expected pre-exit data to render, got: {content}"
);
}
#[gpui::test]
async fn test_push_user_content_block(cx: &mut gpui::TestAppContext) {
init_test(cx);
@@ -2449,6 +2753,81 @@ mod tests {
assert_eq!(content, "two\nthree\n");
// Invalid
let err = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/tmp/foo").into(), Some(6), Some(2), false, cx)
})
.await
.unwrap_err();
assert_eq!(
err.to_string(),
"Invalid params: \"Attempting to read beyond the end of the file, line 5:0\""
);
}
#[gpui::test]
async fn test_reading_empty_file(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(path!("/tmp"), json!({"foo": ""})).await;
let project = Project::test(fs.clone(), [], cx).await;
project
.update(cx, |project, cx| {
project.find_or_create_worktree(path!("/tmp/foo"), true, cx)
})
.await
.unwrap();
let connection = Rc::new(FakeAgentConnection::new());
let thread = cx
.update(|cx| connection.new_thread(project, Path::new(path!("/tmp")), cx))
.await
.unwrap();
// Whole file
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/tmp/foo").into(), None, None, false, cx)
})
.await
.unwrap();
assert_eq!(content, "");
// Only start line
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/tmp/foo").into(), Some(1), None, false, cx)
})
.await
.unwrap();
assert_eq!(content, "");
// Only limit
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/tmp/foo").into(), None, Some(2), false, cx)
})
.await
.unwrap();
assert_eq!(content, "");
// Range
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/tmp/foo").into(), Some(1), Some(1), false, cx)
})
.await
.unwrap();
assert_eq!(content, "");
// Invalid
let err = thread
.update(cx, |thread, cx| {
@@ -2459,9 +2838,40 @@ mod tests {
assert_eq!(
err.to_string(),
"Attempting to read beyond the end of the file, line 5:0"
"Invalid params: \"Attempting to read beyond the end of the file, line 1:0\""
);
}
#[gpui::test]
async fn test_reading_non_existing_file(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(path!("/tmp"), json!({})).await;
let project = Project::test(fs.clone(), [], cx).await;
project
.update(cx, |project, cx| {
project.find_or_create_worktree(path!("/tmp"), true, cx)
})
.await
.unwrap();
let connection = Rc::new(FakeAgentConnection::new());
let thread = cx
.update(|cx| connection.new_thread(project, Path::new(path!("/tmp")), cx))
.await
.unwrap();
// Out of project file
let err = thread
.update(cx, |thread, cx| {
thread.read_text_file(path!("/foo").into(), None, None, false, cx)
})
.await
.unwrap_err();
assert_eq!(err.code, acp::ErrorCode::RESOURCE_NOT_FOUND.code);
}
#[gpui::test]
async fn test_succeeding_canceled_toolcall(cx: &mut TestAppContext) {

View File

@@ -68,7 +68,7 @@ pub trait AgentConnection {
///
/// If the agent does not support model selection, returns [None].
/// This allows sharing the selector in UI components.
fn model_selector(&self) -> Option<Rc<dyn AgentModelSelector>> {
fn model_selector(&self, _session_id: &acp::SessionId) -> Option<Rc<dyn AgentModelSelector>> {
None
}
@@ -177,61 +177,48 @@ pub trait AgentModelSelector: 'static {
/// If the session doesn't exist or the model is invalid, it returns an error.
///
/// # Parameters
/// - `session_id`: The ID of the session (thread) to apply the model to.
/// - `model`: The model to select (should be one from [list_models]).
/// - `cx`: The GPUI app context.
///
/// # Returns
/// A task resolving to `Ok(())` on success or an error.
fn select_model(
&self,
session_id: acp::SessionId,
model_id: AgentModelId,
cx: &mut App,
) -> Task<Result<()>>;
fn select_model(&self, model_id: acp::ModelId, cx: &mut App) -> Task<Result<()>>;
/// Retrieves the currently selected model for a specific session (thread).
///
/// # Parameters
/// - `session_id`: The ID of the session (thread) to query.
/// - `cx`: The GPUI app context.
///
/// # Returns
/// A task resolving to the selected model (always set) or an error (e.g., session not found).
fn selected_model(
&self,
session_id: &acp::SessionId,
cx: &mut App,
) -> Task<Result<AgentModelInfo>>;
fn selected_model(&self, cx: &mut App) -> Task<Result<AgentModelInfo>>;
/// Whenever the model list is updated the receiver will be notified.
fn watch(&self, cx: &mut App) -> watch::Receiver<()>;
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AgentModelId(pub SharedString);
impl std::ops::Deref for AgentModelId {
type Target = SharedString;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for AgentModelId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
/// Optional for agents that don't update their model list.
fn watch(&self, _cx: &mut App) -> Option<watch::Receiver<()>> {
None
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AgentModelInfo {
pub id: AgentModelId,
pub id: acp::ModelId,
pub name: SharedString,
pub description: Option<SharedString>,
pub icon: Option<IconName>,
}
impl From<acp::ModelInfo> for AgentModelInfo {
fn from(info: acp::ModelInfo) -> Self {
Self {
id: info.model_id,
name: info.name.into(),
description: info.description.map(|desc| desc.into()),
icon: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AgentModelGroupName(pub SharedString);

View File

@@ -6,12 +6,7 @@ use itertools::Itertools;
use language::{
Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, Rope, TextBuffer,
};
use std::{
cmp::Reverse,
ops::Range,
path::{Path, PathBuf},
sync::Arc,
};
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
use util::ResultExt;
pub enum Diff {
@@ -21,7 +16,7 @@ pub enum Diff {
impl Diff {
pub fn finalized(
path: PathBuf,
path: String,
old_text: Option<String>,
new_text: String,
language_registry: Arc<LanguageRegistry>,
@@ -36,7 +31,7 @@ impl Diff {
let buffer = new_buffer.clone();
async move |_, cx| {
let language = language_registry
.language_for_file_path(&path)
.load_language_for_file_path(Path::new(&path))
.await
.log_err();
@@ -152,12 +147,15 @@ impl Diff {
let path = match self {
Diff::Pending(PendingDiff {
new_buffer: buffer, ..
}) => buffer.read(cx).file().map(|file| file.path().as_ref()),
Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_path()),
}) => buffer
.read(cx)
.file()
.map(|file| file.path().display(file.path_style(cx))),
Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),
};
format!(
"Diff: {}\n```\n{}\n```\n",
path.unwrap_or(Path::new("untitled")).display(),
path.unwrap_or("untitled".into()),
buffer_text
)
}
@@ -244,8 +242,8 @@ impl PendingDiff {
.new_buffer
.read(cx)
.file()
.map(|file| file.path().as_ref())
.unwrap_or(Path::new("untitled"))
.map(|file| file.path().display(file.path_style(cx)))
.unwrap_or("untitled".into())
.into();
// Replace the buffer in the multibuffer with the snapshot
@@ -348,7 +346,7 @@ impl PendingDiff {
}
pub struct FinalizedDiff {
path: PathBuf,
path: String,
base_text: Arc<String>,
new_buffer: Entity<Buffer>,
multibuffer: Entity<MultiBuffer>,

View File

@@ -126,6 +126,39 @@ impl MentionUri {
abs_path: None,
line_range,
})
} else if let Some(name) = path.strip_prefix("/agent/symbol/") {
let fragment = url
.fragment()
.context("Missing fragment for untitled buffer selection")?;
let line_range = parse_line_range(fragment)?;
let path =
single_query_param(&url, "path")?.context("Missing path for symbol")?;
Ok(Self::Symbol {
name: name.to_string(),
abs_path: path.into(),
line_range,
})
} else if path.starts_with("/agent/file") {
let path =
single_query_param(&url, "path")?.context("Missing path for file")?;
Ok(Self::File {
abs_path: path.into(),
})
} else if path.starts_with("/agent/directory") {
let path =
single_query_param(&url, "path")?.context("Missing path for directory")?;
Ok(Self::Directory {
abs_path: path.into(),
})
} else if path.starts_with("/agent/selection") {
let fragment = url.fragment().context("Missing fragment for selection")?;
let line_range = parse_line_range(fragment)?;
let path =
single_query_param(&url, "path")?.context("Missing path for selection")?;
Ok(Self::Selection {
abs_path: Some(path.into()),
line_range,
})
} else {
bail!("invalid zed url: {:?}", input);
}
@@ -180,20 +213,29 @@ impl MentionUri {
pub fn to_uri(&self) -> Url {
match self {
MentionUri::File { abs_path } => {
Url::from_file_path(abs_path).expect("mention path should be absolute")
let mut url = Url::parse("zed:///").unwrap();
url.set_path("/agent/file");
url.query_pairs_mut()
.append_pair("path", &abs_path.to_string_lossy());
url
}
MentionUri::PastedImage => Url::parse("zed:///agent/pasted-image").unwrap(),
MentionUri::Directory { abs_path } => {
Url::from_directory_path(abs_path).expect("mention path should be absolute")
let mut url = Url::parse("zed:///").unwrap();
url.set_path("/agent/directory");
url.query_pairs_mut()
.append_pair("path", &abs_path.to_string_lossy());
url
}
MentionUri::Symbol {
abs_path,
name,
line_range,
} => {
let mut url =
Url::from_file_path(abs_path).expect("mention path should be absolute");
url.query_pairs_mut().append_pair("symbol", name);
let mut url = Url::parse("zed:///").unwrap();
url.set_path(&format!("/agent/symbol/{name}"));
url.query_pairs_mut()
.append_pair("path", &abs_path.to_string_lossy());
url.set_fragment(Some(&format!(
"L{}:{}",
line_range.start() + 1,
@@ -202,15 +244,16 @@ impl MentionUri {
url
}
MentionUri::Selection {
abs_path: path,
abs_path,
line_range,
} => {
let mut url = if let Some(path) = path {
Url::from_file_path(path).expect("mention path should be absolute")
let mut url = Url::parse("zed:///").unwrap();
if let Some(abs_path) = abs_path {
url.set_path("/agent/selection");
url.query_pairs_mut()
.append_pair("path", &abs_path.to_string_lossy());
} else {
let mut url = Url::parse("zed:///").unwrap();
url.set_path("/agent/untitled-buffer");
url
};
url.set_fragment(Some(&format!(
"L{}:{}",
@@ -295,37 +338,32 @@ mod tests {
#[test]
fn test_parse_file_uri() {
let file_uri = uri!("file:///path/to/file.rs");
let parsed = MentionUri::parse(file_uri).unwrap();
let old_uri = uri!("file:///path/to/file.rs");
let parsed = MentionUri::parse(old_uri).unwrap();
match &parsed {
MentionUri::File { abs_path } => {
assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/file.rs"));
}
_ => panic!("Expected File variant"),
}
assert_eq!(parsed.to_uri().to_string(), file_uri);
let new_uri = parsed.to_uri().to_string();
assert!(new_uri.starts_with("zed:///agent/file"));
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
}
#[test]
fn test_parse_directory_uri() {
let file_uri = uri!("file:///path/to/dir/");
let parsed = MentionUri::parse(file_uri).unwrap();
let old_uri = uri!("file:///path/to/dir/");
let parsed = MentionUri::parse(old_uri).unwrap();
match &parsed {
MentionUri::Directory { abs_path } => {
assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/dir/"));
}
_ => panic!("Expected Directory variant"),
}
assert_eq!(parsed.to_uri().to_string(), file_uri);
}
#[test]
fn test_to_directory_uri_with_slash() {
let uri = MentionUri::Directory {
abs_path: PathBuf::from(path!("/path/to/dir/")),
};
let expected = uri!("file:///path/to/dir/");
assert_eq!(uri.to_uri().to_string(), expected);
let new_uri = parsed.to_uri().to_string();
assert!(new_uri.starts_with("zed:///agent/directory"));
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
}
#[test]
@@ -333,14 +371,15 @@ mod tests {
let uri = MentionUri::Directory {
abs_path: PathBuf::from(path!("/path/to/dir")),
};
let expected = uri!("file:///path/to/dir/");
assert_eq!(uri.to_uri().to_string(), expected);
let uri_string = uri.to_uri().to_string();
assert!(uri_string.starts_with("zed:///agent/directory"));
assert_eq!(MentionUri::parse(&uri_string).unwrap(), uri);
}
#[test]
fn test_parse_symbol_uri() {
let symbol_uri = uri!("file:///path/to/file.rs?symbol=MySymbol#L10:20");
let parsed = MentionUri::parse(symbol_uri).unwrap();
let old_uri = uri!("file:///path/to/file.rs?symbol=MySymbol#L10:20");
let parsed = MentionUri::parse(old_uri).unwrap();
match &parsed {
MentionUri::Symbol {
abs_path: path,
@@ -354,13 +393,15 @@ mod tests {
}
_ => panic!("Expected Symbol variant"),
}
assert_eq!(parsed.to_uri().to_string(), symbol_uri);
let new_uri = parsed.to_uri().to_string();
assert!(new_uri.starts_with("zed:///agent/symbol/MySymbol"));
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
}
#[test]
fn test_parse_selection_uri() {
let selection_uri = uri!("file:///path/to/file.rs#L5:15");
let parsed = MentionUri::parse(selection_uri).unwrap();
let old_uri = uri!("file:///path/to/file.rs#L5:15");
let parsed = MentionUri::parse(old_uri).unwrap();
match &parsed {
MentionUri::Selection {
abs_path: path,
@@ -375,7 +416,9 @@ mod tests {
}
_ => panic!("Expected Selection variant"),
}
assert_eq!(parsed.to_uri().to_string(), selection_uri);
let new_uri = parsed.to_uri().to_string();
assert!(new_uri.starts_with("zed:///agent/selection"));
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
}
#[test]

View File

@@ -8,10 +8,7 @@ use language::{Anchor, Buffer, BufferEvent, DiskState, Point, ToPoint};
use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle};
use std::{cmp, ops::Range, sync::Arc};
use text::{Edit, Patch, Rope};
use util::{
RangeExt, ResultExt as _,
paths::{PathStyle, RemotePathBuf},
};
use util::{RangeExt, ResultExt as _};
/// Tracks actions performed by tools in a thread
pub struct ActionLog {
@@ -62,7 +59,13 @@ impl ActionLog {
let file_path = buffer
.read(cx)
.file()
.map(|file| RemotePathBuf::new(file.full_path(cx), PathStyle::Posix).to_proto())
.map(|file| {
let mut path = file.full_path(cx).to_string_lossy().into_owned();
if file.path_style(cx).is_windows() {
path = path.replace('\\', "/");
}
path
})
.unwrap_or_else(|| format!("buffer_{}", buffer.entity_id()));
let mut result = String::new();
@@ -2301,7 +2304,7 @@ mod tests {
.await;
fs.set_head_for_repo(
path!("/project/.git").as_ref(),
&[("file.txt".into(), "a\nb\nc\nd\ne\nf\ng\nh\ni\nj".into())],
&[("file.txt", "a\nb\nc\nd\ne\nf\ng\nh\ni\nj".into())],
"0000000",
);
cx.run_until_parked();
@@ -2384,7 +2387,7 @@ mod tests {
// - Ignores the last line edit (j stays as j)
fs.set_head_for_repo(
path!("/project/.git").as_ref(),
&[("file.txt".into(), "A\nb\nc\nf\nG\nh\ni\nj".into())],
&[("file.txt", "A\nb\nc\nf\nG\nh\ni\nj".into())],
"0000001",
);
cx.run_until_parked();
@@ -2415,10 +2418,7 @@ mod tests {
// Make another commit that accepts the NEW line but with different content
fs.set_head_for_repo(
path!("/project/.git").as_ref(),
&[(
"file.txt".into(),
"A\nb\nc\nf\nGGG\nh\nDIFFERENT\ni\nj".into(),
)],
&[("file.txt", "A\nb\nc\nf\nGGG\nh\nDIFFERENT\ni\nj".into())],
"0000002",
);
cx.run_until_parked();
@@ -2444,7 +2444,7 @@ mod tests {
// Final commit that accepts all remaining edits
fs.set_head_for_repo(
path!("/project/.git").as_ref(),
&[("file.txt".into(), "A\nb\nc\nf\nGGG\nh\nNEW\ni\nJ".into())],
&[("file.txt", "A\nb\nc\nf\nGGG\nh\nNEW\ni\nJ".into())],
"0000003",
);
cx.run_until_parked();

View File

@@ -9,12 +9,14 @@ pub mod tool_use;
pub use context::{AgentContext, ContextId, ContextLoadResult};
pub use context_store::ContextStore;
use fs::Fs;
use std::sync::Arc;
pub use thread::{
LastRestoreCheckpoint, Message, MessageCrease, MessageId, MessageSegment, Thread, ThreadError,
ThreadEvent, ThreadFeedback, ThreadId, ThreadSummary, TokenUsageRatio,
};
pub use thread_store::{SerializedThread, TextThreadStore, ThreadStore};
pub fn init(cx: &mut gpui::App) {
thread_store::init(cx);
pub fn init(fs: Arc<dyn Fs>, cx: &mut gpui::App) {
thread_store::init(fs, cx);
}

View File

@@ -18,6 +18,7 @@ use std::path::PathBuf;
use std::{ops::Range, path::Path, sync::Arc};
use text::{Anchor, OffsetRangeExt as _};
use util::markdown::MarkdownCodeBlock;
use util::rel_path::RelPath;
use util::{ResultExt as _, post_inc};
pub const RULES_ICON: IconName = IconName::Reader;
@@ -158,7 +159,7 @@ pub struct FileContextHandle {
#[derive(Debug, Clone)]
pub struct FileContext {
pub handle: FileContextHandle,
pub full_path: Arc<Path>,
pub full_path: String,
pub text: SharedString,
pub is_outline: bool,
}
@@ -186,7 +187,7 @@ impl FileContextHandle {
log::error!("file context missing path");
return Task::ready(None);
};
let full_path: Arc<Path> = file.full_path(cx).into();
let full_path = file.full_path(cx).to_string_lossy().into_owned();
let rope = buffer_ref.as_rope().clone();
let buffer = self.buffer.clone();
@@ -235,14 +236,14 @@ pub struct DirectoryContextHandle {
#[derive(Debug, Clone)]
pub struct DirectoryContext {
pub handle: DirectoryContextHandle,
pub full_path: Arc<Path>,
pub full_path: String,
pub descendants: Vec<DirectoryContextDescendant>,
}
#[derive(Debug, Clone)]
pub struct DirectoryContextDescendant {
/// Path within the directory.
pub rel_path: Arc<Path>,
pub rel_path: Arc<RelPath>,
pub fenced_codeblock: SharedString,
}
@@ -273,13 +274,16 @@ impl DirectoryContextHandle {
}
let directory_path = entry.path.clone();
let directory_full_path = worktree_ref.full_path(&directory_path).into();
let directory_full_path = worktree_ref
.full_path(&directory_path)
.to_string_lossy()
.to_string();
let file_paths = collect_files_in_path(worktree_ref, &directory_path);
let descendants_future = future::join_all(file_paths.into_iter().map(|path| {
let worktree_ref = worktree.read(cx);
let worktree_id = worktree_ref.id();
let full_path = worktree_ref.full_path(&path);
let full_path = worktree_ref.full_path(&path).to_string_lossy().into_owned();
let rel_path = path
.strip_prefix(&directory_path)
@@ -360,7 +364,7 @@ pub struct SymbolContextHandle {
#[derive(Debug, Clone)]
pub struct SymbolContext {
pub handle: SymbolContextHandle,
pub full_path: Arc<Path>,
pub full_path: String,
pub line_range: Range<Point>,
pub text: SharedString,
}
@@ -399,7 +403,7 @@ impl SymbolContextHandle {
log::error!("symbol context's file has no path");
return Task::ready(None);
};
let full_path = file.full_path(cx).into();
let full_path = file.full_path(cx).to_string_lossy().into_owned();
let line_range = self.enclosing_range.to_point(&buffer_ref.snapshot());
let text = self.text(cx);
let buffer = self.buffer.clone();
@@ -433,7 +437,7 @@ pub struct SelectionContextHandle {
#[derive(Debug, Clone)]
pub struct SelectionContext {
pub handle: SelectionContextHandle,
pub full_path: Arc<Path>,
pub full_path: String,
pub line_range: Range<Point>,
pub text: SharedString,
}
@@ -472,7 +476,7 @@ impl SelectionContextHandle {
let text = self.text(cx);
let buffer = self.buffer.clone();
let context = AgentContext::Selection(SelectionContext {
full_path: full_path.into(),
full_path: full_path.to_string_lossy().into_owned(),
line_range: self.line_range(cx),
text,
handle: self,
@@ -702,7 +706,7 @@ impl Display for RulesContext {
#[derive(Debug, Clone)]
pub struct ImageContext {
pub project_path: Option<ProjectPath>,
pub full_path: Option<Arc<Path>>,
pub full_path: Option<String>,
pub original_image: Arc<gpui::Image>,
// TODO: handle this elsewhere and remove `ignore-interior-mutability` opt-out in clippy.toml
// needed due to a false positive of `clippy::mutable_key_type`.
@@ -968,7 +972,7 @@ pub fn load_context(
})
}
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
fn collect_files_in_path(worktree: &Worktree, path: &RelPath) -> Vec<Arc<RelPath>> {
let mut files = Vec::new();
for entry in worktree.child_entries(path) {
@@ -982,14 +986,17 @@ fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
files
}
fn codeblock_tag(full_path: &Path, line_range: Option<Range<Point>>) -> String {
fn codeblock_tag(full_path: &str, line_range: Option<Range<Point>>) -> String {
let mut result = String::new();
if let Some(extension) = full_path.extension().and_then(|ext| ext.to_str()) {
if let Some(extension) = Path::new(full_path)
.extension()
.and_then(|ext| ext.to_str())
{
let _ = write!(result, "{} ", extension);
}
let _ = write!(result, "{}", full_path.display());
let _ = write!(result, "{}", full_path);
if let Some(range) = line_range {
if range.start.row == range.end.row {

View File

@@ -14,7 +14,10 @@ use futures::{self, FutureExt};
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
use language::{Buffer, File as _};
use language_model::LanguageModelImage;
use project::{Project, ProjectItem, ProjectPath, Symbol, image_store::is_image_file};
use project::{
Project, ProjectItem, ProjectPath, Symbol, image_store::is_image_file,
lsp_store::SymbolLocation,
};
use prompt_store::UserPromptId;
use ref_cast::RefCast as _;
use std::{
@@ -309,7 +312,7 @@ impl ContextStore {
let item = image_item.read(cx);
this.insert_image(
Some(item.project_path(cx)),
Some(item.file.full_path(cx).into()),
Some(item.file.full_path(cx).to_string_lossy().into_owned()),
item.image.clone(),
remove_if_exists,
cx,
@@ -325,7 +328,7 @@ impl ContextStore {
fn insert_image(
&mut self,
project_path: Option<ProjectPath>,
full_path: Option<Arc<Path>>,
full_path: Option<String>,
image: Arc<Image>,
remove_if_exists: bool,
cx: &mut Context<ContextStore>,
@@ -500,7 +503,7 @@ impl ContextStore {
let Some(context_path) = buffer.project_path(cx) else {
return false;
};
if context_path != symbol.path {
if symbol.path != SymbolLocation::InProject(context_path) {
return false;
}
let context_range = context.range.to_point_utf16(&buffer.snapshot());

View File

@@ -155,7 +155,7 @@ impl HistoryStore {
.iter()
.filter_map(|entry| match entry {
HistoryEntryId::Context(path) => path.file_name().map(|file| {
SerializedRecentOpen::ContextName(file.to_string_lossy().to_string())
SerializedRecentOpen::ContextName(file.to_string_lossy().into_owned())
}),
HistoryEntryId::Thread(id) => Some(SerializedRecentOpen::Thread(id.to_string())),
})

View File

@@ -234,7 +234,6 @@ impl MessageSegment {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectSnapshot {
pub worktree_snapshots: Vec<WorktreeSnapshot>,
pub unsaved_buffer_paths: Vec<String>,
pub timestamp: DateTime<Utc>,
}
@@ -1277,62 +1276,6 @@ impl Thread {
);
}
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 {
for message in self.messages.iter().rev() {
if self.tool_use.message_has_tool_results(message.id) {
return true;
} else if message.role == Role::User {
return false;
}
}
false
}
pub fn to_completion_request(
&self,
model: Arc<dyn LanguageModel>,
@@ -2857,27 +2800,11 @@ impl Thread {
.map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
.collect();
cx.spawn(async move |_, cx| {
cx.spawn(async move |_, _| {
let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
let mut unsaved_buffers = Vec::new();
cx.update(|app_cx| {
let buffer_store = project.read(app_cx).buffer_store();
for buffer_handle in buffer_store.read(app_cx).buffers() {
let buffer = buffer_handle.read(app_cx);
if buffer.is_dirty()
&& let Some(file) = buffer.file()
{
let path = file.path().to_string_lossy().to_string();
unsaved_buffers.push(path);
}
}
})
.ok();
Arc::new(ProjectSnapshot {
worktree_snapshots,
unsaved_buffer_paths: unsaved_buffers,
timestamp: Utc::now(),
})
})
@@ -2892,7 +2819,7 @@ impl Thread {
// Get worktree path and snapshot
let worktree_info = cx.update(|app_cx| {
let worktree = worktree.read(app_cx);
let path = worktree.abs_path().to_string_lossy().to_string();
let path = worktree.abs_path().to_string_lossy().into_owned();
let snapshot = worktree.snapshot();
(path, snapshot)
});
@@ -3275,6 +3202,7 @@ mod tests {
use agent_settings::{AgentProfileId, AgentSettings};
use assistant_tool::ToolRegistry;
use assistant_tools;
use fs::Fs;
use futures::StreamExt;
use futures::future::BoxFuture;
use futures::stream::BoxStream;
@@ -3292,15 +3220,15 @@ mod tests {
use settings::{LanguageModelParameters, Settings, SettingsStore};
use std::sync::Arc;
use std::time::Duration;
use theme::ThemeSettings;
use util::path;
use workspace::Workspace;
#[gpui::test]
async fn test_message_with_context(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(
&fs,
cx,
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
)
@@ -3375,9 +3303,10 @@ fn main() {{
#[gpui::test]
async fn test_only_include_new_contexts(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(
&fs,
cx,
json!({
"file1.rs": "fn function1() {}\n",
@@ -3531,9 +3460,10 @@ fn main() {{
#[gpui::test]
async fn test_message_without_files(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(
&fs,
cx,
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
)
@@ -3610,9 +3540,10 @@ 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);
let fs = init_test_settings(cx);
let project = create_test_project(
&fs,
cx,
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
)
@@ -3738,9 +3669,10 @@ fn main() {{
#[gpui::test]
async fn test_storing_profile_setting_per_thread(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(
&fs,
cx,
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
)
@@ -3760,9 +3692,10 @@ fn main() {{
#[gpui::test]
async fn test_serializing_thread_profile(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(
&fs,
cx,
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
)
@@ -3803,9 +3736,10 @@ fn main() {{
#[gpui::test]
async fn test_temperature_setting(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(
&fs,
cx,
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
)
@@ -3897,9 +3831,9 @@ fn main() {{
#[gpui::test]
async fn test_thread_summary(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let project = create_test_project(&fs, cx, json!({})).await;
let (_, _thread_store, thread, _context_store, model) =
setup_test_environment(cx, project.clone()).await;
@@ -3982,9 +3916,9 @@ fn main() {{
#[gpui::test]
async fn test_thread_summary_error_set_manually(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let project = create_test_project(&fs, cx, json!({})).await;
let (_, _thread_store, thread, _context_store, model) =
setup_test_environment(cx, project.clone()).await;
@@ -4004,9 +3938,9 @@ fn main() {{
#[gpui::test]
async fn test_thread_summary_error_retry(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let project = create_test_project(&fs, cx, json!({})).await;
let (_, _thread_store, thread, _context_store, model) =
setup_test_environment(cx, project.clone()).await;
@@ -4158,9 +4092,9 @@ fn main() {{
#[gpui::test]
async fn test_retry_on_overloaded_error(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let project = create_test_project(&fs, cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
@@ -4236,9 +4170,9 @@ fn main() {{
#[gpui::test]
async fn test_retry_on_internal_server_error(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let project = create_test_project(&fs, cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
@@ -4318,9 +4252,9 @@ fn main() {{
#[gpui::test]
async fn test_exponential_backoff_on_retries(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let project = create_test_project(&fs, cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
@@ -4438,9 +4372,9 @@ fn main() {{
#[gpui::test]
async fn test_max_retries_exceeded(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let project = create_test_project(&fs, cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
@@ -4529,9 +4463,9 @@ fn main() {{
#[gpui::test]
async fn test_retry_message_removed_on_retry(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let project = create_test_project(&fs, cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
@@ -4702,9 +4636,9 @@ fn main() {{
#[gpui::test]
async fn test_successful_completion_clears_retry_state(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let project = create_test_project(&fs, cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
@@ -4868,9 +4802,9 @@ fn main() {{
#[gpui::test]
async fn test_rate_limit_retry_single_attempt(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let project = create_test_project(&fs, cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
@@ -5053,9 +4987,9 @@ fn main() {{
#[gpui::test]
async fn test_ui_only_messages_not_sent_to_model(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let project = create_test_project(&fs, cx, json!({})).await;
let (_, _, thread, _, model) = setup_test_environment(cx, project.clone()).await;
// Insert a regular user message
@@ -5153,9 +5087,9 @@ fn main() {{
#[gpui::test]
async fn test_no_retry_without_burn_mode(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let project = create_test_project(&fs, cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Ensure we're in Normal mode (not Burn mode)
@@ -5226,9 +5160,9 @@ fn main() {{
#[gpui::test]
async fn test_retry_canceled_on_stop(cx: &mut TestAppContext) {
init_test_settings(cx);
let fs = init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let project = create_test_project(&fs, cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Enable Burn Mode to allow retries
@@ -5334,7 +5268,8 @@ fn main() {{
cx.run_until_parked();
}
fn init_test_settings(cx: &mut TestAppContext) {
fn init_test_settings(cx: &mut TestAppContext) -> Arc<dyn Fs> {
let fs = FakeFs::new(cx.executor());
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
@@ -5342,10 +5277,10 @@ fn main() {{
Project::init_settings(cx);
AgentSettings::register(cx);
prompt_store::init(cx);
thread_store::init(cx);
thread_store::init(fs.clone(), cx);
workspace::init_settings(cx);
language_model::init_settings(cx);
ThemeSettings::register(cx);
theme::init(theme::LoadThemes::JustBase, cx);
ToolRegistry::default_global(cx);
assistant_tool::init(cx);
@@ -5356,16 +5291,17 @@ fn main() {{
));
assistant_tools::init(http_client, cx);
});
fs
}
// Helper to create a test project with test files
async fn create_test_project(
fs: &Arc<dyn Fs>,
cx: &mut TestAppContext,
files: serde_json::Value,
) -> Entity<Project> {
let fs = FakeFs::new(cx.executor());
fs.insert_tree(path!("/test"), files).await;
Project::test(fs, [path!("/test").as_ref()], cx).await
fs.as_fake().insert_tree(path!("/test"), files).await;
Project::test(fs.clone(), [path!("/test").as_ref()], cx).await
}
async fn setup_test_environment(

View File

@@ -10,6 +10,7 @@ use assistant_tool::{Tool, ToolId, ToolWorkingSet};
use chrono::{DateTime, Utc};
use collections::HashMap;
use context_server::ContextServerId;
use fs::{Fs, RemoveOptions};
use futures::{
FutureExt as _, StreamExt as _,
channel::{mpsc, oneshot},
@@ -37,9 +38,9 @@ use std::{
cell::{Ref, RefCell},
path::{Path, PathBuf},
rc::Rc,
sync::{Arc, Mutex},
sync::{Arc, LazyLock, Mutex},
};
use util::ResultExt as _;
use util::{ResultExt as _, rel_path::RelPath};
use zed_env_vars::ZED_STATELESS;
@@ -73,20 +74,22 @@ impl Column for DataType {
}
}
const RULES_FILE_NAMES: [&str; 9] = [
".rules",
".cursorrules",
".windsurfrules",
".clinerules",
".github/copilot-instructions.md",
"CLAUDE.md",
"AGENT.md",
"AGENTS.md",
"GEMINI.md",
];
static RULES_FILE_NAMES: LazyLock<[&RelPath; 9]> = LazyLock::new(|| {
[
RelPath::unix(".rules").unwrap(),
RelPath::unix(".cursorrules").unwrap(),
RelPath::unix(".windsurfrules").unwrap(),
RelPath::unix(".clinerules").unwrap(),
RelPath::unix(".github/copilot-instructions.md").unwrap(),
RelPath::unix("CLAUDE.md").unwrap(),
RelPath::unix("AGENT.md").unwrap(),
RelPath::unix("AGENTS.md").unwrap(),
RelPath::unix("GEMINI.md").unwrap(),
]
});
pub fn init(cx: &mut App) {
ThreadsDatabase::init(cx);
pub fn init(fs: Arc<dyn Fs>, cx: &mut App) {
ThreadsDatabase::init(fs, cx);
}
/// A system prompt shared by all threads created by this ThreadStore
@@ -231,11 +234,10 @@ impl ThreadStore {
self.enqueue_system_prompt_reload();
}
project::Event::WorktreeUpdatedEntries(_, items) => {
if items.iter().any(|(path, _, _)| {
RULES_FILE_NAMES
.iter()
.any(|name| path.as_ref() == Path::new(name))
}) {
if items
.iter()
.any(|(path, _, _)| RULES_FILE_NAMES.iter().any(|name| path.as_ref() == *name))
{
self.enqueue_system_prompt_reload();
}
}
@@ -327,7 +329,7 @@ impl ThreadStore {
cx: &mut App,
) -> Task<(WorktreeContext, Option<RulesLoadingError>)> {
let tree = worktree.read(cx);
let root_name = tree.root_name().into();
let root_name = tree.root_name_str().into();
let abs_path = tree.abs_path();
let mut context = WorktreeContext {
@@ -869,13 +871,13 @@ impl ThreadsDatabase {
GlobalThreadsDatabase::global(cx).0.clone()
}
fn init(cx: &mut App) {
fn init(fs: Arc<dyn Fs>, cx: &mut App) {
let executor = cx.background_executor().clone();
let database_future = executor
.spawn({
let executor = executor.clone();
let threads_dir = paths::data_dir().join("threads");
async move { ThreadsDatabase::new(threads_dir, executor) }
async move { ThreadsDatabase::new(fs, threads_dir, executor).await }
})
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
.boxed()
@@ -884,13 +886,17 @@ impl ThreadsDatabase {
cx.set_global(GlobalThreadsDatabase(database_future));
}
pub fn new(threads_dir: PathBuf, executor: BackgroundExecutor) -> Result<Self> {
std::fs::create_dir_all(&threads_dir)?;
pub async fn new(
fs: Arc<dyn Fs>,
threads_dir: PathBuf,
executor: BackgroundExecutor,
) -> Result<Self> {
fs.create_dir(&threads_dir).await?;
let sqlite_path = threads_dir.join("threads.db");
let mdb_path = threads_dir.join("threads-db.1.mdb");
let needs_migration_from_heed = mdb_path.exists();
let needs_migration_from_heed = fs.is_file(&mdb_path).await;
let connection = if *ZED_STATELESS {
Connection::open_memory(Some("THREAD_FALLBACK_DB"))
@@ -932,7 +938,14 @@ impl ThreadsDatabase {
.spawn(async move {
log::info!("Starting threads.db migration");
Self::migrate_from_heed(&mdb_path, db_connection, executor_clone)?;
std::fs::remove_dir_all(mdb_path)?;
fs.remove_dir(
&mdb_path,
RemoveOptions {
recursive: true,
ignore_if_not_exists: true,
},
)
.await?;
log::info!("threads.db migrated to sqlite");
Ok::<(), anyhow::Error>(())
})

View File

@@ -27,6 +27,7 @@ use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::Arc;
use util::ResultExt;
use util::rel_path::RelPath;
const RULES_FILE_NAMES: [&str; 9] = [
".rules",
@@ -56,7 +57,7 @@ struct Session {
pub struct LanguageModels {
/// Access language model by ID
models: HashMap<acp_thread::AgentModelId, Arc<dyn LanguageModel>>,
models: HashMap<acp::ModelId, Arc<dyn LanguageModel>>,
/// Cached list for returning language model information
model_list: acp_thread::AgentModelList,
refresh_models_rx: watch::Receiver<()>,
@@ -132,10 +133,7 @@ impl LanguageModels {
self.refresh_models_rx.clone()
}
pub fn model_from_id(
&self,
model_id: &acp_thread::AgentModelId,
) -> Option<Arc<dyn LanguageModel>> {
pub fn model_from_id(&self, model_id: &acp::ModelId) -> Option<Arc<dyn LanguageModel>> {
self.models.get(model_id).cloned()
}
@@ -146,12 +144,13 @@ impl LanguageModels {
acp_thread::AgentModelInfo {
id: Self::model_id(model),
name: model.name().0,
description: None,
icon: Some(provider.icon()),
}
}
fn model_id(model: &Arc<dyn LanguageModel>) -> acp_thread::AgentModelId {
acp_thread::AgentModelId(format!("{}/{}", model.provider_id().0, model.id().0).into())
fn model_id(model: &Arc<dyn LanguageModel>) -> acp::ModelId {
acp::ModelId(format!("{}/{}", model.provider_id().0, model.id().0).into())
}
fn authenticate_all_language_model_providers(cx: &mut App) -> Task<()> {
@@ -436,7 +435,7 @@ impl NativeAgent {
cx: &mut App,
) -> Task<(WorktreeContext, Option<RulesLoadingError>)> {
let tree = worktree.read(cx);
let root_name = tree.root_name().into();
let root_name = tree.root_name_str().into();
let abs_path = tree.abs_path();
let mut context = WorktreeContext {
@@ -476,7 +475,7 @@ impl NativeAgent {
.into_iter()
.filter_map(|name| {
worktree
.entry_for_path(name)
.entry_for_path(RelPath::unix(name).unwrap())
.filter(|entry| entry.is_file())
.map(|entry| entry.path.clone())
})
@@ -560,7 +559,7 @@ impl NativeAgent {
if items.iter().any(|(path, _, _)| {
RULES_FILE_NAMES
.iter()
.any(|name| path.as_ref() == Path::new(name))
.any(|name| path.as_ref() == RelPath::unix(name).unwrap())
}) {
self.project_context_needs_refresh.send(()).ok();
}
@@ -836,10 +835,15 @@ impl NativeAgentConnection {
}
}
impl AgentModelSelector for NativeAgentConnection {
struct NativeAgentModelSelector {
session_id: acp::SessionId,
connection: NativeAgentConnection,
}
impl acp_thread::AgentModelSelector for NativeAgentModelSelector {
fn list_models(&self, cx: &mut App) -> Task<Result<acp_thread::AgentModelList>> {
log::debug!("NativeAgentConnection::list_models called");
let list = self.0.read(cx).models.model_list.clone();
let list = self.connection.0.read(cx).models.model_list.clone();
Task::ready(if list.is_empty() {
Err(anyhow::anyhow!("No models available"))
} else {
@@ -847,24 +851,24 @@ impl AgentModelSelector for NativeAgentConnection {
})
}
fn select_model(
&self,
session_id: acp::SessionId,
model_id: acp_thread::AgentModelId,
cx: &mut App,
) -> Task<Result<()>> {
log::debug!("Setting model for session {}: {}", session_id, model_id);
fn select_model(&self, model_id: acp::ModelId, cx: &mut App) -> Task<Result<()>> {
log::debug!(
"Setting model for session {}: {}",
self.session_id,
model_id
);
let Some(thread) = self
.connection
.0
.read(cx)
.sessions
.get(&session_id)
.get(&self.session_id)
.map(|session| session.thread.clone())
else {
return Task::ready(Err(anyhow!("Session not found")));
};
let Some(model) = self.0.read(cx).models.model_from_id(&model_id) else {
let Some(model) = self.connection.0.read(cx).models.model_from_id(&model_id) else {
return Task::ready(Err(anyhow!("Invalid model ID {}", model_id)));
};
@@ -872,33 +876,32 @@ impl AgentModelSelector for NativeAgentConnection {
thread.set_model(model.clone(), cx);
});
update_settings_file(self.0.read(cx).fs.clone(), cx, move |settings, _cx| {
let provider = model.provider_id().0.to_string();
let model = model.id().0.to_string();
settings
.agent
.get_or_insert_default()
.set_model(LanguageModelSelection {
provider: provider.into(),
model,
});
});
update_settings_file(
self.connection.0.read(cx).fs.clone(),
cx,
move |settings, _cx| {
let provider = model.provider_id().0.to_string();
let model = model.id().0.to_string();
settings
.agent
.get_or_insert_default()
.set_model(LanguageModelSelection {
provider: provider.into(),
model,
});
},
);
Task::ready(Ok(()))
}
fn selected_model(
&self,
session_id: &acp::SessionId,
cx: &mut App,
) -> Task<Result<acp_thread::AgentModelInfo>> {
let session_id = session_id.clone();
fn selected_model(&self, cx: &mut App) -> Task<Result<acp_thread::AgentModelInfo>> {
let Some(thread) = self
.connection
.0
.read(cx)
.sessions
.get(&session_id)
.get(&self.session_id)
.map(|session| session.thread.clone())
else {
return Task::ready(Err(anyhow!("Session not found")));
@@ -915,8 +918,8 @@ impl AgentModelSelector for NativeAgentConnection {
)))
}
fn watch(&self, cx: &mut App) -> watch::Receiver<()> {
self.0.read(cx).models.watch()
fn watch(&self, cx: &mut App) -> Option<watch::Receiver<()>> {
Some(self.connection.0.read(cx).models.watch())
}
}
@@ -972,8 +975,11 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
Task::ready(Ok(()))
}
fn model_selector(&self) -> Option<Rc<dyn AgentModelSelector>> {
Some(Rc::new(self.clone()) as Rc<dyn AgentModelSelector>)
fn model_selector(&self, session_id: &acp::SessionId) -> Option<Rc<dyn AgentModelSelector>> {
Some(Rc::new(NativeAgentModelSelector {
session_id: session_id.clone(),
connection: self.clone(),
}) as Rc<dyn AgentModelSelector>)
}
fn prompt(
@@ -1196,16 +1202,14 @@ mod tests {
use crate::HistoryEntryId;
use super::*;
use acp_thread::{
AgentConnection, AgentModelGroupName, AgentModelId, AgentModelInfo, MentionUri,
};
use acp_thread::{AgentConnection, AgentModelGroupName, AgentModelInfo, MentionUri};
use fs::FakeFs;
use gpui::TestAppContext;
use indoc::indoc;
use indoc::formatdoc;
use language_model::fake_provider::FakeLanguageModel;
use serde_json::json;
use settings::SettingsStore;
use util::path;
use util::{path, rel_path::rel_path};
#[gpui::test]
async fn test_maintaining_project_context(cx: &mut TestAppContext) {
@@ -1255,14 +1259,17 @@ mod tests {
fs.insert_file("/a/.rules", Vec::new()).await;
cx.run_until_parked();
agent.read_with(cx, |agent, cx| {
let rules_entry = worktree.read(cx).entry_for_path(".rules").unwrap();
let rules_entry = worktree
.read(cx)
.entry_for_path(rel_path(".rules"))
.unwrap();
assert_eq!(
agent.project_context.read(cx).worktrees,
vec![WorktreeContext {
root_name: "a".into(),
abs_path: Path::new("/a").into(),
rules_file: Some(RulesFileContext {
path_in_worktree: Path::new(".rules").into(),
path_in_worktree: rel_path(".rules").into(),
text: "".into(),
project_entry_id: rules_entry.id.to_usize()
})
@@ -1292,7 +1299,25 @@ mod tests {
.unwrap(),
);
let models = cx.update(|cx| connection.list_models(cx)).await.unwrap();
// Create a thread/session
let acp_thread = cx
.update(|cx| {
Rc::new(connection.clone()).new_thread(project.clone(), Path::new("/a"), cx)
})
.await
.unwrap();
let session_id = cx.update(|cx| acp_thread.read(cx).session_id().clone());
let models = cx
.update(|cx| {
connection
.model_selector(&session_id)
.unwrap()
.list_models(cx)
})
.await
.unwrap();
let acp_thread::AgentModelList::Grouped(models) = models else {
panic!("Unexpected model group");
@@ -1302,8 +1327,9 @@ mod tests {
IndexMap::from_iter([(
AgentModelGroupName("Fake".into()),
vec![AgentModelInfo {
id: AgentModelId("fake/fake".into()),
id: acp::ModelId("fake/fake".into()),
name: "Fake".into(),
description: None,
icon: Some(ui::IconName::ZedAssistant),
}]
)])
@@ -1360,8 +1386,9 @@ mod tests {
let session_id = cx.update(|cx| acp_thread.read(cx).session_id().clone());
// Select a model
let model_id = AgentModelId("fake/fake".into());
cx.update(|cx| connection.select_model(session_id.clone(), model_id.clone(), cx))
let selector = connection.model_selector(&session_id).unwrap();
let model_id = acp::ModelId("fake/fake".into());
cx.update(|cx| selector.select_model(model_id.clone(), cx))
.await
.unwrap();
@@ -1391,7 +1418,6 @@ mod tests {
}
#[gpui::test]
#[cfg_attr(target_os = "windows", ignore)] // TODO: Fix this test on Windows
async fn test_save_load_thread(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
@@ -1471,17 +1497,22 @@ mod tests {
model.send_last_completion_stream_text_chunk("Lorem.");
model.end_last_completion_stream();
cx.run_until_parked();
summary_model.send_last_completion_stream_text_chunk("Explaining /a/b.md");
summary_model
.send_last_completion_stream_text_chunk(&format!("Explaining {}", path!("/a/b.md")));
summary_model.end_last_completion_stream();
send.await.unwrap();
let uri = MentionUri::File {
abs_path: path!("/a/b.md").into(),
}
.to_uri();
acp_thread.read_with(cx, |thread, cx| {
assert_eq!(
thread.to_markdown(cx),
indoc! {"
formatdoc! {"
## User
What does [@b.md](file:///a/b.md) mean?
What does [@b.md]({uri}) mean?
## Assistant
@@ -1507,7 +1538,7 @@ mod tests {
history_entries(&history_store, cx),
vec![(
HistoryEntryId::AcpThread(session_id.clone()),
"Explaining /a/b.md".into()
format!("Explaining {}", path!("/a/b.md"))
)]
);
let acp_thread = agent
@@ -1517,10 +1548,10 @@ mod tests {
acp_thread.read_with(cx, |thread, cx| {
assert_eq!(
thread.to_markdown(cx),
indoc! {"
formatdoc! {"
## User
What does [@b.md](file:///a/b.md) mean?
What does [@b.md]({uri}) mean?
## Assistant

View File

@@ -422,17 +422,15 @@ mod tests {
use agent::MessageSegment;
use agent::context::LoadedContext;
use client::Client;
use fs::FakeFs;
use fs::{FakeFs, Fs};
use gpui::AppContext;
use gpui::TestAppContext;
use http_client::FakeHttpClient;
use language_model::Role;
use project::Project;
use serde_json::json;
use settings::SettingsStore;
use util::test::TempTree;
fn init_test(cx: &mut TestAppContext) {
fn init_test(fs: Arc<dyn Fs>, cx: &mut TestAppContext) {
env_logger::try_init().ok();
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
@@ -443,7 +441,7 @@ mod tests {
let http_client = FakeHttpClient::with_404_response();
let clock = Arc::new(clock::FakeSystemClock::new());
let client = Client::new(clock, http_client, cx);
agent::init(cx);
agent::init(fs, cx);
agent_settings::init(cx);
language_model::init(client, cx);
});
@@ -451,10 +449,8 @@ mod tests {
#[gpui::test]
async fn test_retrieving_old_thread(cx: &mut TestAppContext) {
let tree = TempTree::new(json!({}));
util::paths::set_home_dir(tree.path().into());
init_test(cx);
let fs = FakeFs::new(cx.executor());
init_test(fs.clone(), cx);
let project = Project::test(fs, [], cx).await;
// Save a thread using the old agent.

View File

@@ -262,7 +262,7 @@ impl HistoryStore {
.iter()
.filter_map(|entry| match entry {
HistoryEntryId::TextThread(path) => path.file_name().map(|file| {
SerializedRecentOpen::TextThread(file.to_string_lossy().to_string())
SerializedRecentOpen::TextThread(file.to_string_lossy().into_owned())
}),
HistoryEntryId::AcpThread(id) => {
Some(SerializedRecentOpen::AcpThread(id.to_string()))

View File

@@ -48,16 +48,15 @@ The one exception to this is if the user references something you don't know abo
## Code Block Formatting
Whenever you mention a code block, you MUST use ONLY use the following format:
```path/to/Something.blah#L123-456
(code goes here)
```
The `#L123-456` means the line number range 123 through 456, and the path/to/Something.blah
is a path in the project. (If there is no valid path in the project, then you can use
/dev/null/path.extension for its path.) This is the ONLY valid way to format code blocks, because the Markdown parser
does not understand the more common ```language syntax, or bare ``` blocks. It only
understands this path-based syntax, and if the path is missing, then it will error and you will have to do it over again.
The `#L123-456` means the line number range 123 through 456, and the path/to/Something.blah is a path in the project. (If there is no valid path in the project, then you can use /dev/null/path.extension for its path.) This is the ONLY valid way to format code blocks, because the Markdown parser does not understand the more common ```language syntax, or bare ``` blocks. It only understands this path-based syntax, and if the path is missing, then it will error and you will have to do it over again.
Just to be really clear about this, if you ever find yourself writing three backticks followed by a language name, STOP!
You have made a mistake. You can only ever put paths after triple backticks!
<example>
Based on all the information I've gathered, here's a summary of how this system works:
1. The README file is loaded into the system.
@@ -74,6 +73,7 @@ This is the last header in the README.
```
4. Finally, it passes this information on to the next process.
</example>
<example>
In Markdown, hash marks signify headings. For example:
```/dev/null/example.md#L1-3
@@ -82,6 +82,7 @@ In Markdown, hash marks signify headings. For example:
### Level 3 heading
```
</example>
Here are examples of ways you must never render code blocks:
<bad_example_do_not_do_this>
In Markdown, hash marks signify headings. For example:
@@ -91,7 +92,9 @@ In Markdown, hash marks signify headings. For example:
### Level 3 heading
```
</bad_example_do_not_do_this>
This example is unacceptable because it does not include the path.
<bad_example_do_not_do_this>
In Markdown, hash marks signify headings. For example:
```markdown
@@ -101,14 +104,15 @@ In Markdown, hash marks signify headings. For example:
```
</bad_example_do_not_do_this>
This example is unacceptable because it has the language instead of the path.
<bad_example_do_not_do_this>
In Markdown, hash marks signify headings. For example:
# Level 1 heading
## Level 2 heading
### Level 3 heading
</bad_example_do_not_do_this>
This example is unacceptable because it uses indentation to mark the code block
instead of backticks with a path.
This example is unacceptable because it uses indentation to mark the code block instead of backticks with a path.
<bad_example_do_not_do_this>
In Markdown, hash marks signify headings. For example:
```markdown

View File

@@ -1850,8 +1850,18 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
.unwrap();
let connection = NativeAgentConnection(agent.clone());
// Create a thread using new_thread
let connection_rc = Rc::new(connection.clone());
let acp_thread = cx
.update(|cx| connection_rc.new_thread(project, cwd, cx))
.await
.expect("new_thread should succeed");
// Get the session_id from the AcpThread
let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone());
// Test model_selector returns Some
let selector_opt = connection.model_selector();
let selector_opt = connection.model_selector(&session_id);
assert!(
selector_opt.is_some(),
"agent2 should always support ModelSelector"
@@ -1868,23 +1878,16 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
};
assert!(!listed_models.is_empty(), "should have at least one model");
assert_eq!(
listed_models[&AgentModelGroupName("Fake".into())][0].id.0,
listed_models[&AgentModelGroupName("Fake".into())][0]
.id
.0
.as_ref(),
"fake/fake"
);
// Create a thread using new_thread
let connection_rc = Rc::new(connection.clone());
let acp_thread = cx
.update(|cx| connection_rc.new_thread(project, cwd, cx))
.await
.expect("new_thread should succeed");
// Get the session_id from the AcpThread
let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone());
// Test selected_model returns the default
let model = cx
.update(|cx| selector.selected_model(&session_id, cx))
.update(|cx| selector.selected_model(cx))
.await
.expect("selected_model should succeed");
let model = cx

View File

@@ -15,10 +15,11 @@ use agent_settings::{
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::adapt_schema_to_format;
use chrono::{DateTime, Utc};
use client::{ModelRequestUsage, RequestUsage};
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
use client::{ModelRequestUsage, RequestUsage, UserStore};
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, Plan, UsageLimit};
use collections::{HashMap, HashSet, IndexMap};
use fs::Fs;
use futures::stream;
use futures::{
FutureExt,
channel::{mpsc, oneshot},
@@ -34,7 +35,7 @@ use language_model::{
LanguageModelImage, LanguageModelProviderId, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult,
LanguageModelToolResultContent, LanguageModelToolSchemaFormat, LanguageModelToolUse,
LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage,
LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage, ZED_CLOUD_PROVIDER_ID,
};
use project::{
Project,
@@ -585,6 +586,7 @@ pub struct Thread {
pending_title_generation: Option<Task<()>>,
summary: Option<SharedString>,
messages: Vec<Message>,
user_store: Entity<UserStore>,
completion_mode: CompletionMode,
/// Holds the task that handles agent interaction until the end of the turn.
/// Survives across multiple requests as the model performs tool calls and
@@ -641,6 +643,7 @@ impl Thread {
pending_title_generation: None,
summary: None,
messages: Vec::new(),
user_store: project.read(cx).user_store(),
completion_mode: AgentSettings::get_global(cx).preferred_completion_mode,
running_turn: None,
pending_message: None,
@@ -820,6 +823,7 @@ impl Thread {
pending_title_generation: None,
summary: db_thread.detailed_summary,
messages: db_thread.messages,
user_store: project.read(cx).user_store(),
completion_mode: db_thread.completion_mode.unwrap_or_default(),
running_turn: None,
pending_message: None,
@@ -879,27 +883,11 @@ impl Thread {
.map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
.collect();
cx.spawn(async move |_, cx| {
cx.spawn(async move |_, _| {
let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
let mut unsaved_buffers = Vec::new();
cx.update(|app_cx| {
let buffer_store = project.read(app_cx).buffer_store();
for buffer_handle in buffer_store.read(app_cx).buffers() {
let buffer = buffer_handle.read(app_cx);
if buffer.is_dirty()
&& let Some(file) = buffer.file()
{
let path = file.path().to_string_lossy().to_string();
unsaved_buffers.push(path);
}
}
})
.ok();
Arc::new(ProjectSnapshot {
worktree_snapshots,
unsaved_buffer_paths: unsaved_buffers,
timestamp: Utc::now(),
})
})
@@ -914,7 +902,7 @@ impl Thread {
// Get worktree path and snapshot
let worktree_info = cx.update(|app_cx| {
let worktree = worktree.read(app_cx);
let path = worktree.abs_path().to_string_lossy().to_string();
let path = worktree.abs_path().to_string_lossy().into_owned();
let snapshot = worktree.snapshot();
(path, snapshot)
});
@@ -1265,12 +1253,12 @@ impl Thread {
);
log::debug!("Calling model.stream_completion, attempt {}", attempt);
let mut events = model
.stream_completion(request, cx)
.await
.map_err(|error| anyhow!(error))?;
let (mut events, mut error) = match model.stream_completion(request, cx).await {
Ok(events) => (events, None),
Err(err) => (stream::empty().boxed(), Some(err)),
};
let mut tool_results = FuturesUnordered::new();
let mut error = None;
while let Some(event) = events.next().await {
log::trace!("Received completion event: {:?}", event);
match event {
@@ -1318,8 +1306,10 @@ impl Thread {
if let Some(error) = error {
attempt += 1;
let retry =
this.update(cx, |this, _| this.handle_completion_error(error, attempt))??;
let retry = this.update(cx, |this, cx| {
let user_store = this.user_store.read(cx);
this.handle_completion_error(error, attempt, user_store.plan())
})??;
let timer = cx.background_executor().timer(retry.duration);
event_stream.send_retry(retry);
timer.await;
@@ -1346,8 +1336,23 @@ impl Thread {
&mut self,
error: LanguageModelCompletionError,
attempt: u8,
plan: Option<Plan>,
) -> Result<acp_thread::RetryStatus> {
if self.completion_mode == CompletionMode::Normal {
let Some(model) = self.model.as_ref() else {
return Err(anyhow!(error));
};
let auto_retry = if model.provider_id() == ZED_CLOUD_PROVIDER_ID {
match plan {
Some(Plan::V2(_)) => true,
Some(Plan::V1(_)) => self.completion_mode == CompletionMode::Burn,
None => false,
}
} else {
true
};
if !auto_retry {
return Err(anyhow!(error));
}

View File

@@ -9,14 +9,14 @@ use std::sync::Arc;
use util::markdown::MarkdownInlineCode;
/// Copies a file or directory in the project, and returns confirmation that the copy succeeded.
/// Directory contents will be copied recursively (like `cp -r`).
/// Directory contents will be copied recursively.
///
/// This tool should be used when it's desirable to create a copy of a file or directory without modifying the original.
/// It's much more efficient than doing this by separately reading and then writing the file or directory's contents, so this tool should be preferred over that approach whenever copying is the goal.
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct CopyPathToolInput {
/// The source path of the file or directory to copy.
/// If a directory is specified, its contents will be copied recursively (like `cp -r`).
/// If a directory is specified, its contents will be copied recursively.
///
/// <example>
/// If the project has the following files:
@@ -84,9 +84,7 @@ impl AgentTool for CopyPathTool {
.and_then(|project_path| project.entry_for_path(&project_path, cx))
{
Some(entity) => match project.find_project_path(&input.destination_path, cx) {
Some(project_path) => {
project.copy_entry(entity.id, None, project_path.path, cx)
}
Some(project_path) => project.copy_entry(entity.id, project_path, cx),
None => Task::ready(Err(anyhow!(
"Destination path {} was outside the project.",
input.destination_path

View File

@@ -11,7 +11,7 @@ use crate::{AgentTool, ToolCallEventStream};
/// Creates a new directory at the specified path within the project. Returns confirmation that the directory was created.
///
/// This tool creates a directory and all necessary parent directories (similar to `mkdir -p`). It should be used whenever you need to create new directories within the project.
/// This tool creates a directory and all necessary parent directories. It should be used whenever you need to create new directories within the project.
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct CreateDirectoryToolInput {
/// The path of the new directory.

View File

@@ -6,7 +6,7 @@ use language::{DiagnosticSeverity, OffsetRangeExt};
use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{fmt::Write, path::Path, sync::Arc};
use std::{fmt::Write, sync::Arc};
use ui::SharedString;
use util::markdown::MarkdownInlineCode;
@@ -147,9 +147,7 @@ impl AgentTool for DiagnosticsTool {
has_diagnostics = true;
output.push_str(&format!(
"{}: {} error(s), {} warning(s)\n",
Path::new(worktree.read(cx).root_name())
.join(project_path.path)
.display(),
worktree.read(cx).absolutize(&project_path.path).display(),
summary.error_count,
summary.warning_count
));

View File

@@ -17,10 +17,12 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::Settings;
use smol::stream::StreamExt as _;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use ui::SharedString;
use util::ResultExt;
use util::rel_path::RelPath;
const DEFAULT_UI_TEXT: &str = "Editing file";
@@ -148,12 +150,11 @@ impl EditFileTool {
// If any path component matches the local settings folder, then this could affect
// the editor in ways beyond the project source, so prompt.
let local_settings_folder = paths::local_settings_folder_relative_path();
let local_settings_folder = paths::local_settings_folder_name();
let path = Path::new(&input.path);
if path
.components()
.any(|component| component.as_os_str() == local_settings_folder.as_os_str())
{
if path.components().any(|component| {
component.as_os_str() == <_ as AsRef<OsStr>>::as_ref(&local_settings_folder)
}) {
return event_stream.authorize(
format!("{} (local settings)", input.display_description),
cx,
@@ -162,6 +163,7 @@ impl EditFileTool {
// It's also possible that the global config dir is configured to be inside the project,
// so check for that edge case too.
// TODO this is broken when remoting
if let Ok(canonical_path) = std::fs::canonicalize(&input.path)
&& canonical_path.starts_with(paths::config_dir())
{
@@ -216,9 +218,7 @@ impl AgentTool for EditFileTool {
.read(cx)
.short_full_path_for_project_path(&project_path, cx)
})
.unwrap_or(Path::new(&input.path).into())
.to_string_lossy()
.to_string()
.unwrap_or(input.path.to_string_lossy().into_owned())
.into(),
Err(raw_input) => {
if let Some(input) =
@@ -235,9 +235,7 @@ impl AgentTool for EditFileTool {
.read(cx)
.short_full_path_for_project_path(&project_path, cx)
})
.unwrap_or(Path::new(&input.path).into())
.to_string_lossy()
.to_string()
.unwrap_or(input.path)
.into();
}
@@ -478,7 +476,7 @@ impl AgentTool for EditFileTool {
) -> Result<()> {
event_stream.update_diff(cx.new(|cx| {
Diff::finalized(
output.input_path,
output.input_path.to_string_lossy().into_owned(),
Some(output.old_text.to_string()),
output.new_text,
self.language_registry.clone(),
@@ -542,10 +540,12 @@ fn resolve_path(
let file_name = input
.path
.file_name()
.and_then(|file_name| file_name.to_str())
.and_then(|file_name| RelPath::unix(file_name).ok())
.context("Can't create file: invalid filename")?;
let new_file_path = parent_project_path.map(|parent| ProjectPath {
path: Arc::from(parent.path.join(file_name)),
path: parent.path.join(file_name),
..parent
});
@@ -565,7 +565,7 @@ mod tests {
use prompt_store::ProjectContext;
use serde_json::json;
use settings::SettingsStore;
use util::path;
use util::{path, rel_path::rel_path};
#[gpui::test]
async fn test_edit_nonexistent_file(cx: &mut TestAppContext) {
@@ -614,13 +614,13 @@ mod tests {
let mode = &EditFileMode::Create;
let result = test_resolve_path(mode, "root/new.txt", cx);
assert_resolved_path_eq(result.await, "new.txt");
assert_resolved_path_eq(result.await, rel_path("new.txt"));
let result = test_resolve_path(mode, "new.txt", cx);
assert_resolved_path_eq(result.await, "new.txt");
assert_resolved_path_eq(result.await, rel_path("new.txt"));
let result = test_resolve_path(mode, "dir/new.txt", cx);
assert_resolved_path_eq(result.await, "dir/new.txt");
assert_resolved_path_eq(result.await, rel_path("dir/new.txt"));
let result = test_resolve_path(mode, "root/dir/subdir/existing.txt", cx);
assert_eq!(
@@ -642,10 +642,10 @@ mod tests {
let path_with_root = "root/dir/subdir/existing.txt";
let path_without_root = "dir/subdir/existing.txt";
let result = test_resolve_path(mode, path_with_root, cx);
assert_resolved_path_eq(result.await, path_without_root);
assert_resolved_path_eq(result.await, rel_path(path_without_root));
let result = test_resolve_path(mode, path_without_root, cx);
assert_resolved_path_eq(result.await, path_without_root);
assert_resolved_path_eq(result.await, rel_path(path_without_root));
let result = test_resolve_path(mode, "root/nonexistent.txt", cx);
assert_eq!(
@@ -690,14 +690,10 @@ mod tests {
cx.update(|cx| resolve_path(&input, project, cx))
}
fn assert_resolved_path_eq(path: anyhow::Result<ProjectPath>, expected: &str) {
let actual = path
.expect("Should return valid path")
.path
.to_str()
.unwrap()
.replace("\\", "/"); // Naive Windows paths normalization
assert_eq!(actual, expected);
#[track_caller]
fn assert_resolved_path_eq(path: anyhow::Result<ProjectPath>, expected: &RelPath) {
let actual = path.expect("Should return valid path").path;
assert_eq!(actual.as_ref(), expected);
}
#[gpui::test]
@@ -1408,8 +1404,8 @@ mod tests {
// Parent directory references - find_project_path resolves these
(
"project/../other",
false,
"Path with .. is resolved by find_project_path",
true,
"Path with .. that goes outside of root directory",
),
(
"project/./src/file.rs",
@@ -1437,16 +1433,18 @@ mod tests {
)
});
cx.run_until_parked();
if should_confirm {
stream_rx.expect_authorization().await;
} else {
auth.await.unwrap();
assert!(
stream_rx.try_next().is_err(),
"Failed for case: {} - path: {} - expected no confirmation but got one",
description,
path
);
auth.await.unwrap();
}
}
}

View File

@@ -156,10 +156,14 @@ impl AgentTool for FindPathTool {
}
fn search_paths(glob: &str, project: Entity<Project>, cx: &mut App) -> Task<Result<Vec<PathBuf>>> {
let path_matcher = match PathMatcher::new([
// Sometimes models try to search for "". In this case, return all paths in the project.
if glob.is_empty() { "*" } else { glob },
]) {
let path_style = project.read(cx).path_style(cx);
let path_matcher = match PathMatcher::new(
[
// Sometimes models try to search for "". In this case, return all paths in the project.
if glob.is_empty() { "*" } else { glob },
],
path_style,
) {
Ok(matcher) => matcher,
Err(err) => return Task::ready(Err(anyhow!("Invalid glob: {err}"))),
};
@@ -173,9 +177,8 @@ fn search_paths(glob: &str, project: Entity<Project>, cx: &mut App) -> Task<Resu
let mut results = Vec::new();
for snapshot in snapshots {
for entry in snapshot.entries(false, 0) {
let root_name = PathBuf::from(snapshot.root_name());
if path_matcher.is_match(root_name.join(&entry.path)) {
results.push(snapshot.abs_path().join(entry.path.as_ref()));
if path_matcher.is_match(snapshot.root_name().join(&entry.path).as_std_path()) {
results.push(snapshot.absolutize(&entry.path));
}
}
}

View File

@@ -110,12 +110,15 @@ impl AgentTool for GrepTool {
const CONTEXT_LINES: u32 = 2;
const MAX_ANCESTOR_LINES: u32 = 10;
let path_style = self.project.read(cx).path_style(cx);
let include_matcher = match PathMatcher::new(
input
.include_pattern
.as_ref()
.into_iter()
.collect::<Vec<_>>(),
path_style,
) {
Ok(matcher) => matcher,
Err(error) => {
@@ -132,7 +135,7 @@ impl AgentTool for GrepTool {
.iter()
.chain(global_settings.private_files.sources().iter());
match PathMatcher::new(exclude_patterns) {
match PathMatcher::new(exclude_patterns, path_style) {
Ok(matcher) => matcher,
Err(error) => {
return Task::ready(Err(anyhow!("invalid exclude pattern: {error}")));
@@ -834,11 +837,14 @@ mod tests {
"**/.secretdir".to_string(),
"**/.mymetadata".to_string(),
]);
settings.project.worktree.private_files = Some(vec![
"**/.mysecrets".to_string(),
"**/*.privatekey".to_string(),
"**/*.mysensitive".to_string(),
]);
settings.project.worktree.private_files = Some(
vec![
"**/.mysecrets".to_string(),
"**/*.privatekey".to_string(),
"**/*.mysensitive".to_string(),
]
.into(),
);
});
});
});
@@ -1064,7 +1070,8 @@ mod tests {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]);
settings.project.worktree.private_files =
Some(vec!["**/.env".to_string()].into());
});
});
});

View File

@@ -2,12 +2,12 @@ use crate::{AgentTool, ToolCallEventStream};
use agent_client_protocol::ToolKind;
use anyhow::{Result, anyhow};
use gpui::{App, Entity, SharedString, Task};
use project::{Project, WorktreeSettings};
use project::{Project, ProjectPath, WorktreeSettings};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::Settings;
use std::fmt::Write;
use std::{path::Path, sync::Arc};
use std::sync::Arc;
use util::markdown::MarkdownInlineCode;
/// Lists files and directories in a given path. Prefer the `grep` or `find_path` tools when searching the codebase.
@@ -86,13 +86,13 @@ impl AgentTool for ListDirectoryTool {
.read(cx)
.worktrees(cx)
.filter_map(|worktree| {
worktree.read(cx).root_entry().and_then(|entry| {
if entry.is_dir() {
entry.path.to_str()
} else {
None
}
})
let worktree = worktree.read(cx);
let root_entry = worktree.root_entry()?;
if root_entry.is_dir() {
Some(root_entry.path.display(worktree.path_style()))
} else {
None
}
})
.collect::<Vec<_>>()
.join("\n");
@@ -143,7 +143,7 @@ impl AgentTool for ListDirectoryTool {
}
let worktree_snapshot = worktree.read(cx).snapshot();
let worktree_root_name = worktree.read(cx).root_name().to_string();
let worktree_root_name = worktree.read(cx).root_name();
let Some(entry) = worktree_snapshot.entry_for_path(&project_path.path) else {
return Task::ready(Err(anyhow!("Path not found: {}", input.path)));
@@ -165,25 +165,17 @@ impl AgentTool for ListDirectoryTool {
continue;
}
if self
.project
.read(cx)
.find_project_path(&entry.path, cx)
.map(|project_path| {
let worktree_settings = WorktreeSettings::get(Some((&project_path).into()), cx);
worktree_settings.is_path_excluded(&project_path.path)
|| worktree_settings.is_path_private(&project_path.path)
})
.unwrap_or(false)
let project_path: ProjectPath = (worktree_snapshot.id(), entry.path.clone()).into();
if worktree_settings.is_path_excluded(&project_path.path)
|| worktree_settings.is_path_private(&project_path.path)
{
continue;
}
let full_path = Path::new(&worktree_root_name)
let full_path = worktree_root_name
.join(&entry.path)
.display()
.to_string();
.display(worktree_snapshot.path_style())
.into_owned();
if entry.is_dir() {
folders.push(full_path);
} else {
@@ -427,11 +419,14 @@ mod tests {
"**/.mymetadata".to_string(),
"**/.hidden_subdir".to_string(),
]);
settings.project.worktree.private_files = Some(vec![
"**/.mysecrets".to_string(),
"**/*.privatekey".to_string(),
"**/*.mysensitive".to_string(),
]);
settings.project.worktree.private_files = Some(
vec![
"**/.mysecrets".to_string(),
"**/*.privatekey".to_string(),
"**/*.mysensitive".to_string(),
]
.into(),
);
});
});
});
@@ -568,7 +563,8 @@ mod tests {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]);
settings.project.worktree.private_files =
Some(vec!["**/.env".to_string()].into());
});
});
});

View File

@@ -98,7 +98,7 @@ impl AgentTool for MovePathTool {
.and_then(|project_path| project.entry_for_path(&project_path, cx))
{
Some(entity) => match project.find_project_path(&input.destination_path, cx) {
Some(project_path) => project.rename_entry(entity.id, project_path.path, cx),
Some(project_path) => project.rename_entry(entity.id, project_path, cx),
None => Task::ready(Err(anyhow!(
"Destination path {} was outside the project.",
input.destination_path

View File

@@ -104,7 +104,7 @@ mod tests {
async fn test_to_absolute_path(cx: &mut TestAppContext) {
init_test(cx);
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let temp_path = temp_dir.path().to_string_lossy().to_string();
let temp_path = temp_dir.path().to_string_lossy().into_owned();
let fs = FakeFs::new(cx.executor());
fs.insert_tree(

View File

@@ -82,12 +82,12 @@ impl AgentTool for ReadFileTool {
{
match (input.start_line, input.end_line) {
(Some(start), Some(end)) => {
format!("Read file `{}` (lines {}-{})", path.display(), start, end,)
format!("Read file `{path}` (lines {}-{})", start, end,)
}
(Some(start), None) => {
format!("Read file `{}` (from line {})", path.display(), start)
format!("Read file `{path}` (from line {})", start)
}
_ => format!("Read file `{}`", path.display()),
_ => format!("Read file `{path}`"),
}
.into()
} else {
@@ -225,9 +225,12 @@ impl AgentTool for ReadFileTool {
Ok(result.into())
} else {
// No line ranges specified, so check file size to see if it's too big.
let buffer_content =
outline::get_buffer_content_or_outline(buffer.clone(), Some(&abs_path), cx)
.await?;
let buffer_content = outline::get_buffer_content_or_outline(
buffer.clone(),
Some(&abs_path.to_string_lossy()),
cx,
)
.await?;
action_log.update(cx, |log, cx| {
log.buffer_read(buffer.clone(), cx);
@@ -593,11 +596,14 @@ mod test {
"**/.secretdir".to_string(),
"**/.mymetadata".to_string(),
]);
settings.project.worktree.private_files = Some(vec![
"**/.mysecrets".to_string(),
"**/*.privatekey".to_string(),
"**/*.mysensitive".to_string(),
]);
settings.project.worktree.private_files = Some(
vec![
"**/.mysecrets".to_string(),
"**/*.privatekey".to_string(),
"**/*.mysensitive".to_string(),
]
.into(),
);
});
});
});
@@ -804,7 +810,8 @@ mod test {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]);
settings.project.worktree.private_files =
Some(vec!["**/.env".to_string()].into());
});
});
});

View File

@@ -82,7 +82,7 @@ impl AgentTool for TerminalTool {
.into(),
}
} else {
"Run terminal command".into()
"".into()
}
}

View File

@@ -47,6 +47,8 @@ task.workspace = true
tempfile.workspace = true
thiserror.workspace = true
ui.workspace = true
terminal.workspace = true
uuid.workspace = true
util.workspace = true
watch.workspace = true
workspace-hack.workspace = true

View File

@@ -9,6 +9,7 @@ use futures::io::BufReader;
use project::Project;
use project::agent_server_store::AgentServerCommand;
use serde::Deserialize;
use task::Shell;
use util::ResultExt as _;
use std::path::PathBuf;
@@ -19,7 +20,9 @@ use thiserror::Error;
use anyhow::{Context as _, Result};
use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString, Task, WeakEntity};
use acp_thread::{AcpThread, AuthRequired, LoadError};
use acp_thread::{AcpThread, AuthRequired, LoadError, TerminalProviderEvent};
use terminal::TerminalBuilder;
use terminal::terminal_settings::{AlternateScroll, CursorShape};
#[derive(Debug, Error)]
#[error("Unsupported version")]
@@ -44,6 +47,7 @@ pub struct AcpConnection {
pub struct AcpSession {
thread: WeakEntity<AcpThread>,
suppress_abort_err: bool,
models: Option<Rc<RefCell<acp::SessionModelState>>>,
session_modes: Option<Rc<RefCell<acp::SessionModeState>>>,
}
@@ -78,7 +82,7 @@ impl AcpConnection {
is_remote: bool,
cx: &mut AsyncApp,
) -> Result<Self> {
let mut child = util::command::new_smol_command(command.path);
let mut child = util::command::new_smol_command(&command.path);
child
.args(command.args.iter().map(|arg| arg.as_str()))
.envs(command.env.iter().flatten())
@@ -93,6 +97,11 @@ impl AcpConnection {
let stdout = child.stdout.take().context("Failed to take stdout")?;
let stdin = child.stdin.take().context("Failed to take stdin")?;
let stderr = child.stderr.take().context("Failed to take stderr")?;
log::info!(
"Spawning external agent server: {:?}, {:?}",
command.path,
command.args
);
log::trace!("Spawned (pid: {})", child.id());
let sessions = Rc::new(RefCell::new(HashMap::default()));
@@ -264,6 +273,7 @@ impl AgentConnection for AcpConnection {
})?;
let modes = response.modes.map(|modes| Rc::new(RefCell::new(modes)));
let models = response.models.map(|models| Rc::new(RefCell::new(models)));
if let Some(default_mode) = default_mode {
if let Some(modes) = modes.as_ref() {
@@ -326,10 +336,12 @@ impl AgentConnection for AcpConnection {
)
})?;
let session = AcpSession {
thread: thread.downgrade(),
suppress_abort_err: false,
session_modes: modes
session_modes: modes,
models,
};
sessions.borrow_mut().insert(session_id, session);
@@ -376,6 +388,10 @@ impl AgentConnection for AcpConnection {
match result {
Ok(response) => Ok(response),
Err(err) => {
if err.code == acp::ErrorCode::AUTH_REQUIRED.code {
return Err(anyhow!(acp::Error::auth_required()));
}
if err.code != ErrorCode::INTERNAL_ERROR.code {
anyhow::bail!(err)
}
@@ -450,6 +466,27 @@ impl AgentConnection for AcpConnection {
}
}
fn model_selector(
&self,
session_id: &acp::SessionId,
) -> Option<Rc<dyn acp_thread::AgentModelSelector>> {
let sessions = self.sessions.clone();
let sessions_ref = sessions.borrow();
let Some(session) = sessions_ref.get(session_id) else {
return None;
};
if let Some(models) = session.models.as_ref() {
Some(Rc::new(AcpModelSelector::new(
session_id.clone(),
self.connection.clone(),
models.clone(),
)) as _)
} else {
None
}
}
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
@@ -500,6 +537,82 @@ impl acp_thread::AgentSessionModes for AcpSessionModes {
}
}
struct AcpModelSelector {
session_id: acp::SessionId,
connection: Rc<acp::ClientSideConnection>,
state: Rc<RefCell<acp::SessionModelState>>,
}
impl AcpModelSelector {
fn new(
session_id: acp::SessionId,
connection: Rc<acp::ClientSideConnection>,
state: Rc<RefCell<acp::SessionModelState>>,
) -> Self {
Self {
session_id,
connection,
state,
}
}
}
impl acp_thread::AgentModelSelector for AcpModelSelector {
fn list_models(&self, _cx: &mut App) -> Task<Result<acp_thread::AgentModelList>> {
Task::ready(Ok(acp_thread::AgentModelList::Flat(
self.state
.borrow()
.available_models
.clone()
.into_iter()
.map(acp_thread::AgentModelInfo::from)
.collect(),
)))
}
fn select_model(&self, model_id: acp::ModelId, cx: &mut App) -> Task<Result<()>> {
let connection = self.connection.clone();
let session_id = self.session_id.clone();
let old_model_id;
{
let mut state = self.state.borrow_mut();
old_model_id = state.current_model_id.clone();
state.current_model_id = model_id.clone();
};
let state = self.state.clone();
cx.foreground_executor().spawn(async move {
let result = connection
.set_session_model(acp::SetSessionModelRequest {
session_id,
model_id,
meta: None,
})
.await;
if result.is_err() {
state.borrow_mut().current_model_id = old_model_id;
}
result?;
Ok(())
})
}
fn selected_model(&self, _cx: &mut App) -> Task<Result<acp_thread::AgentModelInfo>> {
let state = self.state.borrow();
Task::ready(
state
.available_models
.iter()
.find(|m| m.model_id == state.current_model_id)
.cloned()
.map(acp_thread::AgentModelInfo::from)
.ok_or_else(|| anyhow::anyhow!("Model not found")),
)
}
}
struct ClientDelegate {
sessions: Rc<RefCell<HashMap<acp::SessionId, AcpSession>>>,
cx: AsyncApp,
@@ -595,10 +708,100 @@ impl acp::Client for ClientDelegate {
}
}
// Clone so we can inspect meta both before and after handing off to the thread
let update_clone = notification.update.clone();
// Pre-handle: if a ToolCall carries terminal_info, create/register a display-only terminal.
if let acp::SessionUpdate::ToolCall(tc) = &update_clone {
if let Some(meta) = &tc.meta {
if let Some(terminal_info) = meta.get("terminal_info") {
if let Some(id_str) = terminal_info.get("terminal_id").and_then(|v| v.as_str())
{
let terminal_id = acp::TerminalId(id_str.into());
let cwd = terminal_info
.get("cwd")
.and_then(|v| v.as_str().map(PathBuf::from));
// Create a minimal display-only lower-level terminal and register it.
let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
let builder = TerminalBuilder::new_display_only(
CursorShape::default(),
AlternateScroll::On,
None,
0,
)?;
let lower = cx.new(|cx| builder.subscribe(cx));
thread.on_terminal_provider_event(
TerminalProviderEvent::Created {
terminal_id: terminal_id.clone(),
label: tc.title.clone(),
cwd,
output_byte_limit: None,
terminal: lower,
},
cx,
);
anyhow::Ok(())
});
}
}
}
}
// Forward the update to the acp_thread as usual.
session.thread.update(&mut self.cx.clone(), |thread, cx| {
thread.handle_session_update(notification.update, cx)
thread.handle_session_update(notification.update.clone(), cx)
})??;
// Post-handle: stream terminal output/exit if present on ToolCallUpdate meta.
if let acp::SessionUpdate::ToolCallUpdate(tcu) = &update_clone {
if let Some(meta) = &tcu.meta {
if let Some(term_out) = meta.get("terminal_output") {
if let Some(id_str) = term_out.get("terminal_id").and_then(|v| v.as_str()) {
let terminal_id = acp::TerminalId(id_str.into());
if let Some(s) = term_out.get("data").and_then(|v| v.as_str()) {
let data = s.as_bytes().to_vec();
let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Output {
terminal_id: terminal_id.clone(),
data,
},
cx,
);
});
}
}
}
// terminal_exit
if let Some(term_exit) = meta.get("terminal_exit") {
if let Some(id_str) = term_exit.get("terminal_id").and_then(|v| v.as_str()) {
let terminal_id = acp::TerminalId(id_str.into());
let status = acp::TerminalExitStatus {
exit_code: term_exit
.get("exit_code")
.and_then(|v| v.as_u64())
.map(|i| i as u32),
signal: term_exit
.get("signal")
.and_then(|v| v.as_str().map(|s| s.to_string())),
meta: None,
};
let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Exit {
terminal_id: terminal_id.clone(),
status,
},
cx,
);
});
}
}
}
}
Ok(())
}
@@ -606,25 +809,68 @@ impl acp::Client for ClientDelegate {
&self,
args: acp::CreateTerminalRequest,
) -> Result<acp::CreateTerminalResponse, acp::Error> {
let terminal = self
.session_thread(&args.session_id)?
.update(&mut self.cx.clone(), |thread, cx| {
thread.create_terminal(
args.command,
args.args,
args.env,
args.cwd,
args.output_byte_limit,
let thread = self.session_thread(&args.session_id)?;
let project = thread.read_with(&self.cx, |thread, _cx| thread.project().clone())?;
let mut env = if let Some(dir) = &args.cwd {
project
.update(&mut self.cx.clone(), |project, cx| {
project.directory_environment(&task::Shell::System, dir.clone().into(), cx)
})?
.await
.unwrap_or_default()
} else {
Default::default()
};
for var in args.env {
env.insert(var.name, var.value);
}
// Use remote shell or default system shell, as appropriate
let shell = project
.update(&mut self.cx.clone(), |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
.map(Shell::Program)
})?
.unwrap_or(task::Shell::System);
let (task_command, task_args) = task::ShellBuilder::new(&shell)
.redirect_stdin_to_dev_null()
.build(Some(args.command.clone()), &args.args);
let terminal_entity = project
.update(&mut self.cx.clone(), |project, cx| {
project.create_terminal_task(
task::SpawnInTerminal {
command: Some(task_command),
args: task_args,
cwd: args.cwd.clone(),
env,
..Default::default()
},
cx,
)
})?
.await?;
Ok(
terminal.read_with(&self.cx, |terminal, _| acp::CreateTerminalResponse {
terminal_id: terminal.id().clone(),
meta: None,
})?,
)
// Register with renderer
let terminal_entity = thread.update(&mut self.cx.clone(), |thread, cx| {
thread.register_terminal_created(
acp::TerminalId(uuid::Uuid::new_v4().to_string().into()),
format!("{} {}", args.command, args.args.join(" ")),
args.cwd.clone(),
args.output_byte_limit,
terminal_entity,
cx,
)
})?;
let terminal_id =
terminal_entity.read_with(&self.cx, |terminal, _| terminal.id().clone())?;
Ok(acp::CreateTerminalResponse {
terminal_id,
meta: None,
})
}
async fn kill_terminal_command(

View File

@@ -1,5 +1,6 @@
mod acp;
mod claude;
mod codex;
mod custom;
mod gemini;
@@ -8,6 +9,7 @@ pub mod e2e_tests;
pub use claude::*;
use client::ProxySettings;
pub use codex::*;
use collections::HashMap;
pub use custom::*;
use fs::Fs;
@@ -99,6 +101,9 @@ pub fn load_proxy_env(cx: &mut App) -> HashMap<String, String> {
if let Some(no_proxy) = read_no_proxy_from_env() {
env.insert("NO_PROXY".to_owned(), no_proxy);
} else if proxy_url.is_some() {
// We sometimes need local MCP servers that we don't want to proxy
env.insert("NO_PROXY".to_owned(), "localhost,127.0.0.1".to_owned());
}
env

View File

@@ -62,7 +62,7 @@ impl AgentServer for ClaudeCode {
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
let name = self.name();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().to_string());
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
let store = delegate.store.downgrade();
let extra_env = load_proxy_env(cx);

View File

@@ -0,0 +1,80 @@
use std::rc::Rc;
use std::{any::Any, path::Path};
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
use acp_thread::AgentConnection;
use anyhow::{Context as _, Result};
use gpui::{App, SharedString, Task};
use project::agent_server_store::CODEX_NAME;
#[derive(Clone)]
pub struct Codex;
#[cfg(test)]
pub(crate) mod tests {
use super::*;
crate::common_e2e_tests!(async |_, _, _| Codex, allow_option_id = "proceed_once");
}
impl AgentServer for Codex {
fn telemetry_id(&self) -> &'static str {
"codex"
}
fn name(&self) -> SharedString {
"Codex".into()
}
fn logo(&self) -> ui::IconName {
ui::IconName::AiOpenAi
}
fn connect(
&self,
root_dir: Option<&Path>,
delegate: AgentServerDelegate,
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
let name = self.name();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
let store = delegate.store.downgrade();
let extra_env = load_proxy_env(cx);
let default_mode = self.default_mode(cx);
cx.spawn(async move |cx| {
let (command, root_dir, login) = store
.update(cx, |store, cx| {
let agent = store
.get_external_agent(&CODEX_NAME.into())
.context("Codex is not registered")?;
anyhow::Ok(agent.get_command(
root_dir.as_deref(),
extra_env,
delegate.status_tx,
// For now, report that there are no updates.
// (A future PR will use the GitHub Releases API to fetch them.)
delegate.new_version_available,
&mut cx.to_async(),
))
})??
.await?;
let connection = crate::acp::connect(
name,
command,
root_dir.as_ref(),
default_mode,
is_remote,
cx,
)
.await?;
Ok((connection, login))
})
}
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
}

View File

@@ -67,7 +67,7 @@ impl crate::AgentServer for CustomAgentServer {
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
let name = self.name();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().to_string());
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
let default_mode = self.default_mode(cx);
let store = delegate.store.downgrade();

View File

@@ -483,6 +483,13 @@ pub async fn init_test(cx: &mut TestAppContext) -> Arc<FakeFs> {
default_mode: None,
}),
gemini: Some(crate::gemini::tests::local_command().into()),
codex: Some(BuiltinAgentServerSettings {
path: Some("codex-acp".into()),
args: None,
env: None,
ignore_system_version: None,
default_mode: None,
}),
custom: collections::HashMap::default(),
},
cx,

View File

@@ -31,7 +31,7 @@ impl AgentServer for Gemini {
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
let name = self.name();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().to_string());
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
let store = delegate.store.downgrade();
let mut extra_env = load_proxy_env(cx);

View File

@@ -19,6 +19,7 @@ convert_case.workspace = true
fs.workspace = true
gpui.workspace = true
language_model.workspace = true
project.workspace = true
schemars.workspace = true
serde.workspace = true
settings.workspace = true

View File

@@ -5,13 +5,13 @@ use std::sync::Arc;
use collections::IndexMap;
use gpui::{App, Pixels, px};
use language_model::LanguageModel;
use project::DisableAiSettings;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{
DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection,
NotifyWhenAgentWaiting, Settings, SettingsContent,
};
use util::MergeFrom;
pub use crate::agent_profile::*;
@@ -54,6 +54,10 @@ pub struct AgentSettings {
}
impl AgentSettings {
pub fn enabled(&self, cx: &App) -> bool {
self.enabled && !DisableAiSettings::get_global(cx).disable_ai
}
pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
let settings = Self::get_global(cx);
for setting in settings.model_parameters.iter().rev() {
@@ -147,7 +151,7 @@ impl Default for AgentProfileId {
}
impl Settings for AgentSettings {
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
fn from_settings(content: &settings::SettingsContent) -> Self {
let agent = content.agent.clone().unwrap();
Self {
enabled: agent.enabled.unwrap(),
@@ -183,66 +187,6 @@ impl Settings for AgentSettings {
}
}
fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) {
let Some(value) = &content.agent else { return };
self.enabled.merge_from(&value.enabled);
self.button.merge_from(&value.button);
self.dock.merge_from(&value.dock);
self.default_width
.merge_from(&value.default_width.map(Into::into));
self.default_height
.merge_from(&value.default_height.map(Into::into));
self.default_model = value.default_model.clone().or(self.default_model.take());
self.inline_assistant_model = value
.inline_assistant_model
.clone()
.or(self.inline_assistant_model.take());
self.commit_message_model = value
.clone()
.commit_message_model
.or(self.commit_message_model.take());
self.thread_summary_model = value
.clone()
.thread_summary_model
.or(self.thread_summary_model.take());
self.inline_alternatives
.merge_from(&value.inline_alternatives.clone());
self.default_profile
.merge_from(&value.default_profile.clone().map(AgentProfileId));
self.default_view.merge_from(&value.default_view);
self.always_allow_tool_actions
.merge_from(&value.always_allow_tool_actions);
self.notify_when_agent_waiting
.merge_from(&value.notify_when_agent_waiting);
self.play_sound_when_agent_done
.merge_from(&value.play_sound_when_agent_done);
self.stream_edits.merge_from(&value.stream_edits);
self.single_file_review
.merge_from(&value.single_file_review);
self.preferred_completion_mode
.merge_from(&value.preferred_completion_mode.map(Into::into));
self.enable_feedback.merge_from(&value.enable_feedback);
self.expand_edit_card.merge_from(&value.expand_edit_card);
self.expand_terminal_card
.merge_from(&value.expand_terminal_card);
self.use_modifier_to_send
.merge_from(&value.use_modifier_to_send);
self.model_parameters
.extend_from_slice(&value.model_parameters);
self.message_editor_min_lines
.merge_from(&value.message_editor_min_lines);
if let Some(profiles) = value.profiles.as_ref() {
self.profiles.extend(
profiles
.into_iter()
.map(|(id, profile)| (AgentProfileId(id.clone()), profile.clone().into())),
);
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
if let Some(b) = vscode
.read_value("chat.agent.enabled")

View File

@@ -1,5 +1,6 @@
use std::cell::RefCell;
use std::ops::Range;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
@@ -13,7 +14,7 @@ use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{App, Entity, Task, WeakEntity};
use language::{Buffer, CodeLabel, HighlightId};
use lsp::CompletionContext;
use project::lsp_store::CompletionDocumentation;
use project::lsp_store::{CompletionDocumentation, SymbolLocation};
use project::{
Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, Project,
ProjectPath, Symbol, WorktreeId,
@@ -22,6 +23,7 @@ use prompt_store::PromptStore;
use rope::Point;
use text::{Anchor, ToPoint as _};
use ui::prelude::*;
use util::rel_path::RelPath;
use workspace::Workspace;
use crate::AgentPanel;
@@ -187,7 +189,7 @@ impl ContextPickerCompletionProvider {
pub(crate) fn completion_for_path(
project_path: ProjectPath,
path_prefix: &str,
path_prefix: &RelPath,
is_recent: bool,
is_directory: bool,
source_range: Range<Anchor>,
@@ -195,10 +197,12 @@ impl ContextPickerCompletionProvider {
project: Entity<Project>,
cx: &mut App,
) -> Option<Completion> {
let path_style = project.read(cx).path_style(cx);
let (file_name, directory) =
crate::context_picker::file_context_picker::extract_file_name_and_directory(
&project_path.path,
path_prefix,
path_style,
);
let label =
@@ -250,7 +254,15 @@ impl ContextPickerCompletionProvider {
let label = CodeLabel::plain(symbol.name.clone(), None);
let abs_path = project.read(cx).absolute_path(&symbol.path, cx)?;
let abs_path = match &symbol.path {
SymbolLocation::InProject(project_path) => {
project.read(cx).absolute_path(&project_path, cx)?
}
SymbolLocation::OutsideProject {
abs_path,
signature: _,
} => PathBuf::from(abs_path.as_ref()),
};
let uri = MentionUri::Symbol {
abs_path,
name: symbol.name.clone(),

View File

@@ -203,7 +203,7 @@ impl EntryViewState {
self.entries.drain(range);
}
pub fn agent_font_size_changed(&mut self, cx: &mut App) {
pub fn agent_ui_font_size_changed(&mut self, cx: &mut App) {
for entry in self.entries.iter() {
match entry {
Entry::UserMessage { .. } | Entry::AssistantMessage { .. } => {}
@@ -387,7 +387,7 @@ fn diff_editor_text_style_refinement(cx: &mut App) -> TextStyleRefinement {
font_size: Some(
TextSize::Small
.rems(cx)
.to_pixels(ThemeSettings::get_global(cx).agent_font_size(cx))
.to_pixels(ThemeSettings::get_global(cx).agent_ui_font_size(cx))
.into(),
),
..Default::default()
@@ -414,7 +414,6 @@ mod tests {
use project::Project;
use serde_json::json;
use settings::{Settings as _, SettingsStore};
use theme::ThemeSettings;
use util::path;
use workspace::Workspace;
@@ -544,7 +543,7 @@ mod tests {
Project::init_settings(cx);
AgentSettings::register(cx);
workspace::init_settings(cx);
ThemeSettings::register(cx);
theme::init(theme::LoadThemes::JustBase, cx);
release_channel::init(SemanticVersion::default(), cx);
EditorSettings::register(cx);
});

View File

@@ -47,13 +47,8 @@ use std::{
};
use text::OffsetRangeExt;
use theme::ThemeSettings;
use ui::{
ActiveTheme, AnyElement, App, ButtonCommon, ButtonLike, ButtonStyle, Color, Element as _,
FluentBuilder as _, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label,
LabelCommon, LabelSize, ParentElement, Render, SelectableButton, Styled, TextSize, TintColor,
Toggleable, Window, div, h_flex,
};
use util::{ResultExt, debug_panic};
use ui::{ButtonLike, TintColor, Toggleable, prelude::*};
use util::{ResultExt, debug_panic, rel_path::RelPath};
use workspace::{Workspace, notifications::NotifyResultExt as _};
use zed_actions::agent::Chat;
@@ -81,7 +76,7 @@ pub enum MessageEditorEvent {
impl EventEmitter<MessageEditorEvent> for MessageEditor {}
const COMMAND_HINT_INLAY_ID: usize = 0;
const COMMAND_HINT_INLAY_ID: u32 = 0;
impl MessageEditor {
pub fn new(
@@ -295,18 +290,18 @@ impl MessageEditor {
let snapshot = self
.editor
.update(cx, |editor, cx| editor.snapshot(window, cx));
let Some((excerpt_id, _, _)) = snapshot.buffer_snapshot.as_singleton() else {
let Some((excerpt_id, _, _)) = snapshot.buffer_snapshot().as_singleton() else {
return Task::ready(());
};
let Some(start_anchor) = snapshot
.buffer_snapshot
.buffer_snapshot()
.anchor_in_excerpt(*excerpt_id, start)
else {
return Task::ready(());
};
let end_anchor = snapshot
.buffer_snapshot
.anchor_before(start_anchor.to_offset(&snapshot.buffer_snapshot) + content_len + 1);
.buffer_snapshot()
.anchor_before(start_anchor.to_offset(&snapshot.buffer_snapshot()) + content_len + 1);
let crease = if let MentionUri::File { abs_path } = &mention_uri
&& let Some(extension) = abs_path.extension()
@@ -364,7 +359,7 @@ impl MessageEditor {
let task = match mention_uri.clone() {
MentionUri::Fetch { url } => self.confirm_mention_for_fetch(url, cx),
MentionUri::Directory { abs_path } => self.confirm_mention_for_directory(abs_path, cx),
MentionUri::Directory { .. } => Task::ready(Ok(Mention::UriOnly)),
MentionUri::Thread { id, .. } => self.confirm_mention_for_thread(id, cx),
MentionUri::TextThread { path, .. } => self.confirm_mention_for_text_thread(path, cx),
MentionUri::File { abs_path } => self.confirm_mention_for_file(abs_path, cx),
@@ -457,9 +452,12 @@ impl MessageEditor {
.update(cx, |project, cx| project.open_buffer(project_path, cx));
cx.spawn(async move |_, cx| {
let buffer = buffer.await?;
let buffer_content =
outline::get_buffer_content_or_outline(buffer.clone(), Some(&abs_path), &cx)
.await?;
let buffer_content = outline::get_buffer_content_or_outline(
buffer.clone(),
Some(&abs_path.to_string_lossy()),
&cx,
)
.await?;
Ok(Mention::Text {
content: buffer_content.text,
@@ -468,97 +466,6 @@ impl MessageEditor {
})
}
fn confirm_mention_for_directory(
&mut self,
abs_path: PathBuf,
cx: &mut Context<Self>,
) -> Task<Result<Mention>> {
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc<Path>, PathBuf)> {
let mut files = Vec::new();
for entry in worktree.child_entries(path) {
if entry.is_dir() {
files.extend(collect_files_in_path(worktree, &entry.path));
} else if entry.is_file() {
files.push((entry.path.clone(), worktree.full_path(&entry.path)));
}
}
files
}
let Some(project_path) = self
.project
.read(cx)
.project_path_for_absolute_path(&abs_path, cx)
else {
return Task::ready(Err(anyhow!("project path not found")));
};
let Some(entry) = self.project.read(cx).entry_for_path(&project_path, cx) else {
return Task::ready(Err(anyhow!("project entry not found")));
};
let directory_path = entry.path.clone();
let worktree_id = project_path.worktree_id;
let Some(worktree) = self.project.read(cx).worktree_for_id(worktree_id, cx) else {
return Task::ready(Err(anyhow!("worktree not found")));
};
let project = self.project.clone();
cx.spawn(async move |_, cx| {
let file_paths = worktree.read_with(cx, |worktree, _cx| {
collect_files_in_path(worktree, &directory_path)
})?;
let descendants_future = cx.update(|cx| {
join_all(file_paths.into_iter().map(|(worktree_path, full_path)| {
let rel_path = worktree_path
.strip_prefix(&directory_path)
.log_err()
.map_or_else(|| worktree_path.clone(), |rel_path| rel_path.into());
let open_task = project.update(cx, |project, cx| {
project.buffer_store().update(cx, |buffer_store, cx| {
let project_path = ProjectPath {
worktree_id,
path: worktree_path,
};
buffer_store.open_buffer(project_path, cx)
})
});
cx.spawn(async move |cx| {
let buffer = open_task.await.log_err()?;
let buffer_content = outline::get_buffer_content_or_outline(
buffer.clone(),
Some(&full_path),
&cx,
)
.await
.ok()?;
Some((rel_path, full_path, buffer_content.text, buffer))
})
}))
})?;
let contents = cx
.background_spawn(async move {
let (contents, tracked_buffers) = descendants_future
.await
.into_iter()
.flatten()
.map(|(rel_path, full_path, rope, buffer)| {
((rel_path, full_path, rope), buffer)
})
.unzip();
Mention::Text {
content: render_directory_contents(contents),
tracked_buffers,
}
})
.await;
anyhow::Ok(contents)
})
}
fn confirm_mention_for_fetch(
&mut self,
url: url::Url,
@@ -776,6 +683,7 @@ impl MessageEditor {
pub fn contents(
&self,
full_mention_content: bool,
cx: &mut Context<Self>,
) -> Task<Result<(Vec<acp::ContentBlock>, Vec<Entity<Buffer>>)>> {
// Check for unsupported slash commands before spawning async task
@@ -787,9 +695,12 @@ impl MessageEditor {
return Task::ready(Err(err));
}
let contents = self
.mention_set
.contents(&self.prompt_capabilities.borrow(), cx);
let contents = self.mention_set.contents(
&self.prompt_capabilities.borrow(),
full_mention_content,
self.project.clone(),
cx,
);
let editor = self.editor.clone();
cx.spawn(async move |_, cx| {
@@ -807,7 +718,7 @@ impl MessageEditor {
continue;
};
let crease_range = crease.range().to_offset(&snapshot.buffer_snapshot);
let crease_range = crease.range().to_offset(&snapshot.buffer_snapshot());
if crease_range.start > ix {
//todo(): Custom slash command ContentBlock?
// let chunk = if prevent_slash_commands
@@ -954,11 +865,11 @@ impl MessageEditor {
self.editor.update(cx, |message_editor, cx| {
let snapshot = message_editor.snapshot(window, cx);
let (excerpt_id, _, buffer_snapshot) =
snapshot.buffer_snapshot.as_singleton().unwrap();
snapshot.buffer_snapshot().as_singleton().unwrap();
let text_anchor = buffer_snapshot.anchor_before(buffer_snapshot.len());
let multibuffer_anchor = snapshot
.buffer_snapshot
.buffer_snapshot()
.anchor_in_excerpt(*excerpt_id, text_anchor);
message_editor.edit(
[(
@@ -1039,6 +950,7 @@ impl MessageEditor {
window: &mut Window,
cx: &mut Context<Self>,
) {
let path_style = self.project.read(cx).path_style(cx);
let buffer = self.editor.read(cx).buffer().clone();
let Some(buffer) = buffer.read(cx).as_singleton() else {
return;
@@ -1048,18 +960,15 @@ impl MessageEditor {
let Some(entry) = self.project.read(cx).entry_for_path(&path, cx) else {
continue;
};
let Some(abs_path) = self.project.read(cx).absolute_path(&path, cx) else {
let Some(worktree) = self.project.read(cx).worktree_for_id(path.worktree_id, cx) else {
continue;
};
let path_prefix = abs_path
.file_name()
.unwrap_or(path.path.as_os_str())
.display()
.to_string();
let abs_path = worktree.read(cx).absolutize(&path.path);
let (file_name, _) =
crate::context_picker::file_context_picker::extract_file_name_and_directory(
&path.path,
&path_prefix,
worktree.read(cx).root_name(),
path_style,
);
let uri = if entry.is_dir() {
@@ -1263,7 +1172,103 @@ impl MessageEditor {
}
}
fn render_directory_contents(entries: Vec<(Arc<Path>, PathBuf, String)>) -> String {
fn full_mention_for_directory(
project: &Entity<Project>,
abs_path: &Path,
cx: &mut App,
) -> Task<Result<Mention>> {
fn collect_files_in_path(worktree: &Worktree, path: &RelPath) -> Vec<(Arc<RelPath>, String)> {
let mut files = Vec::new();
for entry in worktree.child_entries(path) {
if entry.is_dir() {
files.extend(collect_files_in_path(worktree, &entry.path));
} else if entry.is_file() {
files.push((
entry.path.clone(),
worktree
.full_path(&entry.path)
.to_string_lossy()
.to_string(),
));
}
}
files
}
let Some(project_path) = project
.read(cx)
.project_path_for_absolute_path(&abs_path, cx)
else {
return Task::ready(Err(anyhow!("project path not found")));
};
let Some(entry) = project.read(cx).entry_for_path(&project_path, cx) else {
return Task::ready(Err(anyhow!("project entry not found")));
};
let directory_path = entry.path.clone();
let worktree_id = project_path.worktree_id;
let Some(worktree) = project.read(cx).worktree_for_id(worktree_id, cx) else {
return Task::ready(Err(anyhow!("worktree not found")));
};
let project = project.clone();
cx.spawn(async move |cx| {
let file_paths = worktree.read_with(cx, |worktree, _cx| {
collect_files_in_path(worktree, &directory_path)
})?;
let descendants_future = cx.update(|cx| {
join_all(file_paths.into_iter().map(|(worktree_path, full_path)| {
let rel_path = worktree_path
.strip_prefix(&directory_path)
.log_err()
.map_or_else(|| worktree_path.clone(), |rel_path| rel_path.into());
let open_task = project.update(cx, |project, cx| {
project.buffer_store().update(cx, |buffer_store, cx| {
let project_path = ProjectPath {
worktree_id,
path: worktree_path,
};
buffer_store.open_buffer(project_path, cx)
})
});
cx.spawn(async move |cx| {
let buffer = open_task.await.log_err()?;
let buffer_content = outline::get_buffer_content_or_outline(
buffer.clone(),
Some(&full_path),
&cx,
)
.await
.ok()?;
Some((rel_path, full_path, buffer_content.text, buffer))
})
}))
})?;
let contents = cx
.background_spawn(async move {
let (contents, tracked_buffers) = descendants_future
.await
.into_iter()
.flatten()
.map(|(rel_path, full_path, rope, buffer)| {
((rel_path, full_path, rope), buffer)
})
.unzip();
Mention::Text {
content: render_directory_contents(contents),
tracked_buffers,
}
})
.await;
anyhow::Ok(contents)
})
}
fn render_directory_contents(entries: Vec<(Arc<RelPath>, String, String)>) -> String {
let mut output = String::new();
for (_relative_path, full_path, content) in entries {
let fence = codeblock_fence_for_path(Some(&full_path), None);
@@ -1288,18 +1293,14 @@ impl Render for MessageEditor {
.flex_1()
.child({
let settings = ThemeSettings::get_global(cx);
let font_size = TextSize::Small
.rems(cx)
.to_pixels(settings.agent_font_size(cx));
let line_height = settings.buffer_line_height.value() * font_size;
let text_style = TextStyle {
color: cx.theme().colors().text,
font_family: settings.buffer_font.family.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_features: settings.buffer_font.features.clone(),
font_size: font_size.into(),
line_height: line_height.into(),
font_size: settings.agent_buffer_font_size(cx).into(),
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
};
@@ -1514,6 +1515,8 @@ impl MentionSet {
fn contents(
&self,
prompt_capabilities: &acp::PromptCapabilities,
full_mention_content: bool,
project: Entity<Project>,
cx: &mut App,
) -> Task<Result<HashMap<CreaseId, (MentionUri, Mention)>>> {
if !prompt_capabilities.embedded_context {
@@ -1527,13 +1530,19 @@ impl MentionSet {
}
let mentions = self.mentions.clone();
cx.spawn(async move |_cx| {
cx.spawn(async move |cx| {
let mut contents = HashMap::default();
for (crease_id, (mention_uri, task)) in mentions {
contents.insert(
crease_id,
(mention_uri, task.await.map_err(|e| anyhow!("{e}"))?),
);
let content = if full_mention_content
&& let MentionUri::Directory { abs_path } = &mention_uri
{
cx.update(|cx| full_mention_for_directory(&project, abs_path, cx))?
.await?
} else {
task.await.map_err(|e| anyhow!("{e}"))?
};
contents.insert(crease_id, (mention_uri, content));
}
Ok(contents)
})
@@ -1541,7 +1550,7 @@ impl MentionSet {
fn remove_invalid(&mut self, snapshot: EditorSnapshot) {
for (crease_id, crease) in snapshot.crease_snapshot.creases() {
if !crease.range().start.is_valid(&snapshot.buffer_snapshot) {
if !crease.range().start.is_valid(&snapshot.buffer_snapshot()) {
self.mentions.remove(&crease_id);
}
}
@@ -1593,7 +1602,7 @@ mod tests {
use serde_json::json;
use text::Point;
use ui::{App, Context, IntoElement, Render, SharedString, Window};
use util::{path, uri};
use util::{path, paths::PathStyle, rel_path::rel_path};
use workspace::{AppState, Item, Workspace};
use crate::acp::{
@@ -1694,7 +1703,7 @@ mod tests {
});
let (content, _) = message_editor
.update(cx, |message_editor, cx| message_editor.contents(cx))
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
.await
.unwrap();
@@ -1757,7 +1766,7 @@ mod tests {
});
let contents_result = message_editor
.update(cx, |message_editor, cx| message_editor.contents(cx))
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
.await;
// Should fail because available_commands is empty (no commands supported)
@@ -1780,7 +1789,7 @@ mod tests {
});
let contents_result = message_editor
.update(cx, |message_editor, cx| message_editor.contents(cx))
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
.await;
assert!(contents_result.is_err());
@@ -1795,7 +1804,7 @@ mod tests {
});
let contents_result = message_editor
.update(cx, |message_editor, cx| message_editor.contents(cx))
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
.await;
// Should succeed because /help is in available_commands
@@ -1807,7 +1816,7 @@ mod tests {
});
let (content, _) = message_editor
.update(cx, |message_editor, cx| message_editor.contents(cx))
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
.await
.unwrap();
@@ -1825,7 +1834,7 @@ mod tests {
// The @ mention functionality should not be affected
let (content, _) = message_editor
.update(cx, |message_editor, cx| message_editor.contents(cx))
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
.await
.unwrap();
@@ -2103,16 +2112,18 @@ mod tests {
let mut cx = VisualTestContext::from_window(*window, cx);
let paths = vec![
path!("a/one.txt"),
path!("a/two.txt"),
path!("a/three.txt"),
path!("a/four.txt"),
path!("b/five.txt"),
path!("b/six.txt"),
path!("b/seven.txt"),
path!("b/eight.txt"),
rel_path("a/one.txt"),
rel_path("a/two.txt"),
rel_path("a/three.txt"),
rel_path("a/four.txt"),
rel_path("b/five.txt"),
rel_path("b/six.txt"),
rel_path("b/seven.txt"),
rel_path("b/eight.txt"),
];
let slash = PathStyle::local().separator();
let mut opened_editors = Vec::new();
for path in paths {
let buffer = workspace
@@ -2120,7 +2131,7 @@ mod tests {
workspace.open_path(
ProjectPath {
worktree_id,
path: Path::new(path).into(),
path: path.into(),
},
None,
false,
@@ -2181,10 +2192,10 @@ mod tests {
assert_eq!(
current_completion_labels(editor),
&[
"eight.txt dir/b/",
"seven.txt dir/b/",
"six.txt dir/b/",
"five.txt dir/b/",
format!("eight.txt dir{slash}b{slash}"),
format!("seven.txt dir{slash}b{slash}"),
format!("six.txt dir{slash}b{slash}"),
format!("five.txt dir{slash}b{slash}"),
]
);
editor.set_text("", window, cx);
@@ -2212,14 +2223,14 @@ mod tests {
assert_eq!(
current_completion_labels(editor),
&[
"eight.txt dir/b/",
"seven.txt dir/b/",
"six.txt dir/b/",
"five.txt dir/b/",
"Files & Directories",
"Symbols",
"Threads",
"Fetch"
format!("eight.txt dir{slash}b{slash}"),
format!("seven.txt dir{slash}b{slash}"),
format!("six.txt dir{slash}b{slash}"),
format!("five.txt dir{slash}b{slash}"),
"Files & Directories".into(),
"Symbols".into(),
"Threads".into(),
"Fetch".into()
]
);
});
@@ -2246,7 +2257,10 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(editor.text(cx), "Lorem @file one");
assert!(editor.has_visible_completions_menu());
assert_eq!(current_completion_labels(editor), vec!["one.txt dir/a/"]);
assert_eq!(
current_completion_labels(editor),
vec![format!("one.txt dir{slash}a{slash}")]
);
});
editor.update_in(&mut cx, |editor, window, cx| {
@@ -2254,7 +2268,11 @@ mod tests {
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
});
let url_one = uri!("file:///dir/a/one.txt");
let url_one = MentionUri::File {
abs_path: path!("/dir/a/one.txt").into(),
}
.to_uri()
.to_string();
editor.update(&mut cx, |editor, cx| {
let text = editor.text(cx);
assert_eq!(text, format!("Lorem [@one.txt]({url_one}) "));
@@ -2271,9 +2289,12 @@ mod tests {
let contents = message_editor
.update(&mut cx, |message_editor, cx| {
message_editor
.mention_set()
.contents(&all_prompt_capabilities, cx)
message_editor.mention_set().contents(
&all_prompt_capabilities,
false,
project.clone(),
cx,
)
})
.await
.unwrap()
@@ -2290,9 +2311,12 @@ mod tests {
let contents = message_editor
.update(&mut cx, |message_editor, cx| {
message_editor
.mention_set()
.contents(&acp::PromptCapabilities::default(), cx)
message_editor.mention_set().contents(
&acp::PromptCapabilities::default(),
false,
project.clone(),
cx,
)
})
.await
.unwrap()
@@ -2341,16 +2365,23 @@ mod tests {
let contents = message_editor
.update(&mut cx, |message_editor, cx| {
message_editor
.mention_set()
.contents(&all_prompt_capabilities, cx)
message_editor.mention_set().contents(
&all_prompt_capabilities,
false,
project.clone(),
cx,
)
})
.await
.unwrap()
.into_values()
.collect::<Vec<_>>();
let url_eight = uri!("file:///dir/b/eight.txt");
let url_eight = MentionUri::File {
abs_path: path!("/dir/b/eight.txt").into(),
}
.to_uri()
.to_string();
{
let [_, (uri, Mention::Text { content, .. })] = contents.as_slice() else {
@@ -2449,11 +2480,20 @@ mod tests {
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
});
let symbol = MentionUri::Symbol {
abs_path: path!("/dir/a/one.txt").into(),
name: "MySymbol".into(),
line_range: 0..=0,
};
let contents = message_editor
.update(&mut cx, |message_editor, cx| {
message_editor
.mention_set()
.contents(&all_prompt_capabilities, cx)
message_editor.mention_set().contents(
&all_prompt_capabilities,
false,
project.clone(),
cx,
)
})
.await
.unwrap()
@@ -2465,12 +2505,7 @@ mod tests {
panic!("Unexpected mentions");
};
pretty_assertions::assert_eq!(content, "1");
pretty_assertions::assert_eq!(
uri,
&format!("{url_one}?symbol=MySymbol#L1:1")
.parse::<MentionUri>()
.unwrap()
);
pretty_assertions::assert_eq!(uri, &symbol);
}
cx.run_until_parked();
@@ -2478,7 +2513,10 @@ mod tests {
editor.read_with(&cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) ")
format!(
"Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) ",
symbol.to_uri(),
)
);
});
@@ -2488,10 +2526,10 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) @file x.png")
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) @file x.png", symbol.to_uri())
);
assert!(editor.has_visible_completions_menu());
assert_eq!(current_completion_labels(editor), &["x.png dir/"]);
assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]);
});
editor.update_in(&mut cx, |editor, window, cx| {
@@ -2501,9 +2539,12 @@ mod tests {
// Getting the message contents fails
message_editor
.update(&mut cx, |message_editor, cx| {
message_editor
.mention_set()
.contents(&all_prompt_capabilities, cx)
message_editor.mention_set().contents(
&all_prompt_capabilities,
false,
project.clone(),
cx,
)
})
.await
.expect_err("Should fail to load x.png");
@@ -2514,7 +2555,10 @@ mod tests {
editor.read_with(&cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) ")
format!(
"Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) ",
symbol.to_uri()
)
);
});
@@ -2524,10 +2568,10 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) @file x.png")
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) @file x.png", symbol.to_uri())
);
assert!(editor.has_visible_completions_menu());
assert_eq!(current_completion_labels(editor), &["x.png dir/"]);
assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]);
});
editor.update_in(&mut cx, |editor, window, cx| {
@@ -2539,18 +2583,24 @@ mod tests {
// Mention was removed
editor.read_with(&cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({url_one}?symbol=MySymbol#L1:1) ")
);
});
assert_eq!(
editor.text(cx),
format!(
"Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) ",
symbol.to_uri()
)
);
});
// Now getting the contents succeeds, because the invalid mention was removed
let contents = message_editor
.update(&mut cx, |message_editor, cx| {
message_editor
.mention_set()
.contents(&all_prompt_capabilities, cx)
message_editor.mention_set().contents(
&all_prompt_capabilities,
false,
project.clone(),
cx,
)
})
.await
.unwrap();

View File

@@ -1,7 +1,6 @@
use std::{cmp::Reverse, rc::Rc, sync::Arc};
use acp_thread::{AgentModelInfo, AgentModelList, AgentModelSelector};
use agent_client_protocol as acp;
use anyhow::Result;
use collections::IndexMap;
use futures::FutureExt;
@@ -10,20 +9,19 @@ use gpui::{Action, AsyncWindowContext, BackgroundExecutor, DismissEvent, Task, W
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate};
use ui::{
AnyElement, App, Context, IntoElement, ListItem, ListItemSpacing, SharedString, Window,
prelude::*, rems,
AnyElement, App, Context, DocumentationAside, DocumentationEdge, DocumentationSide,
IntoElement, ListItem, ListItemSpacing, SharedString, Window, prelude::*, rems,
};
use util::ResultExt;
pub type AcpModelSelector = Picker<AcpModelPickerDelegate>;
pub fn acp_model_selector(
session_id: acp::SessionId,
selector: Rc<dyn AgentModelSelector>,
window: &mut Window,
cx: &mut Context<AcpModelSelector>,
) -> AcpModelSelector {
let delegate = AcpModelPickerDelegate::new(session_id, selector, window, cx);
let delegate = AcpModelPickerDelegate::new(selector, window, cx);
Picker::list(delegate, window, cx)
.show_scrollbar(true)
.width(rems(20.))
@@ -36,61 +34,63 @@ enum AcpModelPickerEntry {
}
pub struct AcpModelPickerDelegate {
session_id: acp::SessionId,
selector: Rc<dyn AgentModelSelector>,
filtered_entries: Vec<AcpModelPickerEntry>,
models: Option<AgentModelList>,
selected_index: usize,
selected_description: Option<(usize, SharedString)>,
selected_model: Option<AgentModelInfo>,
_refresh_models_task: Task<()>,
}
impl AcpModelPickerDelegate {
fn new(
session_id: acp::SessionId,
selector: Rc<dyn AgentModelSelector>,
window: &mut Window,
cx: &mut Context<AcpModelSelector>,
) -> Self {
let mut rx = selector.watch(cx);
let refresh_models_task = cx.spawn_in(window, {
let session_id = session_id.clone();
async move |this, cx| {
async fn refresh(
this: &WeakEntity<Picker<AcpModelPickerDelegate>>,
session_id: &acp::SessionId,
cx: &mut AsyncWindowContext,
) -> Result<()> {
let (models_task, selected_model_task) = this.update(cx, |this, cx| {
(
this.delegate.selector.list_models(cx),
this.delegate.selector.selected_model(session_id, cx),
)
})?;
let rx = selector.watch(cx);
let refresh_models_task = {
cx.spawn_in(window, {
async move |this, cx| {
async fn refresh(
this: &WeakEntity<Picker<AcpModelPickerDelegate>>,
cx: &mut AsyncWindowContext,
) -> Result<()> {
let (models_task, selected_model_task) = this.update(cx, |this, cx| {
(
this.delegate.selector.list_models(cx),
this.delegate.selector.selected_model(cx),
)
})?;
let (models, selected_model) = futures::join!(models_task, selected_model_task);
let (models, selected_model) =
futures::join!(models_task, selected_model_task);
this.update_in(cx, |this, window, cx| {
this.delegate.models = models.ok();
this.delegate.selected_model = selected_model.ok();
this.refresh(window, cx)
})
this.update_in(cx, |this, window, cx| {
this.delegate.models = models.ok();
this.delegate.selected_model = selected_model.ok();
this.refresh(window, cx)
})
}
refresh(&this, cx).await.log_err();
if let Some(mut rx) = rx {
while let Ok(()) = rx.recv().await {
refresh(&this, cx).await.log_err();
}
}
}
refresh(&this, &session_id, cx).await.log_err();
while let Ok(()) = rx.recv().await {
refresh(&this, &session_id, cx).await.log_err();
}
}
});
})
};
Self {
session_id,
selector,
filtered_entries: Vec::new(),
models: None,
selected_model: None,
selected_index: 0,
selected_description: None,
_refresh_models_task: refresh_models_task,
}
}
@@ -182,7 +182,7 @@ impl PickerDelegate for AcpModelPickerDelegate {
self.filtered_entries.get(self.selected_index)
{
self.selector
.select_model(self.session_id.clone(), model_info.id.clone(), cx)
.select_model(model_info.id.clone(), cx)
.detach_and_log_err(cx);
self.selected_model = Some(model_info.clone());
let current_index = self.selected_index;
@@ -233,31 +233,46 @@ impl PickerDelegate for AcpModelPickerDelegate {
};
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.start_slot::<Icon>(model_info.icon.map(|icon| {
Icon::new(icon)
.color(model_icon_color)
.size(IconSize::Small)
}))
div()
.id(("model-picker-menu-child", ix))
.when_some(model_info.description.clone(), |this, description| {
this
.on_hover(cx.listener(move |menu, hovered, _, cx| {
if *hovered {
menu.delegate.selected_description = Some((ix, description.clone()));
} else if matches!(menu.delegate.selected_description, Some((id, _)) if id == ix) {
menu.delegate.selected_description = None;
}
cx.notify();
}))
})
.child(
h_flex()
.w_full()
.pl_0p5()
.gap_1p5()
.w(px(240.))
.child(Label::new(model_info.name.clone()).truncate()),
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.start_slot::<Icon>(model_info.icon.map(|icon| {
Icon::new(icon)
.color(model_icon_color)
.size(IconSize::Small)
}))
.child(
h_flex()
.w_full()
.pl_0p5()
.gap_1p5()
.w(px(240.))
.child(Label::new(model_info.name.clone()).truncate()),
)
.end_slot(div().pr_3().when(is_selected, |this| {
this.child(
Icon::new(IconName::Check)
.color(Color::Accent)
.size(IconSize::Small),
)
})),
)
.end_slot(div().pr_3().when(is_selected, |this| {
this.child(
Icon::new(IconName::Check)
.color(Color::Accent)
.size(IconSize::Small),
)
}))
.into_any_element(),
.into_any_element()
)
}
}
@@ -292,6 +307,21 @@ impl PickerDelegate for AcpModelPickerDelegate {
.into_any(),
)
}
fn documentation_aside(
&self,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) -> Option<ui::DocumentationAside> {
self.selected_description.as_ref().map(|(_, description)| {
let description = description.clone();
DocumentationAside::new(
DocumentationSide::Left,
DocumentationEdge::Bottom,
Rc::new(move |_| Label::new(description.clone()).into_any_element()),
)
})
}
}
fn info_list_to_picker_entries(
@@ -371,6 +401,7 @@ async fn fuzzy_search(
#[cfg(test)]
mod tests {
use agent_client_protocol as acp;
use gpui::TestAppContext;
use super::*;
@@ -383,8 +414,9 @@ mod tests {
models
.into_iter()
.map(|model| acp_thread::AgentModelInfo {
id: acp_thread::AgentModelId(model.to_string().into()),
id: acp::ModelId(model.to_string().into()),
name: model.to_string().into(),
description: None,
icon: None,
})
.collect::<Vec<_>>(),

View File

@@ -1,7 +1,6 @@
use std::rc::Rc;
use acp_thread::AgentModelSelector;
use agent_client_protocol as acp;
use gpui::{Entity, FocusHandle};
use picker::popover_menu::PickerPopoverMenu;
use ui::{
@@ -20,7 +19,6 @@ pub struct AcpModelSelectorPopover {
impl AcpModelSelectorPopover {
pub(crate) fn new(
session_id: acp::SessionId,
selector: Rc<dyn AgentModelSelector>,
menu_handle: PopoverMenuHandle<AcpModelSelector>,
focus_handle: FocusHandle,
@@ -28,7 +26,7 @@ impl AcpModelSelectorPopover {
cx: &mut Context<Self>,
) -> Self {
Self {
selector: cx.new(move |cx| acp_model_selector(session_id, selector, window, cx)),
selector: cx.new(move |cx| acp_model_selector(selector, window, cx)),
menu_handle,
focus_handle,
}

View File

@@ -26,7 +26,7 @@ use gpui::{
CursorStyle, EdgesRefinement, ElementId, Empty, Entity, FocusHandle, Focusable, Hsla, Length,
ListOffset, ListState, PlatformDisplay, SharedString, StyleRefinement, Subscription, Task,
TextStyle, TextStyleRefinement, UnderlineStyle, WeakEntity, Window, WindowHandle, div,
ease_in_out, linear_color_stop, linear_gradient, list, point, prelude::*, pulsating_between,
ease_in_out, linear_color_stop, linear_gradient, list, point, pulsating_between,
};
use language::Buffer;
@@ -289,8 +289,9 @@ pub struct AcpThreadView {
available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
is_loading_contents: bool,
new_server_version_available: Option<SharedString>,
resume_thread_metadata: Option<DbThreadMetadata>,
_cancel_task: Option<Task<()>>,
_subscriptions: [Subscription; 4],
_subscriptions: [Subscription; 5],
}
enum ThreadState {
@@ -380,11 +381,17 @@ impl AcpThreadView {
)
});
let agent_server_store = project.read(cx).agent_server_store().clone();
let subscriptions = [
cx.observe_global_in::<SettingsStore>(window, Self::agent_font_size_changed),
cx.observe_global_in::<AgentFontSize>(window, Self::agent_font_size_changed),
cx.observe_global_in::<SettingsStore>(window, Self::agent_ui_font_size_changed),
cx.observe_global_in::<AgentFontSize>(window, Self::agent_ui_font_size_changed),
cx.subscribe_in(&message_editor, window, Self::handle_message_editor_event),
cx.subscribe_in(&entry_view_state, window, Self::handle_entry_view_event),
cx.subscribe_in(
&agent_server_store,
window,
Self::handle_agent_servers_updated,
),
];
Self {
@@ -392,7 +399,14 @@ impl AcpThreadView {
workspace: workspace.clone(),
project: project.clone(),
entry_view_state,
thread_state: Self::initial_state(agent, resume_thread, workspace, project, window, cx),
thread_state: Self::initial_state(
agent.clone(),
resume_thread.clone(),
workspace.clone(),
project.clone(),
window,
cx,
),
login: None,
message_editor,
model_selector: None,
@@ -421,13 +435,14 @@ impl AcpThreadView {
_cancel_task: None,
focus_handle: cx.focus_handle(),
new_server_version_available: None,
resume_thread_metadata: resume_thread,
}
}
fn reset(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.thread_state = Self::initial_state(
self.agent.clone(),
None,
self.resume_thread_metadata.clone(),
self.workspace.clone(),
self.project.clone(),
window,
@@ -577,23 +592,21 @@ impl AcpThreadView {
AgentDiff::set_active_thread(&workspace, thread.clone(), window, cx);
this.model_selector =
thread
.read(cx)
.connection()
.model_selector()
.map(|selector| {
cx.new(|cx| {
AcpModelSelectorPopover::new(
thread.read(cx).session_id().clone(),
selector,
PopoverMenuHandle::default(),
this.focus_handle(cx),
window,
cx,
)
})
});
this.model_selector = thread
.read(cx)
.connection()
.model_selector(thread.read(cx).session_id())
.map(|selector| {
cx.new(|cx| {
AcpModelSelectorPopover::new(
selector,
PopoverMenuHandle::default(),
this.focus_handle(cx),
window,
cx,
)
})
});
let mode_selector = thread
.read(cx)
@@ -777,6 +790,25 @@ impl AcpThreadView {
cx.notify();
}
fn handle_agent_servers_updated(
&mut self,
_agent_server_store: &Entity<project::AgentServerStore>,
_event: &project::AgentServersUpdated,
window: &mut Window,
cx: &mut Context<Self>,
) {
// If we're in a LoadError state OR have a thread_error set (which can happen
// when agent.connect() fails during loading), retry loading the thread.
// This handles the case where a thread is restored before authentication completes.
let should_retry =
matches!(&self.thread_state, ThreadState::LoadError(_)) || self.thread_error.is_some();
if should_retry {
self.thread_error = None;
self.reset(window, cx);
}
}
pub fn workspace(&self) -> &WeakEntity<Workspace> {
&self.workspace
}
@@ -1014,36 +1046,36 @@ impl AcpThreadView {
};
let connection = thread.read(cx).connection().clone();
if !connection
.auth_methods()
.iter()
.any(|method| method.id.0.as_ref() == "claude-login")
{
let can_login = !connection.auth_methods().is_empty() || self.login.is_some();
// Does the agent have a specific logout command? Prefer that in case they need to reset internal state.
let logout_supported = text == "/logout"
&& self
.available_commands
.borrow()
.iter()
.any(|command| command.name == "logout");
if can_login && !logout_supported {
let this = cx.weak_entity();
let agent = self.agent.clone();
window.defer(cx, |window, cx| {
Self::handle_auth_required(
this,
AuthRequired {
description: None,
provider_id: None,
},
agent,
connection,
window,
cx,
);
});
cx.notify();
return;
};
let this = cx.weak_entity();
let agent = self.agent.clone();
window.defer(cx, |window, cx| {
Self::handle_auth_required(
this,
AuthRequired {
description: None,
provider_id: None,
},
agent,
connection,
window,
cx,
);
});
cx.notify();
return;
}
}
let contents = self
.message_editor
.update(cx, |message_editor, cx| message_editor.contents(cx));
self.send_impl(contents, window, cx)
self.send_impl(self.message_editor.clone(), window, cx)
}
fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@@ -1053,15 +1085,11 @@ impl AcpThreadView {
let cancelled = thread.update(cx, |thread, cx| thread.cancel(cx));
let contents = self
.message_editor
.update(cx, |message_editor, cx| message_editor.contents(cx));
cx.spawn_in(window, async move |this, cx| {
cancelled.await;
this.update_in(cx, |this, window, cx| {
this.send_impl(contents, window, cx);
this.send_impl(this.message_editor.clone(), window, cx);
})
.ok();
})
@@ -1070,10 +1098,23 @@ impl AcpThreadView {
fn send_impl(
&mut self,
contents: Task<Result<(Vec<acp::ContentBlock>, Vec<Entity<Buffer>>)>>,
message_editor: Entity<MessageEditor>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let full_mention_content = self.as_native_thread(cx).is_some_and(|thread| {
// Include full contents when using minimal profile
let thread = thread.read(cx);
AgentSettings::get_global(cx)
.profiles
.get(thread.profile())
.is_some_and(|profile| profile.tools.is_empty())
});
let contents = message_editor.update(cx, |message_editor, cx| {
message_editor.contents(full_mention_content, cx)
});
let agent_telemetry_id = self.agent.telemetry_id();
self.thread_error.take();
@@ -1202,10 +1243,8 @@ impl AcpThreadView {
thread
.update(cx, |thread, cx| thread.rewind(user_message_id, cx))?
.await?;
let contents =
message_editor.update(cx, |message_editor, cx| message_editor.contents(cx))?;
this.update_in(cx, |this, window, cx| {
this.send_impl(contents, window, cx);
this.send_impl(message_editor, window, cx);
})?;
anyhow::Ok(())
})
@@ -1577,21 +1616,23 @@ impl AcpThreadView {
return Task::ready(Ok(()));
};
let project = workspace.read(cx).project().clone();
let cwd = project.read(cx).first_project_directory(cx);
let shell = project.read(cx).terminal_settings(&cwd, cx).shell.clone();
window.spawn(cx, async move |cx| {
let mut task = login.clone();
task.shell = task::Shell::WithArguments {
program: task.command.take().expect("login command should be set"),
args: std::mem::take(&mut task.args),
title_override: None
};
task.full_label = task.label.clone();
task.id = task::TaskId(format!("external-agent-{}-login", task.label));
task.command_label = task.label.clone();
task.use_new_terminal = true;
task.allow_concurrent_runs = true;
task.hide = task::HideStrategy::Always;
task.shell = shell;
let terminal = terminal_panel.update_in(cx, |terminal_panel, window, cx| {
terminal_panel.spawn_task(&login, window, cx)
terminal_panel.spawn_task(&task, window, cx)
})?;
let terminal = terminal.await?;
@@ -2064,27 +2105,6 @@ impl AcpThreadView {
let has_location = tool_call.locations.len() == 1;
let card_header_id = SharedString::from("inner-tool-call-header");
let tool_icon = if tool_call.kind == acp::ToolKind::Edit && has_location {
FileIcons::get_icon(&tool_call.locations[0].path, cx)
.map(Icon::from_path)
.unwrap_or(Icon::new(IconName::ToolPencil))
} else {
Icon::new(match tool_call.kind {
acp::ToolKind::Read => IconName::ToolSearch,
acp::ToolKind::Edit => IconName::ToolPencil,
acp::ToolKind::Delete => IconName::ToolDeleteFile,
acp::ToolKind::Move => IconName::ArrowRightLeft,
acp::ToolKind::Search => IconName::ToolSearch,
acp::ToolKind::Execute => IconName::ToolTerminal,
acp::ToolKind::Think => IconName::ToolThink,
acp::ToolKind::Fetch => IconName::ToolWeb,
acp::ToolKind::SwitchMode => IconName::ArrowRightLeft,
acp::ToolKind::Other => IconName::ToolHammer,
})
}
.size(IconSize::Small)
.color(Color::Muted);
let failed_or_canceled = match &tool_call.status {
ToolCallStatus::Rejected | ToolCallStatus::Canceled | ToolCallStatus::Failed => true,
_ => false,
@@ -2094,41 +2114,16 @@ impl AcpThreadView {
tool_call.status,
ToolCallStatus::WaitingForConfirmation { .. }
);
let is_terminal_tool = matches!(tool_call.kind, acp::ToolKind::Execute);
let is_edit =
matches!(tool_call.kind, acp::ToolKind::Edit) || tool_call.diffs().next().is_some();
let use_card_layout = needs_confirmation || is_edit;
let use_card_layout = needs_confirmation || is_edit || is_terminal_tool;
let is_collapsible = !tool_call.content.is_empty() && !needs_confirmation;
let is_open = needs_confirmation || self.expanded_tool_calls.contains(&tool_call.id);
let gradient_overlay = {
div()
.absolute()
.top_0()
.right_0()
.w_12()
.h_full()
.map(|this| {
if use_card_layout {
this.bg(linear_gradient(
90.,
linear_color_stop(self.tool_card_header_bg(cx), 1.),
linear_color_stop(self.tool_card_header_bg(cx).opacity(0.2), 0.),
))
} else {
this.bg(linear_gradient(
90.,
linear_color_stop(cx.theme().colors().panel_background, 1.),
linear_color_stop(
cx.theme().colors().panel_background.opacity(0.2),
0.,
),
))
}
})
};
let tool_output_display =
if is_open {
match &tool_call.status {
@@ -2213,104 +2208,202 @@ impl AcpThreadView {
}
})
.mr_5()
.child(
h_flex()
.group(&card_header_id)
.relative()
.w_full()
.gap_1()
.justify_between()
.when(use_card_layout, |this| {
this.p_0p5()
.rounded_t(rems_from_px(5.))
.map(|this| {
if is_terminal_tool {
this.child(
v_flex()
.p_1p5()
.gap_0p5()
.text_ui_sm(cx)
.bg(self.tool_card_header_bg(cx))
})
.child(
.child(
Label::new("Run Command")
.buffer_font(cx)
.size(LabelSize::XSmall)
.color(Color::Muted),
)
.child(
MarkdownElement::new(
tool_call.label.clone(),
terminal_command_markdown_style(window, cx),
)
.code_block_renderer(
markdown::CodeBlockRenderer::Default {
copy_button: false,
copy_button_on_hover: false,
border: false,
},
)
),
)
} else {
this.child(
h_flex()
.group(&card_header_id)
.relative()
.w_full()
.h(window.line_height() - px(2.))
.text_size(self.tool_name_font_size())
.gap_1p5()
.when(has_location || use_card_layout, |this| this.px_1())
.when(has_location, |this| {
this.cursor(CursorStyle::PointingHand)
.rounded(rems_from_px(3.)) // Concentric border radius
.hover(|s| s.bg(cx.theme().colors().element_hover.opacity(0.5)))
.gap_1()
.justify_between()
.when(use_card_layout, |this| {
this.p_0p5()
.rounded_t(rems_from_px(5.))
.bg(self.tool_card_header_bg(cx))
})
.overflow_hidden()
.child(tool_icon)
.child(if has_location {
h_flex()
.id(("open-tool-call-location", entry_ix))
.w_full()
.map(|this| {
if use_card_layout {
this.text_color(cx.theme().colors().text)
} else {
this.text_color(cx.theme().colors().text_muted)
}
})
.child(self.render_markdown(
tool_call.label.clone(),
MarkdownStyle {
prevent_mouse_interaction: true,
..default_markdown_style(false, true, window, cx)
},
))
.tooltip(Tooltip::text("Jump to File"))
.on_click(cx.listener(move |this, _, window, cx| {
this.open_tool_call_location(entry_ix, 0, window, cx);
}))
.into_any_element()
} else {
h_flex()
.w_full()
.child(self.render_markdown(
tool_call.label.clone(),
default_markdown_style(false, true, window, cx),
))
.into_any()
})
.when(!has_location, |this| this.child(gradient_overlay)),
)
.when(is_collapsible || failed_or_canceled, |this| {
this.child(
h_flex()
.px_1()
.gap_px()
.when(is_collapsible, |this| {
this.child(
Disclosure::new(("expand", entry_ix), is_open)
.opened_icon(IconName::ChevronUp)
.closed_icon(IconName::ChevronDown)
.visible_on_hover(&card_header_id)
.on_click(cx.listener({
let id = tool_call.id.clone();
move |this: &mut Self, _, _, cx: &mut Context<Self>| {
if is_open {
this.expanded_tool_calls.remove(&id);
} else {
this.expanded_tool_calls.insert(id.clone());
}
cx.notify();
}
})),
.child(self.render_tool_call_label(
entry_ix,
tool_call,
is_edit,
use_card_layout,
window,
cx,
))
.when(is_collapsible || failed_or_canceled, |this| {
this.child(
h_flex()
.px_1()
.gap_px()
.when(is_collapsible, |this| {
this.child(
Disclosure::new(("expand", entry_ix), is_open)
.opened_icon(IconName::ChevronUp)
.closed_icon(IconName::ChevronDown)
.visible_on_hover(&card_header_id)
.on_click(cx.listener({
let id = tool_call.id.clone();
move |this: &mut Self, _, _, cx: &mut Context<Self>| {
if is_open {
this.expanded_tool_calls.remove(&id);
} else {
this.expanded_tool_calls.insert(id.clone());
}
cx.notify();
}
})),
)
})
.when(failed_or_canceled, |this| {
this.child(
Icon::new(IconName::Close)
.color(Color::Error)
.size(IconSize::Small),
)
}),
)
})
.when(failed_or_canceled, |this| {
this.child(
Icon::new(IconName::Close)
.color(Color::Error)
.size(IconSize::Small),
)
}),
)
}),
)
}),
)
}
})
.children(tool_output_display)
}
fn render_tool_call_label(
&self,
entry_ix: usize,
tool_call: &ToolCall,
is_edit: bool,
use_card_layout: bool,
window: &Window,
cx: &Context<Self>,
) -> Div {
let has_location = tool_call.locations.len() == 1;
let tool_icon = if tool_call.kind == acp::ToolKind::Edit && has_location {
FileIcons::get_icon(&tool_call.locations[0].path, cx)
.map(Icon::from_path)
.unwrap_or(Icon::new(IconName::ToolPencil))
} else {
Icon::new(match tool_call.kind {
acp::ToolKind::Read => IconName::ToolSearch,
acp::ToolKind::Edit => IconName::ToolPencil,
acp::ToolKind::Delete => IconName::ToolDeleteFile,
acp::ToolKind::Move => IconName::ArrowRightLeft,
acp::ToolKind::Search => IconName::ToolSearch,
acp::ToolKind::Execute => IconName::ToolTerminal,
acp::ToolKind::Think => IconName::ToolThink,
acp::ToolKind::Fetch => IconName::ToolWeb,
acp::ToolKind::SwitchMode => IconName::ArrowRightLeft,
acp::ToolKind::Other => IconName::ToolHammer,
})
}
.size(IconSize::Small)
.color(Color::Muted);
let gradient_overlay = {
div()
.absolute()
.top_0()
.right_0()
.w_12()
.h_full()
.map(|this| {
if use_card_layout {
this.bg(linear_gradient(
90.,
linear_color_stop(self.tool_card_header_bg(cx), 1.),
linear_color_stop(self.tool_card_header_bg(cx).opacity(0.2), 0.),
))
} else {
this.bg(linear_gradient(
90.,
linear_color_stop(cx.theme().colors().panel_background, 1.),
linear_color_stop(
cx.theme().colors().panel_background.opacity(0.2),
0.,
),
))
}
})
};
h_flex()
.relative()
.w_full()
.h(window.line_height() - px(2.))
.text_size(self.tool_name_font_size())
.gap_1p5()
.when(has_location || use_card_layout, |this| this.px_1())
.when(has_location, |this| {
this.cursor(CursorStyle::PointingHand)
.rounded(rems_from_px(3.)) // Concentric border radius
.hover(|s| s.bg(cx.theme().colors().element_hover.opacity(0.5)))
})
.overflow_hidden()
.child(tool_icon)
.child(if has_location {
h_flex()
.id(("open-tool-call-location", entry_ix))
.w_full()
.map(|this| {
if use_card_layout {
this.text_color(cx.theme().colors().text)
} else {
this.text_color(cx.theme().colors().text_muted)
}
})
.child(self.render_markdown(
tool_call.label.clone(),
MarkdownStyle {
prevent_mouse_interaction: true,
..default_markdown_style(false, true, window, cx)
},
))
.tooltip(Tooltip::text("Jump to File"))
.on_click(cx.listener(move |this, _, window, cx| {
this.open_tool_call_location(entry_ix, 0, window, cx);
}))
.into_any_element()
} else {
h_flex()
.w_full()
.child(self.render_markdown(
tool_call.label.clone(),
default_markdown_style(false, true, window, cx),
))
.into_any()
})
.when(!is_edit, |this| this.child(gradient_overlay))
}
fn render_tool_call_content(
&self,
entry_ix: usize,
@@ -2635,7 +2728,7 @@ impl AcpThreadView {
let output_line_count = output.map(|output| output.content_line_count).unwrap_or(0);
let command_failed = command_finished
&& output.is_some_and(|o| o.exit_status.is_none_or(|status| !status.success()));
&& output.is_some_and(|o| o.exit_status.is_some_and(|status| !status.success()));
let time_elapsed = if let Some(output) = output {
output.ended_at.duration_since(started_at)
@@ -2658,7 +2751,7 @@ impl AcpThreadView {
let working_dir = working_dir
.as_ref()
.map(|path| format!("{}", path.display()))
.map(|path| path.display().to_string())
.unwrap_or_else(|| "current directory".to_string());
let is_expanded = self.expanded_tool_calls.contains(&tool_call.id);
@@ -3296,6 +3389,12 @@ impl AcpThreadView {
.into_any_element()
}
fn activity_bar_bg(&self, cx: &Context<Self>) -> Hsla {
let editor_bg_color = cx.theme().colors().editor_background;
let active_color = cx.theme().colors().element_selected;
editor_bg_color.blend(active_color.opacity(0.3))
}
fn render_activity_bar(
&self,
thread_entity: &Entity<AcpThread>,
@@ -3311,10 +3410,6 @@ impl AcpThreadView {
return None;
}
let editor_bg_color = cx.theme().colors().editor_background;
let active_color = cx.theme().colors().element_selected;
let bg_edit_files_disclosure = editor_bg_color.blend(active_color.opacity(0.3));
// Temporarily always enable ACP edit controls. This is temporary, to lessen the
// impact of a nasty bug that causes them to sometimes be disabled when they shouldn't
// be, which blocks you from being able to accept or reject edits. This switches the
@@ -3325,7 +3420,7 @@ impl AcpThreadView {
v_flex()
.mt_1()
.mx_2()
.bg(bg_edit_files_disclosure)
.bg(self.activity_bar_bg(cx))
.border_1()
.border_b_0()
.border_color(cx.theme().colors().border)
@@ -3366,27 +3461,33 @@ impl AcpThreadView {
.into()
}
fn render_plan_summary(&self, plan: &Plan, window: &mut Window, cx: &Context<Self>) -> Div {
fn render_plan_summary(
&self,
plan: &Plan,
window: &mut Window,
cx: &Context<Self>,
) -> impl IntoElement {
let stats = plan.stats();
let title = if let Some(entry) = stats.in_progress_entry
&& !self.plan_expanded
{
h_flex()
.w_full()
.cursor_default()
.relative()
.w_full()
.gap_1()
.text_xs()
.text_color(cx.theme().colors().text_muted)
.justify_between()
.truncate()
.child(
h_flex()
.gap_1()
.child(
Label::new("Current:")
.size(LabelSize::Small)
.color(Color::Muted),
)
Label::new("Current:")
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(
div()
.text_xs()
.text_color(cx.theme().colors().text_muted)
.line_clamp(1)
.child(MarkdownElement::new(
entry.content.clone(),
plan_label_markdown_style(&entry.status, window, cx),
@@ -3394,10 +3495,23 @@ impl AcpThreadView {
)
.when(stats.pending > 0, |this| {
this.child(
Label::new(format!("{} left", stats.pending))
.size(LabelSize::Small)
.color(Color::Muted)
.mr_1(),
h_flex()
.absolute()
.top_0()
.right_0()
.h_full()
.child(div().min_w_8().h_full().bg(linear_gradient(
90.,
linear_color_stop(self.activity_bar_bg(cx), 1.),
linear_color_stop(self.activity_bar_bg(cx).opacity(0.2), 0.),
)))
.child(
div().pr_0p5().bg(self.activity_bar_bg(cx)).child(
Label::new(format!("{} left", stats.pending))
.size(LabelSize::Small)
.color(Color::Muted),
),
),
)
})
} else {
@@ -3427,23 +3541,19 @@ impl AcpThreadView {
};
h_flex()
.id("plan_summary")
.p_1()
.justify_between()
.w_full()
.gap_1()
.when(self.plan_expanded, |this| {
this.border_b_1().border_color(cx.theme().colors().border)
})
.child(
h_flex()
.id("plan_summary")
.w_full()
.gap_1()
.child(Disclosure::new("plan_disclosure", self.plan_expanded))
.child(title)
.on_click(cx.listener(|this, _, _, cx| {
this.plan_expanded = !this.plan_expanded;
cx.notify();
})),
)
.child(Disclosure::new("plan_disclosure", self.plan_expanded))
.child(title)
.on_click(cx.listener(|this, _, _, cx| {
this.plan_expanded = !this.plan_expanded;
cx.notify();
}))
}
fn render_plan_entries(&self, plan: &Plan, window: &mut Window, cx: &Context<Self>) -> Div {
@@ -3637,15 +3747,15 @@ impl AcpThreadView {
|(index, (buffer, _diff))| {
let file = buffer.read(cx).file()?;
let path = file.path();
let path_style = file.path_style(cx);
let separator = file.path_style(cx).separator();
let file_path = path.parent().and_then(|parent| {
let parent_str = parent.to_string_lossy();
if parent_str.is_empty() {
if parent.is_empty() {
None
} else {
Some(
Label::new(format!("/{}{}", parent_str, std::path::MAIN_SEPARATOR_STR))
Label::new(format!("{}{separator}", parent.display(path_style)))
.color(Color::Muted)
.size(LabelSize::XSmall)
.buffer_font(cx),
@@ -3654,12 +3764,12 @@ impl AcpThreadView {
});
let file_name = path.file_name().map(|name| {
Label::new(name.to_string_lossy().to_string())
Label::new(name.to_string())
.size(LabelSize::XSmall)
.buffer_font(cx)
});
let file_icon = FileIcons::get_icon(path, cx)
let file_icon = FileIcons::get_icon(path.as_std_path(), cx)
.map(Icon::from_path)
.map(|icon| icon.color(Color::Muted).size(IconSize::Small))
.unwrap_or_else(|| {
@@ -3692,7 +3802,7 @@ impl AcpThreadView {
.id(("file-name", index))
.pr_8()
.gap_1p5()
.max_w_full()
.w_full()
.overflow_x_scroll()
.child(file_icon)
.child(h_flex().gap_0p5().children(file_name).children(file_path))
@@ -4502,7 +4612,7 @@ impl AcpThreadView {
.read(cx)
.visible_worktrees(cx)
.next()
.map(|worktree| worktree.read(cx).root_name().to_string())
.map(|worktree| worktree.read(cx).root_name_str().to_string())
});
if let Some(screen_window) = cx
@@ -4844,9 +4954,9 @@ impl AcpThreadView {
)
}
fn agent_font_size_changed(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
fn agent_ui_font_size_changed(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.entry_view_state.update(cx, |entry_view_state, cx| {
entry_view_state.agent_font_size_changed(cx);
entry_view_state.agent_ui_font_size_changed(cx);
});
}
@@ -5476,23 +5586,23 @@ fn default_markdown_style(
}),
code_block: StyleRefinement {
padding: EdgesRefinement {
top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),
left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),
right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),
bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),
},
margin: EdgesRefinement {
top: Some(Length::Definite(Pixels(8.).into())),
left: Some(Length::Definite(Pixels(0.).into())),
right: Some(Length::Definite(Pixels(0.).into())),
bottom: Some(Length::Definite(Pixels(12.).into())),
top: Some(Length::Definite(px(8.).into())),
left: Some(Length::Definite(px(0.).into())),
right: Some(Length::Definite(px(0.).into())),
bottom: Some(Length::Definite(px(12.).into())),
},
border_style: Some(BorderStyle::Solid),
border_widths: EdgesRefinement {
top: Some(AbsoluteLength::Pixels(Pixels(1.))),
left: Some(AbsoluteLength::Pixels(Pixels(1.))),
right: Some(AbsoluteLength::Pixels(Pixels(1.))),
bottom: Some(AbsoluteLength::Pixels(Pixels(1.))),
top: Some(AbsoluteLength::Pixels(px(1.))),
left: Some(AbsoluteLength::Pixels(px(1.))),
right: Some(AbsoluteLength::Pixels(px(1.))),
bottom: Some(AbsoluteLength::Pixels(px(1.))),
},
border_color: Some(colors.border_variant),
background: Some(colors.editor_background.into()),
@@ -5669,23 +5779,6 @@ pub(crate) mod tests {
});
}
#[gpui::test]
async fn test_spawn_external_agent_login_handles_spaces(cx: &mut TestAppContext) {
init_test(cx);
// Verify paths with spaces aren't pre-quoted
let path_with_spaces = "/Users/test/Library/Application Support/Zed/cli.js";
let login_task = task::SpawnInTerminal {
command: Some("node".to_string()),
args: vec![path_with_spaces.to_string(), "/login".to_string()],
..Default::default()
};
// Args should be passed as-is, not pre-quoted
assert!(!login_task.args[0].starts_with('"'));
assert!(!login_task.args[0].starts_with('\''));
}
#[gpui::test]
async fn test_notification_for_tool_authorization(cx: &mut TestAppContext) {
init_test(cx);
@@ -5994,7 +6087,7 @@ pub(crate) mod tests {
Project::init_settings(cx);
AgentSettings::register(cx);
workspace::init_settings(cx);
ThemeSettings::register(cx);
theme::init(theme::LoadThemes::JustBase, cx);
release_channel::init(SemanticVersion::default(), cx);
EditorSettings::register(cx);
prompt_store::init(cx)

View File

@@ -15,6 +15,7 @@ use context_server::ContextServerId;
use editor::{Editor, SelectionEffects, scroll::Autoscroll};
use extension::ExtensionManifest;
use extension_host::ExtensionStore;
use feature_flags::{CodexAcpFeatureFlag, FeatureFlagAppExt as _};
use fs::Fs;
use gpui::{
Action, AnyView, App, AsyncWindowContext, Corner, Entity, EventEmitter, FocusHandle, Focusable,
@@ -26,7 +27,7 @@ use language_model::{
};
use notifications::status_toast::{StatusToast, ToastIcon};
use project::{
agent_server_store::{AgentServerStore, CLAUDE_CODE_NAME, GEMINI_NAME},
agent_server_store::{AgentServerStore, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME},
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
};
use settings::{Settings, SettingsStore, update_settings_file};
@@ -408,7 +409,7 @@ impl AgentConfiguration {
SwitchField::new(
"always-allow-tool-actions-switch",
"Allow running commands without asking for confirmation",
Some("Allow running commands without asking for confirmation"),
Some(
"The agent can perform potentially destructive actions without asking for your confirmation.".into(),
),
@@ -428,7 +429,7 @@ impl AgentConfiguration {
SwitchField::new(
"single-file-review",
"Enable single-file agent reviews",
Some("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| {
@@ -449,7 +450,7 @@ impl AgentConfiguration {
SwitchField::new(
"sound-notification",
"Play sound when finished generating",
Some("Play sound when finished generating"),
Some(
"Hear a notification sound when the agent is done generating changes or needs your input.".into(),
),
@@ -469,7 +470,7 @@ impl AgentConfiguration {
SwitchField::new(
"modifier-send",
"Use modifier to submit a message",
Some("Use modifier to submit a message"),
Some(
"Make a modifier (cmd-enter on macOS, ctrl-enter on Linux or Windows) required to send messages.".into(),
),
@@ -543,35 +544,23 @@ impl AgentConfiguration {
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let mut registry_descriptors = self
let mut context_server_ids = self
.context_server_store
.read(cx)
.all_registry_descriptor_ids(cx);
let server_count = registry_descriptors.len();
.server_ids(cx)
.into_iter()
.collect::<Vec<_>>();
// Sort context servers: non-mcp-server ones first, then mcp-server ones
registry_descriptors.sort_by(|a, b| {
let has_mcp_prefix_a = a.0.starts_with("mcp-server-");
let has_mcp_prefix_b = b.0.starts_with("mcp-server-");
match (has_mcp_prefix_a, has_mcp_prefix_b) {
// Sort context servers: ones without mcp-server- prefix first, then prefixed ones
context_server_ids.sort_by(|a, b| {
const MCP_PREFIX: &str = "mcp-server-";
match (a.0.strip_prefix(MCP_PREFIX), b.0.strip_prefix(MCP_PREFIX)) {
// If one has mcp-server- prefix and other doesn't, non-mcp comes first
(true, false) => std::cmp::Ordering::Greater,
(false, true) => std::cmp::Ordering::Less,
(Some(_), None) => std::cmp::Ordering::Greater,
(None, Some(_)) => std::cmp::Ordering::Less,
// If both have same prefix status, sort by appropriate key
_ => {
let get_sort_key = |server_id: &str| -> String {
if let Some(suffix) = server_id.strip_prefix("mcp-server-") {
suffix.to_string()
} else {
server_id.to_string()
}
};
let key_a = get_sort_key(&a.0);
let key_b = get_sort_key(&b.0);
key_a.cmp(&key_b)
}
(Some(a), Some(b)) => a.cmp(b),
(None, None) => a.0.cmp(&b.0),
}
});
@@ -636,8 +625,8 @@ impl AgentConfiguration {
)
.child(add_server_popover),
)
.child(v_flex().w_full().gap_1().map(|parent| {
if registry_descriptors.is_empty() {
.child(v_flex().w_full().gap_1().map(|mut parent| {
if context_server_ids.is_empty() {
parent.child(
h_flex()
.p_4()
@@ -653,26 +642,18 @@ impl AgentConfiguration {
),
)
} else {
{
parent.children(registry_descriptors.into_iter().enumerate().flat_map(
|(index, context_server_id)| {
let mut elements: Vec<AnyElement> = vec![
self.render_context_server(context_server_id, window, cx)
.into_any_element(),
];
if index < server_count - 1 {
elements.push(
Divider::horizontal()
.color(DividerColor::BorderFaded)
.into_any_element(),
);
}
elements
},
))
for (index, context_server_id) in context_server_ids.into_iter().enumerate() {
if index > 0 {
parent = parent.child(
Divider::horizontal()
.color(DividerColor::BorderFaded)
.into_any_element(),
);
}
parent =
parent.child(self.render_context_server(context_server_id, window, cx));
}
parent
}
}))
}
@@ -1034,7 +1015,9 @@ impl AgentConfiguration {
.agent_server_store
.read(cx)
.external_agents()
.filter(|name| name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME)
.filter(|name| {
name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME && name.0 != CODEX_NAME
})
.cloned()
.collect::<Vec<_>>();
@@ -1097,16 +1080,30 @@ impl AgentConfiguration {
.color(Color::Muted),
),
)
.child(self.render_agent_server(
IconName::AiGemini,
"Gemini CLI",
))
.child(Divider::horizontal().color(DividerColor::BorderFaded))
.child(self.render_agent_server(
IconName::AiClaude,
"Claude Code",
))
.children(user_defined_agents),
.child(Divider::horizontal().color(DividerColor::BorderFaded))
.when(cx.has_flag::<CodexAcpFeatureFlag>(), |this| {
this
.child(self.render_agent_server(
IconName::AiOpenAi,
"Codex",
))
.child(Divider::horizontal().color(DividerColor::BorderFaded))
})
.child(self.render_agent_server(
IconName::AiGemini,
"Gemini CLI",
))
.map(|mut parent| {
for agent in user_defined_agents {
parent = parent.child(Divider::horizontal().color(DividerColor::BorderFaded))
.child(agent);
}
parent
})
)
}

View File

@@ -619,10 +619,10 @@ mod tests {
cx.update(|_window, cx| {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.register_provider(
FakeLanguageModelProvider::new(
Arc::new(FakeLanguageModelProvider::new(
LanguageModelProviderId::new("someprovider"),
LanguageModelProviderName::new("Some Provider"),
),
)),
cx,
);
});

View File

@@ -317,6 +317,8 @@ impl ManageProfilesModal {
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement + use<> {
let is_focused = profile.navigation.focus_handle.contains_focused(window, cx);
div()
.id(SharedString::from(format!("profile-{}", profile.id)))
.track_focus(&profile.navigation.focus_handle)
@@ -328,25 +330,27 @@ impl ManageProfilesModal {
})
.child(
ListItem::new(SharedString::from(format!("profile-{}", profile.id)))
.toggle_state(profile.navigation.focus_handle.contains_focused(window, cx))
.toggle_state(is_focused)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.child(Label::new(profile.name.clone()))
.end_slot(
h_flex()
.gap_1()
.child(
Label::new("Customize")
.size(LabelSize::Small)
.color(Color::Muted),
)
.children(KeyBinding::for_action_in(
&menu::Confirm,
&self.focus_handle,
window,
cx,
)),
)
.when(is_focused, |this| {
this.end_slot(
h_flex()
.gap_1()
.child(
Label::new("Customize")
.size(LabelSize::Small)
.color(Color::Muted),
)
.children(KeyBinding::for_action_in(
&menu::Confirm,
&self.focus_handle,
window,
cx,
)),
)
})
.on_click({
let profile_id = profile.id.clone();
cx.listener(move |this, _, window, cx| {

View File

@@ -562,10 +562,6 @@ impl Item for AgentDiffPane {
self.editor.for_each_project_item(cx, f)
}
fn is_singleton(&self, _: &App) -> bool {
false
}
fn set_nav_history(
&mut self,
nav_history: ItemNavHistory,
@@ -850,7 +846,7 @@ fn render_diff_hunk_controls(
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let position =
hunk_range.end.to_point(&snapshot.buffer_snapshot);
hunk_range.end.to_point(&snapshot.buffer_snapshot());
editor.go_to_hunk_before_or_after_position(
&snapshot,
position,
@@ -886,7 +882,7 @@ fn render_diff_hunk_controls(
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let point =
hunk_range.start.to_point(&snapshot.buffer_snapshot);
hunk_range.start.to_point(&snapshot.buffer_snapshot());
editor.go_to_hunk_before_or_after_position(
&snapshot,
point,
@@ -1818,7 +1814,6 @@ mod tests {
use serde_json::json;
use settings::{Settings, SettingsStore};
use std::{path::Path, rc::Rc};
use theme::ThemeSettings;
use util::path;
#[gpui::test]
@@ -1831,7 +1826,7 @@ mod tests {
AgentSettings::register(cx);
prompt_store::init(cx);
workspace::init_settings(cx);
ThemeSettings::register(cx);
theme::init(theme::LoadThemes::JustBase, cx);
EditorSettings::register(cx);
language_model::init_settings(cx);
});
@@ -1983,7 +1978,7 @@ mod tests {
AgentSettings::register(cx);
prompt_store::init(cx);
workspace::init_settings(cx);
ThemeSettings::register(cx);
theme::init(theme::LoadThemes::JustBase, cx);
EditorSettings::register(cx);
language_model::init_settings(cx);
workspace::register_project_item::<Editor>(cx);

View File

@@ -1,4 +1,4 @@
use std::ops::{Not, Range};
use std::ops::Range;
use std::path::Path;
use std::rc::Rc;
use std::sync::Arc;
@@ -7,7 +7,7 @@ use acp_thread::AcpThread;
use agent2::{DbThreadMetadata, HistoryEntry};
use db::kvp::{Dismissable, KEY_VALUE_STORE};
use project::agent_server_store::{
AgentServerCommand, AllAgentServersSettings, CLAUDE_CODE_NAME, GEMINI_NAME,
AgentServerCommand, AllAgentServersSettings, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME,
};
use serde::{Deserialize, Serialize};
use settings::{
@@ -53,7 +53,7 @@ use gpui::{
};
use language::LanguageRegistry;
use language_model::{ConfigurationError, LanguageModelRegistry};
use project::{DisableAiSettings, Project, ProjectPath, Worktree};
use project::{Project, ProjectPath, Worktree};
use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
use rules_library::{RulesLibrary, open_rules_library};
use search::{BufferSearchBar, buffer_search};
@@ -75,6 +75,7 @@ use zed_actions::{
assistant::{OpenRulesLibrary, ToggleFocus},
};
use feature_flags::{CodexAcpFeatureFlag, FeatureFlagAppExt as _};
const AGENT_PANEL_KEY: &str = "agent_panel";
#[derive(Serialize, Deserialize, Debug)]
@@ -216,6 +217,7 @@ pub enum AgentType {
TextThread,
Gemini,
ClaudeCode,
Codex,
NativeAgent,
Custom {
name: SharedString,
@@ -230,6 +232,7 @@ impl AgentType {
Self::NativeAgent => "Agent 2".into(),
Self::Gemini => "Gemini CLI".into(),
Self::ClaudeCode => "Claude Code".into(),
Self::Codex => "Codex".into(),
Self::Custom { name, .. } => name.into(),
}
}
@@ -239,6 +242,7 @@ impl AgentType {
Self::Zed | Self::NativeAgent | Self::TextThread => None,
Self::Gemini => Some(IconName::AiGemini),
Self::ClaudeCode => Some(IconName::AiClaude),
Self::Codex => Some(IconName::AiOpenAi),
Self::Custom { .. } => Some(IconName::Terminal),
}
}
@@ -249,6 +253,7 @@ impl From<ExternalAgent> for AgentType {
match value {
ExternalAgent::Gemini => Self::Gemini,
ExternalAgent::ClaudeCode => Self::ClaudeCode,
ExternalAgent::Codex => Self::Codex,
ExternalAgent::Custom { name, command } => Self::Custom { name, command },
ExternalAgent::NativeAgent => Self::NativeAgent,
}
@@ -408,6 +413,7 @@ impl ActiveView {
pub struct AgentPanel {
workspace: WeakEntity<Workspace>,
loading: bool,
user_store: Entity<UserStore>,
project: Entity<Project>,
fs: Arc<dyn Fs>,
@@ -513,6 +519,8 @@ impl AgentPanel {
cx,
)
});
panel.as_mut(cx).loading = true;
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width.map(|w| w.round());
@@ -527,6 +535,7 @@ impl AgentPanel {
panel.new_agent_thread(AgentType::NativeAgent, window, cx);
});
}
panel.as_mut(cx).loading = false;
panel
})?;
@@ -674,11 +683,9 @@ impl AgentPanel {
prompt_store,
configuration: None,
configuration_subscription: None,
inline_assist_context_store,
previous_view: None,
history_store: history_store.clone(),
new_thread_menu_handle: PopoverMenuHandle::default(),
agent_panel_menu_handle: PopoverMenuHandle::default(),
assistant_navigation_menu_handle: PopoverMenuHandle::default(),
@@ -691,6 +698,7 @@ impl AgentPanel {
acp_history,
acp_history_store,
selected_agent: AgentType::default(),
loading: false,
}
}
@@ -703,7 +711,6 @@ impl AgentPanel {
if workspace
.panel::<Self>(cx)
.is_some_and(|panel| panel.read(cx).enabled(cx))
&& !DisableAiSettings::get_global(cx).disable_ai
{
workspace.toggle_panel_focus::<Self>(window, cx);
}
@@ -823,6 +830,7 @@ impl AgentPanel {
agent: crate::ExternalAgent,
}
let loading = self.loading;
let history = self.acp_history_store.clone();
cx.spawn_in(window, async move |this, cx| {
@@ -864,7 +872,9 @@ impl AgentPanel {
}
};
telemetry::event!("Agent Thread Started", agent = ext_agent.name());
if !loading {
telemetry::event!("Agent Thread Started", agent = ext_agent.name());
}
let server = ext_agent.server(fs, history);
@@ -1062,15 +1072,15 @@ impl AgentPanel {
WhichFontSize::AgentFont => {
if persist {
update_settings_file(self.fs.clone(), cx, move |settings, cx| {
let agent_font_size =
ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
let agent_ui_font_size =
ThemeSettings::get_global(cx).agent_ui_font_size(cx) + delta;
let _ = settings
.theme
.agent_font_size
.insert(Some(theme::clamp_font_size(agent_font_size).into()));
.agent_ui_font_size
.insert(theme::clamp_font_size(agent_ui_font_size).into());
});
} else {
theme::adjust_agent_font_size(cx, |size| size + delta);
theme::adjust_agent_ui_font_size(cx, |size| size + delta);
}
}
WhichFontSize::BufferFont => {
@@ -1090,10 +1100,10 @@ impl AgentPanel {
) {
if action.persist {
update_settings_file(self.fs.clone(), cx, move |settings, _| {
settings.theme.agent_font_size = None;
settings.theme.agent_ui_font_size = None;
});
} else {
theme::reset_agent_font_size(cx);
theme::reset_agent_ui_font_size(cx);
}
}
@@ -1386,6 +1396,11 @@ impl AgentPanel {
cx,
)
}
AgentType::Codex => {
self.selected_agent = AgentType::Codex;
self.serialize(cx);
self.external_thread(Some(crate::ExternalAgent::Codex), None, None, window, cx)
}
AgentType::Custom { name, command } => self.external_thread(
Some(crate::ExternalAgent::Custom { name, command }),
None,
@@ -1499,7 +1514,7 @@ impl Panel for AgentPanel {
}
fn enabled(&self, cx: &App) -> bool {
DisableAiSettings::get_global(cx).disable_ai.not() && AgentSettings::get_global(cx).enabled
AgentSettings::get_global(cx).enabled(cx)
}
fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
@@ -1898,32 +1913,6 @@ impl AgentPanel {
)
.separator()
.header("External Agents")
.item(
ContextMenuEntry::new("New Gemini CLI Thread")
.icon(IconName::AiGemini)
.icon_color(Color::Muted)
.disabled(is_via_collab)
.handler({
let workspace = workspace.clone();
move |window, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(panel) =
workspace.panel::<AgentPanel>(cx)
{
panel.update(cx, |panel, cx| {
panel.new_agent_thread(
AgentType::Gemini,
window,
cx,
);
});
}
});
}
}
}),
)
.item(
ContextMenuEntry::new("New Claude Code Thread")
.icon(IconName::AiClaude)
@@ -1950,12 +1939,66 @@ impl AgentPanel {
}
}),
)
.when(cx.has_flag::<CodexAcpFeatureFlag>(), |this| {
this.item(
ContextMenuEntry::new("New Codex Thread")
.icon(IconName::AiOpenAi)
.disabled(is_via_collab)
.icon_color(Color::Muted)
.handler({
let workspace = workspace.clone();
move |window, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(panel) =
workspace.panel::<AgentPanel>(cx)
{
panel.update(cx, |panel, cx| {
panel.new_agent_thread(
AgentType::Codex,
window,
cx,
);
});
}
});
}
}
}),
)
})
.item(
ContextMenuEntry::new("New Gemini CLI Thread")
.icon(IconName::AiGemini)
.icon_color(Color::Muted)
.disabled(is_via_collab)
.handler({
let workspace = workspace.clone();
move |window, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(panel) =
workspace.panel::<AgentPanel>(cx)
{
panel.update(cx, |panel, cx| {
panel.new_agent_thread(
AgentType::Gemini,
window,
cx,
);
});
}
});
}
}
}),
)
.map(|mut menu| {
let agent_names = agent_server_store
.read(cx)
.external_agents()
.filter(|name| {
name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME
name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME && name.0 != CODEX_NAME
})
.cloned()
.collect::<Vec<_>>();
@@ -2491,7 +2534,7 @@ impl Render for AgentPanel {
match self.active_view.which_font_size_used() {
WhichFontSize::AgentFont => {
WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx))
WithRemSize::new(ThemeSettings::get_global(cx).agent_ui_font_size(cx))
.size_full()
.child(content)
.into_any()

View File

@@ -167,6 +167,7 @@ enum ExternalAgent {
#[default]
Gemini,
ClaudeCode,
Codex,
NativeAgent,
Custom {
name: SharedString,
@@ -188,6 +189,7 @@ impl ExternalAgent {
Self::NativeAgent => "zed",
Self::Gemini => "gemini-cli",
Self::ClaudeCode => "claude-code",
Self::Codex => "codex",
Self::Custom { .. } => "custom",
}
}
@@ -200,6 +202,7 @@ impl ExternalAgent {
match self {
Self::Gemini => Rc::new(agent_servers::Gemini),
Self::ClaudeCode => Rc::new(agent_servers::ClaudeCode),
Self::Codex => Rc::new(agent_servers::Codex),
Self::NativeAgent => Rc::new(agent2::NativeAgentServer::new(fs, history)),
Self::Custom { name, command: _ } => {
Rc::new(agent_servers::CustomAgentServer::new(name.clone()))
@@ -264,7 +267,7 @@ pub fn init(
init_language_model_settings(cx);
}
assistant_slash_command::init(cx);
agent::init(cx);
agent::init(fs.clone(), cx);
agent_panel::init(cx);
context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
TextThreadEditor::init(cx);

View File

@@ -33,6 +33,8 @@ use thread_context_picker::{
use ui::{
ButtonLike, ContextMenu, ContextMenuEntry, ContextMenuItem, Disclosure, TintColor, prelude::*,
};
use util::paths::PathStyle;
use util::rel_path::RelPath;
use workspace::{Workspace, notifications::NotifyResultExt};
use agent::{
@@ -228,12 +230,19 @@ impl ContextPicker {
let context_picker = cx.entity();
let menu = ContextMenu::build(window, cx, move |menu, _window, cx| {
let Some(workspace) = self.workspace.upgrade() else {
return menu;
};
let path_style = workspace.read(cx).path_style(cx);
let recent = self.recent_entries(cx);
let has_recent = !recent.is_empty();
let recent_entries = recent
.into_iter()
.enumerate()
.map(|(ix, entry)| self.recent_menu_item(context_picker.clone(), ix, entry));
.map(|(ix, entry)| {
self.recent_menu_item(context_picker.clone(), ix, entry, path_style)
})
.collect::<Vec<_>>();
let entries = self
.workspace
@@ -395,6 +404,7 @@ impl ContextPicker {
context_picker: Entity<ContextPicker>,
ix: usize,
entry: RecentEntry,
path_style: PathStyle,
) -> ContextMenuItem {
match entry {
RecentEntry::File {
@@ -413,6 +423,7 @@ impl ContextPicker {
&path,
&path_prefix,
false,
path_style,
context_store.clone(),
cx,
)
@@ -586,7 +597,7 @@ impl Render for ContextPicker {
pub(crate) enum RecentEntry {
File {
project_path: ProjectPath,
path_prefix: Arc<str>,
path_prefix: Arc<RelPath>,
},
Thread(ThreadContextEntry),
}

View File

@@ -13,6 +13,7 @@ use http_client::HttpClientWithUrl;
use itertools::Itertools;
use language::{Buffer, CodeLabel, HighlightId};
use lsp::CompletionContext;
use project::lsp_store::SymbolLocation;
use project::{
Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, ProjectPath,
Symbol, WorktreeId,
@@ -22,6 +23,8 @@ use rope::Point;
use text::{Anchor, OffsetRangeExt, ToPoint};
use ui::prelude::*;
use util::ResultExt as _;
use util::paths::PathStyle;
use util::rel_path::RelPath;
use workspace::Workspace;
use agent::{
@@ -574,11 +577,12 @@ impl ContextPickerCompletionProvider {
fn completion_for_path(
project_path: ProjectPath,
path_prefix: &str,
path_prefix: &RelPath,
is_recent: bool,
is_directory: bool,
excerpt_id: ExcerptId,
source_range: Range<Anchor>,
path_style: PathStyle,
editor: Entity<Editor>,
context_store: Entity<ContextStore>,
cx: &App,
@@ -586,6 +590,7 @@ impl ContextPickerCompletionProvider {
let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory(
&project_path.path,
path_prefix,
path_style,
);
let label =
@@ -657,17 +662,22 @@ impl ContextPickerCompletionProvider {
workspace: Entity<Workspace>,
cx: &mut App,
) -> Option<Completion> {
let path_style = workspace.read(cx).path_style(cx);
let SymbolLocation::InProject(symbol_path) = &symbol.path else {
return None;
};
let path_prefix = workspace
.read(cx)
.project()
.read(cx)
.worktree_for_id(symbol.path.worktree_id, cx)?
.worktree_for_id(symbol_path.worktree_id, cx)?
.read(cx)
.root_name();
let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory(
&symbol.path.path,
&symbol_path.path,
path_prefix,
path_style,
);
let full_path = if let Some(directory) = directory {
format!("{}{}", directory, file_name)
@@ -768,6 +778,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
let text_thread_store = self.text_thread_store.clone();
let editor = self.editor.clone();
let http_client = workspace.read(cx).client().http_client();
let path_style = workspace.read(cx).path_style(cx);
let MentionCompletion { mode, argument, .. } = state;
let query = argument.unwrap_or_else(|| "".to_string());
@@ -834,6 +845,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
mat.is_dir,
excerpt_id,
source_range.clone(),
path_style,
editor.clone(),
context_store.clone(),
cx,
@@ -1064,7 +1076,7 @@ mod tests {
use serde_json::json;
use settings::SettingsStore;
use std::{ops::Deref, rc::Rc};
use util::path;
use util::{path, rel_path::rel_path};
use workspace::{AppState, Item};
#[test]
@@ -1215,16 +1227,18 @@ mod tests {
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
let paths = vec![
path!("a/one.txt"),
path!("a/two.txt"),
path!("a/three.txt"),
path!("a/four.txt"),
path!("b/five.txt"),
path!("b/six.txt"),
path!("b/seven.txt"),
path!("b/eight.txt"),
rel_path("a/one.txt"),
rel_path("a/two.txt"),
rel_path("a/three.txt"),
rel_path("a/four.txt"),
rel_path("b/five.txt"),
rel_path("b/six.txt"),
rel_path("b/seven.txt"),
rel_path("b/eight.txt"),
];
let slash = PathStyle::local().separator();
let mut opened_editors = Vec::new();
for path in paths {
let buffer = workspace
@@ -1232,7 +1246,7 @@ mod tests {
workspace.open_path(
ProjectPath {
worktree_id,
path: Path::new(path).into(),
path: path.into(),
},
None,
false,
@@ -1308,13 +1322,13 @@ mod tests {
assert_eq!(
current_completion_labels(editor),
&[
"seven.txt dir/b/",
"six.txt dir/b/",
"five.txt dir/b/",
"four.txt dir/a/",
"Files & Directories",
"Symbols",
"Fetch"
format!("seven.txt dir{slash}b{slash}"),
format!("six.txt dir{slash}b{slash}"),
format!("five.txt dir{slash}b{slash}"),
format!("four.txt dir{slash}a{slash}"),
"Files & Directories".into(),
"Symbols".into(),
"Fetch".into()
]
);
});
@@ -1341,7 +1355,10 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(editor.text(cx), "Lorem @file one");
assert!(editor.has_visible_completions_menu());
assert_eq!(current_completion_labels(editor), vec!["one.txt dir/a/"]);
assert_eq!(
current_completion_labels(editor),
vec![format!("one.txt dir{slash}a{slash}")]
);
});
editor.update_in(&mut cx, |editor, window, cx| {
@@ -1350,7 +1367,10 @@ mod tests {
});
editor.update(&mut cx, |editor, cx| {
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) ");
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) ")
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
@@ -1361,7 +1381,10 @@ mod tests {
cx.simulate_input(" ");
editor.update(&mut cx, |editor, cx| {
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) ");
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) ")
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
@@ -1374,7 +1397,7 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum ",
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum "),
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
@@ -1388,7 +1411,7 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum @file ",
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum @file "),
);
assert!(editor.has_visible_completions_menu());
assert_eq!(
@@ -1406,7 +1429,7 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) "
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) ")
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
@@ -1423,7 +1446,7 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n@"
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) \n@")
);
assert!(editor.has_visible_completions_menu());
assert_eq!(
@@ -1444,7 +1467,7 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n[@six.txt](@file:dir/b/six.txt) "
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) \n[@six.txt](@file:dir{slash}b{slash}six.txt) ")
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(

View File

@@ -1,4 +1,3 @@
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
@@ -10,7 +9,7 @@ use gpui::{
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
use ui::{ListItem, Tooltip, prelude::*};
use util::ResultExt as _;
use util::{ResultExt as _, paths::PathStyle, rel_path::RelPath};
use workspace::Workspace;
use crate::context_picker::ContextPicker;
@@ -161,6 +160,8 @@ impl PickerDelegate for FileContextPickerDelegate {
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let FileMatch { mat, .. } = &self.matches.get(ix)?;
let workspace = self.workspace.upgrade()?;
let path_style = workspace.read(cx).path_style(cx);
Some(
ListItem::new(ix)
@@ -172,6 +173,7 @@ impl PickerDelegate for FileContextPickerDelegate {
&mat.path,
&mat.path_prefix,
mat.is_dir,
path_style,
self.context_store.clone(),
cx,
)),
@@ -214,14 +216,13 @@ pub(crate) fn search_files(
let file_matches = project.worktrees(cx).flat_map(|worktree| {
let worktree = worktree.read(cx);
let path_prefix: Arc<str> = worktree.root_name().into();
worktree.entries(false, 0).map(move |entry| FileMatch {
mat: PathMatch {
score: 0.,
positions: Vec::new(),
worktree_id: worktree.id().to_usize(),
path: entry.path.clone(),
path_prefix: path_prefix.clone(),
path_prefix: worktree.root_name().into(),
distance_to_relative_ancestor: 0,
is_dir: entry.is_dir(),
},
@@ -251,7 +252,7 @@ pub(crate) fn search_files(
fuzzy::match_path_sets(
candidate_sets.as_slice(),
query.as_str(),
None,
&None,
false,
100,
&cancellation_flag,
@@ -269,51 +270,31 @@ pub(crate) fn search_files(
}
pub fn extract_file_name_and_directory(
path: &Path,
path_prefix: &str,
path: &RelPath,
path_prefix: &RelPath,
path_style: PathStyle,
) -> (SharedString, Option<SharedString>) {
if path == Path::new("") {
(
SharedString::from(
path_prefix
.trim_end_matches(std::path::MAIN_SEPARATOR)
.to_string(),
),
None,
)
} else {
let file_name = path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string()
.into();
let mut directory = path_prefix
.trim_end_matches(std::path::MAIN_SEPARATOR)
.to_string();
if !directory.ends_with('/') {
directory.push('/');
}
if let Some(parent) = path.parent().filter(|parent| parent != &Path::new("")) {
directory.push_str(&parent.to_string_lossy());
directory.push('/');
}
(file_name, Some(directory.into()))
}
let full_path = path_prefix.join(path);
let file_name = full_path.file_name().unwrap_or_default();
let display_path = full_path.display(path_style);
let (directory, file_name) = display_path.split_at(display_path.len() - file_name.len());
(
file_name.to_string().into(),
Some(SharedString::new(directory)).filter(|dir| !dir.is_empty()),
)
}
pub fn render_file_context_entry(
id: ElementId,
worktree_id: WorktreeId,
path: &Arc<Path>,
path_prefix: &Arc<str>,
path: &Arc<RelPath>,
path_prefix: &Arc<RelPath>,
is_directory: bool,
path_style: PathStyle,
context_store: WeakEntity<ContextStore>,
cx: &App,
) -> Stateful<Div> {
let (file_name, directory) = extract_file_name_and_directory(path, path_prefix);
let (file_name, directory) = extract_file_name_and_directory(path, path_prefix, path_style);
let added = context_store.upgrade().and_then(|context_store| {
let project_path = ProjectPath {
@@ -330,9 +311,9 @@ pub fn render_file_context_entry(
});
let file_icon = if is_directory {
FileIcons::get_folder_icon(false, path, cx)
FileIcons::get_folder_icon(false, path.as_std_path(), cx)
} else {
FileIcons::get_icon(path, cx)
FileIcons::get_icon(path.as_std_path(), cx)
}
.map(Icon::from_path)
.unwrap_or_else(|| Icon::new(IconName::File));

View File

@@ -2,13 +2,14 @@ use std::cmp::Reverse;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use anyhow::Result;
use anyhow::{Result, anyhow};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
App, AppContext, DismissEvent, Entity, FocusHandle, Focusable, Stateful, Task, WeakEntity,
};
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate};
use project::lsp_store::SymbolLocation;
use project::{DocumentSymbol, Symbol};
use ui::{ListItem, prelude::*};
use util::ResultExt as _;
@@ -191,7 +192,10 @@ pub(crate) fn add_symbol(
) -> Task<Result<(Option<AgentContextHandle>, bool)>> {
let project = workspace.read(cx).project().clone();
let open_buffer_task = project.update(cx, |project, cx| {
project.open_buffer(symbol.path.clone(), cx)
let SymbolLocation::InProject(symbol_path) = &symbol.path else {
return Task::ready(Err(anyhow!("can't add symbol from outside of project")));
};
project.open_buffer(symbol_path.clone(), cx)
});
cx.spawn(async move |cx| {
let buffer = open_buffer_task.await?;
@@ -291,10 +295,11 @@ pub(crate) fn search_symbols(
.map(|(id, symbol)| {
StringMatchCandidate::new(id, symbol.label.filter_text())
})
.partition(|candidate| {
project
.entry_for_path(&symbols[candidate.id].path, cx)
.is_some_and(|e| !e.is_ignored)
.partition(|candidate| match &symbols[candidate.id].path {
SymbolLocation::InProject(project_path) => project
.entry_for_path(project_path, cx)
.is_some_and(|e| !e.is_ignored),
SymbolLocation::OutsideProject { .. } => false,
})
})
.log_err()
@@ -360,13 +365,18 @@ fn compute_symbol_entries(
}
pub fn render_symbol_context_entry(id: ElementId, entry: &SymbolEntry) -> Stateful<Div> {
let path = entry
.symbol
.path
.path
.file_name()
.map(|s| s.to_string_lossy())
.unwrap_or_default();
let path = match &entry.symbol.path {
SymbolLocation::InProject(project_path) => {
project_path.path.file_name().unwrap_or_default().into()
}
SymbolLocation::OutsideProject {
abs_path,
signature: _,
} => abs_path
.file_name()
.map(|f| f.to_string_lossy())
.unwrap_or_default(),
};
let symbol_location = format!("{} L{}", path, entry.symbol.range.start.0.row + 1);
h_flex()

View File

@@ -18,7 +18,9 @@ use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
use collections::{HashMap, HashSet, VecDeque, hash_map};
use editor::RowExt;
use editor::SelectionEffects;
use editor::scroll::ScrollOffset;
use editor::{
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange,
MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
@@ -144,8 +146,7 @@ impl InlineAssistant {
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
return;
};
let enabled = !DisableAiSettings::get_global(cx).disable_ai
&& AgentSettings::get_global(cx).enabled;
let enabled = AgentSettings::get_global(cx).enabled(cx);
terminal_panel.update(cx, |terminal_panel, cx| {
terminal_panel.set_assistant_enabled(enabled, cx)
});
@@ -257,8 +258,7 @@ impl InlineAssistant {
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let settings = AgentSettings::get_global(cx);
if !settings.enabled || DisableAiSettings::get_global(cx).disable_ai {
if !AgentSettings::get_global(cx).enabled(cx) {
return;
}
@@ -382,7 +382,7 @@ impl InlineAssistant {
if let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) {
for assist_id in &editor_assists.assist_ids {
let assist = &self.assists[assist_id];
let range = assist.range.to_point(&snapshot.buffer_snapshot);
let range = assist.range.to_point(&snapshot.buffer_snapshot());
if range.start.row <= newest_selection.start.row
&& newest_selection.end.row <= range.end.row
{
@@ -402,16 +402,16 @@ impl InlineAssistant {
selection.end.row -= 1;
}
selection.end.column = snapshot
.buffer_snapshot
.buffer_snapshot()
.line_len(MultiBufferRow(selection.end.row));
} else if let Some(fold) =
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))
{
selection.start = fold.range().start;
selection.end = fold.range().end;
if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() {
if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot().max_row() {
let chars = snapshot
.buffer_snapshot
.buffer_snapshot()
.chars_at(Point::new(selection.end.row + 1, 0));
for c in chars {
@@ -427,7 +427,7 @@ impl InlineAssistant {
{
selection.end.row += 1;
selection.end.column = snapshot
.buffer_snapshot
.buffer_snapshot()
.line_len(MultiBufferRow(selection.end.row));
}
}
@@ -447,7 +447,7 @@ impl InlineAssistant {
}
selections.push(selection);
}
let snapshot = &snapshot.buffer_snapshot;
let snapshot = &snapshot.buffer_snapshot();
let newest_selection = newest_selection.unwrap();
let mut codegen_ranges = Vec::new();
@@ -746,7 +746,7 @@ impl InlineAssistant {
let scroll_bottom = scroll_top + editor.visible_line_count().unwrap_or(0.);
editor_assists.scroll_lock = editor
.row_for_block(decorations.prompt_block_id, cx)
.map(|row| row.0 as f32)
.map(|row| row.as_f64())
.filter(|prompt_row| (scroll_top..scroll_bottom).contains(&prompt_row))
.map(|prompt_row| InlineAssistScrollLock {
assist_id,
@@ -912,7 +912,9 @@ impl InlineAssistant {
editor.update(cx, |editor, cx| {
let scroll_position = editor.scroll_position(cx);
let target_scroll_top = editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f32
let target_scroll_top = editor
.row_for_block(decorations.prompt_block_id, cx)?
.as_f64()
- scroll_lock.distance_from_top;
if target_scroll_top != scroll_position.y {
editor.set_scroll_position(point(scroll_position.x, target_scroll_top), window, cx);
@@ -961,8 +963,9 @@ impl InlineAssistant {
if let Some(decorations) = assist.decorations.as_ref() {
let distance_from_top = editor.update(cx, |editor, cx| {
let scroll_top = editor.scroll_position(cx).y;
let prompt_row =
editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f32;
let prompt_row = editor
.row_for_block(decorations.prompt_block_id, cx)?
.0 as ScrollOffset;
Some(prompt_row - scroll_top)
});
@@ -1194,8 +1197,8 @@ impl InlineAssistant {
let mut scroll_target_range = None;
if let Some(decorations) = assist.decorations.as_ref() {
scroll_target_range = maybe!({
let top = editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f32;
let bottom = editor.row_for_block(decorations.end_block_id, cx)?.0 as f32;
let top = editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f64;
let bottom = editor.row_for_block(decorations.end_block_id, cx)?.0 as f64;
Some((top, bottom))
});
if scroll_target_range.is_none() {
@@ -1209,15 +1212,15 @@ impl InlineAssistant {
.start
.to_display_point(&snapshot.display_snapshot)
.row();
let top = start_row.0 as f32;
let top = start_row.0 as ScrollOffset;
let bottom = top + 1.0;
(top, bottom)
});
let mut scroll_target_top = scroll_target_range.0;
let mut scroll_target_bottom = scroll_target_range.1;
scroll_target_top -= editor.vertical_scroll_margin() as f32;
scroll_target_bottom += editor.vertical_scroll_margin() as f32;
scroll_target_top -= editor.vertical_scroll_margin() as ScrollOffset;
scroll_target_bottom += editor.vertical_scroll_margin() as ScrollOffset;
let height_in_lines = editor.visible_line_count().unwrap_or(0.);
let scroll_top = editor.scroll_position(cx).y;
@@ -1545,7 +1548,7 @@ struct EditorInlineAssists {
struct InlineAssistScrollLock {
assist_id: InlineAssistId,
distance_from_top: f32,
distance_from_top: ScrollOffset,
}
impl EditorInlineAssists {
@@ -1788,7 +1791,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
_: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<CodeAction>>> {
if !AgentSettings::get_global(cx).enabled {
if !AgentSettings::get_global(cx).enabled(cx) {
return Task::ready(Ok(Vec::new()));
}

View File

@@ -3,12 +3,20 @@ use agent_settings::{
AgentProfile, AgentProfileId, AgentSettings, AvailableProfiles, builtin_profiles,
};
use fs::Fs;
use gpui::{Action, Entity, FocusHandle, Subscription, prelude::*};
use settings::{DockPosition, Settings as _, SettingsStore, update_settings_file};
use std::sync::Arc;
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
use gpui::{
Action, AnyElement, App, BackgroundExecutor, Context, DismissEvent, Entity, FocusHandle,
Focusable, SharedString, Subscription, Task, Window,
};
use picker::{Picker, PickerDelegate, popover_menu::PickerPopoverMenu};
use settings::{Settings as _, SettingsStore, update_settings_file};
use std::{
sync::atomic::Ordering,
sync::{Arc, atomic::AtomicBool},
};
use ui::{
ContextMenu, ContextMenuEntry, DocumentationEdge, DocumentationSide, PopoverMenu,
PopoverMenuHandle, TintColor, Tooltip, prelude::*,
DocumentationAside, DocumentationEdge, DocumentationSide, HighlightedLabel, LabelSize,
ListItem, ListItemSpacing, PopoverMenuHandle, TintColor, Tooltip, prelude::*,
};
/// Trait for types that can provide and manage agent profiles
@@ -25,9 +33,11 @@ pub trait ProfileProvider {
pub struct ProfileSelector {
profiles: AvailableProfiles,
pending_refresh: bool,
fs: Arc<dyn Fs>,
provider: Arc<dyn ProfileProvider>,
menu_handle: PopoverMenuHandle<ContextMenu>,
picker: Option<Entity<Picker<ProfilePickerDelegate>>>,
picker_handle: PopoverMenuHandle<Picker<ProfilePickerDelegate>>,
focus_handle: FocusHandle,
_subscriptions: Vec<Subscription>,
}
@@ -40,125 +50,91 @@ impl ProfileSelector {
cx: &mut Context<Self>,
) -> Self {
let settings_subscription = cx.observe_global::<SettingsStore>(move |this, cx| {
this.refresh_profiles(cx);
this.pending_refresh = true;
cx.notify();
});
Self {
profiles: AgentProfile::available_profiles(cx),
pending_refresh: false,
fs,
provider,
menu_handle: PopoverMenuHandle::default(),
picker: None,
picker_handle: PopoverMenuHandle::default(),
focus_handle,
_subscriptions: vec![settings_subscription],
}
}
pub fn menu_handle(&self) -> PopoverMenuHandle<ContextMenu> {
self.menu_handle.clone()
pub fn menu_handle(&self) -> PopoverMenuHandle<Picker<ProfilePickerDelegate>> {
self.picker_handle.clone()
}
fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
self.profiles = AgentProfile::available_profiles(cx);
}
fn build_context_menu(
&self,
fn ensure_picker(
&mut self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Entity<ContextMenu> {
ContextMenu::build(window, cx, |mut menu, _window, cx| {
let settings = AgentSettings::get_global(cx);
) -> Entity<Picker<ProfilePickerDelegate>> {
if self.picker.is_none() {
let delegate = ProfilePickerDelegate::new(
self.fs.clone(),
self.provider.clone(),
self.profiles.clone(),
cx.background_executor().clone(),
cx,
);
let mut found_non_builtin = false;
for (profile_id, profile_name) in self.profiles.iter() {
if !builtin_profiles::is_builtin(profile_id) {
found_non_builtin = true;
continue;
}
menu = menu.item(self.menu_entry_for_profile(
profile_id.clone(),
profile_name,
settings,
cx,
));
}
let picker = cx.new(|cx| {
Picker::list(delegate, window, cx)
.show_scrollbar(true)
.width(rems(18.))
.max_height(Some(rems(20.).into()))
});
if found_non_builtin {
menu = menu.separator().header("Custom Profiles");
for (profile_id, profile_name) in self.profiles.iter() {
if builtin_profiles::is_builtin(profile_id) {
continue;
}
menu = menu.item(self.menu_entry_for_profile(
profile_id.clone(),
profile_name,
settings,
cx,
));
}
}
self.picker = Some(picker);
}
menu = menu.separator();
menu = menu.item(ContextMenuEntry::new("Configure Profiles…").handler(
move |window, cx| {
window.dispatch_action(ManageProfiles::default().boxed_clone(), cx);
},
));
menu
})
}
fn menu_entry_for_profile(
&self,
profile_id: AgentProfileId,
profile_name: &SharedString,
settings: &AgentSettings,
cx: &App,
) -> ContextMenuEntry {
let documentation = match profile_name.to_lowercase().as_str() {
builtin_profiles::WRITE => Some("Get help to write anything."),
builtin_profiles::ASK => Some("Chat about your codebase."),
builtin_profiles::MINIMAL => Some("Chat about anything with no tools."),
_ => None,
};
let thread_profile_id = self.provider.profile_id(cx);
let entry = ContextMenuEntry::new(profile_name.clone())
.toggleable(IconPosition::End, profile_id == thread_profile_id);
let entry = if let Some(doc_text) = documentation {
entry.documentation_aside(
documentation_side(settings.dock),
DocumentationEdge::Top,
move |_| Label::new(doc_text).into_any_element(),
)
} else {
entry
};
entry.handler({
let fs = self.fs.clone();
let provider = self.provider.clone();
move |_window, cx| {
update_settings_file(fs.clone(), cx, {
let profile_id = profile_id.clone();
move |settings, _cx| {
settings
.agent
.get_or_insert_default()
.set_profile(profile_id.0);
}
if self.pending_refresh {
if let Some(picker) = &self.picker {
let profiles = AgentProfile::available_profiles(cx);
self.profiles = profiles.clone();
picker.update(cx, |picker, cx| {
let query = picker.query(cx);
picker
.delegate
.refresh_profiles(profiles.clone(), query, cx);
});
provider.set_profile(profile_id.clone(), cx);
}
})
self.pending_refresh = false;
}
self.picker.as_ref().unwrap().clone()
}
}
impl Focusable for ProfileSelector {
fn focus_handle(&self, cx: &App) -> FocusHandle {
if let Some(picker) = &self.picker {
picker.focus_handle(cx)
} else {
self.focus_handle.clone()
}
}
}
impl Render for ProfileSelector {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if !self.provider.profiles_supported(cx) {
return Button::new("tools-not-supported-button", "Tools Unsupported")
.disabled(true)
.label_size(LabelSize::Small)
.color(Color::Muted)
.tooltip(Tooltip::text("This model does not support tools."))
.into_any_element();
}
let picker = self.ensure_picker(window, cx);
let settings = AgentSettings::get_global(cx);
let profile_id = self.provider.profile_id(cx);
let profile = settings.profiles.get(&profile_id);
@@ -166,62 +142,594 @@ impl Render for ProfileSelector {
let selected_profile = profile
.map(|profile| profile.name.clone())
.unwrap_or_else(|| "Unknown".into());
let focus_handle = self.focus_handle.clone();
if self.provider.profiles_supported(cx) {
let this = cx.entity();
let focus_handle = self.focus_handle.clone();
let trigger_button = Button::new("profile-selector-model", selected_profile)
.label_size(LabelSize::Small)
.color(Color::Muted)
.icon(IconName::ChevronDown)
.icon_size(IconSize::XSmall)
.icon_position(IconPosition::End)
.icon_color(Color::Muted)
.selected_style(ButtonStyle::Tinted(TintColor::Accent));
let trigger_button = Button::new("profile-selector", selected_profile)
.label_size(LabelSize::Small)
.color(Color::Muted)
.icon(IconName::ChevronDown)
.icon_size(IconSize::XSmall)
.icon_position(IconPosition::End)
.icon_color(Color::Muted)
.selected_style(ButtonStyle::Tinted(TintColor::Accent));
PopoverMenu::new("profile-selector")
.trigger_with_tooltip(trigger_button, {
move |window, cx| {
Tooltip::for_action_in(
"Toggle Profile Menu",
&ToggleProfileSelector,
&focus_handle,
window,
cx,
)
}
})
.anchor(
if documentation_side(settings.dock) == DocumentationSide::Left {
gpui::Corner::BottomRight
} else {
gpui::Corner::BottomLeft
},
PickerPopoverMenu::new(
picker,
trigger_button,
move |window, cx| {
Tooltip::for_action_in(
"Toggle Profile Menu",
&ToggleProfileSelector,
&focus_handle,
window,
cx,
)
.with_handle(self.menu_handle.clone())
.menu(move |window, cx| {
Some(this.update(cx, |this, cx| this.build_context_menu(window, cx)))
})
.offset(gpui::Point {
x: px(0.0),
y: px(-2.0),
})
.into_any_element()
},
gpui::Corner::BottomRight,
cx,
)
.with_handle(self.picker_handle.clone())
.render(window, cx)
.into_any_element()
}
}
#[derive(Clone)]
struct ProfileCandidate {
id: AgentProfileId,
name: SharedString,
is_builtin: bool,
}
#[derive(Clone)]
struct ProfileMatchEntry {
candidate_index: usize,
positions: Vec<usize>,
}
enum ProfilePickerEntry {
Header(SharedString),
Profile(ProfileMatchEntry),
}
pub(crate) struct ProfilePickerDelegate {
fs: Arc<dyn Fs>,
provider: Arc<dyn ProfileProvider>,
background: BackgroundExecutor,
candidates: Vec<ProfileCandidate>,
string_candidates: Arc<Vec<StringMatchCandidate>>,
filtered_entries: Vec<ProfilePickerEntry>,
selected_index: usize,
query: String,
cancel: Option<Arc<AtomicBool>>,
}
impl ProfilePickerDelegate {
fn new(
fs: Arc<dyn Fs>,
provider: Arc<dyn ProfileProvider>,
profiles: AvailableProfiles,
background: BackgroundExecutor,
cx: &mut Context<ProfileSelector>,
) -> Self {
let candidates = Self::candidates_from(profiles);
let string_candidates = Arc::new(Self::string_candidates(&candidates));
let filtered_entries = Self::entries_from_candidates(&candidates);
let mut this = Self {
fs,
provider,
background,
candidates,
string_candidates,
filtered_entries,
selected_index: 0,
query: String::new(),
cancel: None,
};
this.selected_index = this
.index_of_profile(&this.provider.profile_id(cx))
.unwrap_or_else(|| this.first_selectable_index().unwrap_or(0));
this
}
fn refresh_profiles(
&mut self,
profiles: AvailableProfiles,
query: String,
cx: &mut Context<Picker<Self>>,
) {
self.candidates = Self::candidates_from(profiles);
self.string_candidates = Arc::new(Self::string_candidates(&self.candidates));
self.query = query;
if self.query.is_empty() {
self.filtered_entries = Self::entries_from_candidates(&self.candidates);
} else {
Button::new("tools-not-supported-button", "Tools Unsupported")
.disabled(true)
.label_size(LabelSize::Small)
.color(Color::Muted)
.tooltip(Tooltip::text("This model does not support tools."))
.into_any_element()
let matches = self.search_blocking(&self.query);
self.filtered_entries = self.entries_from_matches(matches);
}
self.selected_index = self
.index_of_profile(&self.provider.profile_id(cx))
.unwrap_or_else(|| self.first_selectable_index().unwrap_or(0));
cx.notify();
}
fn candidates_from(profiles: AvailableProfiles) -> Vec<ProfileCandidate> {
profiles
.into_iter()
.map(|(id, name)| ProfileCandidate {
is_builtin: builtin_profiles::is_builtin(&id),
id,
name,
})
.collect()
}
fn string_candidates(candidates: &[ProfileCandidate]) -> Vec<StringMatchCandidate> {
candidates
.iter()
.enumerate()
.map(|(index, candidate)| StringMatchCandidate::new(index, candidate.name.as_ref()))
.collect()
}
fn documentation(candidate: &ProfileCandidate) -> Option<&'static str> {
match candidate.id.as_str() {
builtin_profiles::WRITE => Some("Get help to write anything."),
builtin_profiles::ASK => Some("Chat about your codebase."),
builtin_profiles::MINIMAL => Some("Chat about anything with no tools."),
_ => None,
}
}
fn entries_from_candidates(candidates: &[ProfileCandidate]) -> Vec<ProfilePickerEntry> {
let mut entries = Vec::new();
let mut inserted_custom_header = false;
for (idx, candidate) in candidates.iter().enumerate() {
if !candidate.is_builtin && !inserted_custom_header {
if !entries.is_empty() {
entries.push(ProfilePickerEntry::Header("Custom Profiles".into()));
}
inserted_custom_header = true;
}
entries.push(ProfilePickerEntry::Profile(ProfileMatchEntry {
candidate_index: idx,
positions: Vec::new(),
}));
}
entries
}
fn entries_from_matches(&self, matches: Vec<StringMatch>) -> Vec<ProfilePickerEntry> {
let mut entries = Vec::new();
for mat in matches {
if self.candidates.get(mat.candidate_id).is_some() {
entries.push(ProfilePickerEntry::Profile(ProfileMatchEntry {
candidate_index: mat.candidate_id,
positions: mat.positions,
}));
}
}
entries
}
fn first_selectable_index(&self) -> Option<usize> {
self.filtered_entries
.iter()
.position(|entry| matches!(entry, ProfilePickerEntry::Profile(_)))
}
fn index_of_profile(&self, profile_id: &AgentProfileId) -> Option<usize> {
self.filtered_entries.iter().position(|entry| {
matches!(entry, ProfilePickerEntry::Profile(profile) if self
.candidates
.get(profile.candidate_index)
.map(|candidate| &candidate.id == profile_id)
.unwrap_or(false))
})
}
fn search_blocking(&self, query: &str) -> Vec<StringMatch> {
if query.is_empty() {
return self
.string_candidates
.iter()
.map(|candidate| StringMatch {
candidate_id: candidate.id,
score: 0.0,
positions: Vec::new(),
string: candidate.string.clone(),
})
.collect();
}
let cancel_flag = AtomicBool::new(false);
self.background.block(match_strings(
self.string_candidates.as_ref(),
query,
false,
true,
100,
&cancel_flag,
self.background.clone(),
))
}
}
impl PickerDelegate for ProfilePickerDelegate {
type ListItem = AnyElement;
fn placeholder_text(&self, _: &mut Window, _: &mut App) -> Arc<str> {
"Search profiles…".into()
}
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> {
let text = if self.candidates.is_empty() {
"No profiles.".into()
} else {
"No profiles match your search.".into()
};
Some(text)
}
fn match_count(&self) -> usize {
self.filtered_entries.len()
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) {
self.selected_index = ix.min(self.filtered_entries.len().saturating_sub(1));
cx.notify();
}
fn can_select(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) -> bool {
match self.filtered_entries.get(ix) {
Some(ProfilePickerEntry::Profile(_)) => true,
Some(ProfilePickerEntry::Header(_)) | None => false,
}
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
if query.is_empty() {
self.query.clear();
self.filtered_entries = Self::entries_from_candidates(&self.candidates);
self.selected_index = self
.index_of_profile(&self.provider.profile_id(cx))
.unwrap_or_else(|| self.first_selectable_index().unwrap_or(0));
cx.notify();
return Task::ready(());
}
if let Some(prev) = &self.cancel {
prev.store(true, Ordering::Relaxed);
}
let cancel = Arc::new(AtomicBool::new(false));
self.cancel = Some(cancel.clone());
let string_candidates = self.string_candidates.clone();
let background = self.background.clone();
let provider = self.provider.clone();
self.query = query.clone();
let cancel_for_future = cancel;
cx.spawn_in(window, async move |this, cx| {
let matches = match_strings(
string_candidates.as_ref(),
&query,
false,
true,
100,
cancel_for_future.as_ref(),
background,
)
.await;
this.update_in(cx, |this, _, cx| {
if this.delegate.query != query {
return;
}
this.delegate.filtered_entries = this.delegate.entries_from_matches(matches);
this.delegate.selected_index = this
.delegate
.index_of_profile(&provider.profile_id(cx))
.unwrap_or_else(|| this.delegate.first_selectable_index().unwrap_or(0));
cx.notify();
})
.ok();
})
}
fn confirm(&mut self, _: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
match self.filtered_entries.get(self.selected_index) {
Some(ProfilePickerEntry::Profile(entry)) => {
if let Some(candidate) = self.candidates.get(entry.candidate_index) {
let profile_id = candidate.id.clone();
let fs = self.fs.clone();
let provider = self.provider.clone();
update_settings_file(fs, cx, {
let profile_id = profile_id.clone();
move |settings, _cx| {
settings
.agent
.get_or_insert_default()
.set_profile(profile_id.0);
}
});
provider.set_profile(profile_id.clone(), cx);
telemetry::event!(
"agent_profile_switched",
profile_id = profile_id.as_str(),
source = "picker"
);
}
cx.emit(DismissEvent);
}
_ => {}
}
}
fn dismissed(&mut self, window: &mut Window, cx: &mut Context<Picker<Self>>) {
cx.defer_in(window, |picker, window, cx| {
picker.set_query("", window, cx);
});
cx.emit(DismissEvent);
}
fn render_match(
&self,
ix: usize,
selected: bool,
_: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
match self.filtered_entries.get(ix)? {
ProfilePickerEntry::Header(label) => Some(
div()
.px_2p5()
.pb_0p5()
.when(ix > 0, |this| {
this.mt_1p5()
.pt_2()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
})
.child(
Label::new(label.clone())
.size(LabelSize::XSmall)
.color(Color::Muted),
)
.into_any_element(),
),
ProfilePickerEntry::Profile(entry) => {
let candidate = self.candidates.get(entry.candidate_index)?;
let active_id = self.provider.profile_id(cx);
let is_active = active_id == candidate.id;
Some(
ListItem::new(SharedString::from(candidate.id.0.clone()))
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.child(HighlightedLabel::new(
candidate.name.clone(),
entry.positions.clone(),
))
.when(is_active, |this| {
this.end_slot(
div()
.pr_2()
.child(Icon::new(IconName::Check).color(Color::Accent)),
)
})
.into_any_element(),
)
}
}
}
fn documentation_aside(
&self,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<DocumentationAside> {
use std::rc::Rc;
let entry = match self.filtered_entries.get(self.selected_index)? {
ProfilePickerEntry::Profile(entry) => entry,
ProfilePickerEntry::Header(_) => return None,
};
let candidate = self.candidates.get(entry.candidate_index)?;
let docs_aside = Self::documentation(candidate)?.to_string();
let settings = AgentSettings::get_global(cx);
let side = match settings.dock {
settings::DockPosition::Left => DocumentationSide::Right,
settings::DockPosition::Bottom | settings::DockPosition::Right => {
DocumentationSide::Left
}
};
Some(DocumentationAside {
side,
edge: DocumentationEdge::Top,
render: Rc::new(move |_| Label::new(docs_aside.clone()).into_any_element()),
})
}
fn render_footer(
&self,
_: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<gpui::AnyElement> {
Some(
h_flex()
.w_full()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.p_1()
.gap_4()
.justify_between()
.child(
Button::new("configure", "Configure")
.icon(IconName::Settings)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, window, cx| {
window.dispatch_action(ManageProfiles::default().boxed_clone(), cx);
}),
)
.into_any(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use fs::FakeFs;
use gpui::TestAppContext;
#[gpui::test]
fn entries_include_custom_profiles(_cx: &mut TestAppContext) {
let candidates = vec![
ProfileCandidate {
id: AgentProfileId("write".into()),
name: SharedString::from("Write"),
is_builtin: true,
},
ProfileCandidate {
id: AgentProfileId("my-custom".into()),
name: SharedString::from("My Custom"),
is_builtin: false,
},
];
let entries = ProfilePickerDelegate::entries_from_candidates(&candidates);
assert!(entries.iter().any(|entry| matches!(
entry,
ProfilePickerEntry::Profile(profile)
if candidates[profile.candidate_index].id.as_str() == "my-custom"
)));
assert!(entries.iter().any(|entry| matches!(
entry,
ProfilePickerEntry::Header(label) if label.as_ref() == "Custom Profiles"
)));
}
#[gpui::test]
fn fuzzy_filter_returns_no_results_and_keeps_configure(cx: &mut TestAppContext) {
let candidates = vec![ProfileCandidate {
id: AgentProfileId("write".into()),
name: SharedString::from("Write"),
is_builtin: true,
}];
let delegate = ProfilePickerDelegate {
fs: FakeFs::new(cx.executor()),
provider: Arc::new(TestProfileProvider::new(AgentProfileId("write".into()))),
background: cx.executor(),
candidates,
string_candidates: Arc::new(Vec::new()),
filtered_entries: Vec::new(),
selected_index: 0,
query: String::new(),
cancel: None,
};
let matches = Vec::new(); // No matches
let _entries = delegate.entries_from_matches(matches);
}
#[gpui::test]
fn active_profile_selection_logic_works(cx: &mut TestAppContext) {
let candidates = vec![
ProfileCandidate {
id: AgentProfileId("write".into()),
name: SharedString::from("Write"),
is_builtin: true,
},
ProfileCandidate {
id: AgentProfileId("ask".into()),
name: SharedString::from("Ask"),
is_builtin: true,
},
];
let delegate = ProfilePickerDelegate {
fs: FakeFs::new(cx.executor()),
provider: Arc::new(TestProfileProvider::new(AgentProfileId("write".into()))),
background: cx.executor(),
candidates,
string_candidates: Arc::new(Vec::new()),
filtered_entries: vec![
ProfilePickerEntry::Profile(ProfileMatchEntry {
candidate_index: 0,
positions: Vec::new(),
}),
ProfilePickerEntry::Profile(ProfileMatchEntry {
candidate_index: 1,
positions: Vec::new(),
}),
],
selected_index: 0,
query: String::new(),
cancel: None,
};
// Active profile should be found at index 0
let active_index = delegate.index_of_profile(&AgentProfileId("write".into()));
assert_eq!(active_index, Some(0));
}
struct TestProfileProvider {
profile_id: AgentProfileId,
}
impl TestProfileProvider {
fn new(profile_id: AgentProfileId) -> Self {
Self { profile_id }
}
}
impl ProfileProvider for TestProfileProvider {
fn profile_id(&self, _cx: &App) -> AgentProfileId {
self.profile_id.clone()
}
fn set_profile(&self, _profile_id: AgentProfileId, _cx: &mut App) {}
fn profiles_supported(&self, _cx: &App) -> bool {
true
}
}
}
fn documentation_side(position: DockPosition) -> DocumentationSide {
match position {
DockPosition::Left => DocumentationSide::Right,
DockPosition::Bottom => DocumentationSide::Left,
DockPosition::Right => DocumentationSide::Left,
}
}

View File

@@ -238,7 +238,7 @@ impl TerminalInlineAssistant {
let latest_output = terminal.last_n_non_empty_lines(DEFAULT_CONTEXT_LINES);
let working_directory = terminal
.working_directory()
.map(|path| path.to_string_lossy().to_string());
.map(|path| path.to_string_lossy().into_owned());
(latest_output, working_directory)
})
.ok()

View File

@@ -17,6 +17,7 @@ use editor::{
BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId,
RenderBlock, ToDisplayPoint,
},
scroll::ScrollOffset,
};
use editor::{FoldPlaceholder, display_map::CreaseId};
use fs::Fs;
@@ -108,7 +109,7 @@ pub enum InsertDraggedFiles {
#[derive(Copy, Clone, Debug, PartialEq)]
struct ScrollPosition {
offset_before_cursor: gpui::Point<f32>,
offset_before_cursor: gpui::Point<ScrollOffset>,
cursor: Anchor,
}
@@ -631,7 +632,7 @@ impl TextThreadEditor {
let snapshot = editor.snapshot(window, cx);
let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
let scroll_top =
cursor_point.row().as_f32() - scroll_position.offset_before_cursor.y;
cursor_point.row().as_f64() - scroll_position.offset_before_cursor.y;
editor.set_scroll_position(
point(scroll_position.offset_before_cursor.x, scroll_top),
window,
@@ -979,7 +980,7 @@ impl TextThreadEditor {
let cursor_row = cursor
.to_display_point(&snapshot.display_snapshot)
.row()
.as_f32();
.as_f64();
let scroll_position = editor
.scroll_manager
.anchor()
@@ -1431,10 +1432,14 @@ impl TextThreadEditor {
else {
continue;
};
let worktree_root_name = worktree.read(cx).root_name().to_string();
let mut full_path = PathBuf::from(worktree_root_name.clone());
full_path.push(&project_path.path);
file_slash_command_args.push(full_path.to_string_lossy().to_string());
let path_style = worktree.read(cx).path_style();
let full_path = worktree
.read(cx)
.root_name()
.join(&project_path.path)
.display(path_style)
.into_owned();
file_slash_command_args.push(full_path);
}
let cmd_name = FileSlashCommand.name();

View File

@@ -48,7 +48,7 @@ impl Render for BurnModeTooltip {
let keybinding = KeyBinding::for_action(&ToggleBurnMode, window, cx)
.map(|kb| kb.size(rems_from_px(12.)));
tooltip_container(window, cx, |this, _, _| {
tooltip_container(cx, |this, _| {
this
.child(
h_flex()

View File

@@ -17,6 +17,7 @@ use agent::context::{
FileContextHandle, ImageContext, ImageStatus, RulesContextHandle, SelectionContextHandle,
SymbolContextHandle, TextThreadContextHandle, ThreadContextHandle,
};
use util::paths::PathStyle;
#[derive(IntoElement)]
pub enum ContextPill {
@@ -303,33 +304,54 @@ impl AddedContext {
cx: &App,
) -> Option<AddedContext> {
match handle {
AgentContextHandle::File(handle) => Self::pending_file(handle, cx),
AgentContextHandle::File(handle) => {
Self::pending_file(handle, project.path_style(cx), cx)
}
AgentContextHandle::Directory(handle) => Self::pending_directory(handle, project, cx),
AgentContextHandle::Symbol(handle) => Self::pending_symbol(handle, cx),
AgentContextHandle::Selection(handle) => Self::pending_selection(handle, cx),
AgentContextHandle::Symbol(handle) => {
Self::pending_symbol(handle, project.path_style(cx), cx)
}
AgentContextHandle::Selection(handle) => {
Self::pending_selection(handle, project.path_style(cx), cx)
}
AgentContextHandle::FetchedUrl(handle) => Some(Self::fetched_url(handle)),
AgentContextHandle::Thread(handle) => Some(Self::pending_thread(handle, cx)),
AgentContextHandle::TextThread(handle) => Some(Self::pending_text_thread(handle, cx)),
AgentContextHandle::Rules(handle) => Self::pending_rules(handle, prompt_store, cx),
AgentContextHandle::Image(handle) => Some(Self::image(handle, model, cx)),
AgentContextHandle::Image(handle) => {
Some(Self::image(handle, model, project.path_style(cx), cx))
}
}
}
fn pending_file(handle: FileContextHandle, cx: &App) -> Option<AddedContext> {
let full_path = handle.buffer.read(cx).file()?.full_path(cx);
Some(Self::file(handle, &full_path, cx))
fn pending_file(
handle: FileContextHandle,
path_style: PathStyle,
cx: &App,
) -> Option<AddedContext> {
let full_path = handle
.buffer
.read(cx)
.file()?
.full_path(cx)
.to_string_lossy()
.to_string();
Some(Self::file(handle, &full_path, path_style, cx))
}
fn file(handle: FileContextHandle, full_path: &Path, cx: &App) -> AddedContext {
let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
let (name, parent) =
extract_file_name_and_directory_from_full_path(full_path, &full_path_string);
fn file(
handle: FileContextHandle,
full_path: &str,
path_style: PathStyle,
cx: &App,
) -> AddedContext {
let (name, parent) = extract_file_name_and_directory_from_full_path(full_path, path_style);
AddedContext {
kind: ContextKind::File,
name,
parent,
tooltip: Some(full_path_string),
icon_path: FileIcons::get_icon(full_path, cx),
tooltip: Some(SharedString::new(full_path)),
icon_path: FileIcons::get_icon(Path::new(full_path), cx),
status: ContextStatus::Ready,
render_hover: None,
handle: AgentContextHandle::File(handle),
@@ -343,19 +365,24 @@ impl AddedContext {
) -> Option<AddedContext> {
let worktree = project.worktree_for_entry(handle.entry_id, cx)?.read(cx);
let entry = worktree.entry_for_id(handle.entry_id)?;
let full_path = worktree.full_path(&entry.path);
Some(Self::directory(handle, &full_path))
let full_path = worktree
.full_path(&entry.path)
.to_string_lossy()
.to_string();
Some(Self::directory(handle, &full_path, project.path_style(cx)))
}
fn directory(handle: DirectoryContextHandle, full_path: &Path) -> AddedContext {
let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
let (name, parent) =
extract_file_name_and_directory_from_full_path(full_path, &full_path_string);
fn directory(
handle: DirectoryContextHandle,
full_path: &str,
path_style: PathStyle,
) -> AddedContext {
let (name, parent) = extract_file_name_and_directory_from_full_path(full_path, path_style);
AddedContext {
kind: ContextKind::Directory,
name,
parent,
tooltip: Some(full_path_string),
tooltip: Some(SharedString::new(full_path)),
icon_path: None,
status: ContextStatus::Ready,
render_hover: None,
@@ -363,9 +390,17 @@ impl AddedContext {
}
}
fn pending_symbol(handle: SymbolContextHandle, cx: &App) -> Option<AddedContext> {
let excerpt =
ContextFileExcerpt::new(&handle.full_path(cx)?, handle.enclosing_line_range(cx), cx);
fn pending_symbol(
handle: SymbolContextHandle,
path_style: PathStyle,
cx: &App,
) -> Option<AddedContext> {
let excerpt = ContextFileExcerpt::new(
&handle.full_path(cx)?.to_string_lossy(),
handle.enclosing_line_range(cx),
path_style,
cx,
);
Some(AddedContext {
kind: ContextKind::Symbol,
name: handle.symbol.clone(),
@@ -383,8 +418,17 @@ impl AddedContext {
})
}
fn pending_selection(handle: SelectionContextHandle, cx: &App) -> Option<AddedContext> {
let excerpt = ContextFileExcerpt::new(&handle.full_path(cx)?, handle.line_range(cx), cx);
fn pending_selection(
handle: SelectionContextHandle,
path_style: PathStyle,
cx: &App,
) -> Option<AddedContext> {
let excerpt = ContextFileExcerpt::new(
&handle.full_path(cx)?.to_string_lossy(),
handle.line_range(cx),
path_style,
cx,
);
Some(AddedContext {
kind: ContextKind::Selection,
name: excerpt.file_name_and_range.clone(),
@@ -485,13 +529,13 @@ impl AddedContext {
fn image(
context: ImageContext,
model: Option<&Arc<dyn language_model::LanguageModel>>,
path_style: PathStyle,
cx: &App,
) -> AddedContext {
let (name, parent, icon_path) = if let Some(full_path) = context.full_path.as_ref() {
let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
let (name, parent) =
extract_file_name_and_directory_from_full_path(full_path, &full_path_string);
let icon_path = FileIcons::get_icon(full_path, cx);
extract_file_name_and_directory_from_full_path(full_path, path_style);
let icon_path = FileIcons::get_icon(Path::new(full_path), cx);
(name, parent, icon_path)
} else {
("Image".into(), None, None)
@@ -540,19 +584,20 @@ impl AddedContext {
}
fn extract_file_name_and_directory_from_full_path(
path: &Path,
name_fallback: &SharedString,
path: &str,
path_style: PathStyle,
) -> (SharedString, Option<SharedString>) {
let name = path
.file_name()
.map(|n| n.to_string_lossy().into_owned().into())
.unwrap_or_else(|| name_fallback.clone());
let parent = path
.parent()
.and_then(|p| p.file_name())
.map(|n| n.to_string_lossy().into_owned().into());
(name, parent)
let (parent, file_name) = path_style.split(path);
let parent = parent.and_then(|parent| {
let parent = parent.trim_end_matches(path_style.separator());
let (_, parent) = path_style.split(parent);
if parent.is_empty() {
None
} else {
Some(SharedString::new(parent))
}
});
(SharedString::new(file_name), parent)
}
#[derive(Debug, Clone)]
@@ -564,25 +609,25 @@ struct ContextFileExcerpt {
}
impl ContextFileExcerpt {
pub fn new(full_path: &Path, line_range: Range<Point>, cx: &App) -> Self {
let full_path_string = full_path.to_string_lossy().into_owned();
let file_name = full_path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| full_path_string.clone());
pub fn new(full_path: &str, line_range: Range<Point>, path_style: PathStyle, cx: &App) -> Self {
let (parent, file_name) = path_style.split(full_path);
let line_range_text = format!(" ({}-{})", line_range.start.row + 1, line_range.end.row + 1);
let mut full_path_and_range = full_path_string;
let mut full_path_and_range = full_path.to_owned();
full_path_and_range.push_str(&line_range_text);
let mut file_name_and_range = file_name;
let mut file_name_and_range = file_name.to_owned();
file_name_and_range.push_str(&line_range_text);
let parent_name = full_path
.parent()
.and_then(|p| p.file_name())
.map(|n| n.to_string_lossy().into_owned().into());
let parent_name = parent.and_then(|parent| {
let parent = parent.trim_end_matches(path_style.separator());
let (_, parent) = path_style.split(parent);
if parent.is_empty() {
None
} else {
Some(SharedString::new(parent))
}
});
let icon_path = FileIcons::get_icon(full_path, cx);
let icon_path = FileIcons::get_icon(Path::new(full_path), cx);
ContextFileExcerpt {
file_name_and_range: file_name_and_range.into(),
@@ -659,7 +704,7 @@ impl ContextPillHover {
impl Render for ContextPillHover {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
tooltip_container(window, cx, move |this, window, cx| {
tooltip_container(cx, move |this, cx| {
this.occlude()
.on_mouse_move(|_, _, cx| cx.stop_propagation())
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
@@ -690,6 +735,7 @@ impl Component for AddedContext {
image_task: Task::ready(Some(LanguageModelImage::empty())).shared(),
},
None,
PathStyle::local(),
cx,
),
);
@@ -710,6 +756,7 @@ impl Component for AddedContext {
.shared(),
},
None,
PathStyle::local(),
cx,
),
);
@@ -725,6 +772,7 @@ impl Component for AddedContext {
image_task: Task::ready(None).shared(),
},
None,
PathStyle::local(),
cx,
),
);
@@ -767,7 +815,8 @@ mod tests {
full_path: None,
};
let added_context = AddedContext::image(image_context, Some(&model), cx);
let added_context =
AddedContext::image(image_context, Some(&model), PathStyle::local(), cx);
assert!(matches!(
added_context.status,
@@ -790,7 +839,7 @@ mod tests {
full_path: None,
};
let added_context = AddedContext::image(image_context, None, cx);
let added_context = AddedContext::image(image_context, None, PathStyle::local(), cx);
assert!(
matches!(added_context.status, ContextStatus::Ready),

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use ai_onboarding::{AgentPanelOnboardingCard, PlanDefinitions};
use client::zed_urls;
use cloud_llm_client::{Plan, PlanV1};
use cloud_llm_client::{Plan, PlanV2};
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
use ui::{Divider, Tooltip, prelude::*};
@@ -112,7 +112,7 @@ impl Component for EndTrialUpsell {
Some(
v_flex()
.child(EndTrialUpsell {
plan: Plan::V1(PlanV1::ZedFree),
plan: Plan::V2(PlanV2::ZedFree),
dismiss_upsell: Arc::new(|_, _| {}),
})
.into_any_element(),

View File

@@ -40,7 +40,7 @@ impl AgentOnboardingModal {
}
fn view_blog(&mut self, _: &ClickEvent, _: &mut Window, cx: &mut Context<Self>) {
cx.open_url("http://zed.dev/blog/fastest-ai-code-editor");
cx.open_url("https://zed.dev/blog/fastest-ai-code-editor");
cx.notify();
agent_onboarding_event!("Blog Link Clicked");

View File

@@ -12,8 +12,8 @@ impl UnavailableEditingTooltip {
}
impl Render for UnavailableEditingTooltip {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
tooltip_container(window, cx, |this, _, _| {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
tooltip_container(cx, |this, _| {
this.child(Label::new("Unavailable Editing")).child(
div().max_w_64().child(
Label::new(format!(

View File

@@ -18,7 +18,6 @@ default = []
client.workspace = true
cloud_llm_client.workspace = true
component.workspace = true
feature_flags.workspace = true
gpui.workspace = true
language_model.workspace = true
serde.workspace = true

View File

@@ -18,7 +18,6 @@ pub use young_account_banner::YoungAccountBanner;
use std::sync::Arc;
use client::{Client, UserStore, zed_urls};
use feature_flags::{BillingV2FeatureFlag, FeatureFlagAppExt as _};
use gpui::{AnyElement, Entity, IntoElement, ParentElement};
use ui::{Divider, RegisterComponent, Tooltip, prelude::*};
@@ -85,7 +84,7 @@ impl ZedAiOnboarding {
self
}
fn render_sign_in_disclaimer(&self, cx: &mut App) -> AnyElement {
fn render_sign_in_disclaimer(&self, _cx: &mut App) -> AnyElement {
let signing_in = matches!(self.sign_in_status, SignInStatus::SigningIn);
v_flex()
@@ -96,7 +95,7 @@ impl ZedAiOnboarding {
.color(Color::Muted)
.mb_2(),
)
.child(PlanDefinitions.pro_plan(cx.has_flag::<BillingV2FeatureFlag>(), false))
.child(PlanDefinitions.pro_plan(true, false))
.child(
Button::new("sign_in", "Try Zed Pro for Free")
.disabled(signing_in)
@@ -307,7 +306,7 @@ impl RenderOnce for ZedAiOnboarding {
fn render(self, _window: &mut ui::Window, cx: &mut App) -> impl IntoElement {
if matches!(self.sign_in_status, SignInStatus::SignedIn) {
match self.plan {
None => self.render_free_plan_state(cx.has_flag::<BillingV2FeatureFlag>(), cx),
None => self.render_free_plan_state(true, cx),
Some(plan @ (Plan::V1(PlanV1::ZedFree) | Plan::V2(PlanV2::ZedFree))) => {
self.render_free_plan_state(plan.is_v2(), cx)
}
@@ -372,7 +371,7 @@ impl Component for ZedAiOnboarding {
"Free Plan",
onboarding(
SignInStatus::SignedIn,
Some(Plan::V1(PlanV1::ZedFree)),
Some(Plan::V2(PlanV2::ZedFree)),
false,
),
),
@@ -380,7 +379,7 @@ impl Component for ZedAiOnboarding {
"Pro Trial",
onboarding(
SignInStatus::SignedIn,
Some(Plan::V1(PlanV1::ZedProTrial)),
Some(Plan::V2(PlanV2::ZedProTrial)),
false,
),
),
@@ -388,7 +387,7 @@ impl Component for ZedAiOnboarding {
"Pro Plan",
onboarding(
SignInStatus::SignedIn,
Some(Plan::V1(PlanV1::ZedPro)),
Some(Plan::V2(PlanV2::ZedPro)),
false,
),
),

View File

@@ -2,7 +2,6 @@ use std::sync::Arc;
use client::{Client, UserStore, zed_urls};
use cloud_llm_client::{Plan, PlanV1, PlanV2};
use feature_flags::{BillingV2FeatureFlag, FeatureFlagAppExt};
use gpui::{AnyElement, App, Entity, IntoElement, RenderOnce, Window};
use ui::{CommonAnimationExt, Divider, Vector, VectorName, prelude::*};
@@ -50,9 +49,7 @@ impl AiUpsellCard {
impl RenderOnce for AiUpsellCard {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let is_v2_plan = self
.user_plan
.map_or(cx.has_flag::<BillingV2FeatureFlag>(), |plan| plan.is_v2());
let is_v2_plan = self.user_plan.map_or(true, |plan| plan.is_v2());
let pro_section = v_flex()
.flex_grow()
@@ -215,7 +212,7 @@ impl RenderOnce for AiUpsellCard {
.child(
footer_container
.child(
Button::new("start_trial", "Start 14-day Free Pro Trial")
Button::new("start_trial", "Start Pro Trial")
.full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
.when_some(self.tab_index, |this, tab_index| {
@@ -230,7 +227,7 @@ impl RenderOnce for AiUpsellCard {
}),
)
.child(
Label::new("No credit card required")
Label::new("14 days, no credit card required")
.size(LabelSize::Small)
.color(Color::Muted),
),
@@ -327,7 +324,7 @@ impl Component for AiUpsellCard {
sign_in_status: SignInStatus::SignedIn,
sign_in: Arc::new(|_, _| {}),
account_too_young: false,
user_plan: Some(Plan::V1(PlanV1::ZedFree)),
user_plan: Some(Plan::V2(PlanV2::ZedFree)),
tab_index: Some(1),
}
.into_any_element(),
@@ -338,7 +335,7 @@ impl Component for AiUpsellCard {
sign_in_status: SignInStatus::SignedIn,
sign_in: Arc::new(|_, _| {}),
account_too_young: true,
user_plan: Some(Plan::V1(PlanV1::ZedFree)),
user_plan: Some(Plan::V2(PlanV2::ZedFree)),
tab_index: Some(1),
}
.into_any_element(),
@@ -349,7 +346,7 @@ impl Component for AiUpsellCard {
sign_in_status: SignInStatus::SignedIn,
sign_in: Arc::new(|_, _| {}),
account_too_young: false,
user_plan: Some(Plan::V1(PlanV1::ZedProTrial)),
user_plan: Some(Plan::V2(PlanV2::ZedProTrial)),
tab_index: Some(1),
}
.into_any_element(),
@@ -360,7 +357,7 @@ impl Component for AiUpsellCard {
sign_in_status: SignInStatus::SignedIn,
sign_in: Arc::new(|_, _| {}),
account_too_young: false,
user_plan: Some(Plan::V1(PlanV1::ZedPro)),
user_plan: Some(Plan::V2(PlanV2::ZedPro)),
tab_index: Some(1),
}
.into_any_element(),

View File

@@ -7,33 +7,62 @@ pub struct PlanDefinitions;
impl PlanDefinitions {
pub const AI_DESCRIPTION: &'static str = "Zed offers a complete agentic experience, with robust editing and reviewing features to collaborate with AI.";
pub fn free_plan(&self, _is_v2: bool) -> impl IntoElement {
List::new()
.child(ListBulletItem::new("50 prompts with Claude models"))
.child(ListBulletItem::new("2,000 accepted edit predictions"))
}
pub fn pro_trial(&self, _is_v2: bool, period: bool) -> impl IntoElement {
List::new()
.child(ListBulletItem::new("150 prompts with Claude models"))
.child(ListBulletItem::new(
"Unlimited edit predictions with Zeta, our open-source model",
))
.when(period, |this| {
this.child(ListBulletItem::new(
"Try it out for 14 days for free, no credit card required",
pub fn free_plan(&self, is_v2: bool) -> impl IntoElement {
if is_v2 {
List::new()
.child(ListBulletItem::new("2,000 accepted edit predictions"))
.child(ListBulletItem::new(
"Unlimited prompts with your AI API keys",
))
})
.child(ListBulletItem::new(
"Unlimited use of external agents like Claude Code",
))
} else {
List::new()
.child(ListBulletItem::new("50 prompts with Claude models"))
.child(ListBulletItem::new("2,000 accepted edit predictions"))
}
}
pub fn pro_plan(&self, _is_v2: bool, price: bool) -> impl IntoElement {
List::new()
.child(ListBulletItem::new("500 prompts with Claude models"))
.child(ListBulletItem::new(
"Unlimited edit predictions with Zeta, our open-source model",
))
.when(price, |this| {
this.child(ListBulletItem::new("$20 USD per month"))
})
pub fn pro_trial(&self, is_v2: bool, period: bool) -> impl IntoElement {
if is_v2 {
List::new()
.child(ListBulletItem::new("Unlimited edit predictions"))
.child(ListBulletItem::new("$20 of tokens"))
.when(period, |this| {
this.child(ListBulletItem::new(
"Try it out for 14 days, no credit card required",
))
})
} else {
List::new()
.child(ListBulletItem::new("150 prompts with Claude models"))
.child(ListBulletItem::new(
"Unlimited edit predictions with Zeta, our open-source model",
))
.when(period, |this| {
this.child(ListBulletItem::new(
"Try it out for 14 days, no credit card required",
))
})
}
}
pub fn pro_plan(&self, is_v2: bool, price: bool) -> impl IntoElement {
if is_v2 {
List::new()
.child(ListBulletItem::new("Unlimited edit predictions"))
.child(ListBulletItem::new("$5 of tokens"))
.child(ListBulletItem::new("Usage-based billing beyond $5"))
} else {
List::new()
.child(ListBulletItem::new("500 prompts with Claude models"))
.child(ListBulletItem::new(
"Unlimited edit predictions with Zeta, our open-source model",
))
.when(price, |this| {
this.child(ListBulletItem::new("$20 USD per month"))
})
}
}
}

View File

@@ -6,7 +6,7 @@ pub struct YoungAccountBanner;
impl RenderOnce for YoungAccountBanner {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
const YOUNG_ACCOUNT_DISCLAIMER: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for free plan usage or Pro plan free trial. To request an exception, reach out to billing-support@zed.dev.";
const YOUNG_ACCOUNT_DISCLAIMER: &str = "To prevent abuse of our service, GitHub accounts created fewer than 30 days ago are not eligible for the Pro trial. You can request an exception by reaching out to billing-support@zed.dev";
let label = div()
.w_full()

View File

@@ -67,7 +67,6 @@ pub enum Model {
alias = "claude-opus-4-1-thinking-latest"
)]
ClaudeOpus4_1Thinking,
#[default]
#[serde(rename = "claude-sonnet-4", alias = "claude-sonnet-4-latest")]
ClaudeSonnet4,
#[serde(
@@ -75,6 +74,14 @@ pub enum Model {
alias = "claude-sonnet-4-thinking-latest"
)]
ClaudeSonnet4Thinking,
#[default]
#[serde(rename = "claude-sonnet-4-5", alias = "claude-sonnet-4-5-latest")]
ClaudeSonnet4_5,
#[serde(
rename = "claude-sonnet-4-5-thinking",
alias = "claude-sonnet-4-5-thinking-latest"
)]
ClaudeSonnet4_5Thinking,
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
Claude3_7Sonnet,
#[serde(
@@ -133,6 +140,14 @@ impl Model {
return Ok(Self::ClaudeOpus4);
}
if id.starts_with("claude-sonnet-4-5-thinking") {
return Ok(Self::ClaudeSonnet4_5Thinking);
}
if id.starts_with("claude-sonnet-4-5") {
return Ok(Self::ClaudeSonnet4_5);
}
if id.starts_with("claude-sonnet-4-thinking") {
return Ok(Self::ClaudeSonnet4Thinking);
}
@@ -180,6 +195,8 @@ impl Model {
Self::ClaudeOpus4_1Thinking => "claude-opus-4-1-thinking-latest",
Self::ClaudeSonnet4 => "claude-sonnet-4-latest",
Self::ClaudeSonnet4Thinking => "claude-sonnet-4-thinking-latest",
Self::ClaudeSonnet4_5 => "claude-sonnet-4-5-latest",
Self::ClaudeSonnet4_5Thinking => "claude-sonnet-4-5-thinking-latest",
Self::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
Self::Claude3_7Sonnet => "claude-3-7-sonnet-latest",
Self::Claude3_7SonnetThinking => "claude-3-7-sonnet-thinking-latest",
@@ -197,6 +214,7 @@ impl Model {
Self::ClaudeOpus4 | Self::ClaudeOpus4Thinking => "claude-opus-4-20250514",
Self::ClaudeOpus4_1 | Self::ClaudeOpus4_1Thinking => "claude-opus-4-1-20250805",
Self::ClaudeSonnet4 | Self::ClaudeSonnet4Thinking => "claude-sonnet-4-20250514",
Self::ClaudeSonnet4_5 | Self::ClaudeSonnet4_5Thinking => "claude-sonnet-4-5-20250929",
Self::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
Self::Claude3_7Sonnet | Self::Claude3_7SonnetThinking => "claude-3-7-sonnet-latest",
Self::Claude3_5Haiku => "claude-3-5-haiku-latest",
@@ -215,6 +233,8 @@ impl Model {
Self::ClaudeOpus4_1Thinking => "Claude Opus 4.1 Thinking",
Self::ClaudeSonnet4 => "Claude Sonnet 4",
Self::ClaudeSonnet4Thinking => "Claude Sonnet 4 Thinking",
Self::ClaudeSonnet4_5 => "Claude Sonnet 4.5",
Self::ClaudeSonnet4_5Thinking => "Claude Sonnet 4.5 Thinking",
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
Self::Claude3_7SonnetThinking => "Claude 3.7 Sonnet Thinking",
@@ -236,6 +256,8 @@ impl Model {
| Self::ClaudeOpus4_1Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::ClaudeSonnet4_5
| Self::ClaudeSonnet4_5Thinking
| Self::Claude3_5Sonnet
| Self::Claude3_5Haiku
| Self::Claude3_7Sonnet
@@ -261,6 +283,8 @@ impl Model {
| Self::ClaudeOpus4_1Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::ClaudeSonnet4_5
| Self::ClaudeSonnet4_5Thinking
| Self::Claude3_5Sonnet
| Self::Claude3_5Haiku
| Self::Claude3_7Sonnet
@@ -280,6 +304,8 @@ impl Model {
| Self::ClaudeOpus4_1Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::ClaudeSonnet4_5
| Self::ClaudeSonnet4_5Thinking
| Self::Claude3_5Sonnet
| Self::Claude3_7Sonnet
| Self::Claude3_7SonnetThinking
@@ -299,6 +325,8 @@ impl Model {
| Self::ClaudeOpus4_1Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::ClaudeSonnet4_5
| Self::ClaudeSonnet4_5Thinking
| Self::Claude3_5Sonnet
| Self::Claude3_7Sonnet
| Self::Claude3_7SonnetThinking
@@ -318,6 +346,7 @@ impl Model {
Self::ClaudeOpus4
| Self::ClaudeOpus4_1
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4_5
| Self::Claude3_5Sonnet
| Self::Claude3_7Sonnet
| Self::Claude3_5Haiku
@@ -327,6 +356,7 @@ impl Model {
Self::ClaudeOpus4Thinking
| Self::ClaudeOpus4_1Thinking
| Self::ClaudeSonnet4Thinking
| Self::ClaudeSonnet4_5Thinking
| Self::Claude3_7SonnetThinking => AnthropicModelMode::Thinking {
budget_tokens: Some(4_096),
},

View File

@@ -16,8 +16,15 @@ anyhow.workspace = true
futures.workspace = true
gpui.workspace = true
net.workspace = true
parking_lot.workspace = true
smol.workspace = true
log.workspace = true
tempfile.workspace = true
util.workspace = true
workspace-hack.workspace = true
zeroize.workspace = true
[target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true
[package.metadata.cargo-machete]
ignored = ["log"]

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