Compare commits

..

443 Commits

Author SHA1 Message Date
Ben Brandt
a6a5f55a05 Add GitHub token authentication to HTTP client
Automatically adds GitHub authentication headers when GITHUB_TOKEN
environment variable is set.
2025-06-05 20:22:38 +02:00
Ben Brandt
dda614091a eval: Add eval unit tests as a CI job (#32152)
We run the unit evals once a day in the middle of the night, and trigger
a Slack post if it fails.


Release Notes:

- N/A

---------

Co-authored-by: Oleksiy Syvokon <oleksiy.syvokon@gmail.com>
2025-06-05 13:16:27 +00:00
Hans
fa9da6ad5b Fix typo (#32160)
Release Notes:

- N/A
2025-06-05 12:59:22 +00:00
Piotr Osiewicz
d082cfdbec lsp: Fix language servers not starting up on save (#32156)
Closes #24349

Release Notes:

- Fixed language servers not starting up when a buffer is saved.

---------

Co-authored-by: 张小白 <364772080@qq.com>
2025-06-05 14:22:34 +02:00
张小白
c71791d64e windows: Fix Japanese IME (#32153)
Fixed an issue where pressing `Escape` wouldn’t clear all pre-edit text
when using Japanese IME.


Release Notes:

- N/A
2025-06-05 12:13:09 +00:00
InfyniteHeap
244d8517f1 Fix Unexpected Console Window When Running Zed Release Build (#32144)
The commit #31073 had introduced `zed-main.rs`, which replaced the
previous `main.rs` to be the "true" entry of the whole program. But as
the macro `#![cfg_attr(not(debug_assertions), windows_subsystem =
"windows")]` only works in the "true" entry, the release build will also
arise the console window if this macro doesn't move to the new entry
(the `zed-main.rs` here).


Release Notes:

- N/A
2025-06-05 19:38:19 +08:00
Oleksiy Syvokon
3884de937b assistant: Partial fix for HTML entities in tools params (#32148)
This problem seems to be specific to Opus 4. Eval shows improvement from
89% to 97%.

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

Release Notes:

- N/A

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-06-05 10:36:55 +00:00
Richard Feldman
8af984ae70 Have tools respect private and excluded file settings (#32036)
Based on a Slack conversation with @notpeter - this prevents secrets in
private/excluded files from being sent by the agent to third parties for
tools that don't require confirmation.

Of course, the agent can still use the terminal tool or MCP to access
these, but those require confirmation before they run (unlike these
tools).

This change doesn't seem to cause any trouble for evals:

<img width="730" alt="Screenshot 2025-06-03 at 8 48 33 PM"
src="https://github.com/user-attachments/assets/d90221be-f946-4af2-b57b-4aa047e86853"
/>


Release Notes:

- N/A
2025-06-05 10:02:11 +02:00
Kirill Bulatov
9d533f9d30 Allow to reuse windows in open remote projects dialogue (#32138)
Closes https://github.com/zed-industries/zed/issues/26276

Same as other "open window" actions like "open recent", add a
`"create_new_window": false` (default `false`) argument into the
`projects::OpenRemote` action.

Make all menus to use this default; allow users to change this in the
keybindings.
Same as with other actions, `cmd`/`ctrl` inverts the parameter value.

<img width="554" alt="default"
src="https://github.com/user-attachments/assets/156d50f0-6511-47b3-b650-7a5133ae9541"
/>

<img width="552" alt="override"
src="https://github.com/user-attachments/assets/cf7d963b-86a3-4925-afec-fdb5414418e1"
/>

Release Notes:

- Allowed to reuse windows in open remote projects dialogue
2025-06-05 07:09:09 +00:00
Ben Swift
274a40b7e0 docs: Fix missing comma in MCP code snippet (#32126)
the docs now contain valid json

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-06-05 00:00:51 -03:00
Cole Miller
9c7b1d19ce Fix a panic in merge conflict parsing (#32119)
Release Notes:

- Fixed a panic that could occur when editing files containing merge
conflicts.
2025-06-04 20:05:26 -04:00
Michael Sloan
3d9881121f Reapply support for pasting images on x11 (#32121)
This brings back [linux(x11): Add support for pasting images from
clipboard · Pull Request
#29387](https://github.com/zed-industries/zed/pull/29387) while fixing
#30523 (which caused it to be reverted).

Commit message from that PR:

> Closes:
https://github.com/zed-industries/zed/pull/29177#issuecomment-2823359242
>
> Removes dependency on
[quininer/x11-clipboard](https://github.com/quininer/x11-clipboard) as
it is in [maintenance
mode](https://github.com/quininer/x11-clipboard/issues/19).
>
> X11 clipboard functionality is now built-in to GPUI which was
accomplished by stripping the non-x11-related code/abstractions from
[1Password/arboard](https://github.com/1Password/arboard) and extending
it to support all image formats already supported by GPUI on wayland and
macos.
>
> A benefit of switching over to the `arboard` implementation, is that
we now make an attempt to have an X11 "clipboard manager" (if available
- something the user has to setup themselves) save the contents of
clipboard (if the last copy operation was within Zed) so that the copied
contents can still be pasted once Zed has completely stopped.

Before the fix for reapply, it was iterating through the formats and
requesting conversion to each. Some clipboard providers just respond
with a different format rather than saying the format is unsupported.
The fix is to use this response if it matches a supported format. It
also now typically avoids this iteration by requesting the `TARGETS` and
taking the highest precedence supported target.

Closes #30523

Release Notes:

- Linux (X11): Restored the ability to paste images.

---------

Co-authored-by: Ben <ben@zed.dev>
2025-06-05 00:05:11 +00:00
Conrad Irwin
a2e98e9f0e Fix potential race-condition in DisplayLink::drop on macOS (#32116)
Fix a segfault in CVDisplayLink

We see 1-2 crashes a day on macOS on the `CVDisplayLink` thread.

```
Segmentation fault: 11 on thread 9325960 (CVDisplayLink)
CoreVideo	CVHWTime::reset()
CoreVideo	CVXTime::reset()
CoreVideo	CVDisplayLink::runIOThread()
libsystem_pthread.dylib	_pthread_start
libsystem_pthread.dylib	thread_start
```

With the help of the Zed AI, I dove into the crash report, which looks
like this:

```
Crashed Thread:        49  CVDisplayLink

Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       KERN_INVALID_ADDRESS at 0x00000000000001f6
Exception Codes:       0x0000000000000001, 0x00000000000001f6

Thread 49 Crashed:: CVDisplayLink
0   CoreVideo                     	       0x18c1ed994 CVHWTime::reset() + 64
1   CoreVideo                     	       0x18c1ee474 CVXTime::reset() + 52
2   CoreVideo                     	       0x18c1ee198 CVDisplayLink::runIOThread() + 176
3   libsystem_pthread.dylib       	       0x18285ac0c _pthread_start + 136
4   libsystem_pthread.dylib       	       0x182855b80 thread_start + 8

Thread 49 crashed with ARM Thread State (64-bit):
    x0: 0x0000000000000000   x1: 0x000000018c206e08   x2: 0x0000002c00001513   x3: 0x0001d4630002a433
    x4: 0x00000e2100000000   x5: 0x0001d46300000000   x6: 0x000000000000002c   x7: 0x0000000000000000
    x8: 0x000000000000002e   x9: 0x000000004d555458  x10: 0x0000000000000000  x11: 0x0000000000000000
   x12: 0x0000000000000000  x13: 0x0000000000000000  x14: 0x0000000000000000  x15: 0x0000000000000000
   x16: 0x0000000182856a9c  x17: 0x00000001f19bc540  x18: 0x0000000000000000  x19: 0x0000600003c56ed8
   x20: 0x000000000002a433  x21: 0x0000000000000000  x22: 0x0000000000000000  x23: 0x0000000000000000
   x24: 0x0000000000000000  x25: 0x0000000000000000  x26: 0x0000000000000000  x27: 0x0000000000000000
   x28: 0x0000000000000000   fp: 0x000000016b02ade0   lr: 0x000000018c1ed984
    sp: 0x000000016b02adc0   pc: 0x000000018c1ed994 cpsr: 0x80001000
   far: 0x00000000000001f6  esr: 0x92000006 (Data Abort) byte read Translation fault

Binary Images:
       0x1828c9000 -        0x182e07fff com.apple.CoreFoundation (6.9) <df489a59-b4f6-32b8-9bb4-9b832960aa52> /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
```

Using lldb to disassemble `CVHWTime::reset()` (and the AI to interpret
it), the crash is caused by dereferencing the pointer at the start of
the CVHWTime struct + 0x1c8. In this case the pointer has (the clearly
nonsense) value 0x2e (and 0x2e + 0x1c8 = 0x1f6, the failing address).

As to how this could happen...

Looking at the implementation of `CVDisplayLinkRelease`, it calls
straight into `CFRelease` on the main thread; and so it is not safe to
call `CVDisplayLinkRelease` concurrently with other threads that access
the CVDisplayLink. While we already stopped the display link, it turns
out that `CVDisplayLinkStop` just sets a flag on the struct to instruct
the io-thread to exit "soon", and returns immediately. That means we
don't know when the other thread will actually exit, and so we can't
safely call `CVDisplayLinkRelease`.

So, for now, we just leak these objects. They should be created
relatively infrequently (when the app is foregrounded/backgrounded), so
I don't think this is a huge problem.

Release Notes:

- Fix a rare crash on macOS when putting the app in the background.
2025-06-04 17:10:27 -06:00
Smit Barmase
7c64737e00 project_panel: Fix drop highlight is not being removed when esc is pressed (#32115)
Release Notes:

- Fixed the issue where pressing `esc` would cancel the drag-and-drop
operation but wouldn’t clear the drop highlight on directories.
2025-06-05 03:53:59 +05:30
Cole Miller
8191a5339d Make editor::Rewrap respect paragraphs (#32046)
Closes #32021 

Release Notes:

- Changed the behavior of `editor::Rewrap` to not join paragraphs
together.
2025-06-04 22:14:38 +00:00
Ben Kunkle
17c3b741ec Validate actions in docs (#31073)
Adds a validation step to docs preprocessing so that actions referenced
in docs are checked against the list of all registered actions in GPUI.

In order for this to work properly, all of the crates that register
actions had to be importable by the `docs_preprocessor` crate and
actually used (see [this
comment](ec16e70336 (diff-2674caf14ae6d70752ea60c7061232393d84e7f61a52915ace089c30a797a1c3))
for why this is challenging).

In order to accomplish this I have moved the entry point of zed into a
separate stub file named `zed_main.rs` so that `main.rs` is importable
by the `docs_preprocessor` crate, this is kind of gross, but ensures
that all actions that are registered in the application are registered
when checking them in `docs_preprocessor`. An alternative solution
suggested by @mikayla-maki was to separate out all our `::init()`
functions into a lib entry point in the `zed` crate that can be imported
instead, however, this turned out to be a far bigger refactor and is in
my opinion better to do in a follow up PR with significant testing to
ensure no regressions in behavior occur.

Release Notes:

- N/A
2025-06-04 19:18:12 +00:00
Martin Pool
52770cd3ad docs: Fix the database path on Linux (and BSD) (#32072)
Updated to reflect the logic in
https://github.com/zed-industries/zed/blob/main/crates/paths/src/paths.rs.

Release Notes:

- N/A
2025-06-04 21:00:27 +03:00
Antonio Scandurra
4ac67ac5ae Automatically keep edits if they are included in a commit (#32093)
Release Notes:

- Improved the review experience in the agent panel. Now, when you
commit changes (generated by the AI agent) using Git, Zed will
automatically dismiss the agent’s review UI for those changes. This
means you won’t have to manually “keep” or approve changes twice—just
commit, and you’re done.
2025-06-04 19:54:24 +02:00
Aaron Ruan
8c1b549683 workspace: Add setting to make dock resize apply to all panels (#30551)
Re: #19015
Close #12667

When dragging a dock’s resize handle, only the active panel grows or
shrinks. This patch introduces an opt-in behaviour that lets users
resize every panel hosted by that dock at once.

Release Notes:

- Added new `resize_all_panels_in_dock` setting to optionally resize
every panel in a dock together.

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2025-06-04 17:40:35 +00:00
Kirill Bulatov
ff6ac60bad Allow running certain Zed actions when headless (#32095)
Rework of https://github.com/zed-industries/zed/pull/30783

Before:

<img width="483" alt="before_1"
src="https://github.com/user-attachments/assets/c08531ce-0c1c-4a91-8375-4542220fc1b1"
/>

<img width="250" alt="before_2"
src="https://github.com/user-attachments/assets/e6f5404e-4e00-4125-bf2b-59a5bc6c41c1"
/>

<img width="369" alt="before_3"
src="https://github.com/user-attachments/assets/6a17c63d-80f6-4d91-a63b-69a9d8fe533a"
/>

After:

<img width="443" alt="after_1"
src="https://github.com/user-attachments/assets/4f7203c2-0065-41da-b7df-02aeba89ab7b"
/>

<img width="246" alt="after_2"
src="https://github.com/user-attachments/assets/585e2e25-bf06-4cdc-bfa5-930e0405c8d0"
/>

<img width="371" alt="after_3"
src="https://github.com/user-attachments/assets/54585f1a-6a9b-45a3-9d77-b0bb1ace580b"
/>


Release Notes:

- Allowed running certain Zed actions when headless
2025-06-04 17:29:08 +00:00
Max Brunsfeld
f8ab51307a Bump tree-sitter-bash to 0.25 (#32091)
Closes https://github.com/zed-industries/zed/issues/23703

Release Notes:

- Fixed a crash that could occur when editing bash files
2025-06-04 13:22:34 -04:00
Nathan Sobo
0a2186c87b Add channel reordering functionality (#31833)
Release Notes:

- Added channel reordering for administrators (use `cmd-up` and
`cmd-down` on macOS or `ctrl-up` `ctrl-down` on Linux to move channels
up or down within their parent)

## Summary

This PR introduces the ability for channel administrators to reorder
channels within their parent context, providing better organizational
control over channel hierarchies. Users can now move channels up or down
relative to their siblings using keyboard shortcuts.

## Problem

Previously, channels were displayed in alphabetical order with no way to
customize their arrangement. This made it difficult for teams to
organize channels in a logical order that reflected their workflow or
importance, forcing users to prefix channel names with numbers or
special characters as a workaround.

## Solution

The implementation adds a persistent `channel_order` field to channels
that determines their display order within their parent. Channels with
the same parent are sorted by this field rather than alphabetically.

## Implementation Details

### Database Schema

Added a new column and index to support efficient ordering:

```sql
-- crates/collab/migrations/20250530175450_add_channel_order.sql
ALTER TABLE channels ADD COLUMN channel_order INTEGER NOT NULL DEFAULT 1;

CREATE INDEX CONCURRENTLY "index_channels_on_parent_path_and_order" ON "channels" ("parent_path", "channel_order");
```

### RPC Protocol

Extended the channel proto with ordering support:

```proto
// crates/proto/proto/channel.proto
message Channel {
    uint64 id = 1;
    string name = 2;
    ChannelVisibility visibility = 3;
    int32 channel_order = 4;
    repeated uint64 parent_path = 5;
}

message ReorderChannel {
    uint64 channel_id = 1;
    enum Direction {
        Up = 0;
        Down = 1;
    }
    Direction direction = 2;
}
```

### Server-side Logic

The reordering is handled by swapping `channel_order` values between
adjacent channels:

```rust
// crates/collab/src/db/queries/channels.rs
pub async fn reorder_channel(
    &self,
    channel_id: ChannelId,
    direction: proto::reorder_channel::Direction,
    user_id: UserId,
) -> Result<Vec<Channel>> {
    // Find the sibling channel to swap with
    let sibling_channel = match direction {
        proto::reorder_channel::Direction::Up => {
            // Find channel with highest order less than current
            channel::Entity::find()
                .filter(
                    channel::Column::ParentPath
                        .eq(&channel.parent_path)
                        .and(channel::Column::ChannelOrder.lt(channel.channel_order)),
                )
                .order_by_desc(channel::Column::ChannelOrder)
                .one(&*tx)
                .await?
        }
        // Similar logic for Down...
    };
    
    // Swap the channel_order values
    let temp_order = channel.channel_order;
    channel.channel_order = sibling_channel.channel_order;
    sibling_channel.channel_order = temp_order;
}
```

### Client-side Sorting

Optimized the sorting algorithm to avoid O(n²) complexity:

```rust
// crates/collab/src/db/queries/channels.rs
// Pre-compute sort keys for efficient O(n log n) sorting
let mut channels_with_keys: Vec<(Vec<i32>, Channel)> = channels
    .into_iter()
    .map(|channel| {
        let mut sort_key = Vec::with_capacity(channel.parent_path.len() + 1);
        
        // Build sort key from parent path orders
        for parent_id in &channel.parent_path {
            sort_key.push(channel_order_map.get(parent_id).copied().unwrap_or(i32::MAX));
        }
        sort_key.push(channel.channel_order);
        
        (sort_key, channel)
    })
    .collect();

channels_with_keys.sort_by(|a, b| a.0.cmp(&b.0));
```

### User Interface

Added keyboard shortcuts and proper context handling:

```json
// assets/keymaps/default-macos.json
{
  "context": "CollabPanel && not_editing",
  "bindings": {
    "cmd-up": "collab_panel::MoveChannelUp",
    "cmd-down": "collab_panel::MoveChannelDown"
  }
}
```

The CollabPanel now properly sets context to distinguish between editing
and navigation modes:

```rust
// crates/collab_ui/src/collab_panel.rs
fn dispatch_context(&self, window: &Window, cx: &Context<Self>) -> KeyContext {
    let mut dispatch_context = KeyContext::new_with_defaults();
    dispatch_context.add("CollabPanel");
    dispatch_context.add("menu");
    
    let identifier = if self.channel_name_editor.focus_handle(cx).is_focused(window) {
        "editing"
    } else {
        "not_editing"
    };
    
    dispatch_context.add(identifier);
    dispatch_context
}
```

## Testing

Comprehensive tests were added to verify:
- Basic reordering functionality (up/down movement)
- Boundary conditions (first/last channels)
- Permission checks (non-admins cannot reorder)
- Ordering persistence across server restarts
- Correct broadcasting of changes to channel members

## Migration Strategy

Existing channels are assigned initial `channel_order` values based on
their current alphabetical sorting to maintain the familiar order users
expect:

```sql
UPDATE channels
SET channel_order = (
    SELECT ROW_NUMBER() OVER (
        PARTITION BY parent_path
        ORDER BY name, id
    )
    FROM channels c2
    WHERE c2.id = channels.id
);
```

## Future Enhancements

While this PR provides basic reordering functionality, potential future
improvements could include:
- Drag-and-drop reordering in the UI
- Bulk reordering operations
- Custom sorting strategies (by activity, creation date, etc.)

## Checklist

- [x] Database migration included
- [x] Tests added for new functionality
- [x] Keybindings work on macOS and Linux
- [x] Permissions properly enforced
- [x] Error handling implemented throughout
- [x] Manual testing completed
- [x] Documentation updated

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-06-04 16:56:33 +00:00
Danilo Leal
c3653f4cb1 docs: Update "Burn Mode" callout in the models page (#31995)
To be merged tomorrow after the release, which will make the "Burn Mode"
terminology live on stable.

Release Notes:

- N/A
2025-06-04 13:56:13 -03:00
Max Brunsfeld
8b28941c14 Bump Tree-sitter to 0.25.6 (#32090)
Fixes #31810 

Release Notes:

- Fixed a crash that could occur when editing YAML files.
2025-06-04 12:55:10 -04:00
Bennet Bo Fenner
aefb798090 inline assistant: Allow to attach images from clipboard (#32087)
Noticed while working on #31848 that we do not support pasting images as
context in the inline assistant

Release Notes:

- agent: Add support for attaching images as context from clipboard in
the inline assistant
2025-06-04 16:43:52 +00:00
Conrad Irwin
2c5aa5891d Don't show invisibles from inlays (#32088)
Closes #24266

Release Notes:

- Whitespace added by inlay hints is no longer shown when
`"show_whitespaces": "all"` is used.

-
2025-06-04 10:33:22 -06:00
Umesh Yadav
7d54d9f45e agent: Show warning for image context pill if model doesn't support images (#31848)
Closes #31781

Currently we don't any warning or error if the image is not supported by
the current model in selected in the agent panel which leads for users
to think it's supported as there is no visual feedback provided by zed.
This PR adds a warning on image context pill to show warning when the
model doesn't support it.

| Before | After |
|--------|-------|
| <img width="374" alt="image"
src="https://github.com/user-attachments/assets/da659fb6-d5da-4c53-8878-7a1c4553f168"
/> | <img width="442" alt="image"
src="https://github.com/user-attachments/assets/0f23d184-6095-47e2-8f2b-0eac64a0942e"
/> |

Release Notes:

- Show warning for image context pill in agent panel when selected model
doesn't support images.

---------

Signed-off-by: Umesh Yadav <git@umesh.dev>
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-06-04 16:20:56 +00:00
Oleksiy Syvokon
cde47e60cd assistant_tools: Disallow extra tool parameters by default (#32081)
This prevents models from hallucinating tool parameters.


Release Notes:

- Prevent models from hallucinating tool parameters
2025-06-04 16:11:40 +00:00
Peter Tripp
79f96a5afe docs: Improve LuaLS formatter example (#32084)
- Closes https://github.com/zed-extensions/lua/issues/4

Release Notes:

- N/A
2025-06-04 11:51:53 -04:00
Tommy D. Rossi
81058ee172 Make alt-left and alt-right skip punctuation like VSCode (#31977)
Closes https://github.com/zed-industries/zed/discussions/25526
Follow up of #29872

Release Notes:

- Make `alt-left` and `alt-right` skip punctuation on Mac OS to respect
the Mac default behaviour. When pressing alt-left and the first
character is a punctuation character like a dot, this character should
be skipped. For example: `hello.|` goes to `|hello.`

This change makes the editor feels much snappier, it now follows the
same behaviour as VSCode and any other Mac OS native application.


@ConradIrwin
2025-06-04 09:48:20 -06:00
Alejandro Fernández Gómez
89743117c6 vim: Add Ctrl-w ] and Ctrl-w Ctrl-] keymaps (#31990)
Closes #31989

Release Notes:

- Added support for `Ctrl-w ]` and `Ctrl-w Ctrl-]` to go to a definition
in a new split
2025-06-04 09:47:42 -06:00
Conrad Irwin
6de37fa57c Don't show squiggles on unnecesarry code (#32082)
Co-Authored-By: @davidhewitt <mail@davidhewitt.dev>

Closes #31747
Closes https://github.com/zed-industries/zed/issues/32080

Release Notes:

- Fixed a recently introduced bug where unnecessary code was underlined
with blue squiggles

Co-authored-by: @davidhewitt <mail@davidhewitt.dev>
2025-06-04 09:46:06 -06:00
Bennet Bo Fenner
beb0d49dc4 agent: Introduce ModelUsageContext (#32076)
This PR is a refactor of the existing `ModelType` in
`agent_model_selector`.

In #31848 we also need to know which context we are operating in, to
check if the configured model has image support.
In order to deduplicate the logic needed, I introduced a new type called
`ModelUsageContext` which can be used throughout the agent crate


Release Notes:

- N/A
2025-06-04 15:35:50 +00:00
Conrad Irwin
c9aadadc4b Add a script to connect to the database. (#32023)
This avoids needing passwords in plaintext on the command line....

Release Notes:

- N/A
2025-06-04 09:23:23 -06:00
Conrad Irwin
bcd182f480 A script to help with PR naggery (#32025)
Release Notes:

- N/A
2025-06-04 09:23:14 -06:00
Joseph T. Lyons
3987b60738 Set upstream tracking when pushing preview branch (#32075)
Release Notes:

- N/A
2025-06-04 10:42:50 -04:00
Joseph T. Lyons
827103908e Bump Zed to v0.191 (#32073)
Release Notes:

-N/A
2025-06-04 14:34:01 +00:00
Vitaly Slobodin
8e9e3ba1a5 ruby: Add sorbet and steep to the list of available language servers (#32008)
Hi, this pull request adds `sorbet` and `steep` to the list of available
language servers for the Ruby language in order to prepare default Ruby
language settings for these LS. Both language servers are disabled by
default. We plan to add both in #104 and #102. Thanks!

Release Notes:

- ruby: Added `sorbet` and `steep` to the list of available language servers.
2025-06-04 10:19:33 -04:00
Danilo Leal
676ed8fb8a agent: Use new has_pending_edit_tool_use state for toolbar review buttons (#32071)
Follow up to https://github.com/zed-industries/zed/pull/31971. Now, the
toolbar review buttons will also appear/be available at the same time as
the panel buttons.

Release Notes:

- N/A
2025-06-04 11:14:34 -03:00
Ben Brandt
4304521655 Remove unused load_model method from LanguageModelProvider (#32070)
Removes the load_model trait method and its implementations in Ollama
and LM Studio providers, along with associated preload_model functions
and unused imports.

Release Notes:

- N/A
2025-06-04 14:07:01 +00:00
Oleksiy Syvokon
04716a0e4a edit_file_tool: Fail when edit location is not unique (#32056)
When `<old_text>` points to more than one location in a file, we used to
edit the first match, confusing the agent along the way. Now we will
return an error, asking to expand `<old_text>` selection.

Closes #ISSUE

Release Notes:

- agent: Fixed incorrect file edits when edit locations are ambiguous
2025-06-04 13:04:01 +03:00
Kirill Bulatov
5e38915d45 Properly register buffers with reused language servers (#32057)
Follow-up of https://github.com/zed-industries/zed/pull/30707

The old code does something odd, re-accessing essentially the same
adapter-server pair for every language server initialized; but that was
done before for "incorrect", non-reused worktree_id hence never resulted
in external worktrees' files registration in this code path.

Release Notes:

- Fixed certain external worktrees' files sometimes not registered with
language servers
2025-06-04 09:59:57 +00:00
Alex
f9257b0efe debugger: Use UUID for Go debug binary names, do not rely on OUT_DIR (#32004)
It seems that there was a regression. `build_config` no longer has an
`OUT_DIR` in it.
On way to mitigate it is to stop relying on it and just use `cwd` as dir
for the test binary to be placed in.

Release Notes:
- N/A

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-06-04 11:18:04 +02:00
Wanten
5d0c96872b editor: Stabilize IME candidate box position during pre-edit on Wayland (#28429)
Modify the `replace_and_mark_text_in_range` method in the `Editor` to
keep the cursor at the start of the preedit range during IME
composition. Previously, the cursor would move to the end of the preedit
text with each update, causing the IME candidate box to shift (e.g.,
when typing pinyin with Fcitx5 on Wayland). This change ensures the
cursor and candidate box remain fixed until the composition is
committed, improving the IME experience.

Closes #21004

Release Notes:

- N/A

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: 张小白 <364772080@qq.com>
2025-06-04 09:14:01 +00:00
Umesh Yadav
071e684be4 bedrock: Fix ci failure due model enum and model name mismatch (#32049)
Release Notes:

- N/A
2025-06-04 10:41:12 +03:00
Shardul Vaidya
2280594408 bedrock: Allow users to pick Thinking vs. Non-Thinking models (#31600)
Release Notes:

- bedrock: Added ability to pick between Thinking and Non-Thinking models
2025-06-04 09:00:41 +03:00
Shardul Vaidya
09a1d51e9a bedrock: Fix Claude 4 output token bug (#31599)
Release Notes:

- Fixed an issue preventing the use of Claude 4 Thinking models with Bedrock
2025-06-04 08:57:31 +03:00
Umesh Yadav
ac15194d11 docs: Add OpenRouter agent support (#32011)
Update few other docs as well. Like recently tool support was added for
deepseek. Also there was recent thinking and images support for ollama
model.

Release Notes:

- N/A
2025-06-04 08:54:00 +03:00
Smit Barmase
988d834c33 project_panel: When initiating a drag the highlight selection should jump to the item you've picked up (#32044)
Closes #14496.

In https://github.com/zed-industries/zed/pull/31976, we modified the
highlighting behavior for entries when certain entries or paths are
being dragged over them. Instead of relying on marked entries for
highlighting, we introduced the `highlight_entry_id` parameter, which
determines which entry and its children should be highlighted when an
item is being dragged over it.

The rationale behind that is that we can now utilize marked entries for
various other functions, such as:

1. When dragging multiple items, we use marked entried to show which
items are being dragged. (This is already covered because to drag
multiple items, you need to use marked entries.)
2. When dragging a single item, set that item to marked entries. (This
PR)


https://github.com/user-attachments/assets/8a03bdd4-b5db-467d-b70f-53d9766fec52

Release Notes:

- Added highlighting to entries being dragged in the Project Panel,
indicating which items are being moved.
2025-06-04 08:30:51 +05:30
Michael Sloan
48eacf3f2a Add #[track_caller] to test utilities that involve marked text (#32043)
Release Notes:

- N/A
2025-06-04 02:37:27 +00:00
Smit Barmase
030d4d2631 project_panel: Holding alt or shift to copy the file should adds a green (+) icon to the mouse cursor (#32040)
Part of https://github.com/zed-industries/zed/issues/14496

Depends on new API https://github.com/zed-industries/zed/pull/32028

Holding `alt` or `shift` to copy the file should add a green (+) icon to
the mouse cursor to indicate this is a copy operation.

1. Press `option` first, then drag:


https://github.com/user-attachments/assets/ae58c441-f1ab-423e-be59-a8ec5cba33b0

2. Drag first, then press `option`:


https://github.com/user-attachments/assets/5136329f-9396-4ab9-a799-07d69cec89e2

Release Notes:

- Added copy-drag cursor when pressing Alt or Shift to copy the file in
Project Panel.
2025-06-04 07:16:56 +05:30
Smit Barmase
10df7b5eb9 gpui: Add API for read and write active drag cursor style (#32028)
Prep for https://github.com/zed-industries/zed/pull/32040

Currently, there’s no way to modify the cursor style of the active drag
state after dragging begins. However, there are scenarios where we might
want to change the cursor style, such as pressing a modifier key while
dragging. This PR introduces an API to update and read the current
active drag state cursor.

Release Notes:

- N/A
2025-06-04 06:53:03 +05:30
Haru Kim
55120c4231 Properly load environment variables from the login shell (#31799)
Fixes #11647
Fixes #13888
Fixes #18771
Fixes #19779
Fixes #22437
Fixes #23649
Fixes #24200
Fixes #27601

Zed’s current method of loading environment variables from the login
shell has two issues:
1. Some shells—​fish in particular—​​write specific escape characters to
`stdout` right before they exit. When this happens, the tail end of the
last environment variable printed by `/usr/bin/env` becomes corrupted.
2. If a multi-line value contains an equals sign, that line is
mis-parsed as a separate name-value pair.

This PR addresses those problems by:
1. Redirecting the shell command's `stdout` directly to a temporary
file, eliminating any side effects caused by the shell itself.
2. Replacing `/usr/bin/env` with `sh -c 'export -p'`, which removes
ambiguity when handling multi-line values.

Additional changes:
- Correctly set the arguments used to launch a login shell under `csh`
or `tcsh`.
- Deduplicate code by sharing the implementation that loads environment
variables on first run with the logic that reloads them for a project.



Release Notes:

- N/A
2025-06-03 19:16:26 -06:00
Piotr Osiewicz
8227c45a11 docs: Document debugger.dock setting (#32038)
Closes #ISSUE

Release Notes:

- N/A
2025-06-03 23:38:41 +00:00
Piotr Osiewicz
d23359e19a debugger: Fix issues with running Zed-installed debugpy + hangs when downloading (#32034)
Closes #32018

Release Notes:

- Fixed issues with launching Python debug adapter downloaded by Zed.
You might need to clear the old install of Debugpy from
`$HOME/.local/share/zed/debug_adapters/Debugpy` (Linux) or
`$HOME/Library/Application Support/Zed/debug_adapters/Debugpy` (Mac).
2025-06-04 01:37:25 +02:00
Kirill Bulatov
936ad0bf10 Use better fallback for empty rerun action (#32031)
When invoking the task rerun action before any task had been run, it
falls back to task selection modal.
Adjust this fall back to use the debugger view, if available:

Before:

![before](https://github.com/user-attachments/assets/737d2dc1-15a4-4eea-a5f9-4aff6c7600cc)


After:

![after](https://github.com/user-attachments/assets/43381b85-5167-44e7-a8b0-865a64eaa6ea)


Release Notes:

- N/A
2025-06-03 22:45:37 +00:00
Kirill Bulatov
faa0bb51c9 Better log canonicalization errors (#32030)
Based on
https://github.com/zed-industries/zed/issues/18673#issuecomment-2933025951

Adds an anyhow error context with the path used for canonicalization
(also, explicitly mention path at the place from the comment).

Release Notes:

- N/A
2025-06-03 22:30:59 +00:00
Joseph T. Lyons
2db2271e3c Do not activate inactive tabs when pinning or unpinning
Closes https://github.com/zed-industries/zed/issues/32024

Release Notes:

- Fixed a bug where inactive tabs would be activated when pinning or
unpinning.
2025-06-03 17:43:06 -04:00
Conrad Irwin
79b1dd7db8 Improve collab cleanup (#32000)
Co-authored-by: Max <max@zed.dev>
Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>

Release Notes:

- N/A
2025-06-03 15:27:28 -06:00
Anthony Eid
81f8e2ed4a Limit BufferSnapshot::surrounding_word search to 256 characters (#32016)
This is the first step to closing #16120. Part of the problem was that
`surrounding_word` would search the whole line for matches with no
limit.

Co-authored-by: Conrad Irwin \<conrad@zed.dev\>
Co-authored-by: Ben Kunkle \<ben@zed.dev\>
Co-authored-by: Cole Miller \<cole@zed.dev\>

Release Notes:

- N/A
2025-06-03 21:08:59 +00:00
Gilles Peiffer
b9256dd469 editor: Apply common_prefix_len refactor suggestion (#31957)
This adds João's nice suggestion from
https://github.com/zed-industries/zed/pull/31818#discussion_r2118582616.

Release Notes:

- N/A

---------

Co-authored-by: João Marcos <marcospb19@hotmail.com>
2025-06-03 15:07:14 -06:00
Bennet Bo Fenner
27d3da678c editor: Fix panic when full width crease is wrapped (#31960)
Closes #31919

Release Notes:

- Fixed a panic that could sometimes occur when the agent panel was too
narrow and contained context included via `@`.

---------

Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
2025-06-03 22:59:27 +02:00
Conrad Irwin
03357f3f7b Fix panic when re-editing old message with creases (#32017)
Co-authored-by: Cole Miller <m@cole-miller.net>

Release Notes:

- agent: Fixed a panic when re-editing old messages

---------

Co-authored-by: Cole Miller <m@cole-miller.net>
Co-authored-by: Cole Miller <cole@zed.dev>
2025-06-03 20:56:18 +00:00
Kirill Bulatov
4aabba6cf6 Improve Zed prompts for file path selection (#32014)
Part of https://github.com/zed-industries/zed/discussions/31653
`"use_system_path_prompts": false` is needed in settings for these to
appear as modals for new file save and file open.

Fixed a very subpar experience of the "save new file" Zed modal,
compared to a similar "open file path" Zed modal by uniting their code.

Before:


https://github.com/user-attachments/assets/c4082b70-6cdc-4598-a416-d491011c8ac4


After:



https://github.com/user-attachments/assets/21ca672a-ae40-426c-b68f-9efee4f93c8c


Also 

* alters both prompts to start in the current worktree directory, with
the fallback to home directory.
* adjusts the code to handle Windows paths better

Release Notes:

- Improved Zed prompts for file path selection

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-06-03 20:35:25 +00:00
Michael Sloan
8c46a4f594 Make completions menu stay open after after it's manually requested (#32015)
Also includes a clarity refactoring to remove
`ignore_completion_provider`.

Closes #15549

Release Notes:

- Fixed completions menu closing on typing after being requested while
`show_completions_on_input: false`.
2025-06-03 20:33:52 +00:00
Luke Naylor
522abe8e59 Change default formatter settings for LaTeX (#28727)
Closes: https://github.com/rzukic/zed-latex/issues/77 

## Default formatter: `latexindent`
Before, formatting was delegated to the language server, which just ran
a `latexindent` executable. There was no benefit to running it through
the language server over running it as an "external" formatter in zed.
In fact this was an issue because there was no way to provide an
explicit path for the executable (causing above extension issue). Having
the default settings configure the formatter directly gives more control
to user and removes the number of indirections making it clearer how to
tweak things like the executable path, or extra CLI args, etc...

## Alternative: `prettier`
Default settings have also been added to allow prettier as the formatter
(by just setting `"formatter": "prettier"` in the "LaTeX" language
settings). This was not possible before because an extra line needed to
be added to the `prettier` crate (similarly to what was done for
https://github.com/zed-industries/zed/issues/19024) to find the plugin
correctly.
> [!NOTE]
> The `prettier-plugin-latex` node module also contained a
`dist/standalone.js` but using that instead of
`dist/prettier-plugin-latex.js` gave an error, and indeed the latter
worked as intended (along with its questionable choices for formatting).

Release Notes:

- LaTeX: added default `latexindent` formatter settings without relying
on `texlab`, as well as allowing `prettier` to be chosen for formatting

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-06-03 19:51:30 +00:00
Michael Sloan
5ae8c4cf09 Fix word completions clobbering the text after the cursor (#32010)
Release Notes:

- N/A
2025-06-03 19:37:26 +00:00
Smit Barmase
d8195a8fd7 project_panel: Highlight containing folder which would be the target of the drop operation (#31976)
Part of https://github.com/zed-industries/zed/issues/14496

This PR adds highlighting on the containing folder which would be the
target of the drop operation. It only highlights those directories where
actual drop is possible, i.e. same directory where drag started is not
highlighted.

- [x] Tests


https://github.com/user-attachments/assets/46528467-e07a-4574-a8d5-beab25e70162

Release Notes:

- Improved project panel to show a highlight on the containing folder
which would be the target of the drop operation.
2025-06-04 00:34:37 +05:30
Danilo Leal
2645591cd5 agent: Allow to accept and reject all via the panel (#31971)
This PR introduces the "Reject All" and "Accept All" buttons in the
panel's edit bar, which appears as soon as the agent starts editing a
file. I'm also adding here a new method to the thread called
`has_pending_edit_tool_uses`, which is a more specific way of knowing,
in comparison to the `is_generating` method, whether or not the
reject/accept all actions can be triggered.

Previously, without this new method, you'd be waiting for the whole
generation to end (e.g., the agent would be generating markdown with
things like change summary) to be able to click those buttons, when the
edit was already there, ready for you. It always felt like waiting for
the whole thing was unnecessary when you really wanted to just wait for
the _edits_ to be done, as so to avoid any potential conflicting state.

<img
src="https://github.com/user-attachments/assets/0927f3a6-c9ee-46ae-8f7b-97157d39a7b5"
width="500"/>

---

Release Notes:

- agent: Added ability to reject and accept all changes from the agent
panel.

---------

Co-authored-by: Agus Zubiaga <hi@aguz.me>
2025-06-03 15:20:25 -03:00
Danilo Leal
526a7c0702 agent: Support AGENT.md and AGENTS.md as rules file names (#31998)
These started to be used more recently, so we should also support them.

Release Notes:

- agent: Added support for `AGENT.md` and `AGENTS.md` as rules file
names.
2025-06-03 15:19:39 -03:00
Danilo Leal
e793740168 agent: Refine rules library window design (#31994)
Just polishing up a bit the Rules Library design. I think the most
confusing part here was the icon that was being used to tag a rule as
default; I've heard feedback more than once saying that was confusing,
so I'm now switching to a rather standard star icon, which I'd assume is
well-understood as a "favoriting" affordance.

Release Notes:

- N/A
2025-06-03 14:59:17 -03:00
Danilo Leal
dea0a58727 docs: Update mentions to GitHub to use correct capitalization (#31996)
That type of thing... 😅 "Github" is the incorrect formatting; "GitHub"
is the correct.

Release Notes:

- N/A
2025-06-03 14:55:24 -03:00
Agus Zubiaga
b7abc9d493 agent: Display full terminal output without scrolling (#31922)
The terminal tool card used a fixed height and scrolling, but this meant
that it was too tall for commands that only outputted a few lines, and
the nested scrolling was undesirable.

This PR makes the card be as too as needed to fit the entire output (no
scrolling), and allows the user to collapse it to fewer lines when
applicable. Making it work the same way as the edit tool card. In fact,
both tools now use a shared UI component.


https://github.com/user-attachments/assets/1127e21d-1d41-4a4b-a99f-7cd70fccbb56


Release Notes:

- Agent: Display full terminal output
- Agent: Allow collapsing terminal output
2025-06-03 10:54:25 -07:00
Peter Tripp
01a77bb231 Add sql language docs (#32003)
Closes: https://github.com/zed-industries/zed/issues/9537

Pairs with removing `prettier-plugin-sql` from the sql extension:
- https://github.com/zed-extensions/sql/pull/19

Release Notes:

- N/A
2025-06-03 13:52:42 -04:00
Daniel Zhu
de225fd242 file_finder: Add option to create new file (#31567)
https://github.com/user-attachments/assets/7c8a05a1-8d59-4371-a1d6-a8cb82aa13b9

While implementing this, I noticed that currently when the search panel
displays only one result, the box oscillates a bit up and down like so:


https://github.com/user-attachments/assets/dd1520e2-fa0b-4307-b27a-984e69b0a644

Not sure how to fix this at the moment, maybe that could be another PR?

Release Notes:

- Add option to create new file in project search panel.
2025-06-03 10:44:57 -07:00
Oleksiy Syvokon
1bc052d76b docs: Gemini thinking budget configuration (#32002)
Release Notes:

- N/A
2025-06-03 20:41:42 +03:00
Ben Kunkle
29cb95a3ca Remove support for changing magnification of active pane (#31981)
Closes #4265
Closes #24600

This setting causes many visual defects, and introduces unnecessary
(maintenance) complexity. as seen by #4265 and #24600


CC: @iamnbutler - How do you feel about this? I recommend looking at
https://github.com/zed-industries/zed/pull/24150#issuecomment-2866706506
for more context

Release Notes:

- Removed support

---------

Co-authored-by: Peter <peter@zed.dev>
2025-06-03 13:32:32 -04:00
Cole Miller
1307b81721 Allow configuring custom git hosting providers in project settings (#31929)
Closes #29229

Release Notes:

- Extended the support for configuring custom git hosting providers to
cover project settings in addition to global settings.

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-06-03 12:23:01 -04:00
Piotr Osiewicz
203754d0db docs: Demote rdbg support in docs (#31993)
Closes #ISSUE

Release Notes:

- N/A
2025-06-03 16:12:21 +00:00
Umesh Yadav
c9c603b1d1 Add support for OpenRouter as a language model provider (#29496)
This pull request adds full integration with OpenRouter, allowing users
to access a wide variety of language models through a single API key.

**Implementation Details:**

* **Provider Registration:** Registers OpenRouter as a new language
model provider within the application's model registry. This includes UI
for API key authentication, token counting, streaming completions, and
tool-call handling.
* **Dedicated Crate:** Adds a new `open_router` crate to manage
interactions with the OpenRouter HTTP API, including model discovery and
streaming helpers.
* **UI & Configuration:** Extends workspace manifests, the settings
schema, icons, and default configurations to surface the OpenRouter
provider and its settings within the UI.
* **Readability:** Reformats JSON arrays within the settings files for
improved readability.

**Design Decisions & Discussion Points:**

* **Code Reuse:** I leveraged much of the existing logic from the
`openai` provider integration due to the significant similarities
between the OpenAI and OpenRouter API specifications.
* **Default Model:** I set the default model to `openrouter/auto`. This
model automatically routes user prompts to the most suitable underlying
model on OpenRouter, providing a convenient starting point.
* **Model Population Strategy:**
* <strike>I've implemented dynamic population of available models by
querying the OpenRouter API upon initialization.
* Currently, this involves three separate API calls: one for all models,
one for tool-use models, and one for models good at programming.
* The data from the tool-use API call sets a `tool_use` flag for
relevant models.
* The data from the programming models API call is used to sort the
list, prioritizing coding-focused models in the dropdown.</strike>
* <strike>**Feedback Welcome:** I acknowledge this multi-call approach
is API-intensive. I am open to feedback and alternative implementation
suggestions if the team believes this can be optimized.</strike>
    * **Update: Now this has been simplified to one api call.**
* **UI/UX Considerations:**
* <strike>Authentication Method: Currently, I've implemented the
standard API key input in settings, similar to other providers like
OpenAI/Anthropic. However, OpenRouter also supports OAuth 2.0 with PKCE.
This could offer a potentially smoother, more integrated setup
experience for users (e.g., clicking a button to authorize instead of
copy-pasting a key). Should we prioritize implementing OAuth PKCE now,
or perhaps add it as an alternative option later?</strike>(PKCE is not
straight forward and complicated so skipping this for now. So that we
can add the support and work on this later.)
* <strike>To visually distinguish models better suited for programming,
I've considered adding a marker (e.g., `</>` or `🧠`) next to their
names. Thoughts on this proposal?</strike>. (This will require a changes
and discussion across model provider. This doesn't fall under the scope
of current PR).
* OpenRouter offers 300+ models. The current implementation loads all of
them. **Feedback Needed:** Should we refine this list or implement more
sophisticated filtering/categorization for better usability?

**Motivation:**

This integration directly addresses one of the most highly upvoted
feature requests/discussions within the Zed community. Adding OpenRouter
support significantly expands the range of AI models accessible to
users.

I welcome feedback from the Zed team on this implementation and the
design choices made. I am eager to refine this feature and make it
available to users.

ISSUES: https://github.com/zed-industries/zed/discussions/16576

Release Notes:

- Added support for OpenRouter as a language model provider.

---------

Signed-off-by: Umesh Yadav <umesh4257@gmail.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-06-03 15:59:46 +00:00
Shardul Vaidya
e13b494c9e bedrock: Fix cross-region inference (#30659)
Closes #30535

Release Notes:

- AWS Bedrock: Add support for Meta Llama 4 Scout and Maverick models.
- AWS Bedrock: Fixed cross-region inference for all regions.
- AWS Bedrock: Updated all models available through Cross Region
inference.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-06-03 15:46:35 +00:00
little-dude
c0397727e0 language_models: Sort Ollama models by name (#31620)
Hello,

This is my first contribution so apologies if I'm not following the
proper process (I haven't seen anything special in
https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md). Also,
I have tested my changes manually, but I could not figure out an easy we
to instantiate a `LanguageModelSelector` in the unit tests, so I didn't
write a test. If you can provide some guidance I'd be happy to write a
test.

---

If the user configured the models with custom names via `display_name`,
we want the ollama models to be sorted based on the name that is
actually displayed.

~~The original issue is only about ollama but this change will also
affect the other providers.~~

Closes #30854

Release Notes:

- Ollama: Changed models to be sorted by name.
2025-06-03 15:37:08 +00:00
Marshall Bowers
9c2b90fb8f collab: Return subscription period from GET /billing/subscriptions (#31987)
This PR updates the `GET /billing/subscriptions` endpoint to return the
subscription period on them.

Release Notes:

- N/A
2025-06-03 15:29:08 +00:00
Marshall Bowers
d108e5f53c collab: Fix deserialization of create meter event response (#31982)
This PR fixes the deserialization of the create meter event response
from the Stripe API.

Release Notes:

- N/A
2025-06-03 15:23:38 +00:00
Marshall Bowers
2551bde1d3 collab: Increase number of returned extensions to 1,000 (#31983)
This PR increases the number of returned extensions from the extension
API to 1,000 (up from 500).

We'll need a better solution at some point, but for now we can keep
bumping this number.

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

Release Notes:

- N/A
2025-06-03 15:03:32 +00:00
Peter Tripp
e7de80c6ae ci: Improve Danger and ci.yml explicitness (#31979)
Allow colons after issue links and for them to in ul.
Change ci references from [self-hosted, test] to more explicit
[self-hosted, macOS]

Release Notes:

- N/A

---------

Co-authored-by: Ben Kunkle <ben.kunkle@gmail.com>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-06-03 10:54:04 -04:00
Peter Tripp
ae210eced8 Fix aggressive indent in shell scripts (#31973)
Closes: https://github.com/zed-industries/zed/issues/31774

Release Notes:

- N/A

Co-authored-by: Ben Kunkle <ben.kunkle@gmail.com>
2025-06-03 10:50:58 -04:00
Piotr Osiewicz
a9d99d8347 docs: Improve docs for debugger (around breakpoints and doc structure) (#31962)
Closes #ISSUE

Release Notes:

- N/A
2025-06-03 16:35:35 +02:00
Thiago Pacheco
3e6435eddc Fix Python virtual environment detection (#31934)
# Fix Python Virtual Environment Detection in Zed

## Problem

Zed was not properly detecting Python virtual environments when a
project didn't contain a `pyrightconfig.json` file. This caused Pyright
(the Python language server) to report `reportMissingImports` errors for
packages installed in virtual environments, even though the virtual
environment was correctly set up and worked fine in other editors.

The issue was that while Zed's `PythonToolchainProvider` correctly
detected virtual environments, this information wasn't being
communicated to Pyright in a format it could understand.

## Root Cause

The main issue was in how Zed communicated virtual environment
configuration to Pyright through the Language Server Protocol (LSP).
When Pyright requests workspace configuration, it expects virtual
environment settings (`venvPath` and `venv`) at the root level of the
configuration object - the same format used in `pyrightconfig.json`
files. However, Zed was attempting to place these settings in various
nested locations that Pyright wasn't checking.

## Solution

The fix involves several coordinated changes to ensure Pyright receives
virtual environment configuration in all the ways it might expect:

### 1. Enhanced Workspace Configuration (`workspace_configuration`
method)
- When a virtual environment is detected, Zed now sets `venvPath` and
`venv` at the root level of the configuration object, matching the exact
format of a `pyrightconfig.json` file
- Uses relative path `"."` when the virtual environment is located in
the workspace root
- Also sets `python.pythonPath` and `python.defaultInterpreterPath` for
compatibility with different Pyright versions

### 2. Environment Variables for All Language Server Binaries
- Updated `check_if_user_installed`, `fetch_server_binary`,
`check_if_version_installed`, and `cached_server_binary` methods to
include shell environment variables
- This ensures environment variables like `VIRTUAL_ENV` are available to
Pyright, helping with automatic virtual environment detection

### 3. Initialization Options
- Added minimal initialization options to enable Pyright's automatic
path searching and import completion features
- Sets `autoSearchPaths: true` and `useLibraryCodeForTypes: true` to
improve Pyright's ability to find packages

## Key Changes

The workspace configuration now properly formats virtual environment
configuration:
- Root level: `venvPath` and `venv` (matches pyrightconfig.json format)
- Python section: `pythonPath` and `defaultInterpreterPath` for
interpreter paths

## Impact

- Users no longer need to create a `pyrightconfig.json` file for virtual
environment detection
- Python projects with virtual environments in standard locations
(`.venv`, `venv`, etc.) will work out of the box
- Import resolution for packages installed in virtual environments now
works correctly
- Maintains compatibility with manual `pyrightconfig.json` configuration
for complex setups

## Testing

The changes were tested with Python projects using virtual environments
without `pyrightconfig.json` files. Pyright now correctly resolves
imports from packages installed in the virtual environment, eliminating
the `reportMissingImports` errors.

## Release Notes

- Fixed Python virtual environment detection when no
`pyrightconfig.json` is present
- Pyright now correctly resolves imports from packages installed in
virtual environments (`.venv`, `venv`, etc.)
- Python projects with virtual environments no longer show false
`reportMissingImports` errors
- Improved Python development experience with automatic virtual
environment configuration

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-06-03 16:35:13 +02:00
Danilo Leal
9e75871d48 agent: Make the sound notification play only if Zed is in the background (#31975)
Users were giving feedback about the sound notification being
annoying/unnecessary if Zed is in the foreground, which I agree! So,
this PR changes it so that it only plays if that is not the case.

Release Notes:

- agent: Improved sound notification behavior by making it play only if
Zed is in the background.
2025-06-03 11:14:26 -03:00
Ben Brandt
707a4c7f20 Remove unused editor_model configuration option (#31492)
It seems that this configuration option is no longer used and can be
removed.


Release Notes:

- Removed unused `agent.editor_model` setting
2025-06-03 13:50:33 +00:00
Oleksiy Syvokon
854076f96d agent: Lower "no thread found" logging level to debug (#31972)
This code path is not really an error, as it can happen due to normal,
albeit uncommon, actions. Like, for example, this scenario:

1. Create a thread X in Zed instance A
2. Open Zed instance B
3. Delete the thread X in instance A
4. Close instance B. This will write non-existing thread id X to
`agent-navigation-history.json`
5. Open Zed instance C. It won't be able to load the thread X.

Another way to get into this state is by running Zed with LMDB and
SQLite thread storages side-by-side.

In any case, this is not severe enough for an error.

Closes #ISSUE

Release Notes:

- N/A
2025-06-03 13:27:58 +00:00
90aca
cf931247d0 Add thinking budget for Gemini custom models (#31251)
Closes #31243

As described in my issue, the [thinking
budget](https://ai.google.dev/gemini-api/docs/thinking) gets
automatically chosen by Gemini unless it is specifically set to
something. In order to have fast responses (inline assistant) I prefer
to set it to 0.

Release Notes:

- ai: Added `thinking` mode for custom Google models with configurable
token budget

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-06-03 13:40:20 +02:00
Ben Brandt
b74477d12e Option to auto-close deleted files with no unsaved edits (#31920)
Closes #27982

Release Notes:

- Added `close_on_file_delete` setting (off by default) to allow closing
open files after they have been deleted on disk

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-06-03 13:18:29 +02:00
Fernando Freire
3077abf9cf google_ai: Parse thought parts in Gemini responses (#31925)
Fixes thinking Gemini models.

Closes #31902

Release Notes:

- Updated Google Gemini client to match the latest API
2025-06-03 10:37:06 +00:00
Kiran_Peraka
07dab4e94a multi_buffer: Merge adjacent matches into a single excerpt when separated by only one line (#31708)
Closes #31252

Release Notes:

- Improved displaying of project search matches or diagnostics when the
excerpts are adjacent.

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
2025-06-03 10:07:59 +00:00
Umesh Yadav
59686f1f44 language_models: Add images support for Ollama vision models (#31883)
Ollama supports vision to process input images. This PR adds support for
same. I have tested this with gemma3:4b and have attached the screenshot
of it working.

<img width="435" alt="image"
src="https://github.com/user-attachments/assets/5f17d742-0a37-4e6c-b4d8-05b750a0a158"
/>


Release Notes:

- Add image support for [Ollama vision models](https://ollama.com/search?c=vision)
2025-06-03 11:12:59 +02:00
Piotr Osiewicz
a60bea8a3d collab: Reconnect to channel notes (#31950)
Closes #31758

Release Notes:

- Fixed channel notes not getting re-connected when a connection to Zed
servers is restored.

---------

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
2025-06-03 11:12:45 +02:00
THELOSTSOUL
b820aa1fcd Add tool support for DeepSeek (#30223)
[deepseek function call
api](https://api-docs.deepseek.com/guides/function_calling)
has been released and it is same as openai.

Release Notes:

- Added tool calling support for Deepseek Models

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-06-03 10:59:36 +02:00
Piotr Osiewicz
55d91bce53 debugger: Add tooltips to the new process modal (#31953)
Closes #ISSUE

Release Notes:

- N/A
2025-06-03 08:49:56 +00:00
clauses3
b798392050 Expand tilde paths in edit prediction settings (#31235)
Release Notes:

- edit_prediction: Handle `~` in paths in `disabled_globs` setting
2025-06-03 10:32:23 +02:00
Finn Evers
657c8b1084 project_panel: Improve behavior for cut-pasting entries (#31931)
Previously, we would move entries each time they were pasted. Thus, if
you were to cut some files and pasted them in folder `a` and then `b`,
they would only occur in folder `b` and not in folder `a`. This is
unintuitive - e.g. the same does not apply to text and does not happen
in other editors.

This PR improves this behavior - after the first paste of a cut
clipboard, we change the clipboard to a copy clipboard, ensuring that
for all folloing pastes, the entries are not moved again. In the above
example, the files would then also be found in folder `a`. This is also
reflected in the added test.

Release Notes:

- Ensured that cut project panel entries are cut-pasted only on the
first use, and copy-pasted on all subsequent pastes.
2025-06-03 03:51:42 -04:00
Piotr Osiewicz
2bb8aa2f73 go_to_line: Show position relative to current excerpt in a multi-buffer (#31947)
Closes #31515

This PR explicitly leaves the behavior of go to line unspecified with
multi-buffer.

Release Notes:

- Fixed wrong line number being shown in the status bar when in
multi-buffer.
2025-06-03 09:41:45 +02:00
Michael Sloan
beeb42da29 snippets: Show completions on first range in tabstop instead of last (#31939)
Release Notes:

- N/A
2025-06-02 23:56:45 -06:00
thebasilisk
6d66ff1d95 Add Helix implementation for Motion::FindForward and Motion::FindBackward (#31547)
Closes #30462 

Release Notes:

- Added text selection for "vim::PushFindForward" and
"vim::PushFindBackward" keybinds in helix mode
2025-06-02 22:15:21 -06:00
Arseny Kapoulkine
e0b818af62 Fix duplicate prefixes when repeating completions in Vim mode (#31818)
When text is completed, new_text contains the entire new completion
which replaces the old_text. In Vim mode, pressing . repeats the
completion; if InputHandled records the full text and no range to
replace, the entire completion gets appended; this happens after the
completion prefix typing repeats, and we get a duplicate prefix.

Using range to replace has some downsides when the completion is
repeated as a standalone action; in a common case, it should be
sufficient to record the new suffix. This is actually what used to
happen before #28586, which removed this code in a larger attempt to fix
completions at multiple cursors:

```rust
let text = &new_text[common_prefix_len..];
let utf16_range_to_replace = ...

cx.emit(EditorEvent::InputHandled {
    utf16_range_to_replace,
    text: text.into(),
});
```

Fixes #30758
Fixes #31759
Fixes #31779

Release Notes:

- Vim: Fix duplicate prefixes when repeating completions via `.`
2025-06-02 21:34:46 -06:00
Fernando Carletti
58a400b1ee keymap: Fix subword navigation and selection on Sublime Text keymap (#31840)
On Linux, the correct modifier key for this action is `alt`, not `ctrl`.
I mistakenly set it to `ctrl` on #30268.

From Sublime's keymap: 
```json
{ "keys": ["ctrl+left"], "command": "move", "args": {"by": "words", "forward": false} },
{ "keys": ["ctrl+right"], "command": "move", "args": {"by": "word_ends", "forward": true} },
{ "keys": ["ctrl+shift+left"], "command": "move", "args": {"by": "words", "forward": false, "extend": true} },
{ "keys": ["ctrl+shift+right"], "command": "move", "args": {"by": "word_ends", "forward": true, "extend": true} },

{ "keys": ["alt+left"], "command": "move", "args": {"by": "subwords", "forward": false} },
{ "keys": ["alt+right"], "command": "move", "args": {"by": "subword_ends", "forward": true} },
{ "keys": ["alt+shift+left"], "command": "move", "args": {"by": "subwords", "forward": false, "extend": true} },
{ "keys": ["alt+shift+right"], "command": "move", "args": {"by": "subword_ends", "forward": true, "extend": true} },
```

Release Notes:

- N/A
2025-06-02 21:22:27 -06:00
tidely
8ab7d44d51 terminal: Match trait bounds with terminal input (#31441)
The core change here is the following:

```rust
fn write_to_pty(&self, input: impl Into<Vec<u8>>);

// into
fn write_to_pty(&self, input: impl Into<Cow<'static, [u8]>>);
```

This matches the trait bounds that's used by the Alacritty crate. We are
now allowed to effectively pass `&'static str` instead of always needing
a `String`.

The main benefit comes from making the `to_esc_str` function return a
`Cow<'static, str>` instead of `String`. We save an allocation in the
following instances:

- When the user presses any special key that isn't alphanumerical (in
the terminal)
- When the uses presses any key while a modifier is active (in the
terminal)
- When focusing/un-focusing the terminal
- When completing or undoing a terminal transaction
- When starting a terminal assist

This basically saves us an allocation on **every key** press in the
terminal.

NOTE: This same optimization can be done for **nearly all** keypresses
in the entirety of Zed by changing the signature of the `Keystroke`
struct in gpui. If the Zed team is interested in a PR for it, let me
know.

Release Notes:

- N/A
2025-06-02 21:12:28 -06:00
Michael Sloan
56d4c0af9f snippets: Preserve leading whitespace (#31933)
Closes #18481

Release Notes:

- Snippet insertions now preserve leading whitespace instead of using
language-specific auto-indentation.
2025-06-03 02:37:06 +00:00
Michael Sloan
feeda7fa37 Add newlines between messages in LSP RPC logs for more navigability (#31863)
Release Notes:

- N/A
2025-06-03 02:12:58 +00:00
Cole Miller
4a5c55a8f2 debugger: Use new icons for quick debug/spawn button (#31932)
This PR wires up the new icons that were added in #31784.

Release Notes:

- N/A
2025-06-03 01:26:41 +00:00
Cole Miller
7c1ae9bcc3 debugger: Go back to loading task contexts asynchronously for new session modal (#31908)
Release Notes:

- N/A
2025-06-02 21:14:30 -04:00
Cole Miller
6f97da3435 debugger: Align zoom behavior with other panels (#31901)
Release Notes:

- Debugger Beta: `shift-escape` (`workspace::ToggleZoom`) now zooms the
entire debug panel; `alt-shift-escape` (`debugger::ToggleExpandItem`)
triggers the old behavior of zooming a specific item.
2025-06-03 00:59:36 +00:00
Danilo Leal
63c1033448 agent: Generate a notification when reaching tool use limit (#31894)
When reaching the consecutive tool call limit, the agent gets blocked
and without a notification, you wouldn't know that. This PR adds the
ability to be notified when that happens, and you can use either sound
_and_ toast, or just one of them.

Release Notes:

- agent: Added support for getting notified (via toast and/or sound)
when reaching the consecutive tool call limit.
2025-06-02 21:57:42 -03:00
Cole Miller
b16911e756 debugger: Extend f5 binding to contextually rerun the last session (#31753)
Release Notes:

- Debugger Beta: if there is no stopped or running session, `f5` now
reruns the last session, or opens the new session modal if there is no
previously-run session.
2025-06-03 00:35:52 +00:00
Cole Miller
b14401f817 Remove agent_diff key context when agent review ends for an editor (#31930)
Release Notes:

- Fixed an issue that prevented `git::Restore` keybindings from working
in editors for buffers that had previously been modified by the agent.

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-06-02 19:46:04 -04:00
Michael Sloan
17cf865d1e Avoid re-querying language server completions when possible (#31872)
Also adds reuse of the markdown documentation cache even when
completions are re-queried, so that markdown documentation doesn't
flicker when `is_incomplete: true` (completions provided by rust
analyzer always set this)

Release Notes:

- Added support for filtering language server completions instead of
re-querying.
2025-06-02 22:19:09 +00:00
Mikayla Maki
b7ec437b13 Simplify debug launcher UI (#31928)
This PR updates the name of the `NewSessionModal` to `NewProcessModal`
(to reflect it's new purpose), changes the tabs in the modal to read
`Run | Debug | Attach | Launch` and changes the associated types in code
to match the tabs. In addition, this PR adds a few labels to the text
fields in the `Launch` tab, and adds a link to open the associated
settings file. In both debug.json files, added links to the zed.dev
debugger docs.

Release Notes:

- Debugger Beta: Improve the new process modal
2025-06-02 21:24:08 +00:00
Piotr Osiewicz
f1aab1120d terminal: Persist pinned tabs in terminal (#31921)
Closes #31098

Release Notes:

- Fixed terminal pinned tab state not persisting across restarts.
2025-06-02 22:36:57 +02:00
Joe Polny
3f90bc81bd gpui: Filter out NoAction bindings from pending input (#30260)
This prevents the 1 second delay happening on input when all of the
pending bindings are NoAction

Closes #30259

Release Notes:

- Fixed unnecessary delay when typing a multi-stroke binding that
doesn't match any non-null bindings

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-06-02 13:22:36 -06:00
AidanV
9d5fb3c3f3 Add :delm[arks] {marks} command to delete vim marks (#31140)
Release Notes:

- Implements `:delm[arks] {marks}` specified
[here](https://vimhelp.org/motion.txt.html#%3Adelmarks)
- Adds `ArgumentRequired` action for vim commands that require arguments

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-06-02 13:18:28 -06:00
Oleksiy Syvokon
864767ad35 agent: Support vim-mode in the agent panel's editor (#31915)
Closes #30081

Release Notes:

- Added vim-mode support in the agent panel's editor

---------

Co-authored-by: Ben Kunkle <ben.kunkle@gmail.com>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-06-02 22:10:31 +03:00
Bennet Bo Fenner
ec69b68e72 indent guides: Fix issue with entirely-whitespace lines (#31916)
Closes #26957

Release Notes:

- Fix an edge case where indent guides would be rendered incorrectly if
lines consisted of entirely whitespace

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-06-02 17:35:00 +00:00
Piotr Osiewicz
9dd18e5ee1 python: Re-land usage of source file path in toolchain picker (#31893)
This reverts commit 1e55e88c18.

Closes #ISSUE

Release Notes:

- Python toolchain selector now uses path to the closest pyproject.toml
as a basis for picking a toolchain. All files under the same
pyproject.toml (in filesystem hierarchy) will share a single virtual
environment. It is possible to have multiple Python virtual environments
selected for disjoint parts of the same project.
2025-06-02 16:29:06 +00:00
Smit Barmase
2ebe16a52f workspace: Fix empty pane becomes unresponsive to keybindings after commit via terminal (#31905)
Closes #27579

This PR fixes issue where keybinding wouldn’t work in a pane after
focusing it from dock using the `ActivatePaneInDirection` action in
certain cases.


https://github.com/user-attachments/assets/9ceca580-a63f-4807-acff-29b61819f424

Release Notes:

- Fixed the issue where keybinding wouldn’t work in a pane after
focusing it from dock using the `ActivatePaneInDirection` action in
certain cases.
2025-06-02 21:52:35 +05:30
Joseph T. Lyons
1ed4647203 Add test for pane: toggle pin tab (#31906)
Also adds the optimization to not move a tab being pinned when its
destination index is the same as its index.

Release Notes:

- N/A
2025-06-02 15:58:10 +00:00
Dino
ebed567adb vim: Handle paste in visual line mode when cursor is at newline (#30791)
This Pull Request fixes the current paste behavior in vim mode, when in
visual mode, and the cursor is at a newline character. Currently this
joins the pasted contents with the line right below it, but in vim this
does not happen, so these changes make it so that Zed's vim mode behaves
the same as vim for this specific case.

Closes #29270 

Release Notes:

- Fixed pasting in vim's visual line mode when cursor is on a newline
character
2025-06-02 09:50:13 -06:00
5brian
a6544c70c5 vim: Fix add empty line (#30987)
Fixes: 

`] space` does not consume counts, and it gets applied to the next
action.

`] space` on an empty line causes cursor to move to the next line.

Release Notes:

- N/A
2025-06-02 09:49:31 -06:00
AidanV
b363e1a482 vim: Add support for :e[dit] {file} command to open files (#31227)
Closes #17786

Release Notes:

- Adds `:e[dit] {file}` command to open files
2025-06-02 09:47:40 -06:00
Umesh Yadav
65e3e84cbc language_models: Add thinking support for ollama (#31665)
This PR updates how we handle Ollama responses, leveraging the new
[v0.9.0](https://github.com/ollama/ollama/releases/tag/v0.9.0) release.
Previously, thinking text was embedded within the model's main content,
leading to it appearing directly in the agent's response. Now, thinking
content is provided as a separate parameter, allowing us to display it
correctly within the agent panel, similar to other providers. I have
tested this with qwen3:8b and works nicely. ~~We can release this once
the ollama is release is stable.~~ It's released now as stable.

<img width="433" alt="image"
src="https://github.com/user-attachments/assets/2983ef06-6679-4033-82c2-231ea9cd6434"
/>


Release Notes:

- Add thinking support for ollama

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-06-02 15:12:41 +00:00
morgankrey
1e1d4430c2 Fixing 404 in AI Configuration Docs (#31899)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-06-02 09:09:00 -05:00
Oleksiy Syvokon
c874f1fa9d agent: Migrate thread storage to SQLite with zstd compression (#31741)
Previously, LMDB was used for storing threads, but it consumed excessive
disk space and was capped at 1GB.

This change migrates thread storage to an SQLite database. Thread JSON
objects are now compressed using zstd.

I considered training a custom zstd dictionary and storing it in a
separate table. However, the additional complexity outweighed the modest
space savings (up to 20%). I ended up using the default dictionary
stored with data.

Threads can be exported relatively easily from outside the application:

```
$ sqlite3 threads.db "SELECT hex(data) FROM threads LIMIT 5;" |
    xxd -r -p |
    zstd -d |
    fx
```

Benchmarks:
- Original heed database: 200MB
- Sqlite uncompressed: 51MB
- sqlite compressed (this PR): 4.0MB
- sqlite compressed with a trained dictionary: 3.8MB


Release Notes:

- Migrated thread storage to SQLite with compression
2025-06-02 17:01:34 +03:00
Alisina Bahadori
9a9e96ed5a Increase terminal inline assistant block height (#31807)
Closes #31806
Closes #28969

Not sure if a static value is best. Maybe it is better to somehow use
`count_lines` function here too.

### Before
<img width="871" alt="449463234-ab1a33a0-2331-4605-aaee-cae60ddd0f9d"
src="https://github.com/user-attachments/assets/1e3bec86-4cad-426c-9f59-5ad3d14fc9d7"
/>


### After
<img width="861" alt="Screenshot 2025-05-31 at 1 12 33 AM"
src="https://github.com/user-attachments/assets/0c8219a9-0812-45af-8125-1f4294fe2142"
/>

Release Notes:

- Fixed terminal inline assistant clipping when cursor is at bottom of
terminal.
2025-06-02 10:55:40 -03:00
Danilo Leal
8c46e290df docs: Add more details to the agent checkpoint section (#31898)
Figured this was worth highlighting as part of the "Restore Checkpoint"
feature behavior.

Release Notes:

- N/A
2025-06-02 10:55:03 -03:00
Thiago Pacheco
aacbb9c2f4 python: Respect picked toolchain (when it's not at the root) when running tests (#31150)
# Fix Python venv Detection for Test Runner
## Problem
Zed’s Python test runner was not reliably detecting and activating the
project’s Python virtual environment (.venv or venv), causing it to
default to the system Python. This led to issues such as missing
dependencies (e.g., pytest) when running tests.
## Solution
Project Root Awareness: The Python context provider now receives the
project root path, ensuring venv detection always starts from the
project root rather than the test file’s directory.
Robust venv Activation: The test runner now correctly detects and
activates the Python interpreter from .venv or venv in the project root,
setting VIRTUAL_ENV and updating PATH as needed.
Minimal Impact: The change is limited in scope, affecting only the
necessary code paths for Python test runner venv detection. No broad
architectural changes were made.
## Additional Improvements
Updated trait and function signatures to thread the project root path
where needed.
Cleaned up linter warnings and unused code.
## Result
Python tests now reliably run using the project’s virtual environment,
matching the behavior of other IDEs and ensuring all dependencies are
available.

Release Notes:

- Fixed Python tasks always running with a toolchain selected for the
root of a workspace.

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-06-02 15:29:34 +02:00
Ben Kunkle
f90333f92e zlog: Check crate name against filters if scope empty (#31892)
Fixes
https://github.com/zed-industries/zed/discussions/29541#discussioncomment-13243073

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-06-02 13:22:32 +00:00
Ben Kunkle
b24f614ca3 docs: Improve documentation around Vulkan/GPU issues on Linux (#31895)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-06-02 13:16:49 +00:00
Danilo Leal
cefa0cbed8 Improve the file finder picker footer design (#31777)
The current filter icon button in the file finder picker footer confused
me because it is a really a toggleable button that adds a specific
filter. From the icon used, I was expecting more configuration options
rather than just one. Also, I was really wanting a way to trigger it
with the keyboard, even if I need my mouse to initially learn about the
keybinding.

So, this PR transforms that icon button into an actual popover trigger,
in which (for now) there's only one filter option. However, being a menu
is cool because it allows to accomodate more items like, for example,
"Include Git Submodule Files" and others, in the future. Also, there's
now a keybinding that you can hit to open that popover, as well as an
indicator that pops in to communicate that a certain item inside it has
been toggled.

Lastly, also added a keybinding to the "Split" menu in the spirit of
making everything more keyboard accessible!

| Before | After |
|--------|--------|
| ![CleanShot 2025-05-30 at 4  29
57@2x](https://github.com/user-attachments/assets/88a30588-289d-4d76-bb50-0a4e7f72ef84)
| ![CleanShot 2025-05-30 at 4  24
31@2x](https://github.com/user-attachments/assets/30b8f3eb-4d5c-43e1-abad-59d32ed7c89f)
|

Release Notes:

- Improved the keyboard navigability of the file finder filtering
options.
2025-06-02 09:54:15 -03:00
Smit Barmase
3fb1023667 editor: Fix columnar selection incorrectly uses cursor to start selection instead of mouse position (#31888)
Closes #13905

This PR fixes columnar selection to originate from mouse position
instead of current cursor position. Now columnar selection behaves as
same as Sublime Text.

1. Columnar selection from click-and-drag on text (New):


https://github.com/user-attachments/assets/f2e721f4-109f-4d81-a25b-8534065bfb37

2. Columnar selection from click-and-drag on empty space (New): 


https://github.com/user-attachments/assets/c2bb02e9-c006-4193-8d76-097233a47a3c

3. Multi cursors at end of line when no interecting text found (New): 


https://github.com/user-attachments/assets/e47d5ab3-0b5f-4e55-81b3-dfe450f149b5

4. Converting normal selection to columnar selection (Existing):


https://github.com/user-attachments/assets/e5715679-ebae-4f5a-ad17-d29864e14e1e


Release Notes:

- Fixed the issue where the columnar selection (`opt+shift`) incorrectly
used the cursor to start the selection instead of the mouse position.
2025-06-02 16:37:36 +05:30
Umesh Yadav
9c715b470e agent: Show actual file name and icon in context pill (#31813)
Previously in the agent context pill if we added images it showed
generic Image tag on the image context pill. This PR make sure if we
have a path available for a image context show the filename which is in
line with other context pills.


Before | After
--- | ---
![Screenshot 2025-05-31 at 3 14 07
PM](https://github.com/user-attachments/assets/b342f046-2c1c-4c18-bb26-2926933d5d34)
| ![Screenshot 2025-05-31 at 3 14 07
PM](https://github.com/user-attachments/assets/90ad4062-cdc6-4274-b9cd-834b76e8e11b)





Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-06-02 10:35:22 +00:00
Oleksiy Syvokon
ae219e9e99 agent: Fix bug with double-counting tokens in Gemini (#31885)
We report the total number of input tokens by summing the numbers of
1. Prompt tokens
2. Cached tokens

But Google API returns prompt tokens (1) that already include cached
tokens (2), so we were double counting tokens in some cases.

Release Notes:

- Fixed bug with double-counting tokens in Gemini
2025-06-02 10:18:44 +00:00
Bennet Bo Fenner
6d99c12796 assistant_context_editor: Fix copy paste regression (#31882)
Closes #31166

Release Notes:

- Fixed an issue where copying and pasting an assistant response in text
threads would result in duplicate text
2025-06-02 11:52:47 +02:00
Kirill Bulatov
8fb7fa941a Suppress log blade_graphics -related logs by default (#31881)
Release Notes:

- N/A
2025-06-02 08:59:57 +00:00
Joseph T. Lyons
22d75b798e Notify when pinning a tab even if the tab isn't moved (#31880)
The optimization to not move a tab being pinned (when the destination
index is the same as its index) in
https://github.com/zed-industries/zed/pull/31871 caused a regression, as
we were no longer calling `cx.notify()` indirectly through `move_item`.
Thanks for catching this, @smitbarmase.

Release Notes:

- N/A
2025-06-02 06:58:57 +00:00
Smit Barmase
06a199da4d editor: Fix completion accept for optional chaining in Typescript (#31878)
Closes #31662

Currently, we assume `insert_range` will always end at the cursor and
`replace_range` will also always end after the cursor for calculating
range to replace. This is a particular case for the rust-analyzer, but
not widely true for other language servers.

This PR fixes this assumption, and now `insert_range` and
`replace_range` both can end before cursor.

In this particular case:
```ts
let x: string | undefined;

x.tostˇ // here insert as well as replace range is just "." while new_text is "?.toString()"
```

This change makes it such that if final range to replace ends before
cursor, we extend it till the cursor.

Bonus:
- Improves suffix and subsequence matching to use `label` over
`new_text` as `new_text` can contain end characters like `()` or `$`
which is not visible while accepting the completion.
- Make suffix and subsequence check case insensitive.
- Fixes broken subsequence matching which was not considering the order
of characters while matching subsequence.

Release Notes:

- Fixed an issue where autocompleting optional chaining methods in
TypeScript, such as `x.tostr`, would result in `x?.toString()tostr`
instead of `x?.toString()`.
2025-06-02 10:45:40 +05:30
Joseph T. Lyons
ab6125ddde Fix bugs around pinned tabs (#31871)
Closes https://github.com/zed-industries/zed/issues/31870

Release Notes:

- Allowed opening 1 more item if `n` tabs are pinned, where `n` equals
`max_tabs` count.
- Fixed a bug where pinned tabs would eventually be closed out when
exceeding the `max_tabs` count.
- Fixed a bug where a tab could be lost when pinning a tab while at the
`max_tabs` count.
- Fixed a bug where pinning a tab when already at the `max_tabs` limit
could cause other tabs to be incorrectly closed.
2025-06-01 19:50:06 -04:00
Joseph T. Lyons
d3bc561f26 Disable close clean menu item when all are dirty (#31859)
This PR disables the "Close Clean" tab context menu action if all items
are dirty.

<img width="595" alt="SCR-20250601-kaev"
src="https://github.com/user-attachments/assets/add30762-b483-4701-9053-141d2dfe9b05"
/>

<img width="573" alt="SCR-20250601-kahl"
src="https://github.com/user-attachments/assets/24f260e4-01d6-48d6-a6f4-a13ae59c246e"
/>

Also did a bit more general refactoring.

Release Notes:

- N/A
2025-06-01 15:15:33 +00:00
Joseph T. Lyons
f13f2dfb70 Ensure item-closing actions do not panic when no items are present (#31845)
This PR adds a comprehensive test that ensures that no item-closing
action will panic when no items are present. A test already existed
(`test_remove_active_empty `) that ensured `CloseActiveItem` didn't
panic, but the new test covers:

- `CloseActiveItem`
- `CloseInactiveItems`
- `CloseAllItems`
- `CloseCleanItems`
- `CloseItemsToTheRight`
- `CloseItemsToTheLeft`

I plan to do a bit more clean up in `pane.rs` and this feels like a good
thing to add before that.

Release Notes:

- N/A
2025-06-01 03:31:38 +00:00
Joseph T. Lyons
24e4446cd3 Refactor item-closing actions (#31838)
While working on 

- https://github.com/zed-industries/zed/pull/31783
- https://github.com/zed-industries/zed/pull/31786

... I noticed some areas that could be improved through refactoring. The
bug in https://github.com/zed-industries/zed/pull/31783 came from having
duplicate code. The fix had been applied to one version, but not the
duplicated code.

This PR attempts to do some initial clean up, through some refactoring.

Release Notes:

- N/A
2025-05-31 19:38:32 -04:00
Aleksei Gusev
cc536655a1 Fix slowness in Terminal when vi-mode is enabled (#31824)
It seems alacritty handles vi-mode motions in a special way and it is up
to the client to decide when redraw is necessary. With this change,
`TerminalView` notifies the context if a keystroke is processed and vi
mode is enabled.

Fixes #31447

Before:


https://github.com/user-attachments/assets/a78d4ba0-23a3-4660-a834-2f92948f586c

After:


https://github.com/user-attachments/assets/cabbb0f4-a1f9-4f1c-87d8-a56a10e35cc8

Release Notes:

- Fixed sluggish cursor motions in Terminal when Vi Mode is enabled
[#31447]
2025-05-31 20:02:56 +03:00
Kartik Vashistha
2a9e73c65d docs: Update Ansible docs (#31817)
Release Notes:

- N/A
2025-05-31 10:30:29 -04:00
Kirill Bulatov
4f1728e5ee Do not unwrap when updating inline diagnostics (#31814)
Also do not hold the strong editor reference while debounced.

Release Notes:

- N/A
2025-05-31 10:10:15 +00:00
Christian Bergschneider
40c91d5df0 gpui: Implement window_handle and display_handle for wayland platform (#28152)
This PR implements the previously unimplemented window_handle and
display_handle methods in the wayland platform. It also exposes the
display_handle method through the Window struct.

Release Notes:

- N/A
2025-05-30 15:45:03 -07:00
Kirill Bulatov
fe1b36671d Do not error on debugger server connection close (#31795)
Start to show islands of logs in a successful debugger runs for Rust:

<img width="1728" alt="1"
src="https://github.com/user-attachments/assets/7400201b-b900-4b20-8adf-21850ae59671"
/>

<img width="1728" alt="2"
src="https://github.com/user-attachments/assets/e75cc5f1-1f74-41d6-b7aa-697a4b2f055b"
/>

Release Notes:

- N/A
2025-05-30 22:37:40 +00:00
Agus Zubiaga
bb9e2b0403 Fix existing CompletionMode deserialization (#31790)
https://github.com/zed-industries/zed/pull/31668 renamed
`CompletionMode::Max` to `CompletionMode::Burn` which is a good change,
but this broke the deserialization for threads whose completion mode was
stored in LMDB. This adds a deserialization alias so that both values
work.

We could make a full new `SerializedThread` version which migrates this
value, but that seems overkill for this single change, we can batch that
with more changes later. Also, people in nightly already have some v1
threads with `burn` stored, so it wouldn't quite work for everybody.

Release Notes:

- N/A
2025-05-30 19:06:08 -03:00
Yaroslav Pietukhov
4f8d7f0a6b Disallow running Zed with root privileges (#31331)
This will fix a lot of weird problems that are based on file access
issues.

As discussed in
https://github.com/zed-industries/zed/pull/31219#issuecomment-2905371710,
for now it's better to just prevent running Zed with root privileges.

Release Notes:

- Explicitly disallow running Zed with root privileges

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-05-30 21:22:52 +00:00
Danilo Leal
caf3d30bf6 Harmonize quick action icons (#31784)
Just ensuring they're more harmonized in size, stroke width, and overall
dimensions. Adding here two new "play" icons, too, that will be used in
the context of the debugger.

<img
src="https://github.com/user-attachments/assets/79bcf0b0-e995-4c8e-9c78-0aba32f42f2d"
width="400" />

Release Notes:

- N/A
2025-05-30 18:18:23 -03:00
Peter Tripp
df0cf22347 Add powershell language docs (#31787)
Release Notes:

- N/A
2025-05-30 17:09:55 -04:00
Piotr Osiewicz
a305eda8d1 debugger: Relax implementation of validate_config to not run validation (#31785)
When we moved to schema-based debug configs, we've added validate_config
- a trait method
that is supposed to both validate the configuration and determine
whether it is a launch configuration
or an attach configuration.

The validation bit is a bit problematic though - we received reports on
Discords about
scenarios not starting up properly; it turned out that Javascript's
implementation was overly strict.
Thus, I got rid of any code that tries to validate the config - let's
let the debug adapter itself
decide whether it can digest the configuration or not. validate_config
is now left unimplemented for most
DebugAdapter implementations (except for PHP), because all adapters use
`request`: 'launch'/'attach' for that.
Let's leave the trait method in place though, as nothing guarantees this
to be true for all adapters.

cc @Anthony-Eid

Release Notes:

- debugger: Improved error messages when the debug scenario is not
valid.
- debugger: Fixed cases where valid configs were rejected.
2025-05-30 23:08:41 +02:00
Joseph T. Lyons
ba7b1db054 Mark items as pinned via ! in tests (#31786)
Release Notes:

- N/A
2025-05-30 21:03:31 +00:00
Joseph T. Lyons
019c8ded77 Fix bug where pinned tabs were closed when closing others (#31783)
Closes https://github.com/zed-industries/zed/issues/28166

Release Notes:

- Fixed a bug where pinned tabs were closed when running `Close Others`
from the tab context menu
2025-05-30 20:48:33 +00:00
Fernando Carletti
1704dbea7e keymap: Add subword navigation and selection to Sublime Text keymap (#30268)
For reference, this is what is set in Sublime Text's default-keymap
files for both MacOS and Linux:

```json
{ "keys": ["ctrl+left"], "command": "move", "args": {"by": "words", "forward": false} },
{ "keys": ["ctrl+right"], "command": "move", "args": {"by": "word_ends", "forward": true} },
{ "keys": ["ctrl+shift+left"], "command": "move", "args": {"by": "words", "forward": false, "extend": true} },
{ "keys": ["ctrl+shift+right"], "command": "move", "args": {"by": "word_ends", "forward": true, "extend": true} },
```

Release Notes:

- Add subword navigation and selection to Sublime keymap

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-05-30 20:38:21 +00:00
Hendrik Sollich
eefa6c4882 Icon theme selector: Don't select last list item when fuzzy searching (#29560)
Adds manual icon-theme selection persistence

Store manually selected icon-themes to maintain selection when query
changes. This allows the theme selector to remember the user's choice
rather than resetting selection when filtering results.

mentioned in #28081 and #28278

Release Notes:

- Improved persistence when selecting themes and icon themes.

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-05-30 20:37:38 +00:00
Alex
1f17df7fb0 debugger: Add support for go tests (#31772)
In the https://github.com/zed-industries/zed/pull/31559 I did not
introduce ability to debug test invocations.
Adding it here. E.g:
![Kapture 2025-05-30 at 19 59
13](https://github.com/user-attachments/assets/1111d4a5-8b0a-42e6-aa98-2d797f61ffe3)

Release Notes:
- Added support for debugging single tests written in go
2025-05-30 22:18:32 +02:00
tidely
6d687a2c2c ollama: Change default context size to 4096 (#31682)
Ollama increased their default context size from 2048 to 4096 tokens in
version v0.6.7, which released over a month ago.

https://github.com/ollama/ollama/releases/tag/v0.6.7

Release Notes:

- ollama: Update default model context to 4096 (matching upstream)
2025-05-30 16:12:39 -04:00
Umesh Yadav
32214abb64 Improve TypeScript shebang detection (#31437)
Closes #13981

Release Notes:

- Improved TypeScript shebang detection
2025-05-30 16:11:13 -04:00
Peter Tripp
a78563b80b ci: Prevent "Tests Pass" job from running on forks (#31778)
Example fork [actions run](https://github.com/alphaArgon/zed/actions/runs/15349715488/job/43194591563)
which would be suppressed in the future.

(Sorry @alphaArgon)

Release Notes:

- N/A
2025-05-30 16:09:22 -04:00
Kirill Bulatov
f881cacd8a Use both language and LSP icons for LSP tasks (#31773)
Make more explicit which language LSP tasks are used.

Before:

![image](https://github.com/user-attachments/assets/27f93c5f-942e-47a0-9b74-2c6d4d6248de)

After:

![image
(1)](https://github.com/user-attachments/assets/5a29fb0a-2e16-4c35-9dda-ae7925eaa034)


![image](https://github.com/user-attachments/assets/d1bf518e-63d1-4ebf-af3d-3c9d464c6532)


Release Notes:

- N/A
2025-05-30 19:28:56 +00:00
Umesh Yadav
a539a38f13 Revert "copilot: Fix vision request detection for follow-up messages" (#31776)
Reverts zed-industries/zed#31760

see this comment for context:
https://github.com/zed-industries/zed/pull/31760#issuecomment-2923158611.

Release Notes:

- N/A
2025-05-30 21:28:31 +02:00
Piotr Osiewicz
ca6fd101c1 debugger: Change console text color, add tooltips (#31765)
- Improved legibility of console text:

| Theme | Dark | Light |
|--------|--------|--------|
| Before |
![image](https://github.com/user-attachments/assets/756da36d-9ef4-495a-9cf9-7249c25d106a)
|
![image](https://github.com/user-attachments/assets/42558ec2-ee08-4973-8f7d-d7f4feb38cf8)
|
| After |
![image](https://github.com/user-attachments/assets/4469f000-b34f-4cbb-819d-4ae1f2f58a4a)
|
![image](https://github.com/user-attachments/assets/3b862114-0fd3-427c-9c76-f030d3442090)
|

Release Notes:

- debugger: Improved legibility of console text
- debugger: Added tooltips to all debugger items.
2025-05-30 19:21:28 +02:00
Aldo Funes
f8097c7c98 Improve compatibility with Wayland clipboard (#30251)
Closes #26672, #20984

Release Notes:

- Fixed issue where some applications won't receive the clipboard
contents from Zed

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-05-30 17:21:00 +00:00
Marshall Bowers
c1427ea802 collab: Remove POST /billing/subscriptions/migrate endpoint (#31770)
This PR removes the `POST /billing/subscriptions/migrate` endpoint, as
it is no longer needed.

Release Notes:

- N/A
2025-05-30 17:16:20 +00:00
Kirill Bulatov
1e83022f03 Add a JS/TS debug locator (#31769)
With this, a semi-working debug session is possible from the JS/TS
gutter tasks:


https://github.com/user-attachments/assets/8db6ed29-b44a-4314-ae8b-a8213291bffc

For now, available in debug builds only as a base to improve on later on
the DAP front.

Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <peterosiewicz@gmail.com>
2025-05-30 17:15:42 +00:00
Chung Wei Leong
0ee900e8fb Support macOS Sequoia titlebar double-click action (#30468)
Closes #16527

Release Notes:

- Added MacOS titlebar double-click action

---

Unfortunately, Apple doesn't seem to make the "Fill" API public or
documented anywhere.

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2025-05-30 17:13:50 +00:00
Marshall Bowers
f9f4be1fc4 collab: Use StripeClient in POST /billing/subscriptions/sync endpoint (#31764)
This PR updates the `POST /billing/subscriptions/sync` endpoint to use
the `StripeClient` trait instead of using `stripe::Client` directly.

Release Notes:

- N/A
2025-05-30 16:37:12 +00:00
Stephen Murray
a00b07371a copilot: Fix vision request detection for follow-up messages (#31760)
Previously, the vision request header was only set if the last message
in a thread contained an image. This caused 400 errors from the Copilot
API when sending follow-up messages in a thread that contained images in
earlier messages.

Modified the `is_vision_request` check to scan all messages in a thread
for image content instead of just the last one, ensuring the proper
header is set for the entire conversation.

Added a unit test to verify all cases function correctly.

Release Notes:

- Fix GitHub Copilot chat provider error when sending follow-up messages
in threads containing images
2025-05-30 16:32:49 +00:00
Marshall Bowers
f725b5e248 collab: Use StripeClient in sync_subscription (#31761)
This PR updates the `sync_subscription` function to use the
`StripeClient` trait instead of using `stripe::Client` directly.

Release Notes:

- N/A

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-05-30 16:08:58 +00:00
Vivek Pothina
07436b4284 breadcrumbs: Stylize filename in breadcrumbs when tab-bar is off and file is dirty (#30507)
Closes [#18870](https://github.com/zed-industries/zed/issues/18870)


- I like to use Zed with tab_bar off
- when the file is modified there is no indicator when tab_bar is off
- this PR aims to fix that

Thanks to @Qkessler for initial PR - #22418 

This is style decided in this discussion - #22418 
@iamnbutler @mikayla-maki [subtle style
decided](https://github.com/zed-industries/zed/pull/22418#issuecomment-2605253667)

Release Notes:
- When tab_bar is off, filename in the breadcrumbs will be the indicator
when file is unsaved.


#### Changes
- when tab_bar is off and file is dirty (unsaved)
<img width="834" alt="image"
src="https://github.com/user-attachments/assets/f205731b-c8e3-4d7a-9214-cbe706e372bf"
/>


- when tab_bar is off and file is not dirty (saved)
<img width="846" alt="image"
src="https://github.com/user-attachments/assets/88ea96eb-16a2-48e8-900d-64a921f0b5c3"
/>


- when tab_bar is on
<img width="741" alt="image"
src="https://github.com/user-attachments/assets/cc543544-9949-46ed-8e09-cdcbe2f47ab8"
/>

<img width="740" alt="image"
src="https://github.com/user-attachments/assets/8d347258-26f7-4bd7-82d4-8f23dbe63d61"
/>

Release Notes:

- Changed the highlighting of the current file to represent the current
saved state, when the tab bar is turned off.
2025-05-30 08:32:54 -07:00
tidely
8bec4cbecb assistant_tools: Reduce allocations (#30776)
Another batch of allocation savings. Noteworthy ones are
`find_path_tool.rs` where one clone of *all* found matches was saved and
`web_tool_search.rs` where the tooltip no longer clones the entire url
on every hover.

I'd also like to propose using `std::borrow::Cow` a lot more around the
codebase instead of Strings. There are hundreds if not 1000+ clones that
can be saved pretty regularly simply by switching to Cow. ´Cow´'s are
likely not used because they aren't compatible with futures and because
it could cause lifetime bloat. However if we use `Cow<'static, str>`
(static lifetime) for when we need to pass them into futures, we could
save a TON of allocations for `&'static str`. Additionally I often see
structs being created using `String`'s just to be deserialized
afterwards, which only requires a reference.

Release Notes:

- N/A
2025-05-30 08:28:22 -07:00
Jason Lee
047e7eacec gpui: Improve window.prompt to support ESC with non-English cancel text on macOS (#29538)
Release Notes:

- N/A

----

The before version GPUI used `Cancel` for cancel text, if we use
non-English text (e.g.: "取消" in Chinese), then the press `Esc` to cancel
will not work.

So this PR to change it by use `PromptButton` to instead the `&str`,
then we can use `PromptButton::cancel("取消")` for the `Cancel` button.

Run `cargo run -p gpui --example window` to test.

---

Platform Test:

- [x] macOS
- [x] Windows
- [x] Linux (x11 and Wayland)

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-05-30 15:26:27 +00:00
laizy
1d5d3de85c gpui: Optimize the ordering update in the BoundsTree (#31025)
Release Notes:

- N/A
2025-05-30 08:23:27 -07:00
laizy
c4dbaa91f0 gpui: Fix race condition when upgrading a weak reference (#30952)
Release Notes:

- N/A
2025-05-30 08:18:25 -07:00
Ben Brandt
97c01c6720 Fix model deduplication to use provider ID and model ID (#31750)
Previously only used model ID for deduplication, which incorrectly
filtered models with the same name from different providers.

Release Notes:

- Fix to make sure all provider models are shown in the model picker
2025-05-30 13:49:09 +00:00
Marshall Bowers
310ea43048 danger: Check for changes in prompt files (#31744)
This PR adds a Danger check to remind engineers that any changes to our
various prompts need to be verified against the LLM Worker.

When changes to the prompt files are detected, we will fail the PR with
a message:

<img width="929" alt="Screenshot 2025-05-30 at 8 40 58 AM"
src="https://github.com/user-attachments/assets/79afab4e-e799-45f1-a90e-0fd7c9a73706"
/>

Once the corresponding changes have been made (or no changes to the LLM
Worker have been determined to be necessary), including the indicated
attestation message will convert the errors into informational messages:

<img width="926" alt="Screenshot 2025-05-30 at 8 41 52 AM"
src="https://github.com/user-attachments/assets/ff51c17a-7a76-46a7-b468-a7d864d480c3"
/>

Release Notes:

- N/A
2025-05-30 13:46:41 +00:00
Piotr Osiewicz
6bb4b5fa64 Revert "debugger beta: Fix bug where debug Rust main running action f… (#31743)
…ailed (#31291)"

This reverts commit aab76208b5.

Closes #31737

I cannot repro the original issue that this commit was trying to solve
anymore.

Release Notes:

- N/A
2025-05-30 14:32:59 +02:00
Alejandro Fernández Gómez
e0fa3032ec docs: Properly nest the docs for the inline git blame settings (#31739)
From a report on
[discord](https://discord.com/channels/869392257814519848/873292398204170290/1377943171320774656):
reorders the docs for the inline git blame feature so they are properly
nested.

<img width="1719" alt="Screenshot_2025-05-30_at_11 32 17"
src="https://github.com/user-attachments/assets/3c20bda3-de81-4ac3-b8e2-e1d4eac0ce88"
/>


Release Notes:

- N/A
2025-05-30 08:18:15 -03:00
Danilo Leal
9cf6be2057 agent: Add Burn Mode setting migrator (#31718)
Follow-up https://github.com/zed-industries/zed/pull/31470.

Release Notes:

- N/A
2025-05-30 08:10:12 -03:00
Piotr Osiewicz
5462e199fb debugger: Fix wrong path to the downloaded delve-shim-dap (#31738)
Closes #ISSUE

Release Notes:

- N/A
2025-05-30 10:43:29 +00:00
Piotr Osiewicz
3a60420b41 debugger: Fix delve-dap-shim path on Windows (#31735)
Closes #ISSUE

Release Notes:

- debugger: Fixed wrong path being picked up for delve on Windows
- debugger: Fixed delve not respecting the user-provided binary path
from settings.
2025-05-30 08:53:19 +00:00
Simon Pham
89c184a26f markdown_preview: Fix release notes title being overridden (#31703)
Closes: #31701

Screenshot:

<img width="383" alt="image"
src="https://github.com/user-attachments/assets/7fd8ce70-2208-4aca-bc70-860d6c649765"
/>



Release Notes:

- Fixed in-app release notes having an incorrect title

---------

Co-authored-by: Gilles Peiffer <gilles.peiffer.yt@gmail.com>
2025-05-30 08:29:52 +00:00
Michael Sloan
d7f0241d7b editor: Defer the effects of change_selections to end of transact (#31731)
In quite a few places the selection is changed multiple times in a
transaction. For example, `backspace` might do it 3 times:

* `select_autoclose_pair`
* selection of the ranges to delete
* `insert` of empty string also updates selection

Before this change, each of these selection changes appended to
selection history and did a bunch of work that's only relevant to
selections the user actually sees. So for each backspace,
`editor::UndoSelection` would need to be invoked 3-4 times before the
cursor actually moves. It still needs to be run twice after this change,
but that is a separate issue.

Signature help even had a `backspace_pressed: bool` as an incomplete
workaround, to avoid it flickering due to the selection switching
between being a range and being cursor-like.

The original motivation for this change is work I'm doing on not
re-querying completions when the language server provides a response
that has `is_incomplete: false`. Whether the menu is still visible is
determined by the cursor position, and this was complicated by it seeing
`backspace` temporarily moving the head of the selection 1 character to
the left.

This change also removes some redundant uses of
`push_to_selection_history`.

Not super stoked with the name `DeferredSelectionEffectsState`. Naming
is hard.

Release Notes:

- N/A
2025-05-30 01:53:02 -06:00
Cole Miller
1445af559b Unify the tasks modal and the new session modal (#31646)
Release Notes:

- Debugger Beta: added a button to the quick action bar to start a debug
session or spawn a task, depending on which of these actions was taken
most recently.
- Debugger Beta: incorporated the tasks modal into the new session modal
as an additional tab.

---------

Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
Co-authored-by: Julia Ryan <p1n3appl3@users.noreply.github.com>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Mikayla <mikayla@zed.dev>
2025-05-29 21:33:52 -04:00
Anthony Eid
804de3316e debugger: Update docs with more examples (#31597)
This PR also shows more completion items when defining a debug config in
a `debug.json` file. Mainly when using a pre build task argument.

### Follow ups
- Add docs for Go, JS, PHP
- Add attach docs

Release Notes:

- debugger beta: Show build task completions when editing a debug.json
configuration with a pre build task
- debugger beta: Add Python and Native Code debug config
[examples](https://zed.dev/docs/debugger)
2025-05-30 04:22:16 +03:00
Smit Barmase
a387bf5f54 zed: Fix migration banner not hiding after migration has been carried out (#31723)
- https://github.com/zed-industries/zed/pull/30444

This PR broke migration notification to only emit event when content is
migrated. This resulted in the migration banner not going away after
clicking "Backup and Migrate". It should also emit event when it's not
migrated which removes the banner.

Future: I think we should have better tests in place for banner
visibility.

Release Notes:

- Fixed an issue where migration banner wouldn't go away after clicking
"Backup and Migrate".
2025-05-30 06:00:37 +05:30
Marshall Bowers
c7047d5f0a collab: Fully move StripeBilling over to using StripeClient (#31722)
This PR moves over the last method on `StripeBilling` to use the
`StripeClient` trait, allowing us to fully mock out Stripe behaviors for
`StripeBilling` in tests.

Release Notes:

- N/A
2025-05-29 23:49:14 +00:00
Kirill Bulatov
406d975f39 Cleanup corresponding task history on task file update (#31720)
Closes https://github.com/zed-industries/zed/issues/31715

Release Notes:

- Fixed old task history not erased after task file update
2025-05-29 23:02:59 +00:00
Finn Evers
cbed580db0 workspace: Ensure pane handle hitbox blocks mouse events (#31719)
Follow-up to #31712

Pane handle hitboxes were opaque prior to the linked PR. This was the
case because pane handles have an intentionally larger hitbox than the
pane dividers size to allow for easier dragging. The cursor style is
also updated for that hitbox to indicate that resizing is possible:


9086784038/crates/workspace/src/pane_group.rs (L1297-L1301)

Not blocking the mouse events here causes mouse events to bleed through
this hitbox whilst actually any clicks will only cause a pane resize to
happen. Hence, this hitbox should continue to block mouse events to
avoid any confusion when resizing panes.

I considered using `HitboxBehavior::BlockMouseExceptScroll` here,
however, due to the reasons mentioned above, I decided against it. The
cursor will not indicate that scrolling should be possible. Since all
other mouse events on underlying elements (like hovers) are blocked, it
felt more reasonable to just go with `HitboxBehavior::BlockMouse`.

Release Notes:

- N/A
2025-05-29 16:35:22 -06:00
Michael Sloan
8aef64bbfa Remove block_mouse_down in favor of stop_mouse_events_except_scroll (#30401)
This method was added in #20649 to be an alternative of `occlude` which
allows scroll events. It seems a bit arbitrary to only stop left mouse
downs, so this seems like it's probably an improvement.

Release Notes:

- N/A
2025-05-29 22:07:34 +00:00
Michael Sloan
9086784038 gpui: Support hitbox blocking mouse interaction except scrolling (#31712)
tl;dr: This adds `.block_mouse_except_scroll()` which should typically
be used instead of `.occlude()` for cases when the mouse shouldn't
interact with elements drawn below an element. The rationale for
treating scroll events differently:

* Mouse move / click / styles / tooltips are for elements the user is
interacting with directly.
* Mouse scroll events are about finding the current outer scroll
container.

Most use of `occlude` should probably be switched to this, but I figured
I'd derisk this change by minimizing behavior changes to just the 3 uses
of `block_mouse_except_scroll`.

GPUI changes:

* Added `InteractiveElement::block_mouse_except_scroll()`, and removes
`stop_mouse_events_except_scroll()`

* Added `Hitbox::should_handle_scroll()` to be used when handling scroll
wheel events.

* `Window::insert_hitbox` now takes `HitboxBehavior` instead of
`occlude: bool`.

    - `false` for that bool is now `HitboxBehavior::Normal`.

    - `true` for that bool is now `HitboxBehavior::BlockMouse`.
    
    - The new mode is `HitboxBehavior::BlockMouseExceptScroll`.

* Removes `Default` impl for `HitboxId` since applications should not
manually create `HitboxId(0)`.

Release Notes:

- N/A
2025-05-29 21:41:15 +00:00
Kirill Bulatov
2abc5893c1 Improve TypeScript task detection (#31711)
Parses project's package.json to better detect Jasmine, Jest, Vitest and
Mocha and `test`, `build` scripts presence.
Also tries to detect `pnpm` and `npx` as test runners, falls back to
`npm`.


https://github.com/user-attachments/assets/112d3d8b-8daa-4ba5-8cb5-2f483036bd98

Release Notes:

- Improved TypeScript task detection
2025-05-29 20:51:20 +00:00
Marshall Bowers
a23ee61a4b Pass up intent with completion requests (#31710)
This PR adds a new `intent` field to completion requests to assist in
categorizing them correctly.

Release Notes:

- N/A

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-05-29 20:43:12 +00:00
Simon Pham
38e45e828b Add View Release Notes to Help menu (#31704)
<img width="891" alt="image"
src="https://github.com/user-attachments/assets/59e98fdb-c1b5-4948-8d69-661561d838f1"
/>



Release Notes:

- Added `View Release Notes` to `Help` menu
2025-05-29 19:39:54 +00:00
Danilo Leal
181bf78b7d agent: Change the navigation menu keybinding (#31709)
As much as I enjoyed the previous keybinding, it was causing a conflict
with the editor where it wouldn't open on text threads. To not get into
a rabbit hole and complicate the fix too much, I figured simply changing
it to something non-conflictual would be a good move.

Release Notes:

- agent: Fixed a bug where the panel navigation menu wouldn't open with
the keybinding on text threads.
2025-05-29 19:31:57 +00:00
Piotr Osiewicz
c42d060509 Update debug.json in Zed repo to run the build on session startup (#31707)
Closes #ISSUE

Release Notes:

- N/A
2025-05-29 21:29:18 +02:00
Peter Tripp
6ea9abdc1b Cursor keymap (#31702)
To use this, spawn `weclome: toggle base keymap selector` from the
command palette.

<img width="589" alt="Screenshot 2025-05-29 at 14 07 35"
src="https://github.com/user-attachments/assets/0d4c4eff-6a3b-40f4-9032-5d8ca7664d20"
/>

MacOS is well tested to match Cursor. The [curors keymap
documentation](https://docs.cursor.com/kbd) is does not explicitly state
windows/linux keymap entries only "All Cmd keys can be replaced with
Ctrl on Windows." so that is what we've done. We welcome feedback /
refinements.

Note, because this provides a mapping for `cmd-k` (macos) and `ctrl-k`
(linux/windows) using this keymap will disable all of the default
chorded keymap entries which have `cmd-k` / `ctrl-k` as a prefix. For
example `cmd-k cmd-s` for open keymap will no longer function.

Release Notes:

- Added Cursor compatibility keymap

---------

Co-authored-by: Joseph Lyons <joseph@zed.dev>
2025-05-29 15:20:58 -04:00
Piotr Osiewicz
070eac28e3 go: Use delve-dap-shim for spawning delve (#31700)
This allows us to support terminal with go sessions

Closes #ISSUE

Release Notes:

- debugger: Add support for terminal when debugging Go programs
2025-05-29 21:19:56 +02:00
Danilo Leal
05692e298a agent: Fix panel "go back" button (#31706)
Closes https://github.com/zed-industries/zed/issues/31652.

Release Notes:

- agent: Fixed a bug where the "go back" button wouldn't go back to the
Text Thread after visiting another view from it.
2025-05-29 16:00:37 -03:00
Richard Feldman
ccb049bd97 Format streamed edits on save (#31623)
Re-enables format on save for agent changes (when the user has that
enabled in settings), except differently from before:
- Now we do the format-on-save in the separate buffer the edit tool
uses, *before* the diff
- This means it never triggers separate staleness
- It has the downside that edits are now blocked on the formatter
completing, but that's true of saving in general.

Release Notes:

- N/A

---------

Co-authored-by: Agus Zubiaga <hi@aguz.me>
2025-05-29 14:33:41 -04:00
Danilo Leal
fe57eedb44 agent: Rename PromptEditor to TextThread in the panel's ActiveView (#31705)
Was touching this part of the Agent Panel and thought it could be a
quick name consistency win here, so it is aligned with the terminology
we're currently actively using in the product/docs.

Release Notes:

- N/A
2025-05-29 15:31:35 -03:00
5brian
c57e6bc784 tab_switcher: Add placeholder text (#31697)
| Before | After |
|---|---|
|<img width="478" alt="image"
src="https://github.com/user-attachments/assets/5baba783-ee31-42cd-9760-7ee19edb1123"
/>|<img width="478" alt="image"
src="https://github.com/user-attachments/assets/1b149500-4a97-4085-80e5-fd628c92471a"
/>|

Release Notes:

- N/A
2025-05-29 16:09:07 +00:00
Piotr Osiewicz
83135e98e6 Introduce $ZED_CUSTOM_PYTHON_ACTIVE_ZED_TOOLCHAIN_RAW to work around (#31685)
Follow up to #31674 

Release Notes:

- N/A

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
2025-05-29 13:44:55 +00:00
Umesh Yadav
703ee29658 Rename Max Mode to Burn Mode throughout code and docs (#31668)
Follow up to https://github.com/zed-industries/zed/pull/31470.

I started looking at config and changed preferred_completion_mode to
burn to only find its max so made changes to align it better with
rebrand. As this is in preview build now.

This doesn't touch zed_llm_client. Only the Zed changes the code and doc
to match the new UI of burn mode. There are still more things to be
renamed, though.

Release Notes:

- N/A

---------

Signed-off-by: Umesh Yadav <git@umesh.dev>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-05-29 13:12:42 +00:00
Kirill Bulatov
f792827a01 Allow to reuse PickerPopoverMenu outside of the model selector (#31684)
LSP button preparation step: move out the component that will be used to
build the button's context menu.

Release Notes:

- N/A
2025-05-29 12:55:47 +00:00
Danilo Leal
45f9edcbb9 docs: Add small refinements to CSS adjacent pages (#31683)
Follow up to https://github.com/zed-industries/zed/pull/31681. Was
visiting some of these pages and noticed these somewhat small formatting
and copywriting improvement opportunities. The docs for Svelte in
particular felt somewhat unorganized.

Release Notes:

- N/A
2025-05-29 08:43:54 -03:00
Danilo Leal
e3354543c0 docs: Improve the Tailwind CSS page (#31681)
Namely, ensuring we mention the support for their Prettier plugins.

Release Notes:

- N/A
2025-05-29 08:15:59 -03:00
Oleksiy Syvokon
cb187b0b4d evals: Configurable number of max dialog turns (#31680)
Release Notes:

- N/A
2025-05-29 10:35:29 +00:00
Kirill Bulatov
d989b2260b Do not react on settings change for disabled minimaps (#31677)
Turning minimap on during debug sessions would cause the console editor
to gain the minimap, despite it being explicitly disabled in the code.

Release Notes:

- N/A
2025-05-29 10:04:27 +00:00
Dhruvin Gandhi
ae076fa415 task: Add ZED_RELATIVE_DIR task variable (#31657)
This is my first contribution to zed, let me know if I missed anything.

There is no corresponding issue/discussion.

`$ZED_RELATIVE_DIR` can be used in cases where a task's command's
filesystem namespace (e.g. inside a container) is different than the
host, where absolute paths cannot work.

I modified `relative_path` to `relative_file` after the addition of
`relative_dir`.

For top-level files, where `relative_file.parent() == Some("")`, I use
`"."` for `$ZED_RELATIVE_DIR`, which is a valid relative path in both
*nix and windows.

Thank you for building zed, and open-sourcing it. I hope to contribute
more as I use it as my primary editor.

Release Notes:

- Added ZED_RELATIVE_DIR (path to current file's directory relative to
worktree root) task variable.
2025-05-29 11:50:36 +02:00
Kirill Bulatov
b4af61edfe Revert "task: Wrap programs in ""s (#31537)" (#31674)
That commit broke a lot, as our one-off tasks (alt-enter in the tasks
modal), npm, jest tasks are all not real commands, but a composition of
commands and arguments.

This reverts commit 5db14d315b.

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

Release Notes:

- N/A

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-05-29 09:19:23 +00:00
Smit Barmase
ea8a3be91b recent_projects: Move SSH server entry to initialize once instead of every render (#31650)
Currently, `RemoteEntry::SshConfig` for `ssh_config_servers` initializes
on every render. This leads to side effects like a new focus handle
being created on every render, which leads to breaking navigating
up/down for `ssh_config_servers` items.

This PR fixes it by moving the logic of remote entry
for`ssh_config_servers` into `default_mode`, and only rebuilding it when
`ssh_config_servers` actually changes.

Before:


https://github.com/user-attachments/assets/8c7187d3-16b5-4f96-aa73-fe4f8227b7d0

After:


https://github.com/user-attachments/assets/21588628-8b1c-43fb-bcb8-0b93c70a1e2b

Release Notes:

- Fixed issue navigating SSH config servers in Remote Projects with
keyboard.
2025-05-29 09:24:39 +05:30
Smit Barmase
5173a1a968 recent_projects: Fix remote projects not regaining focus after SSH server connect (#31651)
Closes #28071

Release Notes:

- Fixed issue preventing remote projects modal from regaining focus
after a successful SSH server connection.
2025-05-29 08:55:29 +05:30
Smit Barmase
87f097a0ab terminal_view: Fix terminal stealing focus on editor selection (#31639)
Closes #28234

Release Notes:

- Fixed the issue where the terminal focused when the mouse hovered over
it after selecting text in the editor.
2025-05-29 08:55:12 +05:30
Cole Miller
f9407db7d6 debugger: Add spinners while session is starting up (#31548)
Release Notes:

- Debugger Beta: Added a spinner to the debug panel when a session is
starting up.

---------

Co-authored-by: Remco Smits <djsmits12@gmail.com>
Co-authored-by: Julia <julia@zed.dev>
2025-05-29 01:58:40 +00:00
Cole Miller
384b11392a debugger: Disambiguate child session labels (#31526)
Add `(child)` instead of using the same label.

Release Notes:

- Debugger Beta: Made child sessions appear distinct from their parents
in the session selector.
2025-05-28 21:44:15 -04:00
Cole Miller
f20596c33b debugger: Don't open non-absolute paths from stack frame list (#31534)
Follow-up to #31524 with a more general fix

Release Notes:

- N/A

---------

Co-authored-by: Piotr <piotr@zed.dev>
2025-05-28 21:44:00 -04:00
Marshall Bowers
eb863f8fd6 collab: Use StripeClient when creating Stripe Checkout sessions (#31644)
This PR updates the `StripeBilling::checkout_with_zed_pro` and
`StripeBilling::checkout_with_zed_pro_trial` methods to use the
`StripeClient` trait instead of using `stripe::Client` directly.

Release Notes:

- N/A
2025-05-29 00:57:04 +00:00
Max Brunsfeld
97579662e6 Fix editor rendering slowness with large folds (#31569)
Closes https://github.com/zed-industries/zed/issues/31565

* Looking up settings on every row was very slow in the case of large
folds, especially if there was an `.editorconfig` file with numerous
glob patterns
* Checking whether each indent guide was within a fold was very slow,
when a fold spanned many indent guides.

Release Notes:

- Fixed slowness that could happen when editing in the presence of large
folds.
2025-05-28 23:05:06 +00:00
Marshall Bowers
53849cf983 collab: Remove Zed Free as an option when initiating a checkout session (#31638)
This PR removes Zed Free as an option when initiating a checkout
session, as we manage this plan automatically now.

Release Notes:

- N/A
2025-05-28 23:00:54 +00:00
Danilo Leal
1e25249055 docs: Adjust the channels page a bit (#31636)
All the docs related to collaboration could use some deep revamp, but
this PR is just formatting tweaks so it doesn't look broken. The images
weren't showing at all!

Release Notes:

- N/A
2025-05-28 19:27:47 -03:00
Marshall Bowers
469824c350 collab: Use StripeClient for creating model usage meter events (#31633)
This PR updates the `StripeBilling::bill_model_request_usage` method to
use the `StripeClient` trait.

Release Notes:

- N/A
2025-05-28 22:19:43 +00:00
Danilo Leal
a1c645e57e docs: Improve footer button design (#31634)
Just touching up these a little bit. I think including the page
destination as a label here is a good move!

Release Notes:

- N/A
2025-05-28 19:16:40 -03:00
Danilo Leal
0791596cda docs: Hide "on this page" element when there are no headings (#31635)
We were still showing the "On this page" element even when the page
didn't contain any h2s or h3s.

Release Notes:

- N/A
2025-05-28 19:16:32 -03:00
Finn Evers
9cc1851be7 python: Improve docstring highlighting (#31628)
This PR broadens the highlighting for docstrings in Python. 

Previously, only the first docstring for e.g. type aliases was
highlighted in Python files. This happened as only the first occurrence
in the module was considered a docstring. With this change, now all
existing docstrings are actually highlighted as such.

| `main` | This PR | 
| --- | --- |
|
![main](https://github.com/user-attachments/assets/facc96a9-4e98-4063-8b93-d6e9884221ff)
|
![PR](https://github.com/user-attachments/assets/9da557a1-b327-466a-be87-65d6a811e24c)
|

Release Notes:

- Added more docstring highlights for Python.
2025-05-29 00:02:40 +02:00
Finn Evers
50bd8770bd file_finder: Reduce vertical padding in footer (#31632)
Follow-up to #31542

This PR reduces the vertical padding in the file finders footer. We can
remove this padding as we already apply it just above


a5a116439e/crates/file_finder/src/file_finder.rs (L1500)

This also ensures that the items on the right side have the same padding
to the border as the icon on the left side. Currently, due to the
padding being applied twice, the items on the right side have `pr_4` as
well as `py_4` in practice, which seems a little excessive.

| `main` | This PR |
| --- | --- |
|
![file_finder_main](https://github.com/user-attachments/assets/352d2ac9-04a9-487d-96ca-b009b797809b)
|
![file_finder_pr](https://github.com/user-attachments/assets/c0b44beb-ff2c-4e93-a5b1-2393652a2a58)
|


Release Notes:

- N/A
2025-05-28 21:29:51 +00:00
Marshall Bowers
00bdebc89d collab: Use StripeClient in StripeBilling::subscribe_to_price (#31631)
This PR updates the `StripeBilling::subscribe_to_price` method to use
the `StripeClient` trait.

Release Notes:

- N/A
2025-05-28 21:17:11 +00:00
Danilo Leal
d5134062ac agent: Add keybinding to toggle Burn Mode (#31630)
One caveat with this PR is that the keybinding still doesn't work for text threads. Will do that in a follow-up.

Release Notes:

- agent: Added a keybinding to toggle Burn Mode on and off.
2025-05-28 18:08:58 -03:00
Julia Ryan
0e9f6986cf nix: Add job names and garnix substitutor (#31625)
This should result in some additional cache hits as I personally use
garnix.

Also added `-v` cachix arg to try to figure out why CI jobs aren't
pushing any paths. Right now they just show ["Pushing is
disabled."](https://github.com/zed-industries/zed/actions/runs/15293723678/job/43018512167#step:13:3)
but I'm not sure if that's due to the `pushFilter` or misconfigured
secrets.

Release Notes:

- N/A
2025-05-28 13:32:12 -07:00
Finn Evers
1035c6aab5 editor: Fix horizontal scrollbar alignment if indent guides are disabled (#31621)
Follow-up to #24887
Follow-up to #31510

This PR ensures that [this misalignment of the horizontal
scrollbar](https://github.com/zed-industries/zed/pull/31510#issuecomment-2912842457)
does not occur. See the entire discussion in the first linked PR as to
why this gap is there in the first place.

I am also aware of the general stance towards comments. Yet, I felt for
this case it is better to just straight up explain how these two things
are connected, as I do believe this is not intuitively clear after all.

Might also be a good time to bring
https://github.com/zed-industries/zed/issues/25519 up again. The
horizontal scrollbar seems huge for the edit file tool card.
Furthermore, since we do not reserve space for the horizontal scrollbar
(yet), this will lead to the last line being not clickable.

Release Notes:

- N/A
2025-05-28 22:59:51 +03:00
Marshall Bowers
75e69a5ae9 collab: Use StripeClient to retrieve prices and meters from Stripe (#31624)
This PR updates `StripeBilling` to use the `StripeClient` trait to
retrieve prices and meters from Stripe instead of using the
`stripe::Client` directly.

Release Notes:

- N/A
2025-05-28 19:51:06 +00:00
Oleksiy Syvokon
05afe95539 agent: Fix bug in creating empty files (#31626)
Release Notes:

- NA
2025-05-28 19:31:54 +00:00
Oleksiy Syvokon
a5a116439e agent: Rejecting agent changes shouldn't discard user edits (#31617)
The fix prevents data loss, but it also results in a somewhat confusing
UX. Specifically, after the user has made changes to an AI-created file,
selecting "Reject" will leave AI changes in place.

This is because there's no trivial way to disentangle user edits from
the edits made by the AI.

A better solution might exist. In the meantime, this change should do.
    
Closes
* #30527 

Release Notes:

- Prevent data loss when reverting changes in an agent-created file
2025-05-28 18:44:49 +00:00
Marshall Bowers
361ceee72b collab: Introduce StripeClient trait to abstract over Stripe interactions (#31615)
This PR introduces a new `StripeClient` trait to abstract over
interacting with the Stripe API.

This will allow us to more easily test our billing code.

This initial cut is small and focuses just on making
`StripeBilling::find_or_create_customer_by_email` testable. I'll follow
up with using the `StripeClient` in more places.

Release Notes:

- N/A
2025-05-28 18:34:44 +00:00
Danilo Leal
68724ea99e agent: Make clicking on the backdrop to dismiss message editing more reliable (#31614)
Previously, the click on the backdrop to dismiss the message editing was
unreliable. You would click on it and sometimes it would work and others
it wouldn't. This PR fixes that now.

Release Notes:

- agent: Fixes the previous message dismissal by clicking on the
backdrop
2025-05-28 15:29:52 -03:00
Danilo Leal
e12106e025 agent: Move focus to the panel after dismissing a user message edit (#31611)
Previously, when you clicked on a previous message to edit it and then
dismissed it, your focus would jump to the buffer. This caught me
several times as the most obvious place to return to for me was the
agent panel main message editor, so I can continue prompting something
else. And this is what this PR changes.

Release Notes:

- agent: Improved previous message editing UX by returning focus to the
main panel's text area after dismissing it.
2025-05-28 15:24:58 -03:00
Umesh Yadav
77aa667bf3 docs: Update LM Studio docs to show tool use is supported (#31610)
As the lmstudio tool call support was added recently:
https://github.com/zed-industries/zed/pull/30589. This updates the doc
to reflect it.

Release Notes:

- N/A
2025-05-28 20:09:20 +02:00
Peter Tripp
8b47b40dc0 Improve AI GitHub Issue template (#31598)
Release Notes:

- N/A
2025-05-28 13:54:07 -04:00
Max Brunsfeld
01990c8375 Bump Tree-sitter to 0.25.5 for YAML-editing crash fix (#31603)
Closes https://github.com/zed-industries/zed/issues/31380

See https://github.com/tree-sitter/tree-sitter/pull/4472 for the fix

Release Notes:

- Fixed a crash that could occur when editing YAML files.
2025-05-28 10:12:27 -07:00
Umesh Yadav
4e7dc37f01 language_models: Remove handling of WrappedTextContent in tool result content (#31605)
Fixes ci pipeline

Release Notes:

- N/A
2025-05-28 16:43:08 +00:00
Richard Feldman
00fd045844 Make language model deserialization more resilient (#31311)
This expands our deserialization of JSON from models to be more tolerant
of different variations that the model may send, including
capitalization, wrapping things in objects vs. being plain strings, etc.

Also when deserialization fails, it reports the entire error in the JSON
so we can see what failed to deserialize. (Previously these errors were
very unhelpful at diagnosing the problem.)

Finally, also removes the `WrappedText` variant since the custom
deserializer just turns that style of JSON into a normal `Text` variant.

Release Notes:

- N/A
2025-05-28 12:06:07 -04:00
Joseph T. Lyons
7443fde4e9 Show version info when downloading and installing updates (#31568)
Follow up to #31179 

In addition to seeing the version when in the `Click to restart and
update Zed` status, this PR allows us to see the version when in
`Downloading Zed update…` or `Installing Zed update…` status, in a
tooltip, when hovering on the activity indicator.

Will merge after tomorrow's release.

Release Notes:

- Added version information, in a tooltip, when hovering on the activity
indicator for both the download and install status.
2025-05-28 11:51:21 -04:00
Joseph T. Lyons
d5ab42aeb8 Clean up some auto updater code (#31543)
This PR simply does a tiny bit of cleanup on some code, where I wasn't
quite happy with the naming and ordering of parameters of the now
`check_if_fetched_version_is_newer` function. There should be no
functional changes here, but I will wait until after tomorrow's release
to merge.

Release Notes:

- N/A
2025-05-28 11:46:41 -04:00
Kirill Bulatov
07403f0b08 Improve LSP tasks ergonomics (#31551)
* stopped fetching LSP tasks for too long (but still use the hardcoded
value for the time being — the LSP tasks settings part is a simple bool
key and it's not very simple to fit in another value there)

* introduced `prefer_lsp` language task settings value, to control
whether in the gutter/modal/both/none LSP tasks are shown exclusively,
if possible

Release Notes:

- Added a way to prefer LSP tasks over Zed tasks
2025-05-28 18:36:25 +03:00
Remco Smits
00bc154c46 debugger: Fix invalid schema for pathMappings (#31595)
See
https://github.com/xdebug/vscode-php-debug?tab=readme-ov-file#remote-host-debugging

Release Notes:

- Debugger Beta: Fixed invalid schema for `pathMappings`
2025-05-28 15:16:12 +00:00
Joseph T. Lyons
f627ac92ee Bump Zed to v0.190 (#31592)
Release Notes:

-N/A
2025-05-28 14:36:50 +00:00
Cole Miller
218e8d09c5 Revert "Fix text wrapping in commit message editors (#31030)" (#31587)
This reverts commit f2601ce52c.

Release Notes:

- N/A
2025-05-28 10:16:34 -04:00
Peter Tripp
2c4b75ab30 Remove agent label for github issues (#31591)
Release Notes:

- N/A
2025-05-28 14:09:35 +00:00
Anthony Eid
aab76208b5 debugger beta: Fix bug where debug Rust main running action failed (#31291)
@osiewicz @SomeoneToIgnore If you guys have time to look this over it
would be greatly appreciated. I wanted to move the bug fix into the task
resolution code but wasn't sure if there was a reason that we didn't
already.

The bug is caused by an env variable being empty when we send it as a
terminal command. When the shell resolves all the env variables there's
an extra space that gets added due to the empty env variable being
placed between two other variables.

Closes #31240

Release Notes:

- debugger beta: Fix a bug where debug main Rust runner action wouldn't
work
2025-05-28 13:59:48 +00:00
Umesh Yadav
f3f0766242 assistant_tools: Remove description.md files of removed tools (#31586)
This pull request removes orphaned description.md files for tools that
were deleted in [PR
#29808](https://github.com/zed-industries/zed/pull/29808). These
descriptions are no longer needed as their corresponding tools no longer
exist
Closes #ISSUE

Release Notes:

- N/A
2025-05-28 09:55:38 -04:00
Ben Brandt
148e9adec2 Revert "agent: Namespace MCP server tools" (#31588)
Reverts zed-industries/zed#30600
2025-05-28 13:25:53 +00:00
Alvaro Parker
e314963f5b agent: Add max mode on text threads (#31361)
Related discussions #30240 #30596

Release Notes:

- Added the ability to use max mode on text threads.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-05-28 10:12:38 -03:00
Antonio Scandurra
957e4adc3f Fix lag when interacting with MarkdownElement (#31585)
Previously, we forgot to associate the `Markdown` entity to
`MarkdownElement` during `prepaint`. This caused calls to
`Context<Markdown>::notify` to not invalidate the view cache, which
meant we would have to wait for some other invalidation before seeing
the results of that initial notify.

Release Notes:

- Improved responsiveness of mouse interactions with the agent panel.

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-05-28 12:50:45 +00:00
Anthony Eid
fee6f13887 debugger: Fix go locator creating false scenarios (#31583)
This caused other locators to fail because go would accept build tasks
that it couldn't actually resolve

Release Notes:

- N/A
2025-05-28 12:34:14 +00:00
Antonio Scandurra
4f78165ee8 Show progress as the agent locates which range it needs to edit (#31582)
Release Notes:

- Improved latency when the agent starts streaming edits.

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-05-28 12:32:54 +00:00
Alex
94a5fe265d debugger: Improve Go support (#31559)
Supersedes https://github.com/zed-industries/zed/pull/31345 
This PR does not have any terminal/console related stuff so that it can
be solved separately.

Introduces inline hints in debugger:
<img width="1141" alt="image"
src="https://github.com/user-attachments/assets/b0575f1e-ddf8-41fe-8958-2da6d4974912"
/>
Adds locators for go, so that you can your app in debug mode:
<img width="706" alt="image"
src="https://github.com/user-attachments/assets/df29bba5-8264-4bea-976f-686c32a5605b"
/>
As well is allows you to specify an existing compiled binary:
<img width="604" alt="image"
src="https://github.com/user-attachments/assets/548f2ab5-88c1-41fb-af84-115a19e685ea"
/>

Release Notes:

- Added inline value hints for Go debugging, displaying variable values
directly in the editor during debug sessions
- Added Go debug locator support, enabling debugging of Go applications
through task templates
- Improved Go debug adapter to support both source debugging (mode:
"debug") and binary execution (mode: "exec") based on program path

cc @osiewicz, @Anthony-Eid
2025-05-28 12:59:05 +02:00
Piotr Osiewicz
c0a5ace8b8 debugger: Add locator for Python tasks (#31533)
Closes #ISSUE

Release Notes:

- debugger: Python tests/main functions can now we debugged from the
gutter.

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-05-28 12:27:12 +02:00
Smit Barmase
15d59fcda9 vim: Fix crash when using ‘ge’ motion on multibyte character (#31566)
Closes #30919

- [x] Test

Release Notes:

- Fixed the issue where using the Vim motion `ge` on multibyte character
would cause Zed to crash.
2025-05-28 06:30:51 +05:30
Smit Barmase
6545c5ebe0 linux: Fix crash when switching repository via git panel (#31556)
Closes #30409

Handles edge case where `f32` turns into `Nan` and causes panic down the
code.

Release Notes:

- Fixed issue where Zed crashes on switching repository via git panel on
Linux.
2025-05-28 05:26:00 +05:30
Michael Sloan
506beafe10 Add caching of parsed completion documentation markdown to reduce flicker when selecting (#31546)
Related to #31460 and #28635.

Release Notes:

- Fixed redraw delay of documentation from language server completions
and added caching to reduce flicker when using arrow keys to change
selection.
2025-05-27 23:12:38 +00:00
tongjicoder
31d908fc74 Remove redundant words in comments (#31512)
remove redundant word in comment


Release Notes:

- N/A

Signed-off-by: tongjicoder <tongjicoder@icloud.com>
2025-05-27 23:01:31 +00:00
Danilo Leal
0731097ee5 agent: Improve consecutive tool call UX and rebrand Max Mode (#31470)
This PR improves the consecutive tool call UX by allowing users to
quickly continue an interrupted with one-click. What we do here is
insert a hidden "Continue" message that will just nudge the LLM to keep
going. We're also using the opportunity to upsell the previously called
"Max Mode", now rebranded as "Burn Mode", which allows users to don't be
interrupted anymore if they ever have 25 consecutive tool calls again.

Release Notes:

- agent: Improve consecutive tool call UX by allowing users to quickly
continue an interrupted thread with one click.

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Agus Zubiaga <hi@aguz.me>
Co-authored-by: Agus Zubiaga <agus@zed.dev>
2025-05-27 19:44:10 -03:00
Finn Evers
233b73b385 ui: Implement hover color for scrollbar component (#25525)
This PR implements color changing for the scrollbar component based upon
user mouse interaction.


https://github.com/user-attachments/assets/2fd14e2d-cc5c-4272-906e-bd39bfb007e4


This PR also already adds the state for a scrollbar being actively
dragged. However, as themes currently do not provide a color for this
scenario, this implementation re-uses the hover color as a placeholder
instead. If this feature is at all wanted, I can quickly open up a
follow-up PR which adds support for that property to themes as well as
this component.

Release Notes:

- Added hover state to scrollbars outside of the editor.
2025-05-27 18:16:04 -04:00
Marshall Bowers
0145e2c101 inline_completion_button: Fix links to account page (#31558)
This PR fixes an issue where the various links to the account page from
the Edit Prediction menu were not working.

The `OpenZedUrl` action is opening URLs that deep-link _into_ Zed.

Fixes https://github.com/zed-industries/zed/issues/31060.

Release Notes:

- Fixed an issue with opening links to the Zed account page from the
Edit Prediction menu.
2025-05-27 21:52:42 +00:00
Marshall Bowers
09fc64e0c5 collab: Downgrade non-collab queries to READ COMMITTED isolation level (#31552)
This PR downgrades a number of database queries that aren't part of the
actual collaboration from `SERIALIZABLE` to `READ COMMITTED`.

The serializable isolation level is overkill for these queries.

Release Notes:

- N/A
2025-05-27 17:02:27 -04:00
Marshall Bowers
fc803ce9d4 collab: Increase max database connections to 250 (#31553)
This PR increases the number of max database connections to 250.

Release Notes:

- N/A
2025-05-27 16:48:50 -04:00
Max Brunsfeld
697c2ba71f Enable merge conflict parsing for currently-unmerged files (#31549)
Previously, we only enabled merge conflict parsing for files that were
unmerged at the last time a change was detected to the repo's merge
heads. Now we enable the parsing for these files *and* any files that
are currently unmerged.

The old strategy meant that conflicts produced via `git stash pop` would
not be parsed.

Release Notes:

- Fixed parsing of merge conflicts when the conflict was produced by a
`git stash pop`
2025-05-27 13:34:39 -07:00
Richard Feldman
f54c057001 Add warning message when editing a message in a thread (#31508)
<img width="479" alt="Screenshot 2025-05-27 at 9 42 44 AM"
src="https://github.com/user-attachments/assets/7bd9e1b9-26b4-4396-9f93-e92a5f4ac2e1"
/>

Release Notes:

- Added notice that editing a message in the agent panel will restart
the thread from that point.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-05-27 16:26:47 -04:00
Marshall Bowers
32848e9c8a collab: Add support for overage billing for Claude Opus 4 (#31544)
This PR adds support for billing for overages for Claude Opus 4.

Release Notes:

- N/A
2025-05-27 18:37:57 +00:00
Anthony Eid
86b75759d1 debugger beta: Autoscroll to recently saved debug scenario when saving a scenario (#31528)
I added a test to this too as one of my first steps of improving
`NewSessionModal`'s test coverage.


Release Notes:

- debugger beta: Select saved debug config when opening debug.json from
`NewSessionModal`
2025-05-27 21:35:17 +03:00
Kirill Bulatov
94c006236e Properly handle ignored files in the file finder (#31542)
Follow-up of https://github.com/zed-industries/zed/pull/31457

Add a button and also allows to use `search::ToggleIncludeIgnored`
action in the file finder to toggle whether to show gitignored files or
not.
By default, returns back to the gitignored treatment before the PR
above.


![image](https://github.com/user-attachments/assets/c3117488-9c51-4b34-b630-42098fe14b4d)


Release Notes:

- Improved file finder to include indexed gitignored files in its search
results
2025-05-27 18:34:28 +00:00
Julia Ryan
5b6b911946 nix: Refactor gh-actions and re-enable nightly builds (#31489)
Now that the nix build is working again, re-enable nightly builds and
refactor the workflow for re-use between nightly releases and CI jobs.

Release Notes:

- N/A

---------

Co-authored-by: Rahul Butani <rrbutani@users.noreply.github.com>
2025-05-27 11:34:15 -07:00
Ben Kunkle
b9a5d437db Cursor settings import (#31424)
Closes #ISSUE

Release Notes:

- Added support for importing settings from cursor. Cursor settings can
be imported using the `zed: import cursor settings` command from the
command palette
2025-05-27 14:14:25 -04:00
Bennet Bo Fenner
21bd91a773 agent: Namespace MCP server tools (#30600)
This fixes an issue where requests were failing when MCP servers were
registering tools with the same name.
We now prefix the tool names with the context server name, in the UI we
still show the name that the MCP server gives us

Release Notes:

- agent: Fix an error were requests would fail if two MCP servers were
using an identical tool name
2025-05-27 17:47:44 +00:00
Piotr Osiewicz
5db14d315b task: Wrap programs in ""s (#31537)
This commit effectively re-implements #21981 in task system. commands
with spaces cannot be spawned currently, and we don't want to have to
deal with shell variables wrapped in "" in DAP locators.

Closes #ISSUE

Release Notes:

- Fixed an issue where tasks with spaces in `command` field could not be
spawned.
2025-05-27 19:33:16 +02:00
Anthony Eid
b63cea1f17 debugger beta: Fix gdb/delve JSON data conversion from New Session Modal (#31501)
test that check's that each conversion works properly based on the
adapter's config validation function. 

Co-authored-by: Zed AI \<ai@zed.dev\>

Release Notes:

- debugger beta: Fix bug where Go/GDB configuration's wouldn't work from
NewSessionModal
2025-05-27 17:28:41 +00:00
5brian
b7c5540075 git_ui: Replace spaces with hyphens in new branch names (#27873)
This PR improves UX by converting spaces to hyphens, following branch
naming conventions and allowing users to create branches without
worrying about naming restrictions.

I think a few other git tools do this, which was nice.



![image](https://github.com/user-attachments/assets/db40ec31-e461-4ab3-a3de-e249559994fc)

Release Notes:

- Updated the branch picker to convert spaces to hyphens when creating
new branch names.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-05-27 17:10:20 +00:00
Cole Miller
b01f7c848b Make it possible to use cargo-zigbuild for ZED_BUILD_REMOTE_SERVER (#31467)
This is significantly faster for me than using Cross.

Release Notes:

- N/A
2025-05-27 16:56:27 +00:00
Finn Evers
3476705bbb docs_preprocessor: Ensure keybind is found for actions with arguments (#27224)
Tried fixing a keybind in
https://github.com/zed-industries/zed/pull/27217 just to find out it
[still doesnt render
afterwards](https://zed.dev/docs/extensions/languages#language-metadata)
😅 This PR is a quick follow-up to fix this issue.

Issue here is (as seen in the code comment) that the
`editor::ToggleComments` command has additional arguments which caused
the match to fail. However, simply adding the missing arguments does not
work, since the regex only matches the first closing brace and fails to
match multiple closing braces. I decided against changing the matching
since it additionally looked confusing and unintuitive to use.

To not be too intrusive with this change, I just decided to add some
processing for the action string (the `KeymapAction` is not exported
from the settings and the `Value` it holds is also private). The
processing basically reverts the conversion done in `keymap_file.rs`
4b5df2189b/crates/settings/src/keymap_file.rs (L102-L115)
and extracts just the action name. It changes nothing for existing
keybinds and fixes the aforementioned issue.

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-05-27 16:56:03 +00:00
张小白
ba6b5a59f9 windows: Fix title bar not responsing (#31532)
Closes #31431

Release Notes:

- N/A
2025-05-27 16:43:48 +00:00
Antonio Scandurra
28d6362964 Revert "Highlight file finder entries according to their git status" (#31529)
Reverts zed-industries/zed#31469

This isn't looking great, so reverting for now.

/cc @SomeoneToIgnore
2025-05-27 16:10:49 +00:00
Ben Kunkle
b4a03989b1 javascript/typescript/tsx: Highlight private properties (#31527)
Closes #28411

Release Notes:

- Fixed the lack of highlighting for private properties in classes for
JavaScript/TypeScript/TSX files
2025-05-27 16:05:53 +00:00
Cole Miller
19b6892c8d debugger: Don't try to open <node_internals> paths (#31524)
The JS DAP returns these, and they don't point to anything real on the
filesystem.

Release Notes:

- N/A
2025-05-27 15:32:48 +00:00
Ben Brandt
b5c2b25a76 agent: Keep horizontal scrollbar in edit file tool cards (#31510)
Previously disabled both scrollbars, but horizontal scrolling is still
needed when
lines exceed the viewport width. Now editors can disable a single scroll
axis, not just both.

Release Notes:

- N/A
2025-05-27 17:24:22 +02:00
Marshall Bowers
8faeb34367 Rename assistant_settings to agent_settings (#31513)
This PR renames the `assistant_settings` crate to `agent_settings`, as
well a number of constructs within it.

Release Notes:

- N/A
2025-05-27 15:16:55 +00:00
Oleksiy Syvokon
61a40e293d evals: Allow threads explorer to search for JSON files recursively (#31509)
It's just more convenient to call it from CLI this way.

+ minor fixes in evals

Release Notes:

- N/A
2025-05-27 14:18:47 +00:00
Cole Miller
239ffa49e1 debugger: Improve keyboard navigability of variable list (#31462)
This PR adds actions for copying variable names and values and editing
variable values from the variable list. Previously these were only
accessible using the mouse. It also fills in keybindings for expanding
and collapsing entries on Linux that we already had on macOS.

Release Notes:

- Debugger Beta: Added the `variable_list::EditVariable`,
`variable_list::CopyVariableName`, and
`variable_list::CopyVariableValue` actions and default keybindings.
2025-05-27 13:50:41 +00:00
Richard Feldman
a4978ee5ff Restore Checkpoint now appears if you press Cancel (#31310)
## Before


https://github.com/user-attachments/assets/0da54afd-78bb-4fee-ab0c-f6ff96f89550

## After


https://github.com/user-attachments/assets/e840e642-714b-4ed7-99cf-a972f50361ba

Release Notes:

- In the Agent Panel, Restore Checkpoint now appears if you press Cancel
during generation.
2025-05-27 09:22:42 -04:00
Marshall Bowers
a8ca7e9c04 Fix Claude Sonnet 4 model ID (#31505)
This PR is a follow-up to
https://github.com/zed-industries/zed/pull/31415 that fixes the model ID
for Claude Sonnet 4.

With the release of the Claude 4 models, the model version now appears
at the end.

Release Notes:

- N/A
2025-05-27 13:10:29 +00:00
shenjack
ee6ce78fed Remove once_cell dependency (#31493)
removing once_cell dep imported from #31439

it should work just fine

Release Notes:

- N/A
2025-05-27 11:17:50 +00:00
Anthony Eid
3ff62ef289 debugger beta: Update Javascript's DAP to allow passing in url instead of program (#31494)
Closes #31375

Release Notes:

- debugger beta: Allow passing in URL instead of program for Javascript
launch request
2025-05-27 11:02:16 +00:00
Danilo Leal
f8f36d0c17 docs: Improve agent's "get notified" section (#31496)
Quick docs refinement as a follow-up to
https://github.com/zed-industries/zed/pull/31472.

Release Notes:

- N/A
2025-05-27 08:00:37 -03:00
Raphael Lüthy
05763b2fe3 debugger beta: Fix install detection for Debugpy in venv (#31339)
Based on my report on discord when chatting with Anthony and Remco:
https://discord.com/channels/869392257814519848/1375129714645012530

Root Cause: Zed was incorrectly trying to execute a directory path
instead of properly invoking the debugpy module when debugpy was
installed via package managers (pip, conda, etc.) rather than downloaded
from GitHub releases.

Solution:

- Automatic Detection: Zed now automatically detects whether debugpy is
installed via pip/conda or downloaded from GitHub
- Correct Invocation: For pip-installed debugpy, Zed now uses python -m
debugpy.adapter instead of trying to execute file paths
- Added a `installed_in_venv` flag to differentiate the setup properly
- Backward Compatibility: GitHub-downloaded debugpy releases continue to
work as before
- Enhanced Logging: Added logging to show which debugpy installation
method is being used (I had to verify it somehow)

I verified with the following setups (can be confirmed with the debug
logs):
- `conda` with installed debugpy, went to installed instance
- `uv` with installed debugpy, went to installed instance
- `uv` without installed debugpy, went to github releases
- Homebrew global python install, went to github releases

Release Notes:

- Fix issue where debugpy from different environments won't load as
intended
2025-05-27 12:45:55 +03:00
Oleksiy Syvokon
7ec61ceec9 agent: Indiciate files and folders in list_directory (#31448)
Otherwise, the agent confuses directories with files in cases where dirs
are named like files (`TODO`, `task.js`, etc.)

Release Notes:

- N/A
2025-05-27 09:26:17 +00:00
Ben Brandt
119beb210a Update default models to newer versions (#31415)
Follow up to: https://github.com/zed-industries/zed/pull/31209
Changes default models across multiple providers:
- Zed.dev Default Models in settings: claude-3-7-sonnet-latest →
claude-4-sonnet-latest
- Bedrock Default Model: Claude 3.5 Sonnet v2 → Claude Sonnet 4
- Google AI Default Fast Model: Gemini 1.5 Flash → Gemini 2.0 Flash

Release Notes:

- N/A
2025-05-27 10:54:42 +02:00
Michael Sloan
0d3fad7764 Fix some completion docs render delays (#31486)
Closes #31460

While this is now much better than it was, the documentation still
flickers when changing selection. Hoping to fix that, but it will be a
much more involved change. So leaving release notes as "N/A" for now, in
anticipation of the full fix.

Release Notes:

- N/A
2025-05-27 04:58:02 +00:00
Joseph T. Lyons
450a10facf Revert to calling .update in eval fixture (#31483)
Looks like I accidentally touched a line of code in an eval fixture in
#31479, despite intentionally trying to avoid that code. Thanks
@cole-miller!

Release Notes:

- N/A
2025-05-27 03:59:44 +00:00
Joseph T. Lyons
c208532693 Use read-only access methods for read-only entity operations (#31479)
Another follow-up to #31254

Release Notes:

- N/A
2025-05-26 23:04:31 -04:00
Cole Miller
4a577fff4a git: Fix hunk controls blocking scrolling (#31476)
Thanks @mgsloan for introducing `stop_mouse_events_except_scroll` which
is exactly what we want here!

Release Notes:

- Fixed being unable to scroll editors when the cursor is positioned on
diff hunk controls.
2025-05-27 01:35:00 +00:00
Cole Miller
03071a9152 debugger: Add an action to rerun the last session (#31442)
This works the same as selecting the first history match in the new
session modal.

Release Notes:

- Debugger Beta: Added the `debugger: rerun last session` action, bound
by default to `alt-f4`.
2025-05-26 21:21:11 -04:00
Cole Miller
092be31b2b debugger: Add missing StepOut handler (#31463)
Closes #31317

Release Notes:

- Debugger Beta: Fixed a bug that prevented keybindings for the
`StepOut` action from working.
2025-05-26 21:19:07 -04:00
Cole Miller
62545b985f debugger: Fix wrong port used for SSH debugging (#31474)
We were trying to connect on the user's machine to the port number used
by the debugger on the remote machine, instead of the randomly-assigned
local available port.

Release Notes:

- Debugger Beta: Fixed a bug that caused connecting to a debug adapter
over SSH to hang.
2025-05-26 21:18:10 -04:00
Smit Barmase
5e72c2a870 editor: Show hidden mouse cursor on window activation (#31475)
Closes #31349

Release Notes:

- Fixed issue where hidden mouse cursor would stay hidden even after
switching windows.
2025-05-27 06:40:22 +05:30
Smit Barmase
2a8242ac90 editor: Add Python auto-indent test for same row bracket pair (#31473)
We [recently](https://github.com/zed-industries/zed/pull/31260) added a
condition which fixes certain edge cases detecting indent ranges when a
bracket pair is on the same row for suggested indent languages. This PR
adds a test for that so we don't regress in the future. Ref:
https://github.com/zed-industries/zed/issues/31362


f9592c6b92/crates/language/src/buffer.rs (L2910)

Release Notes:

- N/A
2025-05-27 06:02:41 +05:30
Danilo Leal
d211f88d23 agent: Add sound notification when done generating (#31472)
This PR adds the ability to hear a sound notification when the agent is
done generating and/or needs user input. This setting is turned off by
default and can be used together with the visual notification. The
specific sound I'm using here comes from the [Material Design 2 Sound
Library](https://m2.material.io/design/sound/sound-resources.html#).

Release Notes:

- agent: Added the ability to have a sound notification when the agent
is done generating and/or needs user input.
2025-05-26 21:20:41 -03:00
Kirill Bulatov
fe0bcd14d2 Activate last item if item's number is greater than the last one's (#31471)
Release Notes:

- N/A
2025-05-26 23:24:53 +00:00
Kirill Bulatov
e84463648a Highlight file finder entries according to their git status (#31469)
Configure this with the
```json5
"file_finder": {
  "git_status": true
}
```
settings value.

Before:
<img width="864" alt="before"
src="https://github.com/user-attachments/assets/5943e30f-1105-445e-9398-ea6dd35877c8"
/>

After:
<img width="864" alt="image"
src="https://github.com/user-attachments/assets/56b2fad6-8cdc-4f28-b238-920745231b1f"
/>

After with search matches:
<img width="577" alt="image"
src="https://github.com/user-attachments/assets/8c414575-7daf-43a8-89c2-98137d52b7a0"
/>

Release Notes:

- Start highlighting file finder entries according to their git status
2025-05-26 23:14:16 +00:00
Finn Evers
24809c4219 editor: Ensure minimap top offset is never NaN (#31466)
(Late) Follow-up to
https://github.com/zed-industries/zed/pull/26893#discussion_r2073427393

The mentioned issue of needed zero-division for scrollbars is now fixed
via #30189.
However, whilst the linked PR fixed the issue for the layouting of the
scrollbar thumb, I sadly did not address the (somewhat rare) case of
`document_lines == visible_editor_lines` within the calculation of the
minimap top offset.

This PR adds coverage for that case and ensures that the
`minimap_top_offset` never ends up being `NaN`.

Release Notes:

- N/A
2025-05-26 22:21:19 +00:00
Kirill Bulatov
f8365c5375 Move to splits more ergonomically (#31449)
Part of https://github.com/zed-industries/zed/discussions/24889

Release Notes:

- Made `workspace::MoveItemToPaneInDirection` and
`workspace::MoveItemToPane` to create non-existing panes
2025-05-27 00:59:47 +03:00
Finn Evers
2c8049270a language_tools: Increase available space for language server logs (#30742)
This PR contains some small improvements for the language server log
editors. Due to the large gutter as well as the introduction of the
minimap, the horizontally available space was rather small. As these
editors soft wrap at the editor width, it resulted in the logs becoming
vertically larger and somewhat harder to read.

The improvement here is to disable all elements in the gutter that will
never appear or be used in the logs anyway. Furthermore, I opted to
disable the minimap altogether, since from my point of view it did not
contain any valuable information about the logs being shown.

First image is the current main, second is this branch. I put these
below each other so the difference is easier to spot.


![main](https://github.com/user-attachments/assets/b3796e5f-4fe3-48c8-95a4-d3b84c607963)

![PR](https://github.com/user-attachments/assets/bd8a4e6c-dbbb-4a9e-99aa-474fa073196f)


Release Notes:

- N/A
2025-05-27 00:57:45 +03:00
Kirill Bulatov
6840a4e5bc Parse .//a//b/-prefixed paths more leniently in the file finder (#31459)
Closes https://github.com/zed-industries/zed/issues/15081
Closes https://github.com/zed-industries/zed/issues/31064

Release Notes:

- Parse `./`/`a/`/`b/`-prefixed paths more leniently in the file finder
2025-05-26 20:38:12 +00:00
Kirill Bulatov
5b320d6714 Be more lenient when looking up gitignored files in file finder (#31457)
The lookup was disabled due to concerns of being forced to traverse many
gitignored file entries. Since Zed does not index these eagerly, but
only contents of the directories that are parent to the gitignored file
entries, it might be not that bad — let's see how much improvement it
provides.

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

Release Notes:

- Improved file finder to include indexed gitignored files in its search
results
2025-05-26 23:24:32 +03:00
Joseph T. Lyons
534bb0620d Use read() over read_with() to improve readability in simple cases (#31455)
Follow up to: #31263 

Release Notes:

- N/A
2025-05-26 16:14:07 -04:00
Alistair Smith
5bafb2b160 Add holding opt/alt for fast scrolling (#31056)
Fixes #14612

This was a feature I dearly missed from VSCode, so adding this helped me
migrate to Zed without disrupting my workflow. I found that `4.0` was a
nice goldilocks multiplier and felt close/the same as the speed in
VSCode.

Release Notes:

- Added faster scrolling in the editor while holding opt/alt
2025-05-26 19:42:20 +00:00
Cole Miller
ee415de45f debugger: Add keyboard navigation for breakpoint list (#31221)
Release Notes:

- Debugger Beta: made it possible to navigate the breakpoint list using
menu keybindings.
2025-05-26 19:40:07 +00:00
Antonio Scandurra
4acb4730a5 Tolerate edits ending with </edits> instead of </new_text> (#31453)
Release Notes:

- Improve reliability of the agent when a model outputs malformed edits.
2025-05-26 19:36:58 +00:00
Kirill Bulatov
4567360fd9 Allow LSP adapters to decide, which diagnostics to underline (#31450)
Closes
https://github.com/zed-industries/zed/pull/31355#issuecomment-2910439798

<img width="1728" alt="image"
src="https://github.com/user-attachments/assets/2eaa8e9b-00bc-4e99-ac09-fceb2d932e41"
/>


Release Notes:

- N/A
2025-05-26 22:19:02 +03:00
Finn Evers
4c396bcc91 theme: Add colors for minimap thumb and border (#30785)
A user on Discord reported an issue where the minimap thumb was fully
opaque:

<img
src="https://github.com/user-attachments/assets/5049c6a3-f89a-4ceb-9d1b-ec06e7fe9151"
height="300">

This can happen because the scrollbar and its thumb might not
neccessarily be transparent at all.

Thus, this PR adds the`minimap.thumb.background` and
`minimap.thumb.border` colors to themes so theme authors can specify
custom colors for both here.
Furthermore, I ensured that the minimap thumb background fallback value
can never be entirely opaque. The values were arbitrarily chosen to
avoid the issue from occuring whilst keeping currently working setups
working. With the new properties added, authors (and users) should be
able to avoid running into this issue altogether so I would argue for
this special casing to be fine. However, open to change it should a
different approach be preferrred.

Release Notes:

- Added `minimap.thumb.background` and `minimap.thumb.border` to themes
to customize the thumb color and background of the minimap.
- Fixed an issue where the minimap thumb could be opaque if the theme
did not specify a color for the thumb.
2025-05-26 18:23:41 +00:00
Danilo Leal
8a24f9f280 agent: Refine naming for the panel default_view setting (#31446)
Follow up to https://github.com/zed-industries/zed/pull/31353. Just
ensuring we're walking toward a more consistent use of the multiple
terms we have floating around in the AI realm. In this case, `thread` is
the term for the now default view, the one that has agentic features;
`text_thread` is the term for the original view, the one where it's just
text. The settings now reflect this. Also took advantage of the
opportunity to add some docs, too.

Release Notes:

- N/A
2025-05-26 15:09:13 -03:00
claytonrcarter
f4b361f04d language: Select language based on longest matching path extension (#29716)
Closes #8408  
Closes #10997

This is a reboot of [my original
PR](https://github.com/zed-industries/zed/pull/11697) from last year. I
believe that I've addressed all the comments raised in that original
review, but Zed has changed a lot in the past year, so I'm sure there
will be some new stuff to consider too.

- updates the language matching and lookup to consider not just "does
the suffix/glob match" but also "... and is it the longest such match"
- adds a new `LanguageCustomFileTypes` struct to pass user globs from
settings to the registry
- _minor/unrelated:_ updates a test for the JS extension that wasn't
actually testing what is intended to
- _minor/unrelated:_ removed 2 redundant path extensions from the JS
lang extension

**Languages that may use this**

- Laravel Blade templates use the `blade.php` compound extension
-
[apparently](https://github.com/zed-industries/zed/issues/10765#issuecomment-2091293304)
Angular uses `component.html`
  - see also https://github.com/zed-industries/extensions/issues/169
- _hypothetically_ someone could publish a "JS test" extension w/ custom
highlights and/or snippets; many JS tests use `test.js` or `spec.js`

**Verifying these changes**

I added a number of assertions for this new behavior, and I also
confirmed that the (recently patched) [Laravel Blade
extension](https://github.com/bajrangCoder/zed-laravel-blade) opens as
expected for `blade.php` files, whereas on `main` it does not.

cc @maxbrunsfeld (reviewed my original PR last year), @osiewicz and
@MrSubidubi (have recently been in this part of the code)

Release Notes:

- Added support for "compound" file extensions in language extensions,
such `blade.php` and `component.html`. Closes #8408 and #10997.
2025-05-26 11:00:05 -07:00
Michael Sloan
649072d140 Add a live Rust style editor to inspector to edit a sequence of no-argument style modifiers (#31443)
Editing JSON styles is not very helpful for bringing style changes back
to the actual code. This PR adds a buffer that pretends to be Rust,
applying any style attribute identifiers it finds. Also supports
completions with display of documentation. The effect of the currently
selected completion is previewed. Warning diagnostics appear on any
unrecognized identifier.


https://github.com/user-attachments/assets/af39ff0a-26a5-4835-a052-d8f642b2080c

Adds a `#[derive_inspector_reflection]` macro which allows these methods
to be enumerated and called by their name. The macro code changes were
95% generated by Zed Agent + Opus 4.

Release Notes:

* Added an element inspector for development. On debug builds,
`dev::ToggleInspector` will open a pane allowing inspecting of element
info and modifying styles.
2025-05-26 17:43:57 +00:00
Oleksiy Syvokon
6253b95f82 agent: Fix creating files with Gemini (#31439)
This change instructs models to wrap new file content in Markdown fences
and introduces a parser for this format. The reasons are:

1. This is the format we put a lot of effort into explaining in the
system prompt.
2. Gemini really prefers to do it.
3. It adds an option for a model to think before writing the content

The `eval_zode` pass rate for GEmini models goes from 0% to 100%. Other
models were already at 100%, this hasn't changed.


Release Notes:

- N/A
2025-05-26 16:36:21 +00:00
jvmncs
bffde7c6b4 nix: Make zeditor symlink in package output (#31354)
home-manager expects a `zeditor` binary to wrap (because the nixpkgs
derivation names the CLI `zeditor` instead of `zed`)

Release Notes:

- N/A
2025-05-26 08:59:52 -07:00
ADmad
7e87916642 Fix VS Code settings file location on Linux (#31242)
Refs #30117

Release Notes:

- N/A
2025-05-26 08:58:03 -07:00
Vinicius Akira
29f0762b6c Add block_comment to JS, TSX, and TS (#31400)
This is the first step of ["Solution proposal for folding multiline
comments with no
indentation"](https://github.com/zed-industries/zed/discussions/31395):

> 1. Add block_comment in the config.toml for the languages javascript,
typescript, tsx. These are simple languages for this feature, and I am
already familiar with them.

The next step will be:

> 2. Modify the function `crease_for_buffer_row` in `DisplaySnapshot` to
handle multiline comments. `editor::fold` and `editor::fold_all` will
handle multiline comments after this change. To my knowledge,
`editor::unfold`, `editor::unfold_all`, and the **unfold** indicator in
the gutter will already work after folding, but there will be no
**fold** indicator.

Release Notes:

- N/A
2025-05-26 11:53:22 -04:00
Cole Miller
10af3c7e58 debugger: Fix misleading error logs (#31293)
Release Notes:

- N/A
2025-05-26 15:49:03 +00:00
Ben Kunkle
c0aa8f63fd zlog: Replace usages of env_logger in tests with zlog (#31436)
Also fixes:
https://github.com/zed-industries/zed/pull/31400#issuecomment-2908165249

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-05-26 11:48:50 -04:00
Shardul Vaidya
0c27aaecb3 docs: Bedrock Configuration docs (#31043)
Release Notes:

- Added documentation for Amazon Bedrock

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-05-26 12:09:49 -03:00
Alvaro Parker
8e5d50b85b agent: Add a setting choose the default view (#31353)
Related discussions #30240 #30596

Release Notes:

- Added an option on the settings file to choose either the `agent`
panel or the `thread` panel as the default assistant panel when you
first open it. On `settings.json`:
```json
{
  "agent": {
    "default_view": "thread", // default is agent
    }
}
```
2025-05-26 16:33:18 +02:00
Smit Barmase
625bf09830 editor: Inline Code Actions Indicator (#31432)
Follow up to https://github.com/zed-industries/zed/pull/30140 and
https://github.com/zed-industries/zed/pull/31236

This PR introduces an inline code action indicator that shows up at the
start of a buffer line when there's enough space. If space is tight, it
adjusts to lines above or below instead. It also adjusts when cursor is
near indicator.

The indicator won't appear if there's no space within about 8 rows in
either direction, and it also stays hidden for folded ranges. It also
won't show up in case there is not space in multi buffer excerpt. These
cases account for very little because practically all languages do have
indents.


https://github.com/user-attachments/assets/1363ee8a-3178-4665-89a7-c86c733f2885

This PR also sets the existing `toolbar.code_actions` setting to `false`
in favor of this.

Release Notes:

- Added code action indicator which shows up inline at the start of the
row. This can be disabled by setting `inline_code_actions` to `false`.
2025-05-26 19:41:19 +05:30
Ben Brandt
5a0a8ce30a extension: Update to wasm32-wasip2 target (#30953)
Cleans things up now that wasm32-wasip2 is a supported target.

Before we merge, I will need to test against the current extensions to
make sure this is fine.

However, since our wit world isn't using any wasi package imports, this
shouldn't be a breaking change.

Release Notes:

- N/A
2025-05-26 16:06:38 +02:00
CharlesChen0823
d9a5dc2dfe windows: Using ctrl+drag to copy in windows platform (#31433)
Closes #31328 

> There should be other places in Zed that were supposed to handle mouse
modifiers differently based on the platform, might worth checking for
them.
reference
[comments](https://github.com/zed-industries/zed/pull/29921#issuecomment-2908922764)

Release Notes:

- N/A
2025-05-26 13:45:19 +00:00
loczek
d4926626d8 snippets: Add icons and file names to snippet scope selector (#30212)
I added the language icons to the snippet scope selector so that it
matches the language selector.

The file names are displayed for each scope where there is a existing
snippets file since it wasn't clear if a scope had a file already or
not.

| Before | After |
| - | - |
|
![before](https://github.com/user-attachments/assets/89f62889-d4a9-4681-999a-00c00f7bec3b)|
![after](https://github.com/user-attachments/assets/2d64f04c-ef8f-40f5-aedd-eca239c960e9)
|


Release Notes:

- Added language icons and file names to snippet scope selector

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-05-26 13:44:09 +00:00
CharlesChen0823
2a973109d4 pane: Add functional clone on drop with control modifier (#29921)
Release Notes:

- Added a way to split and clone tab on with alt (macOS) / ctrl-mouse
drop
2025-05-26 13:40:19 +00:00
tidely
2e62f16149 gpui: Apply cfg at compile time (#31428)
- Use compile time `cfg` macro instead of a runtime check
- Use `Modifiers` instead of a bunch of `bool` when parsing a
`Keystroke`.

Release Notes:

- N/A
2025-05-26 13:34:04 +00:00
Cole Miller
f2601ce52c Fix text wrapping in commit message editors (#31030)
Don't hard wrap interactively; instead, soft wrap in `Bounded` mode
(editor width or 72 chars, whichever is smaller), and then hard wrap
before sending the commit message to git.

This also makes the soft wrap mode and width for commit messages
configurable in language settings.

Previously we didn't support soft wrap modes other than `EditorWidth` in
auto-height editors; I tried to add support for this by analogy with
code that was already there, and it seems to work pretty well.

Closes #27508

Release Notes:

- Fixed confusing wrapping behavior in commit message editors.
2025-05-26 13:11:56 +00:00
Kirill Bulatov
a58c48f629 Fix a clippy issue (#31429)
A cherry-pick from `main`,
https://github.com/zed-industries/zed/pull/31425 , failed with a clippy
error:
https://github.com/zed-industries/zed/actions/runs/15253598167/job/42895919271

Release Notes:

- N/A
2025-05-26 12:59:20 +00:00
Danilo Leal
ddbcab2b5b picker: Improve input padding (#31422)
This is a tiny PR to make the picker input padding match the list item
results horizontal spacing. They were previously misaligned and it was
getting to me. 😬

| Before | After |
|--------|--------|
| ![CleanShot 2025-05-26 at 8  03
59@2x](https://github.com/user-attachments/assets/e3d8c10a-7ded-4e40-bc69-dc9d35038785)
| ![CleanShot 2025-05-26 at 8  04
09@2x](https://github.com/user-attachments/assets/a8273174-edcb-45a8-809b-622ea18af37a)
|

Release Notes:

- N/A
2025-05-26 09:55:47 -03:00
Patrick Leibersperger
7497deff7a agent: Add a whitespace after inserting @-mention to allow for continuous typing (#30381)
Release Notes:

- agent: Added a space after @-mentioning something in the message
editor to allow for continuous typing.

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-05-26 09:55:19 -03:00
Joseph T. Lyons
e78b726ed8 Remove the ability to book onboarding (#31404)
Closes: https://github.com/zed-industries/zed/issues/31394

Onboarding has been valuable, but we're moving into a new phase as our
user base grows, and our ability to chat with everyone who books a call
will not scale linearly. For now, we are removing the option to book a
call from the application.

Release Notes:

- N/A
2025-05-26 15:02:51 +03:00
Fedor Nezhivoi
998542b048 language_models: Add support for tool use to LM Studio provider (#30589)
Closes #30004

**Quick demo:**


https://github.com/user-attachments/assets/0ac93851-81d7-4128-a34b-1f3ae4bcff6d

**Additional notes:**

I've tried to stick to existing code in OpenAI provider as much as
possible without changing much to keep the diff small.

This PR is done in collaboration with @yagil from LM Studio. We agreed
upon the format in which LM Studio will return information about tool
use support for the model in the upcoming version. As of current stable
version nothing is going to change for the users, but once they update
to a newer LM Studio tool use gets automatically enabled for them. I
think this is much better UX then defaulting to true right now.


Release Notes:

- Added support for tool calls to LM Studio provider

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-05-26 13:54:17 +02:00
Finn Evers
6363fdab88 editor: Do not offset text in single line editors by default (#30599)
Follow-up to #30138

In the linked PR, I enabled the content offset for all editors by
default. However, this introduced a small regression: There are some
editors where we do not want the text to be offset, most notably the
rename and the filename editor.

This PR adds a method to disable the content offset for specific
editors. I specifically decided on an opt-out approach, since I think
that having the small offset for most editors is actually a benefit
instead of a disadvantage. However, open to change that or to disable
the offset for all editors but full mode editors by default if that
should be preferred.

| `main` | This PR |
| --- | --- |
|
![main](https://github.com/user-attachments/assets/a7e9249e-ac5c-422f-9f30-021ebf21850b)
|
![pr](https://github.com/user-attachments/assets/c5eef4e6-fad8-46ab-9f2d-d0ebdca01e2c)
|


Release Notes:

- N/A
2025-05-26 08:47:10 -03:00
Adrian Furo
e6f51966a1 open_ai: Fix parallel tools issue (#30467)
There is no ISSUE opened on this topic

Release Notes:

- N/A

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-05-26 11:46:35 +00:00
Oleksiy Syvokon
9da9ef860b agent: Don't track large and common binary files (#31352)
## Issue

The agent may run very slowly on projects that contain many or large
binary files not listed in `.gitignore`.


## Solution

Temporarily rewrite `.git/info/exludes` to ignore:
- Common binary files based on the extension
- Files larger than 2 MB

## Benchmark

I measure the time between sending an agent message in UI ("hitting
Enter") and actually sending it to an LLM. Ideally, it should be
instant. Numbers for a 7.7 GB Rust project with no .gitignore.

Filter                            | Time
----------------------------------|-----
No filter (= before this change)  | 62 s
Exclude common file types only    | 1.46 s
Exclude files >2MB only           | 1.16 s
Exclude both                      | 0.10 s


## Planned changes:

- [x] Exclude common binary file types
- [x] Exclude large files
- [ ] Track files added by agent so we could delete them (we can't rely
on git for that anymore)
- [ ] Don't block on waiting for a checkpoint to complete until we
really need it
- [ ] Only `git add` files that are about to change


Closes #ISSUE

Release Notes:

- Improved agent latency on repositories containing many files or large
files
2025-05-26 11:31:25 +00:00
CharlesChen0823
134463f043 markdown: Fix parse inline code display (#30937)
inline code startswith "\`", "\`\`", "\`\`\`", should all valid. current
implement only work with startswith "\`".

Release Notes:

- N/A
2025-05-26 11:28:44 +00:00
Finn Evers
a47fd1d723 Ensure horizontal scrollbars show as needed (#30964)
This PR fixes an issue where the horizontal scrollbar was sometimes not
rendered despite being needed for the outline and project panels.

The issue occured since `self.width` does not neccessarily have to be
set when the scrollbar is rendered (it is only set on panel resize).
However, the check for a `width` is not needed at all since the
scrollbar constructor determines whether a scrollbar has to be rendered
or not. Hence, this does not need to be special-cased.

Furthermore, since `Scrollbar::horizontal()` returns `Some(...)` when a
scrollbar needs to be rendered, we do not have to check for this
seperately on the scroll handle and can just map on the option. This
simplifies the code a bit.

| `main` | This PR |
| --- | --- | 
|
![main](https://github.com/user-attachments/assets/db1d4524-716e-42c1-a6f9-7cfd59c94b30)
|
![PR](https://github.com/user-attachments/assets/12536d28-616e-487d-b948-653f53da36b4)
|



Release Notes:

- Fixed an issue where the horizontal scrollbar would not render in the
project and outline panels.
2025-05-26 14:06:19 +03:00
Ben Brandt
ef0e1cb2ba open_ai: Make Assistant message content optional (#31418)
Fixes regression caused by:
https://github.com/zed-industries/zed/pull/30639

Assistant messages can come back with no content, and we no longer
allowed that in the deserialization.

Release Notes:

- open_ai: fixed deserialization issue if assistant content was empty
2025-05-26 09:59:39 +00:00
Floyd Wang
c73af0a52f gpui: Add more shapes for PathBuilder (#30904)
- Add `arc` for drawing elliptical arc.
- Add `polygon` support.

<img width="1136" alt="image"
src="https://github.com/user-attachments/assets/97032b02-e6ff-4985-a587-3689500bfd56"
/>

Release Notes:

- N/A
2025-05-26 12:49:42 +03:00
Abdelhakim Qbaich
e42cf21703 Default to fast model first for commit messages (#31385)
I was surprised to see this being done for thread summaries, but not
commit messages.

I believe it's a better default as most people would want a faster
commit message generation without spending premium requests.

Considering how the default fast model for copilot is set to the base
one, this is ideal for me (and likely many others), as opposed to
tweaking the configuration every time the base model changes.

Release Notes:

- git: Default to fast model first if not configured for generating
commit messages
2025-05-26 10:37:44 +02:00
Nitin K. M.
2c114f7df6 docs: Include slimmer C++ build tools only installation for Windows (#31107)
Edit:
This PR adds docs for a slimmer build tools only installation for
compiling Zed on Windows.
The disk space required is 7 GB for the builds tools vs 8GB with the
editor.

<details>
<summary>Old description</summary>

Fixes the incorrect Visual Studio configuration faced by many people.
#29899
#29901

I have added the required workload in Visual Studio.
Can someone please confirm the minimum config required to compile on
Windows?

c8f56e38b1/docs/src/development/windows.md (L20-L32)

After installing the Desktop C++ build tools as [outlined in the rustup
website](https://rust-lang.github.io/rustup/installation/windows-msvc.html#walkthrough-installing-visual-studio-2022),
I have this config now:
```json
{
  "version": "1.0",
  "components": [
    "Microsoft.VisualStudio.Component.CoreEditor",
    "Microsoft.VisualStudio.Workload.CoreEditor",
    "Microsoft.VisualStudio.Component.Roslyn.Compiler",
    "Microsoft.Component.MSBuild",
    "Microsoft.VisualStudio.Component.TextTemplating",
    "Microsoft.VisualStudio.Component.VC.CoreIde",
    "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
    "Microsoft.VisualStudio.Component.Windows11SDK.26100",
    "Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
    "Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
    "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.CMake",
    "Microsoft.VisualStudio.Component.VC.CMake.Project",
    "Microsoft.VisualStudio.Component.VC.ASAN",
    "Microsoft.VisualStudio.Workload.NativeDesktop",
    "Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre"
  ],
  "extensions": []
}
```
</details>

Release Notes:

- N/A
2025-05-26 08:32:13 +00:00
Jonathan LEI
49f3ec7f35 context_server: Fix casing of mimeType in tool responses (#30703)
Closes #30243

Release Notes:

- Fixed wrong casing for the `mimeType` field when parsing MCP server
image responses.
2025-05-26 10:29:45 +02:00
Luke Janssen
748840519c Update terminal file icon associations in "FILE_SUFFIX_BY_ICON_KEY" (#31110)
This PR updates terminal file icon associations in icon_theme.rs

I've added a `bash_login` file as mentioned in the gnu docs for bash
startup files. For zsh I updated the startup files to more accurately
reflect the zsh startup file documentation such as adding `zlogin` and
removing `zsh_profile` in favor of `zprofile`. I also added the default
`zsh_history` file that is set on MacOS.

Sources:
- [bash docs - Bash Startup
Files](https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html)
- [zsh docs - Startup
Files](https://zsh.sourceforge.io/Intro/intro_3.html)

Release Notes: 
- Improved file icon associations in icon_theme.rs to support more shell
configuration files
2025-05-26 11:22:43 +03:00
tidely
8b59776320 gpui: Remove unnecessary String (#31314)
Replaces a `String` with `&'static str`

Release Notes:

- N/A
2025-05-26 11:17:03 +03:00
Mikal Sande
206be2b348 Make sure GoPlsAdapter produces output that can be rendered as Markdown (#30911)
Closes [#30695](https://github.com/zed-industries/zed/issues/30695)

Adds `diagnostic_message_to_markdown()` to GoLspAdapter to ensure that
Go diagnostic messages are considered Markdown formatted and leading
whitespace is removed to get the Markdown to display properly.

Before:
<img width="805" alt="image"
src="https://github.com/user-attachments/assets/8a09276a-1f45-42de-9744-d7e620d5ebb6"
/>


After:
<img width="805" alt="image"
src="https://github.com/user-attachments/assets/bbe4aeaf-a3c6-4e85-bb10-89b3f4e27965"
/>

Release Notes:

- Fixed display of Go diagnostics, it should be displayed as Markdown
not as escaped string.
2025-05-26 11:04:13 +03:00
Mani Rash Ahmadi
51b25b5c22 agent: Ensure context meter updates when context is cleared (#30320)
Addresses an issue where the agent context token meter in the panel
toolbar (showing usage like "X / Y tokens") failed to update its count
after the user cleared the current context via the context editor UI.
While the meter updated correctly when adding items, clearing them left
the display showing the old count.

The root cause was traced to the `ContextStore::clear` method in
`crates/agent/src/context_store.rs`. This method correctly cleared the
internal data structures holding the context items but neglected to call
`cx.notify()` to inform listeners of the state change. Consequently, the
UI components responsible for displaying the token count were not
triggered to re-render with the new (presumably lower) count.

This PR fixes the issue by adding the missing `cx.notify()` call to the
`ContextStore::clear` method. This ensures listeners are notified when
the context set is cleared, allowing the token meter UI to update
correctly.

Release Notes:

- Fixed an issue where the agent context token meter did not update when
the context was cleared.
2025-05-26 09:51:00 +02:00
5brian
2f274b2a89 vim: Document ctrl-s override (#30803)
Closes https://github.com/zed-industries/zed/issues/30559

Release Notes:

- N/A
2025-05-26 10:30:00 +03:00
Tymoteusz Makowski
88fb623efa Display the correct git push flag when force-pushing (#30818)
When I force pushed via the Git panel and noticed that `git push
--force` command got logged at the bottom. I wanted to add an option to
use `--force-with-lease` instead. However, upon investigation, it seems
`--force-with-lease` is already being used for the executed command:


5112fcebeb/crates/git/src/repository.rs (L1100)

And there is a mismatch with the displayed message:


5112fcebeb/crates/project/src/git_store.rs (L3555)

Release Notes:

- Fixed the displayed flag name when force pushing
2025-05-26 10:26:45 +03:00
Michael Angerman
df98d94a24 gpui: Activate the window example along with the Quit action (#30790)
Make the gpui examples more consistent by activating the window upon
startup.
Most of the examples have 

```rust
activate(true) 
```

so this one should as well.

Make it easier to exit the example with the `cmd-q` KeyBinding

Release Notes:

- N/A
2025-05-26 10:10:35 +03:00
waffle
c7da6283cc gpui: Fix typo in doc comment (#31397)
Release Notes:

- N/A
2025-05-26 07:59:51 +03:00
Richard Feldman
7ceb792a58 Revert having edit_file_tool format on save (#31403)
An unintended consequence of format on save is that we start (correctly)
informing the model that the file changed on disk every time the
formatter changes anything, which in turn can lead the model to things
like extra reads.

Until we have a solution in place to prevent this downside, we're going
back to not formatting on save by reverting
cb112a4012.

Release Notes:

- N/A
2025-05-26 02:01:55 +00:00
Joseph T. Lyons
83af7b30eb Add agent: chat with follow action (experimental) (#31401)
This PR introduces a new `agent: chat with follow` action that
automatically enables "Follow Agent" when submitting a chat message with
`cmd-enter` or `ctrl-enter`. This is experimental. I'm not super
thrilled with the name, but the root action to submit a chat is called
`agent: chat`, so I'm following that wording. I'm also unsure if the
binding feels right or not.

Release Notes:

- Added an `agent: chat with follow` action via `cmd-enter` on macOS and
`ctrl-enter` on Linux
2025-05-25 20:15:06 -04:00
Joseph T. Lyons
3d0147aafc Preserve selection direction when running an editor: open selections in multibuffer (#31399)
Release Notes:

- Preserve selection direction when running an `editor: open selections
in multibuffer`
2025-05-25 22:52:40 +00:00
Danilo Leal
1b3f20bdf4 docs: Refine a few AI pages (#31381)
Mostly sharpening the words, simplifying, and removing duplicate
content.

Release Notes:

- N/A
2025-05-25 11:03:14 -03:00
Finn Evers
4c28d2c2e2 language: Improve auto-indentation when using round brackets in Python (#31260)
Follow-up to #29625 and #30902

This PR reintroduces auto-intents for brackets in Python and fixes some
cases where an indentation would be triggered if it should not. For
example, upon typing

```python
a = []
```
and inserting a newline after, the next line would be indented although
it shoud not be.

Bracket auto-indentation was tested prior to #29625 but removed there
and the test updated accordingly. #30902 reintroduced this for all
brackets but `()`. I reintroduced this here, reverted the changes to the
test so that indents also happen after typing `()`. This is frequently
used for tuples and multiline statements in Python.

Release Notes:

- Improved auto-indentation when using round brackets in Python.
2025-05-25 02:25:15 +05:30
Finn Evers
a204510cfc editor: Add toggle diagnostics to command palette (#31358)
Follow-up to #30316

This PR adds the `editor: toggle diagnostics` action to the comand
palette so that it can also be invoked that way.

I also ensures this, the `toggle inline diagnostics` and `toggle
minimap` actions are only registered if these are supported by the
current editor instance.

Release Notes:

- N/A
2025-05-24 20:41:02 +00:00
Finn Evers
34be7830a3 editor: Do not start scroll when hovering the scroll thumb during dragging events (#30782)
Closes #30756
Closes #30729
Follow-up to #28064

The issue arose because GPUI does still propagate mouse events to all
event handlers during dragging actions even if the dragging action does
not belong to the current handler. I forgot about this in the other PR.

This resulted in an incorrect hover being registered for the thumb,
which was sufficient to trigger scrolling in the next frame, since
`dragging_scrollbar_axis` did not consider the actual thumb state (this
was generally sufficient, but not with this incorrectly registered
hover).

Theoretically, either of the both commits would suffice for fixing the
issue. However, I think it is better to fix both issues at hand instead
of just one. Now, we will only start the scroll on actual scrollbar
clicks and not show a hover on the thumb if any other drag is currently
going on.


https://github.com/user-attachments/assets/6634ffa0-78fc-428f-99b2-7bc23a320676

Release Notes:

- Fixed an issue where editor scrollbars would start scrolling when
hovering over the thumb whilst already dragging something else.
2025-05-24 23:39:02 +03:00
Finn Evers
d312a13f8a ui: Fix content shift when selecting last tab (#31266)
Follow-up to #29061

This PR ensures that the last tab does not flicker when either
selecting. It also fixes an issue where the layout would shift in the
new last tab when closing the last tab.


https://github.com/user-attachments/assets/529a2a92-f25c-4ced-a992-fb6b2d3b5f61

This happened because in #29061, the left padding was removed due to
issues with borders. However, the padding is relevant for the content to
not shift (we are basically doing border-box sizing manually here).
Instead, we need to remove the padding on the right side, as there is
already a border present on the right side and this padding would make
the last tab slightly larger than all other tabs.


https://github.com/user-attachments/assets/c3a10b3c-4a1d-4160-9b68-7538207bb46e


Release Notes:

- Removed a small flicker when selecting or closing the last tab in a
pane.
2025-05-24 23:36:51 +03:00
Kirill Bulatov
20a0956fb2 Do not underline unnecessary diagnostics (#31355)
Closes
https://github.com/zed-industries/zed/pull/31229#issuecomment-2906946881

Follow
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnosticTag

> Clients are allowed to render diagnostics with this tag faded out
instead of having an error squiggle.

and do not underline any unnecessary diagnostic at all.

Release Notes:

- Fixed clangd's inactive regions diagnostics excessive highlights
2025-05-24 20:08:46 +00:00
Anthony Eid
6f918ed99b debugger beta: Fix regression where we sent launch args twice to any dap (#31325)
This regression happens because our tests weren't properly catching this
edge case anymore. I updated the tests to only send the raw config to
the Fake Adapter Client.

Release Notes:

- debugger beta: Fix bug where launch args were sent twice
2025-05-24 06:52:36 +00:00
Marshall Bowers
7fb9569c15 language_model: Remove CloudModel enum (#31322)
This PR removes the `CloudModel` enum, as it is no longer needed after
#31316.

Release Notes:

- N/A
2025-05-24 02:04:51 +00:00
Marshall Bowers
fc8702a8f8 agent: Don't show "Tools Unsupported" when no model is selected (#31321)
This PR makes it so we don't show "Tools Unsupported" when no model is
selected.

Release Notes:

- N/A
2025-05-24 01:45:10 +00:00
Michael Sloan
ab59982bf7 Add initial element inspector for Zed development (#31315)
Open inspector with `dev: toggle inspector` from command palette or
`cmd-alt-i` on mac or `ctrl-alt-i` on linux.

https://github.com/user-attachments/assets/54c43034-d40b-414e-ba9b-190bed2e6d2f

* Picking of elements via the mouse, with scroll wheel to inspect
occluded elements.

* Temporary manipulation of the selected element.

* Layout info and JSON-based style manipulation for `Div`.

* Navigation to code that constructed the element.

Big thanks to @as-cii and @maxdeviant for sorting out how to implement
the core of an inspector.

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
Co-authored-by: Federico Dionisi <code@fdionisi.me>
2025-05-23 23:08:59 +00:00
Marshall Bowers
685933b5c8 language_models: Fetch Zed models from the server (#31316)
This PR updates the Zed LLM provider to fetch the available models from
the server instead of hard-coding them in the binary.

Release Notes:

- Updated the Zed provider to fetch the list of available language
models from the server.
2025-05-23 23:00:35 +00:00
Joseph T. Lyons
172e0df2d8 Remove duplicate ThreadHistory key binding object (#31309)
Release Notes:

- N/A
2025-05-23 20:59:46 +00:00
Kirill Bulatov
7341ab3980 Keep file permissions when extracting zip archives on Unix (#31304)
Follow-up of https://github.com/zed-industries/zed/pull/31080

Stop doing

```rs
#[cfg(not(windows))]
{
    file.set_permissions(<fs::Permissions as fs::unix::PermissionsExt>::from_mode(
        0o755,
    ))
    .await?;
}
```

after extracting zip archives on Unix, and use an API that provides the
file permissions data for each archive entry.

Release Notes:

- N/A
2025-05-23 20:45:32 +00:00
Ben Brandt
ca72efe701 Add overdue invoices check (#31290)
- Rename current_user_account_too_young to account_too_young for
consistency
- Add has_overdue_invoices field to track billing status
- Block edit predictions when user has overdue invoices
- Add overdue invoice warning to inline completion menu

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-05-23 20:02:02 +00:00
Richard Feldman
cb112a4012 Have edit_file_tool respect format_on_save (#31047)
Release Notes:

- Agents now automatically format after edits if `format_on_save` is
enabled.
2025-05-23 19:59:19 +00:00
Michael Sloan
f3c2e71ca7 Update syn crate from 1.0.109 to 2.0.101 (#31301)
Nearly all generated by Zed Agent + Claude Opus 4. I just wrote the test
`Args` struct and pointed it at the [2.0 release
notes](https://github.com/dtolnay/syn/releases/tag/2.0.0).

Release Notes:

- N/A
2025-05-23 19:31:25 +00:00
Richard Feldman
208f525a11 Don't always scroll to bottom on every new message (#31295)
This is a partial reversion of
https://github.com/zed-industries/zed/pull/30878 - having it always
scroll to bottom whenever a new message is added makes it so that when
you're scrolled up, you don't have time to read what you're trying to
read before it autoscrolls to the end.

@danilo-leal when you're back, we can pair on addressing that in a
different way!

Release Notes:

- Fixed bug where scrolling up in the agent panel didn't prevent
automatic scroll-to-end whenever a new message arrived.
2025-05-23 15:11:29 -04:00
smit
697c838455 languages: Allow complete override for ESLint settings (#31302)
Closes #17088

This PR allows users to override ESLint settings as they want instead of
depending on a few set of hardcoded keys.

Release Notes:

- Added support for configuring all ESLint server settings instead of
only a limited set of predefined options.
2025-05-24 00:30:35 +05:30
Marshall Bowers
1683e2f144 collab: Prevent canceling the free plan (#31292)
This PR makes it so the Zed Free plan cannot be canceled.

We were already preventing this on the zed.dev side, but this will make
it more airtight.

Release Notes:

- N/A
2025-05-23 15:48:21 +00:00
Cole Miller
2f1d9284b7 debugger: Fix adapter names in initial-debug-tasks.json (#31283)
Closes #31134

Release Notes:

- N/A

---------

Co-authored-by: Piotr <piotr@zed.dev>
2025-05-23 15:22:04 +00:00
Oleksiy Syvokon
68a46c3627 evals: Configurable judge model (#31282)
This is needed for apples-to-apples comparison of different agent
models.

Another change is that now `cargo -p eval` accepts model names as
`provider_id/model_id` instead of separate `--provider` and `--model`
params.


Release Notes:

- N/A
2025-05-23 15:03:09 +00:00
Joseph T. Lyons
3a1053bf0c Use shortened SHA when displaying version to install (#31281)
This PR uses a shortened SHA when displaying the nightly version to
install in the update status, for nicer tooltip formatting.

Release Notes:

- N/A
2025-05-23 14:53:53 +00:00
Anthony Eid
14d9a4189f debugger beta: Auto download Delve (Go's DAP) & fix grammar errors in docs (#31273)
Release Notes:

- debugger beta: Go's debug adapter will now automatically download if
not found on user's PATH

Co-authored-by: Remco Smits <djsmits12@gmail.com>
2025-05-23 14:19:24 +00:00
Anthony Eid
9c01119b3c debugger beta: Add error handling when gdb doesn't send thread names (#31279)
If gdb doesn't send a thread name we display the thread's process id in
the thread drop down menu instead now.

Co-authored-by: Remco Smits \<djsmits12@gmail.com\>

Release Notes:

- debugger beta: Handle bug where DAPs don't send thread names
2025-05-23 10:07:47 -04:00
Antonio Scandurra
9dba8e5b0d Ensure client reconnects after erroring during the handshake (#31278)
Release Notes:

- Fixed a bug that prevented Zed from reconnecting after erroring during
the initial handshake with the server.
2025-05-23 15:46:30 +02:00
smit
03ac3fb91a editor: Fix issue where newline on * as prefix adds comment delimiter (#31271)
Release Notes:

- Fixed issue where pressing Enter on a line starting with * incorrectly
added comment delimiter.

---------

Co-authored-by: Piotr Osiewicz <peterosiewicz@gmail.com>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-05-23 18:46:55 +05:30
Ben Brandt
0201d1e0b4 agent: Unfollow agent on completion cancellation (#31258)
Handle unfollowing agent and clearing agent location when completion
is canceled.

Release Notes:

- N/A
2025-05-23 14:55:08 +02:00
Piotr Osiewicz
f48b6b583e debugger: Change placeholder text for Custom/Run text input (#31264)
Before: 

![image](https://github.com/user-attachments/assets/6cdef5bb-c901-4954-a2ec-39c59f8314db)

After:

![image](https://github.com/user-attachments/assets/c4f60a23-249c-47ab-8a9e-a39e2277dd00)


Release Notes:

- N/A
2025-05-23 14:53:20 +02:00
Kirill Bulatov
d8fc23a5e9 toml: Bump to v0.1.4 (#31272)
Closes https://github.com/zed-industries/zed/issues/31261

Changes:

* https://github.com/zed-industries/zed/pull/31267


Release Notes:

- N/A
2025-05-23 12:18:24 +00:00
Joseph T. Lyons
6206150e27 Use read() over read_with() to improve readability in simple cases (#31263)
Release Notes:

- N/A
2025-05-23 12:08:49 +00:00
Antonio Scandurra
e88cad29e5 Reduce the amount of queries performed when updating plan (#31268)
Release Notes:

- N/A
2025-05-23 12:03:50 +00:00
Kirill Bulatov
9b7d849879 Fix taplo artifact naming (#31267)
Part of https://github.com/zed-industries/zed/issues/31261

https://github.com/tamasfe/taplo/pull/598#issuecomment-2292984164
renamed all artifacts almost a year ago, and now had released it
finally.
Have to abide to that YOLO move.

Release Notes:

- N/A
2025-05-23 11:59:20 +00:00
Cole Miller
c4677c21a9 debugger: More focus tweaks (#31232)
- Make remembering focus work with `ActivatePaneDown` as well
- Tone down the console's focus-in behavior so clicking doesn't
misbehave

Release Notes:

- N/A
2025-05-23 07:51:23 -04:00
Remco Smits
26318b5b6a debugger: Detect debugpy from virtual env (#31211)
Release Notes:

- Debugger Beta: Detect debugpy from virtual env
2025-05-23 13:34:07 +02:00
tidely
4266f0da85 terminal: Consume event during processing (#30869)
By consuming the event during processing we save a few clones during
event processing.

Overall in this PR we save one Clone each during:

- Paste to the terminal
- Writing to the terminal
- Setting the title
- On every terminal transaction
- On every ViMotion when not using shift

Release Notes:

- N/A
2025-05-23 14:28:53 +03:00
tidely
c50093d68c project: Use VecDeque in SearchHistory (#31224)
`SearchHistory` internally enforced the max length of the search history
by popping elements from the front using `.remove(0)`. For a `Vec` this
is a `O(n)` operation. Use a `VecDeque` to make this `O(1)`

I also made it so the excess element is popped before the new one is
added, which keeps the allocation at the desired size.

Release Notes:

- N/A
2025-05-23 14:25:40 +03:00
smit
1cad1cbbfc Add Code Actions to the Toolbar (#31236)
Closes issue #31120.


https://github.com/user-attachments/assets/a4b3c86d-7358-49ac-b8d9-e9af50daf671

Release Notes:

- Added a code actions icon to the toolbar. This icon can be disabled by
setting `toolbar.code_actions` to `false`.
2025-05-23 16:55:29 +05:30
tidely
fbc922ad46 Reduce allocations (#31223)
Release Notes:

- N/A
2025-05-23 14:25:17 +03:00
Joseph T. Lyons
f435304209 Use read-only access methods for read-only entity operations (#31254)
This PR replaces some `update()` calls with either `read()` or
`read_with()` when the `update()` call performed read-only operations on
the entity.

Many more likely exist, will follow-up with more PRs.

Release Notes:

- N/A
2025-05-23 06:13:49 -04:00
Ben Brandt
508ccde363 Better messaging for accounts that are too young (#31212)
Right now you find this out the first time you try and submit a
completion.

These changes communicate much earlier to the user what the issue is
with their account and what they can do about it.

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
2025-05-23 09:32:03 +00:00
Bo Lopker
9f7987c532 Change default diagnostics_max_severity to 'hint' (#31229)
Closes https://github.com/blopker/codebook/issues/79

Recently, the setting `diagnostics_max_severity` was changed from `null`
to `warning`in this PR: https://github.com/zed-industries/zed/pull/30316
This change has caused the various spell checking extensions to not work
as expected by default, most of which use the `hint` diagnostic. This
goes against user expectations when installing one of these extensions.

Without `hint` as the default, extension authors will either need to
change the diagnostic levels, or instruct users to add
`diagnostics_max_severity` to their settings as an additional step,
neither of which is a great user experience.

This PR sets the default `hint`, which is closer to the original
behavior before the aforementioned PR.

Release Notes:

- Changed `diagnostics_max_severity` to `hint` instead of `warning` by
default

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-05-23 07:30:38 +00:00
Marshall Bowers
cb52acbf3d eval: Don't read the model from the user settings (#31230)
This PR fixes an issue where the eval was incorrectly pulling the
provider/model from the user settings, which could cause problems when
running certain evals.

Was introduced in #30168 due to the restructuring after the removal of
the `assistant` crate.

Release Notes:

- N/A
2025-05-23 00:21:35 +00:00
Marshall Bowers
f8b997b25c docs: Fix Claude Sonnet 4 model name (#31226)
This PR fixes the model name for Claude Sonnet 4 to match Anthropic's
new ordering.

Release Notes:

- N/A
2025-05-22 22:05:55 +00:00
morgankrey
73a5856fb8 docs: Add Claude 4 Sonnet to docs (#31225)
Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-05-22 22:02:37 +00:00
Shardul Vaidya
e3b6fa2c30 bedrock: Support Claude 4 models (#31214)
Release Notes:

- AWS Bedrock: Added support for Claude 4.
2025-05-22 21:59:23 +00:00
Marshall Bowers
ceb5164114 agent: Remove last turn after a refusal (#31220)
This is a follow-up to https://github.com/zed-industries/zed/pull/31217
that removes the last turn after we get a `refusal` stop reason, as
advised by the Anthropic docs.

Meant to include it in that PR, but accidentally merged it before
pushing these changes 🤦🏻‍♂️.

Release Notes:

- N/A
2025-05-22 21:38:33 +00:00
Umesh Yadav
24a108d876 anthropic: Fix Claude 4 model display names to match official order (#31218)
Release Notes:

- N/A
2025-05-22 21:00:54 +00:00
Marshall Bowers
5c0b161563 Handle new refusal stop reason from Claude 4 models (#31217)
This PR adds support for handling the new [`refusal` stop
reason](https://docs.anthropic.com/en/docs/test-and-evaluate/strengthen-guardrails/handle-streaming-refusals)
from Claude 4 models.

<img width="409" alt="Screenshot 2025-05-22 at 4 31 56 PM"
src="https://github.com/user-attachments/assets/707b04f5-5a52-4a19-95d9-cbd2be2dd86f"
/>

Release Notes:

- Added handling for `"stop_reason": "refusal"` from Claude 4 models.
2025-05-22 16:56:59 -04:00
Cole Miller
ad4645c59b debugger: Fix environment variables not being substituted in debug tasks (#31198)
Release Notes:

- Debugger Beta: Fixed a bug where environment variables were not
substituted in debug tasks in some cases.

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Remco Smits <djsmits12@gmail.com>
2025-05-22 15:14:05 -04:00
Marshall Bowers
37047a6fde language_models: Update default/recommended Anthropic models to Claude Sonnet 4 (#31209)
This PR updates the default/recommended models for the Anthropic and Zed
providers to be Claude Sonnet 4.

Release Notes:

- Updated default/recommended Anthropic models to Claude Sonnet 4.
2025-05-22 19:10:08 +00:00
Marshall Bowers
fc78408ee4 language_model: Allow Max Mode for Claude 4 models (#31207)
This PR adds the Claude 4 models to the list of models that support Max
Mode.

Release Notes:

- Added Max Mode support for Claude 4 models.
2025-05-22 18:50:30 +00:00
Marshall Bowers
37f49ce304 collab: Add support for overage billing for Claude Sonnet 4 (#31206)
This PR adds support for billing for overages for Claude Sonnet 4.

Release Notes:

- N/A
2025-05-22 18:40:06 +00:00
Umesh Yadav
cc428330a9 mistral: Add DevstralSmallLatest model to Mistral and Ollama (#31099)
Mistral just released a sota coding model:
https://mistral.ai/news/devstral

This PR adds support for it in both ollama and mistral

Release Notes:

- Add DevstralSmallLatest model to Mistral and Ollama
2025-05-22 14:22:35 -04:00
Marshall Bowers
1475ace6f1 anthropic: Add support for Claude 4 (#31203)
This PR adds support for [Claude
4](https://www.anthropic.com/news/claude-4).

Release Notes:

- Added support for Claude Opus 4 and Claude Sonnet 4.

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2025-05-22 18:09:35 +00:00
smit
dd4e8b9e66 editor: Fix block comment incorrectly continues to next line in some cases (#31204)
Closes #31138

Fix edge case where adding newline if there is text afterwards end
delimiter of multiline comment, would continue the comment prefix. This
is fixed by checking for end delimiter on whole line instead of just
assuming it would always be at end.

- [x] Tests

Release Notes:

- Fixed the issue where in some cases the block comment continues to the
next line even though the comment block is already closed.
2025-05-22 23:27:03 +05:30
Joseph T. Lyons
b188e5d3aa Fix update status logic to preserve previous status (#31202)
Release Notes:

- N/A
2025-05-22 17:34:14 +00:00
Anthony Eid
e3d3daec92 Fix bug where deleted toolchains stay selected on startup (#30562)
This affects python's when debugging because the selected toolchain is
used as the python binary to spawn Debugpy

Release Notes:

- Fix bug where selected toolchain didn't exist
2025-05-22 20:18:33 +03:00
Anthony Eid
ced8e4d88e debugger beta: Move path resolution to resolve scenario instead of just in new session modal (#31185)
This move was done so debug configs could use path resolution, and
saving a configuration from the new session modal wouldn't resolve paths
beforehand.

I also added an integration test to make sure path resolution happens
from an arbitrary config. The test was placed under the new session
modal directory because it has to do with starting a session, and that's
what the new session modal typically does, even if it's implicitly used
in the test.

In the future, I plan to add more tests to the new session modal too.

Release Notes:

- debugger beta: Allow configs from debug.json to resolve paths
2025-05-22 16:59:59 +00:00
Piotr Osiewicz
fa1abd8201 debugger: Always focus the active session whenever it is stopped (#31182)
Closes #ISSUE

Release Notes:

- debugger: Fixed child debug sessions taking precedence over the
parents when spawned.
2025-05-22 15:23:31 +00:00
张小白
ee4e43f1b6 linux: Fix wrong keys are reported when using German layout (#31193)
Part of #31174

Because the keyboard layout parameter wasn’t set correctly, characters
don’t show up properly when using the German layout at launch.

To reproduce:
Switch to the German layout, launch Zed, and press the `7` key. it
should output `7`, but instead it outputs `è`.


Release Notes:

- N/A
2025-05-22 14:47:23 +00:00
Anthony Eid
d61e1e24a7 docs: Fix debugger docs link from summary page (#31195)
Release Notes:

- N/A
2025-05-22 14:39:10 +00:00
Piotr Osiewicz
3c03d53e3e debugger: Use integrated terminal for Python (#31190)
Closes #ISSUE

Release Notes:

- debugger: Use integrated terminal for Python, allowing one to interact
with standard input/output when debugging Python projects.
2025-05-22 14:34:10 +00:00
Anthony Eid
8ab664a52c debugger beta: Update debugger docs for beta (#31192)
The docs include basic information on starting a session but will need
to be further iterated upon once we get deeper into the beta

Release Notes:

- N/A
2025-05-22 10:21:48 -04:00
Finn Evers
2044426634 gpui: Improve displayed keybinds shown in macOS application menus (#28440)
Closes #28164

This PR adresses inproper keybinds being shown in MacOS application
menus. The issue arises because the keybinds shown in MacOS application
menus are unaware of keybind contexts (they are only ever updated [on a
keymap-change](6d1dd109f5/crates/zed/src/zed.rs (L1421))).
Thus, using the keybind that was added last in the keymap can result in
incorrect keybindings being shown quite frequently, as they might belong
to a different context not generally available (applies the same for the
default keymap as well as for user-keymaps).

For example, the linked issue arises because the keybind found last in
the iterator is
6d1dd109f5/assets/keymaps/vim.json (L759),
which is not even available in most contexts (and, additionally, the `e`
of `escape` is rendered here as a keybind which seems to be a seperate
issue).

Additionally, this would result in inconsistent behavior with some
Vim-keybinds. A vim-keybind would be used only when available but
otherwise the default binding would be shown (see `Undo` and `Redo` as
an example below), which seems inconsistent.

This PR fixes this by instead using the first keybind found in keymaps,
which is expected to be the keybind available in most contexts.
Additionally, this allows rendering some more keybinds for actions which
vim-keybind cannot be displayed (Find In Project for example) .This
seems to be more reasonable until [this related
comment](6d1dd109f5/crates/gpui/src/keymap.rs (L199-L204))
is resolved.

This includes a revert of #25878 as well. With this change, the change
made in #25878 becomes obsolete and would also regress the behavior back
to the state prior to that PR.

|  | `main` | This PR |
| --- | --- | --- |
| Edit-menu | <img width="220" alt="main_edit"
src="https://github.com/user-attachments/assets/9f793b64-80b6-4a5b-b7e5-628f0d552166"
/> | <img width="220" alt="PR_edit"
src="https://github.com/user-attachments/assets/bccb444c-7a49-41d5-9377-d90b1639a3ed"
/> |
| View-menu | <img width="214" alt="main_view"
src="https://github.com/user-attachments/assets/0e6a6632-df02-4883-9f5a-facb4d0263b5"
/> | <img width="214" alt="PR_view"
src="https://github.com/user-attachments/assets/14600ece-fcaa-447a-94ef-4fa350eca49c"
/> |


Release Notes:

- Improved keybinds displayed for actions in MacOS application menus.
2025-05-22 09:51:51 -04:00
Joseph T. Lyons
02fa6f6fc2 Surface version to install in update status tooltip (#31179)
Release Notes:

- Surfaced the version that will be installed, in a tooltip, when
hovering on the `Click to restart and update Zed` status.
2025-05-22 12:37:18 +00:00
Anthony Eid
80a00cd241 debugger beta: Fix panic that could occur when parsing an invalid dap schema (#31175)
Release Notes:

- N/A
2025-05-22 07:25:07 -04:00
Anthony Eid
06f725d51b debugger beta: Fix dap_schema for DAP extensions (#31173)
We now actually call dap_schema provided by extensions instead of
defaulting to a null `serde_json::Value`. We still need to update the
Json LSP whenever a new dap is installed.

Release Notes:

- N/A
2025-05-22 07:24:46 -04:00
Julia Ryan
baf6d82cd4 Handle ~ in debugger launch modal (#31087)
@Anthony-Eid I'm pretty sure this maintains the behavior of #30680, and
I added some tests to be sure.

Release Notes:

- `~` now expands to the home directory in the debugger launch modal.

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-05-22 11:20:00 +00:00
Piotr Osiewicz
28ec7fbb81 debugger: Add telemetry for new session experience (#31171)
This includes the following data:
- Where we spawned the session from (gutter, scenario list, custom form
filled by the user)
- Which debug adapter was used
- Which dock the debugger is in

Closes #ISSUE

Release Notes:

- debugger: Added telemetry for new session experience that includes
data about:
    - How a session was spawned (gutter, scenario list or custom form)
    - Which debug adapter was used
    - Which dock the debugger is in

---------

Co-authored-by: Joseph T. Lyons <JosephTLyons@gmail.com>
2025-05-22 11:15:33 +00:00
Remco Smits
0415e853d5 debugger: Use current worktree directory when spawning an adapter (#31054)
/cc @osiewicz 

I think bringing this back should fix **bloveless** his issue with go
debugger.
This is also nice, so people are not forced to give us a working
directory, because most adapters will use their **cwd** as the project
root directory. For JavaScript, you don't need to specify the **cwd**
anymore because it can already infer it

Release Notes:

- debugger beta: Fixed some adapters fail to determine the right root level of the
debug program.
2025-05-22 06:47:47 -04:00
Anthony Eid
1c9b818342 debugger: Use DAP schema to configure daps (#30833)
This PR allows DAPs to define their own schema so users can see
completion items when editing their debug.json files.

Users facing this aren’t the biggest chance, but behind the scenes, this
affected a lot of code because we manually translated common fields from
Zed's config format to be adapter-specific. Now we store the raw JSON
from a user's configuration file and just send that.

I'm ignoring the Protobuf CICD error because the DebugTaskDefinition
message is not yet user facing and we need to deprecate some fields in
it.

Release Notes:

- debugger beta: Show completion items when editing debug.json
- debugger beta: Breaking change, debug.json schema now relays on what
DAP you have selected instead of always having the same based values.

---------

Co-authored-by: Remco Smits <djsmits12@gmail.com>
Co-authored-by: Cole Miller <m@cole-miller.net>
Co-authored-by: Cole Miller <cole@zed.dev>
2025-05-22 05:48:26 -04:00
Ben Kunkle
0d7f4842f3 Restore scroll after undo edit prediction (#31162)
Closes #29652

Release Notes:

- Fixed an issue where the scroll and cursor position would not be
restored after undoing an inline completion
2025-05-22 09:16:11 +00:00
Oleksiy Syvokon
ab017129d8 agent: Improve Gemini support in the edit_file tool (#31116)
This change improves `eval_extract_handle_command_output` results for
all models:

Model                       | Pass rate before | Pass rate after
----------------------------|------------------|----------------
claude-3.7-sonnet           |  0.96            | 0.98
gemini-2.5-pro              |  0.35            | 0.86
gpt-4.1                     |  0.81            | 1.00

Part of this improvement comes from more robust evaluation, which now
accepts multiple possible outcomes. Another part is from the prompt
adaptation: addressing common Gemini failure modes, adding a few-shot
example, and, in the final commit, auto-rewriting instructions for
clarity and conciseness.

This change still needs validation from larger end-to-end evals.


Release Notes:

- N/A
2025-05-22 12:01:43 +03:00
711 changed files with 40929 additions and 13333 deletions

View File

@@ -1,8 +1,8 @@
name: Bug Report (Agent Panel)
name: Bug Report (AI Related)
description: Zed Agent Panel Bugs
type: "Bug"
labels: ["agent", "ai"]
title: "Agent Panel: <a short description of the Agent Panel bug>"
labels: ["ai"]
title: "AI: <a short description of the AI Related bug>"
body:
- type: textarea
attributes:
@@ -14,7 +14,6 @@ body:
### Description
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
<!-- Please include the LLM provider and model name you are using -->
Steps to trigger the problem:
1.
2.
@@ -22,6 +21,13 @@ body:
Actual Behavior:
Expected Behavior:
### Model Provider Details
- Provider: (Anthropic via ZedPro, Anthropic via API key, Copilot Chat, Mistral, OpenAI, etc)
- Model Name:
- Mode: (Agent Panel, Inline Assistant, Terminal Assistant or Text Threads)
- MCP Servers in-use:
- Other Details:
validations:
required: true

26
.github/actions/build_docs/action.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: "Build docs"
description: "Build the docs"
runs:
using: "composite"
steps:
- name: Setup mdBook
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2
with:
mdbook-version: "0.4.37"
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
- name: Install Linux dependencies
shell: bash -euxo pipefail {0}
run: ./script/linux
- name: Build book
shell: bash -euxo pipefail {0}
run: |
mkdir -p target/deploy
mdbook build ./docs --dest-dir=../target/deploy/docs/

View File

@@ -73,7 +73,7 @@ jobs:
timeout-minutes: 60
runs-on:
- self-hosted
- test
- macOS
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -191,6 +191,27 @@ jobs:
with:
config: ./typos.toml
check_docs:
timeout-minutes: 60
name: Check docs
needs: [job_spec]
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-8vcpu-ubuntu-2204
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Build docs
uses: ./.github/actions/build_docs
macos_tests:
timeout-minutes: 60
name: (macOS) Run Clippy and tests
@@ -200,7 +221,7 @@ jobs:
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- self-hosted
- test
- macOS
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -482,7 +503,9 @@ jobs:
- macos_tests
- windows_clippy
- windows_tests
if: always()
if: |
github.repository_owner == 'zed-industries' &&
always()
steps:
- name: Check all tests passed
run: |
@@ -524,7 +547,6 @@ jobs:
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
@@ -611,7 +633,6 @@ jobs:
needs: [linux_tests]
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
@@ -669,7 +690,6 @@ jobs:
needs: [linux_tests]
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
@@ -717,49 +737,13 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
nix-build:
timeout-minutes: 60
name: Nix Build
continue-on-error: true
name: Build with Nix
uses: ./.github/workflows/nix.yml
if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix')
strategy:
fail-fast: false
matrix:
system:
- os: x86 Linux
runner: buildjet-16vcpu-ubuntu-2204
install_nix: true
- os: arm Mac
runner: [macOS, ARM64, test]
install_nix: false
runs-on: ${{ matrix.system.runner }}
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
GIT_LFS_SKIP_SMUDGE: 1 # breaks the livekit rust sdk examples which we don't actually depend on
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Set path
if: ${{ ! matrix.system.install_nix }}
run: |
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
- uses: cachix/install-nix-action@d1ca217b388ee87b2507a9a93bf01368bde7cec2 # v31
if: ${{ matrix.system.install_nix }}
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
name: zed-industries
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
skipPush: true
- run: nix build .#debug
- name: Limit /nix/store to 50GB
run: "[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d"
with:
flake-output: debug
# excludes the final package to only cache dependencies
cachix-filter: "-zed-editor-[0-9.]*-nightly"
auto-release-preview:
name: Auto release preview

View File

@@ -9,7 +9,7 @@ jobs:
deploy-docs:
name: Deploy Docs
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
runs-on: buildjet-16vcpu-ubuntu-2204
steps:
- name: Checkout repo
@@ -17,24 +17,11 @@ jobs:
with:
clean: false
- name: Setup mdBook
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2
with:
mdbook-version: "0.4.37"
- name: Set up default .cargo/config.toml
run: cp ./.cargo/collab-config.toml ./.cargo/config.toml
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install libxkbcommon-dev libxkbcommon-x11-dev
- name: Build book
run: |
set -euo pipefail
mkdir -p target/deploy
mdbook build ./docs --dest-dir=../target/deploy/docs/
- name: Build docs
uses: ./.github/actions/build_docs
- name: Deploy Docs
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3

View File

@@ -15,7 +15,7 @@ jobs:
if: github.repository_owner == 'zed-industries'
runs-on:
- self-hosted
- test
- macOS
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -33,7 +33,7 @@ jobs:
name: Run tests
runs-on:
- self-hosted
- test
- macOS
needs: style
steps:
- name: Checkout repo

66
.github/workflows/nix.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: "Nix build"
on:
workflow_call:
inputs:
flake-output:
type: string
default: "default"
cachix-filter:
type: string
default: ""
jobs:
nix-build:
timeout-minutes: 60
name: (${{ matrix.system.os }}) Nix Build
continue-on-error: true # TODO: remove when we want this to start blocking CI
strategy:
fail-fast: false
matrix:
system:
- os: x86 Linux
runner: buildjet-16vcpu-ubuntu-2204
install_nix: true
- os: arm Mac
runner: [macOS, ARM64, test]
install_nix: false
if: github.repository_owner == 'zed-industries'
runs-on: ${{ matrix.system.runner }}
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
GIT_LFS_SKIP_SMUDGE: 1 # breaks the livekit rust sdk examples which we don't actually depend on
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
# on our macs we manually install nix. for some reason the cachix action is running
# under a non-login /bin/bash shell which doesn't source the proper script to add the
# nix profile to PATH, so we manually add them here
- name: Set path
if: ${{ ! matrix.system.install_nix }}
run: |
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
if: ${{ matrix.system.install_nix }}
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
name: zed
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
pushFilter: "${{ inputs.cachix-filter }}"
cachixArgs: '-v'
- run: nix build .#${{ inputs.flake-output }} -L --accept-flake-config
- name: Limit /nix/store to 50GB on macs
if: ${{ ! matrix.system.install_nix }}
run: |
[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d || :

View File

@@ -20,7 +20,7 @@ jobs:
if: github.repository_owner == 'zed-industries'
runs-on:
- self-hosted
- test
- macOS
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -40,7 +40,7 @@ jobs:
if: github.repository_owner == 'zed-industries'
runs-on:
- self-hosted
- test
- macOS
needs: style
steps:
- name: Checkout repo
@@ -68,7 +68,6 @@ jobs:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
@@ -104,7 +103,6 @@ jobs:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -144,7 +142,6 @@ jobs:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -170,6 +167,11 @@ jobs:
- name: Upload Zed Nightly
run: script/upload-nightly linux-targz
bundle-nix:
name: Build and cache Nix package
needs: tests
uses: ./.github/workflows/nix.yml
update-nightly-tag:
name: Update nightly tag
if: github.repository_owner == 'zed-industries'

85
.github/workflows/unit_evals.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: Run Unit Evals
on:
schedule:
# GitHub might drop jobs at busy times, so we choose a random time in the middle of the night.
- cron: "47 1 * * *"
workflow_dispatch:
concurrency:
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
jobs:
unit_evals:
timeout-minutes: 60
name: Run unit evals
runs-on:
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Install Rust
shell: bash -euxo pipefail {0}
run: |
cargo install cargo-nextest --locked
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "18"
- name: Limit target directory size
shell: bash -euxo pipefail {0}
run: script/clear-target-dir-if-larger-than 100
- name: Run unit evals
shell: bash -euxo pipefail {0}
run: cargo nextest run --workspace --no-fail-fast --features eval --no-capture -E 'test(::eval_)' --test-threads 1
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Send the pull request link into the Slack channel
if: ${{ failure() }}
uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52
with:
method: chat.postMessage
token: ${{ secrets.SLACK_APP_ZED_UNIT_EVALS_BOT_TOKEN }}
payload: |
channel: C04UDRNNJFQ
text: "Unit Evals Failed: https://github.com/zed-industries/zed/actions/runs/${{ github.run_id }}"
# Even the Linux runner is not stateful, in theory there is no need to do this cleanup.
# But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code
# to clean up the config file, Ive included the cleanup code here as a precaution.
# While its not strictly necessary at this moment, I believe its better to err on the side of caution.
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo

6
.rules
View File

@@ -5,6 +5,12 @@
* Prefer implementing functionality in existing files unless it is a new logical component. Avoid creating many small files.
* Avoid using functions that panic like `unwrap()`, instead use mechanisms like `?` to propagate errors.
* Be careful with operations like indexing which may panic if the indexes are out of bounds.
* Never silently discard errors with `let _ =` on fallible operations. Always handle errors appropriately:
- Propagate errors with `?` when the calling function should handle them
- Use `.log_err()` or similar when you need to ignore errors but want visibility
- Use explicit error handling with `match` or `if let Err(...)` when you need custom logic
- Example: avoid `let _ = client.request(...).await?;` - use `client.request(...).await?;` instead
* When implementing async operations that may fail, ensure errors propagate to the UI layer so users get meaningful feedback.
* Never create files with `mod.rs` paths - prefer `src/some_module.rs` instead of `src/some_module/mod.rs`.
# GPUI

View File

@@ -2,16 +2,11 @@
{
"label": "Debug Zed (CodeLLDB)",
"adapter": "CodeLLDB",
"program": "target/debug/zed",
"request": "launch"
"build": { "label": "Build Zed", "command": "cargo", "args": ["build"] }
},
{
"label": "Debug Zed (GDB)",
"adapter": "GDB",
"program": "target/debug/zed",
"request": "launch",
"initialize_args": {
"stopAtBeginningOfMainSubprogram": true
}
"build": { "label": "Build Zed", "command": "cargo", "args": ["build"] }
}
]

311
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,11 +3,11 @@ resolver = "2"
members = [
"crates/activity_indicator",
"crates/agent",
"crates/agent_settings",
"crates/anthropic",
"crates/askpass",
"crates/assets",
"crates/assistant_context_editor",
"crates/assistant_settings",
"crates/assistant_slash_command",
"crates/assistant_slash_commands",
"crates/assistant_tool",
@@ -73,6 +73,7 @@ members = [
"crates/indexed_docs",
"crates/inline_completion",
"crates/inline_completion_button",
"crates/inspector_ui",
"crates/install_cli",
"crates/jj",
"crates/jj_ui",
@@ -99,6 +100,7 @@ members = [
"crates/notifications",
"crates/ollama",
"crates/open_ai",
"crates/open_router",
"crates/outline",
"crates/outline_panel",
"crates/panel",
@@ -210,12 +212,12 @@ edition = "2024"
activity_indicator = { path = "crates/activity_indicator" }
agent = { path = "crates/agent" }
agent_settings = { path = "crates/agent_settings" }
ai = { path = "crates/ai" }
anthropic = { path = "crates/anthropic" }
askpass = { path = "crates/askpass" }
assets = { path = "crates/assets" }
assistant_context_editor = { path = "crates/assistant_context_editor" }
assistant_settings = { path = "crates/assistant_settings" }
assistant_slash_command = { path = "crates/assistant_slash_command" }
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
assistant_tool = { path = "crates/assistant_tool" }
@@ -279,6 +281,7 @@ image_viewer = { path = "crates/image_viewer" }
indexed_docs = { path = "crates/indexed_docs" }
inline_completion = { path = "crates/inline_completion" }
inline_completion_button = { path = "crates/inline_completion_button" }
inspector_ui = { path = "crates/inspector_ui" }
install_cli = { path = "crates/install_cli" }
jj = { path = "crates/jj" }
jj_ui = { path = "crates/jj_ui" }
@@ -305,6 +308,7 @@ node_runtime = { path = "crates/node_runtime" }
notifications = { path = "crates/notifications" }
ollama = { path = "crates/ollama" }
open_ai = { path = "crates/open_ai" }
open_router = { path = "crates/open_router", features = ["schemars"] }
outline = { path = "crates/outline" }
outline_panel = { path = "crates/outline_panel" }
panel = { path = "crates/panel" }
@@ -430,7 +434,7 @@ core-foundation-sys = "0.8.6"
core-video = { version = "0.4.3", features = ["metal"] }
criterion = { version = "0.5", features = ["html_reports"] }
ctor = "0.4.0"
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "be69a016ba710191b9fdded28c8b042af4b617f7" }
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "68516de327fa1be15214133a0a2e52a12982ce75" }
dashmap = "6.0"
derive_more = "0.99.17"
dirs = "4.0"
@@ -447,6 +451,7 @@ futures-batch = "0.6.1"
futures-lite = "1.13"
git2 = { version = "0.20.1", default-features = false }
globset = "0.4"
hashbrown = "0.15.3"
handlebars = "4.3"
heck = "0.5"
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
@@ -462,6 +467,7 @@ 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"
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
@@ -549,7 +555,7 @@ streaming-iterator = "0.1"
strsim = "0.11"
strum = { version = "0.27.0", features = ["derive"] }
subtle = "2.5.0"
syn = { version = "1.0.72", features = ["full", "extra-traits"] }
syn = { version = "2.0.101", features = ["full", "extra-traits"] }
sys-locale = "0.3.1"
sysinfo = "0.31.0"
take-until = "0.2.0"
@@ -568,8 +574,8 @@ tokio = { version = "1" }
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
toml = "0.8"
tower-http = "0.4.4"
tree-sitter = { version = "0.25.3", features = ["wasm"] }
tree-sitter-bash = "0.23"
tree-sitter = { version = "0.25.6", features = ["wasm"] }
tree-sitter-bash = "0.25.0"
tree-sitter-c = "0.23"
tree-sitter-cpp = "0.23"
tree-sitter-css = "0.23"
@@ -599,7 +605,6 @@ url = "2.2"
urlencoding = "2.1.2"
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
walkdir = "2.5"
wasi-preview1-component-adapter-provider = "29"
wasm-encoder = "0.221"
wasmparser = "0.221"
wasmtime = { version = "29", default-features = false, features = [
@@ -613,9 +618,8 @@ wasmtime = { version = "29", default-features = false, features = [
] }
wasmtime-wasi = "29"
which = "6.0.0"
wit-component = "0.221"
workspace-hack = "0.1.0"
zed_llm_client = "0.8.2"
zed_llm_client = "0.8.4"
zstd = "0.11"
[workspace.dependencies.async-stripe]

View File

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

After

Width:  |  Height:  |  Size: 575 B

View File

@@ -1,3 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.76019 3.50003H6.50231C6.71012 3.50003 6.89761 3.62971 6.95698 3.82346C7.04292 4.01876 6.98823 4.23906 6.83199 4.37656L2.83214 7.87643C2.65558 8.02954 2.39731 8.04204 2.20857 7.90455C2.01967 7.76705 1.95092 7.51706 2.04295 7.30301L3.24462 4.49999H1.48844C1.29423 4.49999 1.10767 4.37031 1.0344 4.17657C0.961132 3.98126 1.01643 3.76096 1.17323 3.62346L5.17261 0.123753C5.34917 -0.0299914 5.60697 -0.0417097 5.79603 0.0954726C5.98508 0.232749 6.05383 0.482177 5.96165 0.69695L4.76013 3.49981L4.76019 3.50003Z" fill="white"/>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.3 1.75L3 7.35H5.8L4.7 12.25L11 6.65H8.2L9.3 1.75Z" stroke="black" stroke-width="1.25" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 633 B

After

Width:  |  Height:  |  Size: 227 B

View File

@@ -0,0 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.76019 3.50003H6.50231C6.71012 3.50003 6.89761 3.62971 6.95698 3.82346C7.04292 4.01876 6.98823 4.23906 6.83199 4.37656L2.83214 7.87643C2.65558 8.02954 2.39731 8.04204 2.20857 7.90455C2.01967 7.76705 1.95092 7.51706 2.04295 7.30301L3.24462 4.49999H1.48844C1.29423 4.49999 1.10767 4.37031 1.0344 4.17657C0.961132 3.98126 1.01643 3.76096 1.17323 3.62346L5.17261 0.123753C5.34917 -0.0299914 5.60697 -0.0417097 5.79603 0.0954726C5.98508 0.232749 6.05383 0.482177 5.96165 0.69695L4.76013 3.49981L4.76019 3.50003Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 633 B

View File

@@ -1,5 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 20H16C14.9391 20 13.9217 19.6629 13.1716 19.0627C12.4214 18.4626 12 17.6487 12 16.8V7.2C12 6.35131 12.4214 5.53737 13.1716 4.93726C13.9217 4.33714 14.9391 4 16 4H17" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 20H8C9.06087 20 10.0783 19.5786 10.8284 18.8284C11.5786 18.0783 12 17.0609 12 16V15" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 4H8C9.06087 4 10.0783 4.42143 10.8284 5.17157C11.5786 5.92172 12 6.93913 12 8V9" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 13H10.4C9.76346 13 9.15302 12.7893 8.70296 12.4142C8.25284 12.0391 8 11.5304 8 11V5C8 4.46957 8.25284 3.96086 8.70296 3.58579C9.15302 3.21071 9.76346 3 10.4 3H11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 13H5.6C6.23654 13 6.84698 12.7893 7.29704 12.4142C7.74716 12.0391 8 11.5304 8 11V5C8 4.46957 7.74716 3.96086 7.29704 3.58579C6.84698 3.21071 6.23654 3 5.6 3H5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 715 B

After

Width:  |  Height:  |  Size: 617 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-todo-icon lucide-list-todo"><rect x="3" y="5" width="6" height="6" rx="1"/><path d="m3 17 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/></svg>

After

Width:  |  Height:  |  Size: 373 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 3L13 8L4 13V3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 214 B

View File

@@ -0,0 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 12C2.35977 11.85 1 10.575 1 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.00875 15.2C1.00875 13.625 0.683456 12.275 4.00001 12.2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 9C7 10.575 5.62857 11.85 4 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 12.2C6.98117 12.2 7 13.625 7 15.2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="2.5" y="9" width="3" height="6" rx="1.5" fill="black"/>
<path d="M9 10L13 8L4 3V7.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 813 B

View File

@@ -1,3 +1,8 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.36667 3.79167C5.53364 3.79167 4.85833 4.46697 4.85833 5.3C4.85833 6.13303 5.53364 6.80833 6.36667 6.80833C7.1997 6.80833 7.875 6.13303 7.875 5.3C7.875 4.46697 7.1997 3.79167 6.36667 3.79167ZM2.1 5.925H3.67944C3.9626 7.14732 5.05824 8.05833 6.36667 8.05833C7.67509 8.05833 8.77073 7.14732 9.05389 5.925H14.9C15.2452 5.925 15.525 5.64518 15.525 5.3C15.525 4.95482 15.2452 4.675 14.9 4.675H9.05389C8.77073 3.45268 7.67509 2.54167 6.36667 2.54167C5.05824 2.54167 3.9626 3.45268 3.67944 4.675H2.1C1.75482 4.675 1.475 4.95482 1.475 5.3C1.475 5.64518 1.75482 5.925 2.1 5.925ZM13.3206 12.325C13.0374 13.5473 11.9418 14.4583 10.6333 14.4583C9.32491 14.4583 8.22927 13.5473 7.94611 12.325H2.1C1.75482 12.325 1.475 12.0452 1.475 11.7C1.475 11.3548 1.75482 11.075 2.1 11.075H7.94611C8.22927 9.85268 9.32491 8.94167 10.6333 8.94167C11.9418 8.94167 13.0374 9.85268 13.3206 11.075H14.9C15.2452 11.075 15.525 11.3548 15.525 11.7C15.525 12.0452 15.2452 12.325 14.9 12.325H13.3206ZM9.125 11.7C9.125 10.867 9.8003 10.1917 10.6333 10.1917C11.4664 10.1917 12.1417 10.867 12.1417 11.7C12.1417 12.533 11.4664 13.2083 10.6333 13.2083C9.8003 13.2083 9.125 12.533 9.125 11.7Z" fill="black"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 5H4" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M8 5L14 5" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M12 11L14 11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M2 11H8" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="6" cy="5" r="2" fill="black" fill-opacity="0.1" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="10" cy="11" r="2" fill="black" fill-opacity="0.1" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 657 B

View File

@@ -1 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.97942 1.25171L6.9585 1.30199L5.58662 4.60039C5.54342 4.70426 5.44573 4.77523 5.3336 4.78422L1.7727 5.0697L1.71841 5.07405L1.38687 5.10063L1.08608 5.12475C0.820085 5.14607 0.712228 5.47802 0.914889 5.65162L1.14406 5.84793L1.39666 6.06431L1.43802 6.09974L4.15105 8.42374C4.23648 8.49692 4.2738 8.61176 4.24769 8.72118L3.41882 12.196L3.40618 12.249L3.32901 12.5725L3.25899 12.866C3.19708 13.1256 3.47945 13.3308 3.70718 13.1917L3.9647 13.0344L4.24854 12.861L4.29502 12.8326L7.34365 10.9705C7.43965 10.9119 7.5604 10.9119 7.6564 10.9705L10.705 12.8326L10.7515 12.861L11.0354 13.0344L11.2929 13.1917C11.5206 13.3308 11.803 13.1256 11.7411 12.866L11.671 12.5725L11.5939 12.249L11.5812 12.196L10.7524 8.72118C10.7263 8.61176 10.7636 8.49692 10.849 8.42374L13.562 6.09974L13.6034 6.06431L13.856 5.84793L14.0852 5.65162C14.2878 5.47802 14.18 5.14607 13.914 5.12475L13.6132 5.10063L13.2816 5.07405L13.2274 5.0697L9.66645 4.78422C9.55432 4.77523 9.45663 4.70426 9.41343 4.60039L8.04155 1.30199L8.02064 1.25171L7.89291 0.944609L7.77702 0.665992C7.67454 0.419604 7.32551 0.419604 7.22303 0.665992L7.10715 0.944609L6.97942 1.25171ZM7.50003 2.60397L6.50994 4.98442C6.32273 5.43453 5.89944 5.74207 5.41351 5.78103L2.84361 5.98705L4.8016 7.66428C5.17183 7.98142 5.33351 8.47903 5.2204 8.95321L4.62221 11.461L6.8224 10.1171C7.23842 9.86302 7.76164 9.86302 8.17766 10.1171L10.3778 11.461L9.77965 8.95321C9.66654 8.47903 9.82822 7.98142 10.1984 7.66428L12.1564 5.98705L9.58654 5.78103C9.10061 5.74207 8.67732 5.43453 8.49011 4.98442L7.50003 2.60397Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.68323 1.53C7.71245 1.47097 7.75758 1.42129 7.81353 1.38655C7.86949 1.35181 7.93404 1.3334 7.9999 1.3334C8.06576 1.3334 8.13031 1.35181 8.18626 1.38655C8.24222 1.42129 8.28735 1.47097 8.31656 1.53L9.85656 4.64933C9.95802 4.85465 10.1078 5.03227 10.293 5.16697C10.4782 5.30167 10.6933 5.38941 10.9199 5.42267L14.3639 5.92667C14.4292 5.93612 14.4905 5.96365 14.5409 6.00613C14.5913 6.04862 14.6289 6.10437 14.6492 6.16707C14.6696 6.22978 14.6721 6.29694 14.6563 6.36096C14.6405 6.42498 14.6071 6.4833 14.5599 6.52933L12.0692 8.95467C11.905 9.11473 11.7821 9.31232 11.7111 9.53042C11.6402 9.74852 11.6233 9.98059 11.6619 10.2067L12.2499 13.6333C12.2614 13.6986 12.2544 13.7657 12.2296 13.8271C12.2048 13.8885 12.1632 13.9417 12.1096 13.9807C12.056 14.0196 11.9926 14.0427 11.9265 14.0473C11.8604 14.0519 11.7944 14.0378 11.7359 14.0067L8.65723 12.388C8.45438 12.2815 8.22868 12.2258 7.99956 12.2258C7.77044 12.2258 7.54475 12.2815 7.3419 12.388L4.2639 14.0067C4.20545 14.0376 4.1395 14.0515 4.07353 14.0468C4.00757 14.0421 3.94424 14.019 3.89076 13.9801C3.83728 13.9413 3.79579 13.8881 3.771 13.8268C3.74622 13.7655 3.73914 13.6985 3.75056 13.6333L4.3379 10.2073C4.3767 9.98116 4.35989 9.74893 4.28892 9.5307C4.21796 9.31246 4.09497 9.11477 3.93056 8.95467L1.4399 6.53C1.39229 6.48402 1.35856 6.4256 1.34254 6.36138C1.32652 6.29717 1.32886 6.22975 1.34928 6.16679C1.36971 6.10384 1.40741 6.04789 1.45808 6.00532C1.50876 5.96275 1.57037 5.93527 1.6359 5.926L5.07923 5.42267C5.30607 5.38967 5.52149 5.30204 5.70695 5.16733C5.89242 5.03261 6.04237 4.85485 6.1439 4.64933L7.68323 1.53Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.22303 0.665992C7.32551 0.419604 7.67454 0.419604 7.77702 0.665992L9.41343 4.60039C9.45663 4.70426 9.55432 4.77523 9.66645 4.78422L13.914 5.12475C14.18 5.14607 14.2878 5.47802 14.0852 5.65162L10.849 8.42374C10.7636 8.49692 10.7263 8.61176 10.7524 8.72118L11.7411 12.866C11.803 13.1256 11.5206 13.3308 11.2929 13.1917L7.6564 10.9705C7.5604 10.9119 7.43965 10.9119 7.34365 10.9705L3.70718 13.1917C3.47945 13.3308 3.19708 13.1256 3.25899 12.866L4.24769 8.72118C4.2738 8.61176 4.23648 8.49692 4.15105 8.42374L0.914889 5.65162C0.712228 5.47802 0.820086 5.14607 1.08608 5.12475L5.3336 4.78422C5.44573 4.77523 5.54342 4.70426 5.58662 4.60039L7.22303 0.665992Z" fill="currentColor"></path></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.68323 1.53C7.71245 1.47097 7.75758 1.42129 7.81353 1.38655C7.86949 1.35181 7.93404 1.3334 7.9999 1.3334C8.06576 1.3334 8.13031 1.35181 8.18626 1.38655C8.24222 1.42129 8.28735 1.47097 8.31656 1.53L9.85656 4.64933C9.95802 4.85465 10.1078 5.03227 10.293 5.16697C10.4782 5.30167 10.6933 5.38941 10.9199 5.42267L14.3639 5.92667C14.4292 5.93612 14.4905 5.96365 14.5409 6.00613C14.5913 6.04862 14.6289 6.10437 14.6492 6.16707C14.6696 6.22978 14.6721 6.29694 14.6563 6.36096C14.6405 6.42498 14.6071 6.4833 14.5599 6.52933L12.0692 8.95467C11.905 9.11473 11.7821 9.31232 11.7111 9.53042C11.6402 9.74852 11.6233 9.98059 11.6619 10.2067L12.2499 13.6333C12.2614 13.6986 12.2544 13.7657 12.2296 13.8271C12.2048 13.8885 12.1632 13.9417 12.1096 13.9807C12.056 14.0196 11.9926 14.0427 11.9265 14.0473C11.8604 14.0519 11.7944 14.0378 11.7359 14.0067L8.65723 12.388C8.45438 12.2815 8.22868 12.2258 7.99956 12.2258C7.77044 12.2258 7.54475 12.2815 7.3419 12.388L4.2639 14.0067C4.20545 14.0376 4.1395 14.0515 4.07353 14.0468C4.00757 14.0421 3.94424 14.019 3.89076 13.9801C3.83728 13.9413 3.79579 13.8881 3.771 13.8268C3.74622 13.7655 3.73914 13.6985 3.75056 13.6333L4.3379 10.2073C4.3767 9.98116 4.35989 9.74893 4.28892 9.5307C4.21796 9.31246 4.09497 9.11477 3.93056 8.95467L1.4399 6.53C1.39229 6.48402 1.35856 6.4256 1.34254 6.36138C1.32652 6.29717 1.32886 6.22975 1.34928 6.16679C1.36971 6.10384 1.40741 6.04789 1.45808 6.00532C1.50876 5.96275 1.57037 5.93527 1.6359 5.926L5.07923 5.42267C5.30607 5.38967 5.52149 5.30204 5.70695 5.16733C5.89242 5.03261 6.04237 4.85485 6.1439 4.64933L7.68323 1.53Z" fill="black" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 794 B

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,5 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 1.75L5.88467 5.14092C5.82759 5.31446 5.73055 5.47218 5.60136 5.60136C5.47218 5.73055 5.31446 5.82759 5.14092 5.88467L1.75 7L5.14092 8.11533C5.31446 8.17241 5.47218 8.26945 5.60136 8.39864C5.73055 8.52782 5.82759 8.68554 5.88467 8.85908L7 12.25L8.11533 8.85908C8.17241 8.68554 8.26945 8.52782 8.39864 8.39864C8.52782 8.26945 8.68554 8.17241 8.85908 8.11533L12.25 7L8.85908 5.88467C8.68554 5.82759 8.52782 5.73055 8.39864 5.60136C8.26945 5.47218 8.17241 5.31446 8.11533 5.14092L7 1.75Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.91667 1.75V4.08333M1.75 2.91667H4.08333" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.0833 9.91667V12.25M9.91667 11.0833H12.25" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 2L6.72534 5.87534C6.6601 6.07367 6.5492 6.25392 6.40155 6.40155C6.25392 6.5492 6.07367 6.6601 5.87534 6.72534L2 8L5.87534 9.27466C6.07367 9.3399 6.25392 9.4508 6.40155 9.59845C6.5492 9.74608 6.6601 9.92633 6.72534 10.1247L8 14L9.27466 10.1247C9.3399 9.92633 9.4508 9.74608 9.59845 9.59845C9.74608 9.4508 9.92633 9.3399 10.1247 9.27466L14 8L10.1247 6.72534C9.92633 6.6601 9.74608 6.5492 9.59845 6.40155C9.4508 6.25392 9.3399 6.07367 9.27466 5.87534L8 2Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.33334 2V4.66666M2 3.33334H4.66666" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.6665 11.3333V14M11.3333 12.6666H13.9999" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 998 B

View File

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.99207 8.14741C5.37246 8.14741 5.73726 7.9963 6.00623 7.72733C6.27521 7.45836 6.42631 7.09355 6.42631 6.71317C6.42631 5.92147 6.13946 5.56578 5.85262 4.99208C5.23761 3.76265 5.72411 2.66631 7.00001 1.5499C7.28686 2.98414 8.1474 4.36101 9.2948 5.27893C10.4422 6.19684 11.0159 7.28687 11.0159 8.43426C11.0159 8.96163 10.912 9.48384 10.7102 9.97107C10.5084 10.4583 10.2126 10.901 9.83967 11.2739C9.46676 11.6468 9.02405 11.9426 8.53682 12.1444C8.04959 12.3463 7.52738 12.4501 7.00001 12.4501C6.47264 12.4501 5.95043 12.3463 5.4632 12.1444C4.97597 11.9426 4.53326 11.6468 4.16035 11.2739C3.78745 10.901 3.49164 10.4583 3.28982 9.97107C3.088 9.48384 2.98413 8.96163 2.98413 8.43426C2.98413 7.77279 3.23254 7.1182 3.55783 6.71317C3.55783 7.09355 3.70894 7.45836 3.97791 7.72733C4.24688 7.9963 4.61169 8.14741 4.99207 8.14741Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1018 B

View File

@@ -0,0 +1,13 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2595_5640)">
<path d="M4.99207 8.14741C5.37246 8.14741 5.73726 7.9963 6.00623 7.72733C6.27521 7.45836 6.42631 7.09355 6.42631 6.71317C6.42631 5.92147 6.13946 5.56578 5.85262 4.99208C5.23761 3.76265 5.72411 2.66631 7.00001 1.5499C7.28686 2.98414 8.1474 4.36101 9.2948 5.27893C10.4422 6.19684 11.0159 7.28687 11.0159 8.43426C11.0159 8.96163 10.912 9.48384 10.7102 9.97107C10.5084 10.4583 10.2126 10.901 9.83967 11.2739C9.46676 11.6468 9.02405 11.9426 8.53682 12.1444C8.04959 12.3463 7.52738 12.4501 7.00001 12.4501C6.47264 12.4501 5.95043 12.3463 5.4632 12.1444C4.97597 11.9426 4.53326 11.6468 4.16035 11.2739C3.78745 10.901 3.49164 10.4583 3.28982 9.97107C3.088 9.48384 2.98413 8.96163 2.98413 8.43426C2.98413 7.77279 3.23254 7.1182 3.55783 6.71317C3.55783 7.09355 3.70894 7.45836 3.97791 7.72733C4.24688 7.9963 4.61169 8.14741 4.99207 8.14741Z" fill="black" fill-opacity="0.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 4C2.55228 4 3 3.55228 3 3C3 2.44772 2.55228 2 2 2C1.44772 2 1 2.44772 1 3C1 3.55228 1.44772 4 2 4Z" fill="black"/>
<path d="M10 2C10.5523 2 11 1.55228 11 1C11 0.44772 10.5523 0 10 0C9.44772 0 9 0.44772 9 1C9 1.55228 9.44772 2 10 2Z" fill="black"/>
<path d="M13 5C13.5522 5 14 4.55228 14 4C14 3.44772 13.5522 3 13 3C12.4478 3 12 3.44772 12 4C12 4.55228 12.4478 5 13 5Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_2595_5640">
<rect width="14" height="14" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,14 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2489_484)">
<path d="M11 8.9V11C8.51716 11 7.48284 11 5 11V10.4L11 5.6V5H5V7.1" stroke="black" stroke-width="1.5"/>
<path d="M1.5 5.5V1.5H5" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M14.5 5.5V1.5H11" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M1.5 10.5V14.5H5" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M14.5 10.5V14.5H11" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
</g>
<defs>
<clipPath id="clip0_2489_484">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 687 B

View File

@@ -31,7 +31,6 @@
"ctrl-,": "zed::OpenSettings",
"ctrl-q": "zed::Quit",
"f4": "debugger::Start",
"f5": "debugger::Continue",
"shift-f5": "debugger::Stop",
"ctrl-shift-f5": "debugger::Restart",
"f6": "debugger::Pause",
@@ -121,14 +120,12 @@
"ctrl-'": "editor::ToggleSelectedDiffHunks",
"ctrl-\"": "editor::ExpandAllDiffHunks",
"ctrl-i": "editor::ShowSignatureHelp",
"alt-g b": "editor::ToggleGitBlame",
"alt-g b": "git::Blame",
"menu": "editor::OpenContextMenu",
"shift-f10": "editor::OpenContextMenu",
"ctrl-shift-e": "editor::ToggleEditPrediction",
"f9": "editor::ToggleBreakpoint",
"shift-f9": "editor::EditLogBreakpoint",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
"ctrl-shift-alt-backspace": "editor::GoToNextChange"
"shift-f9": "editor::EditLogBreakpoint"
}
},
{
@@ -147,6 +144,8 @@
"ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
"ctrl-shift-alt-backspace": "editor::GoToNextChange",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
}
},
@@ -243,11 +242,14 @@
"ctrl-i": "agent::ToggleProfileSelector",
"ctrl-alt-/": "agent::ToggleModelSelector",
"ctrl-shift-a": "agent::ToggleContextPicker",
"ctrl-shift-o": "agent::ToggleNavigationMenu",
"ctrl-shift-j": "agent::ToggleNavigationMenu",
"ctrl-shift-i": "agent::ToggleOptionsMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl-alt-e": "agent::RemoveAllContext",
"ctrl-shift-e": "project_panel::ToggleFocus"
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
"alt-enter": "agent::ContinueWithBurnMode",
"ctrl-alt-b": "agent::ToggleBurnMode"
}
},
{
@@ -274,8 +276,11 @@
"context": "MessageEditor > Editor",
"bindings": {
"enter": "agent::Chat",
"ctrl-enter": "agent::ChatWithFollow",
"ctrl-i": "agent::ToggleProfileSelector",
"shift-ctrl-r": "agent::OpenAgentDiff"
"shift-ctrl-r": "agent::OpenAgentDiff",
"ctrl-shift-y": "agent::KeepAll",
"ctrl-shift-n": "agent::RejectAll"
}
},
{
@@ -507,14 +512,14 @@
{
"context": "Workspace",
"bindings": {
"alt-open": ["projects::OpenRecent", { "create_new_window": false }],
// Change the default action on `menu::Confirm` by setting the parameter
// "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }],
"alt-open": "projects::OpenRecent",
"alt-ctrl-o": "projects::OpenRecent",
"alt-shift-open": "projects::OpenRemote",
"alt-ctrl-shift-o": "projects::OpenRemote",
"alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": false }],
"alt-shift-open": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }],
// Change to open path modal for existing remote connection by setting the parameter
// "alt-ctrl-shift-o": "["projects::OpenRemote", { "from_existing_connection": true }]",
"alt-ctrl-shift-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }],
"alt-ctrl-shift-b": "branches::OpenRecent",
"alt-shift-enter": "toast::RunAction",
"ctrl-~": "workspace::NewTerminal",
@@ -578,11 +583,24 @@
"ctrl-alt-r": "task::Rerun",
"alt-t": "task::Rerun",
"alt-shift-t": "task::Spawn",
"alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
"alt-shift-r": ["task::Spawn", { "reveal_target": "center" }],
// also possible to spawn tasks by name:
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
// or by tag:
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
"f5": "debugger::RerunLastSession"
}
},
{
"context": "Workspace && debugger_running",
"bindings": {
"f5": "zed::NoAction"
}
},
{
"context": "Workspace && debugger_stopped",
"bindings": {
"f5": "debugger::Continue"
}
},
{
@@ -675,7 +693,8 @@
{
"bindings": {
"ctrl-alt-shift-f": "workspace::FollowNextCollaborator",
"ctrl-alt-i": "zed::DebugElements"
// Only available in debug builds: opens an element inspector for development.
"ctrl-alt-i": "dev::ToggleInspector"
}
},
{
@@ -867,14 +886,34 @@
"context": "DebugPanel",
"bindings": {
"ctrl-t": "debugger::ToggleThreadPicker",
"ctrl-i": "debugger::ToggleSessionPicker"
"ctrl-i": "debugger::ToggleSessionPicker",
"shift-alt-escape": "debugger::ToggleExpandItem"
}
},
{
"context": "VariableList",
"bindings": {
"left": "variable_list::CollapseSelectedEntry",
"right": "variable_list::ExpandSelectedEntry",
"enter": "variable_list::EditVariable",
"ctrl-c": "variable_list::CopyVariableValue",
"ctrl-alt-c": "variable_list::CopyVariableName"
}
},
{
"context": "BreakpointList",
"bindings": {
"space": "debugger::ToggleEnableBreakpoint",
"backspace": "debugger::UnsetBreakpoint"
}
},
{
"context": "CollabPanel && not_editing",
"bindings": {
"ctrl-backspace": "collab_panel::Remove",
"space": "menu::Confirm"
"space": "menu::Confirm",
"ctrl-up": "collab_panel::MoveChannelUp",
"ctrl-down": "collab_panel::MoveChannelDown"
}
},
{
@@ -905,6 +944,13 @@
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "FileFinder",
"bindings": {
"ctrl-shift-a": "file_finder::ToggleSplitMenu",
"ctrl-shift-i": "file_finder::ToggleFilterMenu"
}
},
{
"context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)",
"bindings": {
@@ -996,5 +1042,12 @@
"bindings": {
"enter": "menu::Confirm"
}
},
{
"context": "RunModal",
"bindings": {
"ctrl-tab": "pane::ActivateNextItem",
"ctrl-shift-tab": "pane::ActivatePreviousItem"
}
}
]

View File

@@ -1,21 +1,9 @@
[
// Moved before Standard macOS bindings so that `cmd-w` is not the last binding for
// `workspace::CloseWindow` and displayed/intercepted by macOS
{
"context": "PromptLibrary",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "rules_library::NewRule",
"cmd-shift-s": "rules_library::ToggleDefaultRule",
"cmd-w": "workspace::CloseWindow"
}
},
// Standard macOS bindings
{
"use_key_equivalents": true,
"bindings": {
"f4": "debugger::Start",
"f5": "debugger::Continue",
"shift-f5": "debugger::Stop",
"shift-cmd-f5": "debugger::Restart",
"f6": "debugger::Pause",
@@ -150,7 +138,7 @@
"cmd-;": "editor::ToggleLineNumbers",
"cmd-'": "editor::ToggleSelectedDiffHunks",
"cmd-\"": "editor::ExpandAllDiffHunks",
"cmd-alt-g b": "editor::ToggleGitBlame",
"cmd-alt-g b": "git::Blame",
"cmd-i": "editor::ShowSignatureHelp",
"f9": "editor::ToggleBreakpoint",
"shift-f9": "editor::EditLogBreakpoint",
@@ -289,11 +277,14 @@
"cmd-i": "agent::ToggleProfileSelector",
"cmd-alt-/": "agent::ToggleModelSelector",
"cmd-shift-a": "agent::ToggleContextPicker",
"cmd-shift-o": "agent::ToggleNavigationMenu",
"cmd-shift-j": "agent::ToggleNavigationMenu",
"cmd-shift-i": "agent::ToggleOptionsMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"cmd-alt-e": "agent::RemoveAllContext",
"cmd-shift-e": "project_panel::ToggleFocus"
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-shift-enter": "agent::ContinueThread",
"alt-enter": "agent::ContinueWithBurnMode",
"cmd-alt-b": "agent::ToggleBurnMode"
}
},
{
@@ -322,8 +313,11 @@
"use_key_equivalents": true,
"bindings": {
"enter": "agent::Chat",
"cmd-enter": "agent::ChatWithFollow",
"cmd-i": "agent::ToggleProfileSelector",
"shift-ctrl-r": "agent::OpenAgentDiff"
"shift-ctrl-r": "agent::OpenAgentDiff",
"cmd-shift-y": "agent::KeepAll",
"cmd-shift-n": "agent::RejectAll"
}
},
{
@@ -368,18 +362,21 @@
"ctrl--": "pane::GoBack"
}
},
{
"context": "ThreadHistory",
"bindings": {
"ctrl--": "pane::GoBack"
}
},
{
"context": "ThreadHistory > Editor",
"bindings": {
"shift-backspace": "agent::RemoveSelectedThread"
}
},
{
"context": "PromptLibrary",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "rules_library::NewRule",
"cmd-shift-s": "rules_library::ToggleDefaultRule",
"cmd-w": "workspace::CloseWindow"
}
},
{
"context": "BufferSearchBar",
"use_key_equivalents": true,
@@ -549,9 +546,7 @@
"cmd-\\": "pane::SplitRight",
"cmd-k v": "markdown::OpenPreviewToTheSide",
"cmd-shift-v": "markdown::OpenPreview",
"ctrl-cmd-c": "editor::DisplayCursorNames",
"cmd-shift-backspace": "editor::GoToPreviousChange",
"cmd-shift-alt-backspace": "editor::GoToNextChange"
"ctrl-cmd-c": "editor::DisplayCursorNames"
}
},
{
@@ -559,7 +554,9 @@
"use_key_equivalents": true,
"bindings": {
"cmd-shift-o": "outline::Toggle",
"ctrl-g": "go_to_line::Toggle"
"ctrl-g": "go_to_line::Toggle",
"cmd-shift-backspace": "editor::GoToPreviousChange",
"cmd-shift-alt-backspace": "editor::GoToNextChange"
}
},
{
@@ -587,9 +584,9 @@
"bindings": {
// Change the default action on `menu::Confirm` by setting the parameter
// "alt-cmd-o": ["projects::OpenRecent", {"create_new_window": true }],
"alt-cmd-o": "projects::OpenRecent",
"ctrl-cmd-o": "projects::OpenRemote",
"ctrl-cmd-shift-o": ["projects::OpenRemote", { "from_existing_connection": true }],
"alt-cmd-o": ["projects::OpenRecent", { "create_new_window": false }],
"ctrl-cmd-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }],
"ctrl-cmd-shift-o": ["projects::OpenRemote", { "from_existing_connection": true, "create_new_window": false }],
"alt-cmd-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",
"cmd-s": "workspace::Save",
@@ -638,7 +635,8 @@
"cmd-k shift-right": "workspace::SwapPaneRight",
"cmd-k shift-up": "workspace::SwapPaneUp",
"cmd-k shift-down": "workspace::SwapPaneDown",
"cmd-shift-x": "zed::Extensions"
"cmd-shift-x": "zed::Extensions",
"f5": "debugger::RerunLastSession"
}
},
{
@@ -655,6 +653,20 @@
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
}
},
{
"context": "Workspace && debugger_running",
"use_key_equivalents": true,
"bindings": {
"f5": "zed::NoAction"
}
},
{
"context": "Workspace && debugger_stopped",
"use_key_equivalents": true,
"bindings": {
"f5": "debugger::Continue"
}
},
// Bindings from Sublime Text
{
"context": "Editor",
@@ -743,7 +755,8 @@
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
// TODO: Move this to a dock open action
"cmd-shift-c": "collab_panel::ToggleFocus",
"cmd-alt-i": "zed::DebugElements"
// Only available in debug builds: opens an element inspector for development.
"cmd-alt-i": "dev::ToggleInspector"
}
},
{
@@ -846,7 +859,10 @@
"use_key_equivalents": true,
"bindings": {
"left": "variable_list::CollapseSelectedEntry",
"right": "variable_list::ExpandSelectedEntry"
"right": "variable_list::ExpandSelectedEntry",
"enter": "variable_list::EditVariable",
"cmd-c": "variable_list::CopyVariableValue",
"cmd-alt-c": "variable_list::CopyVariableName"
}
},
{
@@ -935,7 +951,15 @@
"context": "DebugPanel",
"bindings": {
"cmd-t": "debugger::ToggleThreadPicker",
"cmd-i": "debugger::ToggleSessionPicker"
"cmd-i": "debugger::ToggleSessionPicker",
"shift-alt-escape": "debugger::ToggleExpandItem"
}
},
{
"context": "BreakpointList",
"bindings": {
"space": "debugger::ToggleEnableBreakpoint",
"backspace": "debugger::UnsetBreakpoint"
}
},
{
@@ -943,7 +967,9 @@
"use_key_equivalents": true,
"bindings": {
"ctrl-backspace": "collab_panel::Remove",
"space": "menu::Confirm"
"space": "menu::Confirm",
"cmd-up": "collab_panel::MoveChannelUp",
"cmd-down": "collab_panel::MoveChannelDown"
}
},
{
@@ -979,6 +1005,14 @@
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "FileFinder",
"use_key_equivalents": true,
"bindings": {
"cmd-shift-a": "file_finder::ToggleSplitMenu",
"cmd-shift-i": "file_finder::ToggleFilterMenu"
}
},
{
"context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)",
"use_key_equivalents": true,
@@ -1101,5 +1135,13 @@
"bindings": {
"enter": "menu::Confirm"
}
},
{
"context": "RunModal",
"use_key_equivalents": true,
"bindings": {
"ctrl-tab": "pane::ActivateNextItem",
"ctrl-shift-tab": "pane::ActivatePreviousItem"
}
}
]

View File

@@ -13,7 +13,7 @@
}
},
{
"context": "Editor && vim_mode == insert",
"context": "Editor",
"bindings": {
// "j k": ["workspace::SendKeystrokes", "escape"]
}

View File

@@ -0,0 +1,85 @@
[
// Cursor for MacOS. See: https://docs.cursor.com/kbd
{
"context": "Workspace",
"use_key_equivalents": true,
"bindings": {
"ctrl-i": "agent::ToggleFocus",
"ctrl-shift-i": "agent::ToggleFocus",
"ctrl-l": "agent::ToggleFocus",
"ctrl-shift-l": "agent::ToggleFocus",
"ctrl-alt-b": "agent::ToggleFocus",
"ctrl-shift-j": "agent::OpenConfiguration"
}
},
{
"context": "Editor && mode == full",
"use_key_equivalents": true,
"bindings": {
"ctrl-i": "agent::ToggleFocus",
"ctrl-shift-i": "agent::ToggleFocus",
"ctrl-shift-l": "assistant::QuoteSelection", // In cursor uses "Ask" mode
"ctrl-l": "assistant::QuoteSelection", // In cursor uses "Agent" mode
"ctrl-k": "assistant::InlineAssist",
"ctrl-shift-k": "assistant::InsertIntoEditor"
}
},
{
"context": "InlineAssistEditor",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-backspace": "editor::Cancel"
// "alt-enter": // Quick Question
// "ctrl-shift-enter": // Full File Context
// "ctrl-shift-k": // Toggle input focus (editor <> inline assist)
}
},
{
"context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-i": "workspace::ToggleRightDock",
"ctrl-shift-i": "workspace::ToggleRightDock",
"ctrl-l": "workspace::ToggleRightDock",
"ctrl-shift-l": "workspace::ToggleRightDock",
"ctrl-alt-b": "workspace::ToggleRightDock",
"ctrl-w": "workspace::ToggleRightDock", // technically should close chat
"ctrl-.": "agent::ToggleProfileSelector",
"ctrl-/": "agent::ToggleModelSelector",
"ctrl-shift-backspace": "editor::Cancel",
"ctrl-r": "agent::NewThread",
"ctrl-shift-v": "editor::Paste",
"ctrl-shift-k": "assistant::InsertIntoEditor"
// "escape": "agent::ToggleFocus"
///// Enable when Zed supports multiple thread tabs
// "ctrl-t": // new thread tab
// "ctrl-[": // next thread tab
// "ctrl-]": // next thread tab
///// Enable if Zed adds support for keyboard navigation of thread elements
// "tab": // cycle to next message
// "shift-tab": // cycle to previous message
}
},
{
"context": "Editor && editor_agent_diff",
"use_key_equivalents": true,
"bindings": {
"ctrl-enter": "agent::KeepAll",
"ctrl-backspace": "agent::RejectAll"
}
},
{
"context": "Editor && mode == full && edit_prediction",
"use_key_equivalents": true,
"bindings": {
"ctrl-right": "editor::AcceptPartialEditPrediction"
}
},
{
"context": "Terminal",
"use_key_equivalents": true,
"bindings": {
"ctrl-k": "assistant::InlineAssist"
}
}
]

View File

@@ -51,7 +51,11 @@
"ctrl-k ctrl-l": "editor::ConvertToLowerCase",
"shift-alt-m": "markdown::OpenPreviewToTheSide",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd"
"ctrl-delete": "editor::DeleteToNextWordEnd",
"alt-right": "editor::MoveToNextSubwordEnd",
"alt-left": "editor::MoveToPreviousSubwordStart",
"alt-shift-right": "editor::SelectToNextSubwordEnd",
"alt-shift-left": "editor::SelectToPreviousSubwordStart"
}
},
{

View File

@@ -0,0 +1,85 @@
[
// Cursor for MacOS. See: https://docs.cursor.com/kbd
{
"context": "Workspace",
"use_key_equivalents": true,
"bindings": {
"cmd-i": "agent::ToggleFocus",
"cmd-shift-i": "agent::ToggleFocus",
"cmd-l": "agent::ToggleFocus",
"cmd-shift-l": "agent::ToggleFocus",
"cmd-alt-b": "agent::ToggleFocus",
"cmd-shift-j": "agent::OpenConfiguration"
}
},
{
"context": "Editor && mode == full",
"use_key_equivalents": true,
"bindings": {
"cmd-i": "agent::ToggleFocus",
"cmd-shift-i": "agent::ToggleFocus",
"cmd-shift-l": "assistant::QuoteSelection", // In cursor uses "Ask" mode
"cmd-l": "assistant::QuoteSelection", // In cursor uses "Agent" mode
"cmd-k": "assistant::InlineAssist",
"cmd-shift-k": "assistant::InsertIntoEditor"
}
},
{
"context": "InlineAssistEditor",
"use_key_equivalents": true,
"bindings": {
"cmd-shift-backspace": "editor::Cancel"
// "alt-enter": // Quick Question
// "cmd-shift-enter": // Full File Context
// "cmd-shift-k": // Toggle input focus (editor <> inline assist)
}
},
{
"context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
"use_key_equivalents": true,
"bindings": {
"cmd-i": "workspace::ToggleRightDock",
"cmd-shift-i": "workspace::ToggleRightDock",
"cmd-l": "workspace::ToggleRightDock",
"cmd-shift-l": "workspace::ToggleRightDock",
"cmd-alt-b": "workspace::ToggleRightDock",
"cmd-w": "workspace::ToggleRightDock", // technically should close chat
"cmd-.": "agent::ToggleProfileSelector",
"cmd-/": "agent::ToggleModelSelector",
"cmd-shift-backspace": "editor::Cancel",
"cmd-r": "agent::NewThread",
"cmd-shift-v": "editor::Paste",
"cmd-shift-k": "assistant::InsertIntoEditor"
// "escape": "agent::ToggleFocus"
///// Enable when Zed supports multiple thread tabs
// "cmd-t": // new thread tab
// "cmd-[": // next thread tab
// "cmd-]": // next thread tab
///// Enable if Zed adds support for keyboard navigation of thread elements
// "tab": // cycle to next message
// "shift-tab": // cycle to previous message
}
},
{
"context": "Editor && editor_agent_diff",
"use_key_equivalents": true,
"bindings": {
"cmd-enter": "agent::KeepAll",
"cmd-backspace": "agent::RejectAll"
}
},
{
"context": "Editor && mode == full && edit_prediction",
"use_key_equivalents": true,
"bindings": {
"cmd-right": "editor::AcceptPartialEditPrediction"
}
},
{
"context": "Terminal",
"use_key_equivalents": true,
"bindings": {
"cmd-k": "assistant::InlineAssist"
}
}
]

View File

@@ -53,7 +53,11 @@
"cmd-shift-j": "editor::JoinLines",
"shift-alt-m": "markdown::OpenPreviewToTheSide",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd"
"ctrl-delete": "editor::DeleteToNextWordEnd",
"ctrl-right": "editor::MoveToNextSubwordEnd",
"ctrl-left": "editor::MoveToPreviousSubwordStart",
"ctrl-shift-right": "editor::SelectToNextSubwordEnd",
"ctrl-shift-left": "editor::SelectToPreviousSubwordStart"
}
},
{

View File

@@ -198,6 +198,8 @@
"9": ["vim::Number", 9],
"ctrl-w d": "editor::GoToDefinitionSplit",
"ctrl-w g d": "editor::GoToDefinitionSplit",
"ctrl-w ]": "editor::GoToDefinitionSplit",
"ctrl-w ctrl-]": "editor::GoToDefinitionSplit",
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",
"ctrl-w g shift-d": "editor::GoToTypeDefinitionSplit",
"ctrl-w space": "editor::OpenExcerptsSplit",
@@ -838,6 +840,19 @@
"tab": "editor::AcceptEditPrediction"
}
},
{
"context": "MessageEditor > Editor && VimControl",
"bindings": {
"enter": "agent::Chat",
// TODO: Implement search
"/": null,
"?": null,
"#": null,
"*": null,
"n": null,
"shift-n": null
}
},
{
"context": "os != macos && Editor && edit_prediction_conflict",
"bindings": {
@@ -846,13 +861,5 @@
// and Windows.
"alt-l": "editor::AcceptEditPrediction"
}
},
{
// Fixes https://github.com/zed-industries/zed/issues/29095 by ensuring that
// the last binding for editor::ToggleComments is not ctrl-c.
"context": "hack_to_fix_ctrl-c",
"bindings": {
"g c": "editor::ToggleComments"
}
}
]

View File

@@ -17,13 +17,13 @@ You are a highly skilled software engineer with extensive knowledge in many prog
4. Use only the tools that are currently available.
5. DO NOT use a tool that is not available just because it appears in the conversation. This means the user turned it off.
6. NEVER run commands that don't terminate on their own such as web servers (like `npm run start`, `npm run dev`, `python -m http.server`, etc) or file watchers.
7. Avoid HTML entity escaping - use plain characters instead.
## Searching and Reading
If you are unsure how to fulfill the user's request, gather more information with tool calls and/or clarifying questions.
{{! TODO: If there are files, we should mention it but otherwise omit that fact }}
{{#if has_tools}}
If appropriate, use tool calls to explore the current project, which contains the following root directories:
{{#each worktrees}}
@@ -38,7 +38,6 @@ If appropriate, use tool calls to explore the current project, which contains th
- As you learn about the structure of the project, use that information to scope `grep` searches to targeted subtrees of the project.
- The user might specify a partial file path. If you don't know the full path, use `find_path` (not `grep`) before you read the file.
{{/if}}
{{/if}}
{{else}}
You are being tasked with providing a response, but you have no ability to use tools or to read or write any aspect of the user's system (other than any context the user might have provided to you).

View File

@@ -73,9 +73,6 @@
"unnecessary_code_fade": 0.3,
// Active pane styling settings.
"active_pane_modifiers": {
// The factor to grow the active pane by. Defaults to 1.0
// which gives the same size as all other panes.
"magnification": 1.0,
// Inset border size of the active pane, in pixels.
"border_size": 0.0,
// Opacity of the inactive panes. 0 means transparent, 1 means opaque.
@@ -128,6 +125,8 @@
//
// Default: true
"restore_on_file_reopen": true,
// Whether to automatically close files that have been deleted on disk.
"close_on_file_delete": false,
// Size of the drop target in the editor.
"drop_target_size": 0.2,
// Whether the window should be closed when using 'close active item' on a window with no tabs.
@@ -213,6 +212,8 @@
// Whether to show the signature help after completion or a bracket pair inserted.
// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
"show_signature_help_after_edits": false,
// Whether to show code action button at start of buffer line.
"inline_code_actions": true,
// What to do when go to definition yields no results.
//
// 1. Do nothing: `none`
@@ -230,11 +231,11 @@
// Possible values:
// - "off" — no diagnostics are allowed
// - "error"
// - "warning" (default)
// - "warning"
// - "info"
// - "hint"
// - null — allow all diagnostics
"diagnostics_max_severity": "warning",
// - null — allow all diagnostics (default)
"diagnostics_max_severity": null,
// Whether to show wrap guides (vertical rulers) in the editor.
// Setting this to true will show a guide at the 'preferred_line_length' value
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
@@ -322,7 +323,9 @@
// Whether to show the Selections menu in the editor toolbar.
"selections_menu": true,
// Whether to show agent review buttons in the editor toolbar.
"agent_review": true
"agent_review": true,
// Whether to show code action buttons in the editor toolbar.
"code_actions": false
},
// Titlebar related settings
"title_bar": {
@@ -471,6 +474,10 @@
// Scroll sensitivity multiplier. This multiplier is applied
// to both the horizontal and vertical delta values while scrolling.
"scroll_sensitivity": 1.0,
// Scroll sensitivity multiplier for fast scrolling. This multiplier is applied
// to both the horizontal and vertical delta values while scrolling. Fast scrolling
// happens when a user holds the alt or option key while scrolling.
"fast_scroll_sensitivity": 4.0,
"relative_line_numbers": false,
// If 'search_wrap' is disabled, search result do not wrap around the end of the file.
"search_wrap": true,
@@ -526,6 +533,9 @@
"function": false
}
},
// Whether to resize all the panels in a dock when resizing the dock.
// Can be a combination of "left", "right" and "bottom".
"resize_all_panels_in_dock": ["left"],
"project_panel": {
// Whether to show the project panel button in the status bar
"button": true,
@@ -706,7 +716,7 @@
"version": "2",
// Whether the agent is enabled.
"enabled": true,
/// What completion mode to start new threads in, if available. Can be 'normal' or 'max'.
/// What completion mode to start new threads in, if available. Can be 'normal' or 'burn'.
"preferred_completion_mode": "normal",
// Whether to show the agent panel button in the status bar.
"button": true,
@@ -721,14 +731,7 @@
// The provider to use.
"provider": "zed.dev",
// The model to use.
"model": "claude-3-7-sonnet-latest"
},
// The model to use when applying edits from the agent.
"editor_model": {
// The provider to use.
"provider": "zed.dev",
// The model to use.
"model": "claude-3-7-sonnet-latest"
"model": "claude-sonnet-4"
},
// Additional parameters for language model requests. When making a request to a model, parameters will be taken
// from the last entry in this list that matches the model's provider and name. In each entry, both provider
@@ -748,7 +751,7 @@
// To set parameters for a specific provider and model:
// {
// "provider": "zed.dev",
// "model": "claude-3-7-sonnet-latest",
// "model": "claude-sonnet-4",
// "temperature": 1.0
// }
],
@@ -814,7 +817,12 @@
// "primary_screen" - Show the notification only on your primary screen (default)
// "all_screens" - Show these notifications on all screens
// "never" - Never show these notifications
"notify_when_agent_waiting": "primary_screen"
"notify_when_agent_waiting": "primary_screen",
// Whether to play a sound when the agent has either completed
// its response, or needs user input.
// Default: false
"play_sound_when_agent_done": false
},
// The settings for slash commands.
"slash_commands": {
@@ -946,7 +954,17 @@
// "skip_focus_for_active_in_search": false
//
// Default: true
"skip_focus_for_active_in_search": true
"skip_focus_for_active_in_search": true,
// Whether to show the git status in the file finder.
"git_status": true,
// Whether to use gitignored files when searching.
// Only the file Zed had indexed will be used, not necessary all the gitignored files.
//
// Can accept 3 values:
// * `true`: Use all gitignored files
// * `false`: Use only the files Zed had indexed
// * `null`: Be smart and search for ignored when called from a gitignored worktree
"include_ignored": null
},
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
@@ -1291,7 +1309,17 @@
// Settings related to running tasks.
"tasks": {
"variables": {},
"enabled": true
"enabled": true,
// Use LSP tasks over Zed language extension ones.
// If no LSP tasks are returned due to error/timeout or regular execution,
// Zed language extension tasks will be used instead.
//
// Other Zed tasks will still be shown:
// * Zed task from either of the task config file
// * Zed task from history (e.g. one-off task was spawned before)
//
// Default: true
"prefer_lsp": true
},
// An object whose keys are language names, and whose values
// are arrays of filenames or extensions of files that should
@@ -1472,11 +1500,11 @@
}
},
"LaTeX": {
"format_on_save": "on",
"formatter": "language_server",
"language_servers": ["texlab", "..."],
"prettier": {
"allowed": false
"allowed": true,
"plugins": ["prettier-plugin-latex"]
}
},
"Markdown": {
@@ -1500,7 +1528,7 @@
"allow_rewrap": "anywhere"
},
"Ruby": {
"language_servers": ["solargraph", "!ruby-lsp", "!rubocop", "..."]
"language_servers": ["solargraph", "!ruby-lsp", "!rubocop", "!sorbet", "!steep", "..."]
},
"SCSS": {
"prettier": {
@@ -1577,6 +1605,9 @@
"version": "1",
"api_url": "https://api.openai.com/v1"
},
"open_router": {
"api_url": "https://openrouter.ai/api/v1"
},
"lmstudio": {
"api_url": "http://localhost:1234/api/v0"
},

View File

@@ -1,32 +1,34 @@
// Some example tasks for common languages.
//
// For more documentation on how to configure debug tasks,
// see: https://zed.dev/docs/debugger
[
{
"label": "Debug active PHP file",
"adapter": "php",
"adapter": "PHP",
"program": "$ZED_FILE",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT"
},
{
"label": "Debug active Python file",
"adapter": "python",
"adapter": "Debugpy",
"program": "$ZED_FILE",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT"
},
{
"label": "Debug active JavaScript file",
"adapter": "javascript",
"adapter": "JavaScript",
"program": "$ZED_FILE",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT"
},
{
"label": "JavaScript debug terminal",
"adapter": "javascript",
"adapter": "JavaScript",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT",
"initialize_args": {
"console": "integratedTerminal"
}
"console": "integratedTerminal"
}
]

View File

@@ -0,0 +1,5 @@
// Project-local debug tasks
//
// For more documentation on how to configure debug tasks,
// see: https://zed.dev/docs/debugger
[]

BIN
assets/sounds/agent_done.wav Executable file

Binary file not shown.

View File

@@ -24,8 +24,9 @@ project.workspace = true
smallvec.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
[dev-dependencies]
editor = { workspace = true, features = ["test-support"] }
release_channel.workspace = true

View File

@@ -1,4 +1,4 @@
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage, VersionCheckType};
use editor::Editor;
use extension_host::ExtensionStore;
use futures::StreamExt;
@@ -60,6 +60,7 @@ struct Content {
message: String,
on_click:
Option<Arc<dyn Fn(&mut ActivityIndicator, &mut Window, &mut Context<ActivityIndicator>)>>,
tooltip_message: Option<String>,
}
impl ActivityIndicator {
@@ -262,6 +263,7 @@ impl ActivityIndicator {
});
window.dispatch_action(Box::new(workspace::OpenLog), cx);
})),
tooltip_message: None,
});
}
// Show any language server has pending activity.
@@ -305,6 +307,32 @@ impl ActivityIndicator {
),
message,
on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),
tooltip_message: None,
});
}
if let Some(session) = self
.project
.read(cx)
.dap_store()
.read(cx)
.sessions()
.find(|s| !s.read(cx).is_started())
{
return Some(Content {
icon: Some(
Icon::new(IconName::ArrowCircle)
.size(IconSize::Small)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(2)).repeat(),
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
)
.into_any_element(),
),
message: format!("Debug: {}", session.read(cx).adapter()),
tooltip_message: Some(session.read(cx).label().to_string()),
on_click: None,
});
}
@@ -332,6 +360,7 @@ impl ActivityIndicator {
),
message: job_info.message.into(),
on_click: None,
tooltip_message: None,
});
}
}
@@ -374,6 +403,7 @@ impl ActivityIndicator {
.retain(|status| !downloading.contains(&status.name));
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: None,
});
}
@@ -402,6 +432,7 @@ impl ActivityIndicator {
.retain(|status| !checking_for_update.contains(&status.name));
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: None,
});
}
@@ -428,6 +459,7 @@ impl ActivityIndicator {
on_click: Some(Arc::new(|this, window, cx| {
this.show_error_message(&Default::default(), window, cx)
})),
tooltip_message: None,
});
}
@@ -446,6 +478,7 @@ impl ActivityIndicator {
});
window.dispatch_action(Box::new(workspace::OpenLog), cx);
})),
tooltip_message: None,
});
}
@@ -462,8 +495,9 @@ impl ActivityIndicator {
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: None,
}),
AutoUpdateStatus::Downloading => Some(Content {
AutoUpdateStatus::Downloading { version } => Some(Content {
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
@@ -473,8 +507,9 @@ impl ActivityIndicator {
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: Some(Self::version_tooltip_message(&version)),
}),
AutoUpdateStatus::Installing => Some(Content {
AutoUpdateStatus::Installing { version } => Some(Content {
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
@@ -484,8 +519,12 @@ impl ActivityIndicator {
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: Some(Self::version_tooltip_message(&version)),
}),
AutoUpdateStatus::Updated { binary_path, .. } => Some(Content {
AutoUpdateStatus::Updated {
binary_path,
version,
} => Some(Content {
icon: None,
message: "Click to restart and update Zed".to_string(),
on_click: Some(Arc::new({
@@ -494,6 +533,7 @@ impl ActivityIndicator {
};
move |_, _, cx| workspace::reload(&reload, cx)
})),
tooltip_message: Some(Self::version_tooltip_message(&version)),
}),
AutoUpdateStatus::Errored => Some(Content {
icon: Some(
@@ -505,6 +545,7 @@ impl ActivityIndicator {
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: None,
}),
AutoUpdateStatus::Idle => None,
};
@@ -524,6 +565,7 @@ impl ActivityIndicator {
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: None,
});
}
}
@@ -531,6 +573,17 @@ impl ActivityIndicator {
None
}
fn version_tooltip_message(version: &VersionCheckType) -> String {
format!("Version: {}", {
match version {
auto_update::VersionCheckType::Sha(sha) => format!("{}", sha.short()),
auto_update::VersionCheckType::Semantic(semantic_version) => {
semantic_version.to_string()
}
}
})
}
fn toggle_language_server_work_context_menu(
&mut self,
window: &mut Window,
@@ -575,7 +628,14 @@ impl Render for ActivityIndicator {
)
.tooltip(Tooltip::text(content.message))
} else {
button.child(Label::new(content.message).size(LabelSize::Small))
button
.child(Label::new(content.message).size(LabelSize::Small))
.when_some(
content.tooltip_message,
|this, tooltip_message| {
this.tooltip(Tooltip::text(tooltip_message))
},
)
}
})
.when_some(content.on_click, |this, handler| {
@@ -655,3 +715,26 @@ impl StatusItemView for ActivityIndicator {
) {
}
}
#[cfg(test)]
mod tests {
use gpui::SemanticVersion;
use release_channel::AppCommitSha;
use super::*;
#[test]
fn test_version_tooltip_message() {
let message = ActivityIndicator::version_tooltip_message(&VersionCheckType::Semantic(
SemanticVersion::new(1, 0, 0),
));
assert_eq!(message, "Version: 1.0.0");
let message = ActivityIndicator::version_tooltip_message(&VersionCheckType::Sha(
AppCommitSha::new("14d9a4189f058d8736339b06ff2340101eaea5af".to_string()),
));
assert_eq!(message, "Version: 14d9a41…");
}
}

View File

@@ -19,13 +19,14 @@ test-support = [
]
[dependencies]
agent_settings.workspace = true
anyhow.workspace = true
assistant_context_editor.workspace = true
assistant_settings.workspace = true
assistant_slash_command.workspace = true
assistant_slash_commands.workspace = true
assistant_tool.workspace = true
async-watch.workspace = true
audio.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
client.workspace = true
@@ -45,6 +46,7 @@ git.workspace = true
gpui.workspace = true
heed.workspace = true
html_to_markdown.workspace = true
indoc.workspace = true
http_client.workspace = true
indexed_docs.workspace = true
inventory.workspace = true
@@ -76,8 +78,8 @@ serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
settings.workspace = true
smallvec.workspace = true
smol.workspace = true
sqlez.workspace = true
streaming_diff.workspace = true
telemetry.workspace = true
telemetry_events.workspace = true
@@ -97,6 +99,7 @@ workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true
zed_llm_client.workspace = true
zstd.workspace = true
[dev-dependencies]
buffer_diff = { workspace = true, features = ["test-support"] }

View File

@@ -1,9 +1,8 @@
use crate::AgentPanel;
use crate::context::{AgentContextHandle, RULES_ICON};
use crate::context_picker::{ContextPicker, MentionLink};
use crate::context_store::ContextStore;
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
use crate::message_editor::insert_message_creases;
use crate::message_editor::{extract_message_creases, insert_message_creases};
use crate::thread::{
LastRestoreCheckpoint, MessageCrease, MessageId, MessageSegment, Thread, ThreadError,
ThreadEvent, ThreadFeedback, ThreadSummary,
@@ -13,9 +12,11 @@ use crate::tool_use::{PendingToolUseStatus, ToolUse};
use crate::ui::{
AddedContext, AgentNotification, AgentNotificationEvent, AnimatedLabel, ContextPill,
};
use crate::{AgentPanel, ModelUsageContext};
use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
use anyhow::Context as _;
use assistant_settings::{AssistantSettings, NotifyWhenAgentWaiting};
use assistant_tool::ToolUseStatus;
use audio::{Audio, Sound};
use collections::{HashMap, HashSet};
use editor::actions::{MoveUp, Paste};
use editor::scroll::Autoscroll;
@@ -52,8 +53,9 @@ use ui::{
};
use util::ResultExt as _;
use util::markdown::MarkdownCodeBlock;
use workspace::Workspace;
use workspace::{CollaboratorId, Workspace};
use zed_actions::assistant::OpenRulesLibrary;
use zed_llm_client::CompletionIntent;
pub struct ActiveThread {
context_store: Entity<ContextStore>,
@@ -971,7 +973,22 @@ impl ActiveThread {
ThreadEvent::ShowError(error) => {
self.last_error = Some(error.clone());
}
ThreadEvent::NewRequest | ThreadEvent::CompletionCanceled => {
ThreadEvent::NewRequest => {
cx.notify();
}
ThreadEvent::CompletionCanceled => {
self.thread.update(cx, |thread, cx| {
thread.project().update(cx, |project, cx| {
project.set_agent_location(None, cx);
})
});
self.workspace
.update(cx, |workspace, cx| {
if workspace.is_being_followed(CollaboratorId::Agent) {
workspace.unfollow(CollaboratorId::Agent, window, cx);
}
})
.ok();
cx.notify();
}
ThreadEvent::StreamedCompletion
@@ -981,9 +998,10 @@ impl ActiveThread {
}
ThreadEvent::Stopped(reason) => match reason {
Ok(StopReason::EndTurn | StopReason::MaxTokens) => {
let thread = self.thread.read(cx);
let used_tools = self.thread.read(cx).used_tools_since_last_user_message();
self.play_notification_sound(window, cx);
self.show_notification(
if thread.used_tools_since_last_user_message() {
if used_tools {
"Finished running tools"
} else {
"New message"
@@ -996,8 +1014,18 @@ impl ActiveThread {
_ => {}
},
ThreadEvent::ToolConfirmationNeeded => {
self.play_notification_sound(window, cx);
self.show_notification("Waiting for tool confirmation", IconName::Info, window, cx);
}
ThreadEvent::ToolUseLimitReached => {
self.play_notification_sound(window, cx);
self.show_notification(
"Consecutive tool use limit reached.",
IconName::Warning,
window,
cx,
);
}
ThreadEvent::StreamedAssistantText(message_id, text) => {
if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) {
rendered_message.append_text(text, cx);
@@ -1018,7 +1046,6 @@ impl ActiveThread {
self.push_message(message_id, &message_segments, window, cx);
}
self.scroll_to_bottom(cx);
self.save_thread(cx);
cx.notify();
}
@@ -1133,6 +1160,13 @@ impl ActiveThread {
cx.notify();
}
fn play_notification_sound(&self, window: &Window, cx: &mut App) {
let settings = AgentSettings::get_global(cx);
if settings.play_sound_when_agent_done && !window.is_window_active() {
Audio::play_sound(Sound::AgentDone, cx);
}
}
fn show_notification(
&mut self,
caption: impl Into<SharedString>,
@@ -1146,7 +1180,7 @@ impl ActiveThread {
let title = self.thread.read(cx).summary().unwrap_or("Agent Panel");
match AssistantSettings::get_global(cx).notify_when_agent_waiting {
match AgentSettings::get_global(cx).notify_when_agent_waiting {
NotifyWhenAgentWaiting::PrimaryScreen => {
if let Some(primary) = cx.primary_display() {
self.pop_up(icon, caption.into(), title.clone(), window, primary, cx);
@@ -1314,6 +1348,7 @@ impl ActiveThread {
Some(self.text_thread_store.downgrade()),
context_picker_menu_handle.clone(),
SuggestContextKind::File,
ModelUsageContext::Thread(self.thread.clone()),
window,
cx,
)
@@ -1412,12 +1447,13 @@ impl ActiveThread {
let request = language_model::LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: None,
mode: None,
messages: vec![request_message],
tools: vec![],
tool_choice: None,
stop: vec![],
temperature: AssistantSettings::temperature_for_model(
temperature: AgentSettings::temperature_for_model(
&configured_model.model,
cx,
),
@@ -1467,7 +1503,7 @@ impl ActiveThread {
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.context_store.update(cx, |store, _cx| store.clear());
self.context_store.update(cx, |store, cx| store.clear(cx));
cx.notify();
}
@@ -1482,36 +1518,25 @@ impl ActiveThread {
}
fn paste(&mut self, _: &Paste, _window: &mut Window, cx: &mut Context<Self>) {
let images = cx
.read_from_clipboard()
.map(|item| {
item.into_entries()
.filter_map(|entry| {
if let ClipboardEntry::Image(image) = entry {
Some(image)
} else {
None
}
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
if images.is_empty() {
return;
}
cx.stop_propagation();
self.context_store.update(cx, |store, cx| {
for image in images {
store.add_image_instance(Arc::new(image), cx);
}
});
attach_pasted_images_as_context(&self.context_store, cx);
}
fn cancel_editing_message(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
fn cancel_editing_message(
&mut self,
_: &menu::Cancel,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.editing_message.take();
cx.notify();
if let Some(workspace) = self.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
panel.focus_handle(cx).focus(window);
}
});
}
}
fn confirm_editing_message(
@@ -1538,6 +1563,8 @@ impl ActiveThread {
let edited_text = state.editor.read(cx).text(cx);
let creases = state.editor.update(cx, extract_message_creases);
let new_context = self
.context_store
.read(cx)
@@ -1562,6 +1589,7 @@ impl ActiveThread {
message_id,
Role::User,
vec![MessageSegment::Text(edited_text)],
creases,
Some(context.loaded_context),
checkpoint.ok(),
cx,
@@ -1573,7 +1601,12 @@ impl ActiveThread {
this.thread.update(cx, |thread, cx| {
thread.advance_prompt_id();
thread.send_to_model(model.model, Some(window.window_handle()), cx);
thread.send_to_model(
model.model,
CompletionIntent::UserPrompt,
Some(window.window_handle()),
cx,
);
});
this._load_edited_message_context_task = None;
cx.notify();
@@ -1754,6 +1787,11 @@ impl ActiveThread {
let Some(message) = self.thread.read(cx).message(message_id) else {
return Empty.into_any();
};
if message.is_hidden {
return Empty.into_any();
}
let message_creases = message.creases.clone();
let Some(rendered_message) = self.rendered_messages_by_id.get(&message_id) else {
@@ -1765,9 +1803,10 @@ impl ActiveThread {
// Get all the data we need from thread before we start using it in closures
let checkpoint = thread.checkpoint_for_message(message_id);
let configured_model = thread.configured_model().map(|m| m.model);
let added_context = thread
.context_for_message(message_id)
.map(|context| AddedContext::new_attached(context, cx))
.map(|context| AddedContext::new_attached(context, configured_model.as_ref(), cx))
.collect::<Vec<_>>();
let tool_uses = thread.tool_uses_for_message(message_id, cx);
@@ -1789,6 +1828,7 @@ impl ActiveThread {
let colors = cx.theme().colors();
let editor_bg_color = colors.editor_background;
let panel_bg = colors.panel_background;
let open_as_markdown = IconButton::new(("open-as-markdown", ix), IconName::DocumentText)
.icon_size(IconSize::XSmall)
@@ -1809,7 +1849,6 @@ impl ActiveThread {
const RESPONSE_PADDING_X: Pixels = px(19.);
let show_feedback = thread.is_turn_end(ix);
let feedback_container = h_flex()
.group("feedback_container")
.mt_1()
@@ -1874,7 +1913,7 @@ impl ActiveThread {
.child(open_as_markdown),
)
.into_any_element(),
None if AssistantSettings::get_global(cx).enable_feedback =>
None if AgentSettings::get_global(cx).enable_feedback =>
feedback_container
.child(
div().visible_on_hover("feedback_container").child(
@@ -1982,65 +2021,89 @@ impl ActiveThread {
.border_1()
.border_color(colors.border)
.hover(|hover| hover.border_color(colors.text_accent.opacity(0.5)))
.cursor_pointer()
.child(
h_flex()
v_flex()
.p_2p5()
.gap_1()
.items_end()
.children(message_content)
.when_some(editing_message_state, |this, state| {
let focus_handle = state.editor.focus_handle(cx).clone();
this.w_full().justify_between().child(
this.child(
h_flex()
.gap_0p5()
.w_full()
.gap_1()
.justify_between()
.flex_wrap()
.child(
IconButton::new(
"cancel-edit-message",
IconName::Close,
)
.shape(ui::IconButtonShape::Square)
.icon_color(Color::Error)
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Cancel Edit",
&menu::Cancel,
&focus_handle,
window,
cx,
)
}
})
.on_click(cx.listener(Self::handle_cancel_click)),
h_flex()
.gap_1p5()
.child(
div()
.opacity(0.8)
.child(
Icon::new(IconName::Warning)
.size(IconSize::Indicator)
.color(Color::Warning)
),
)
.child(
Label::new("Editing will restart the thread from this point.")
.color(Color::Muted)
.size(LabelSize::XSmall),
),
)
.child(
IconButton::new(
"confirm-edit-message",
IconName::Return,
)
.disabled(state.editor.read(cx).is_empty(cx))
.shape(ui::IconButtonShape::Square)
.icon_color(Color::Muted)
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Regenerate",
&menu::Confirm,
&focus_handle,
window,
cx,
h_flex()
.gap_0p5()
.child(
IconButton::new(
"cancel-edit-message",
IconName::Close,
)
}
})
.on_click(
cx.listener(Self::handle_regenerate_click),
),
),
.shape(ui::IconButtonShape::Square)
.icon_color(Color::Error)
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Cancel Edit",
&menu::Cancel,
&focus_handle,
window,
cx,
)
}
})
.on_click(cx.listener(Self::handle_cancel_click)),
)
.child(
IconButton::new(
"confirm-edit-message",
IconName::Return,
)
.disabled(state.editor.read(cx).is_empty(cx))
.shape(ui::IconButtonShape::Square)
.icon_color(Color::Muted)
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Regenerate",
&menu::Confirm,
&focus_handle,
window,
cx,
)
}
})
.on_click(
cx.listener(Self::handle_regenerate_click),
),
),
)
)
}),
)
@@ -2082,16 +2145,14 @@ impl ActiveThread {
message_id > *editing_message_id
});
let panel_background = cx.theme().colors().panel_background;
let backdrop = div()
.id("backdrop")
.stop_mouse_events_except_scroll()
.id(("backdrop", ix))
.size_full()
.absolute()
.inset_0()
.size_full()
.bg(panel_background)
.bg(panel_bg)
.opacity(0.8)
.block_mouse_except_scroll()
.on_click(cx.listener(Self::handle_cancel_click));
v_flex()
@@ -3054,7 +3115,7 @@ impl ActiveThread {
.on_click(cx.listener(
move |this, event, window, cx| {
if let Some(fs) = fs.clone() {
update_settings_file::<AssistantSettings>(
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
|settings, _| {
@@ -3568,6 +3629,38 @@ pub(crate) fn open_context(
}
}
pub(crate) fn attach_pasted_images_as_context(
context_store: &Entity<ContextStore>,
cx: &mut App,
) -> bool {
let images = cx
.read_from_clipboard()
.map(|item| {
item.into_entries()
.filter_map(|entry| {
if let ClipboardEntry::Image(image) = entry {
Some(image)
} else {
None
}
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
if images.is_empty() {
return false;
}
cx.stop_propagation();
context_store.update(cx, |store, cx| {
for image in images {
store.add_image_instance(Arc::new(image), cx);
}
});
true
}
fn open_editor_at_position(
project_path: project::ProjectPath,
target_position: Point,
@@ -3593,3 +3686,248 @@ fn open_editor_at_position(
}
})
}
#[cfg(test)]
mod tests {
use assistant_tool::{ToolRegistry, ToolWorkingSet};
use editor::{EditorSettings, display_map::CreaseMetadata};
use fs::FakeFs;
use gpui::{AppContext, TestAppContext, VisualTestContext};
use language_model::{
ConfiguredModel, LanguageModel, LanguageModelRegistry,
fake_provider::{FakeLanguageModel, FakeLanguageModelProvider},
};
use project::Project;
use prompt_store::PromptBuilder;
use serde_json::json;
use settings::SettingsStore;
use util::path;
use workspace::CollaboratorId;
use crate::{ContextLoadResult, thread_store};
use super::*;
#[gpui::test]
async fn test_agent_is_unfollowed_after_cancelling_completion(cx: &mut TestAppContext) {
init_test_settings(cx);
let project = create_test_project(
cx,
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
)
.await;
let (cx, _active_thread, workspace, thread, model) =
setup_test_environment(cx, project.clone()).await;
// Insert user message without any context (empty context vector)
thread.update(cx, |thread, cx| {
thread.insert_user_message(
"What is the best way to learn Rust?",
ContextLoadResult::default(),
None,
vec![],
cx,
);
});
// Stream response to user message
thread.update(cx, |thread, cx| {
let request =
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx);
thread.stream_completion(request, model, cx.active_window(), cx)
});
// Follow the agent
cx.update(|window, cx| {
workspace.update(cx, |workspace, cx| {
workspace.follow(CollaboratorId::Agent, window, cx);
})
});
assert!(cx.read(|cx| workspace.read(cx).is_being_followed(CollaboratorId::Agent)));
// Cancel the current completion
thread.update(cx, |thread, cx| {
thread.cancel_last_completion(cx.active_window(), cx)
});
cx.executor().run_until_parked();
// No longer following the agent
assert!(!cx.read(|cx| workspace.read(cx).is_being_followed(CollaboratorId::Agent)));
}
#[gpui::test]
async fn test_reinserting_creases_for_edited_message(cx: &mut TestAppContext) {
init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let (cx, active_thread, _, thread, model) =
setup_test_environment(cx, project.clone()).await;
cx.update(|_, cx| {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.set_default_model(
Some(ConfiguredModel {
provider: Arc::new(FakeLanguageModelProvider),
model,
}),
cx,
);
});
});
let creases = vec![MessageCrease {
range: 14..22,
metadata: CreaseMetadata {
icon_path: "icon".into(),
label: "foo.txt".into(),
},
context: None,
}];
let message = thread.update(cx, |thread, cx| {
let message_id = thread.insert_user_message(
"Tell me about @foo.txt",
ContextLoadResult::default(),
None,
creases,
cx,
);
thread.message(message_id).cloned().unwrap()
});
active_thread.update_in(cx, |active_thread, window, cx| {
active_thread.start_editing_message(
message.id,
message.segments.as_slice(),
message.creases.as_slice(),
window,
cx,
);
let editor = active_thread
.editing_message
.as_ref()
.unwrap()
.1
.editor
.clone();
editor.update(cx, |editor, cx| editor.edit([(0..13, "modified")], cx));
active_thread.confirm_editing_message(&Default::default(), window, cx);
});
cx.run_until_parked();
let message = thread.update(cx, |thread, _| thread.message(message.id).cloned().unwrap());
active_thread.update_in(cx, |active_thread, window, cx| {
active_thread.start_editing_message(
message.id,
message.segments.as_slice(),
message.creases.as_slice(),
window,
cx,
);
let editor = active_thread
.editing_message
.as_ref()
.unwrap()
.1
.editor
.clone();
let text = editor.update(cx, |editor, cx| editor.text(cx));
assert_eq!(text, "modified @foo.txt");
});
}
fn init_test_settings(cx: &mut TestAppContext) {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
AgentSettings::register(cx);
prompt_store::init(cx);
thread_store::init(cx);
workspace::init_settings(cx);
language_model::init_settings(cx);
ThemeSettings::register(cx);
EditorSettings::register(cx);
ToolRegistry::default_global(cx);
});
}
// Helper to create a test project with test files
async fn create_test_project(
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
}
async fn setup_test_environment(
cx: &mut TestAppContext,
project: Entity<Project>,
) -> (
&mut VisualTestContext,
Entity<ActiveThread>,
Entity<Workspace>,
Entity<Thread>,
Arc<dyn LanguageModel>,
) {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let thread_store = cx
.update(|_, cx| {
ThreadStore::load(
project.clone(),
cx.new(|_| ToolWorkingSet::default()),
None,
Arc::new(PromptBuilder::new(None).unwrap()),
cx,
)
})
.await
.unwrap();
let text_thread_store = cx
.update(|_, cx| {
TextThreadStore::new(
project.clone(),
Arc::new(PromptBuilder::new(None).unwrap()),
Default::default(),
cx,
)
})
.await
.unwrap();
let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
let context_store =
cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
let model = FakeLanguageModel::default();
let model: Arc<dyn LanguageModel> = Arc::new(model);
let language_registry = LanguageRegistry::new(cx.executor());
let language_registry = Arc::new(language_registry);
let active_thread = cx.update(|window, cx| {
cx.new(|cx| {
ActiveThread::new(
thread.clone(),
thread_store.clone(),
text_thread_store,
context_store.clone(),
language_registry.clone(),
workspace.downgrade(),
window,
cx,
)
})
});
(cx, active_thread, workspace, thread, model)
}
}

View File

@@ -28,14 +28,16 @@ mod ui;
use std::sync::Arc;
use assistant_settings::{AgentProfileId, AssistantSettings, LanguageModelSelection};
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
use assistant_slash_command::SlashCommandRegistry;
use client::Client;
use feature_flags::FeatureFlagAppExt as _;
use fs::Fs;
use gpui::{App, actions, impl_actions};
use gpui::{App, Entity, actions, impl_actions};
use language::LanguageRegistry;
use language_model::{LanguageModelId, LanguageModelProviderId, LanguageModelRegistry};
use language_model::{
ConfiguredModel, LanguageModel, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry,
};
use prompt_store::PromptBuilder;
use schemars::JsonSchema;
use serde::Deserialize;
@@ -69,6 +71,7 @@ actions!(
AddContextServer,
RemoveSelectedThread,
Chat,
ChatWithFollow,
CycleNextInlineAssist,
CyclePreviousInlineAssist,
FocusUp,
@@ -86,6 +89,9 @@ actions!(
Follow,
ResetTrialUpsell,
ResetTrialEndUpsell,
ContinueThread,
ContinueWithBurnMode,
ToggleBurnMode,
]
);
@@ -111,20 +117,47 @@ impl ManageProfiles {
impl_actions!(agent, [NewThread, ManageProfiles]);
#[derive(Clone)]
pub(crate) enum ModelUsageContext {
Thread(Entity<Thread>),
InlineAssistant,
}
impl ModelUsageContext {
pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
match self {
Self::Thread(thread) => thread.read(cx).configured_model(),
Self::InlineAssistant => {
LanguageModelRegistry::read_global(cx).inline_assistant_model()
}
}
}
pub fn language_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> {
self.configured_model(cx)
.map(|configured_model| configured_model.model)
}
}
/// Initializes the `agent` crate.
pub fn init(
fs: Arc<dyn Fs>,
client: Arc<Client>,
prompt_builder: Arc<PromptBuilder>,
language_registry: Arc<LanguageRegistry>,
is_eval: bool,
cx: &mut App,
) {
AssistantSettings::register(cx);
AgentSettings::register(cx);
SlashCommandSettings::register(cx);
assistant_context_editor::init(client.clone(), cx);
rules_library::init(cx);
init_language_model_settings(cx);
if !is_eval {
// Initializing the language model from the user settings messes with the eval, so we only initialize them when
// we're not running inside of the eval.
init_language_model_settings(cx);
}
assistant_slash_command::init(cx);
thread_store::init(cx);
agent_panel::init(cx);
@@ -168,7 +201,7 @@ fn init_language_model_settings(cx: &mut App) {
}
fn update_active_language_model_from_settings(cx: &mut App) {
let settings = AssistantSettings::get_global(cx);
let settings = AgentSettings::get_global(cx);
fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
language_model::SelectedModel {

View File

@@ -5,7 +5,7 @@ mod tool_picker;
use std::{sync::Arc, time::Duration};
use assistant_settings::AssistantSettings;
use agent_settings::AgentSettings;
use assistant_tool::{ToolSource, ToolWorkingSet};
use collections::HashMap;
use context_server::ContextServerId;
@@ -249,7 +249,7 @@ impl AgentConfiguration {
}
fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let always_allow_tool_actions = AssistantSettings::get_global(cx).always_allow_tool_actions;
let always_allow_tool_actions = AgentSettings::get_global(cx).always_allow_tool_actions;
h_flex()
.gap_4()
@@ -277,7 +277,7 @@ impl AgentConfiguration {
let fs = self.fs.clone();
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AssistantSettings>(
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _| {
@@ -290,7 +290,7 @@ impl AgentConfiguration {
}
fn render_single_file_review(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let single_file_review = AssistantSettings::get_global(cx).single_file_review;
let single_file_review = AgentSettings::get_global(cx).single_file_review;
h_flex()
.gap_4()
@@ -315,7 +315,7 @@ impl AgentConfiguration {
let fs = self.fs.clone();
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AssistantSettings>(
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _| {
@@ -327,6 +327,44 @@ impl AgentConfiguration {
)
}
fn render_sound_notification(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let play_sound_when_agent_done = AgentSettings::get_global(cx).play_sound_when_agent_done;
h_flex()
.gap_4()
.justify_between()
.flex_wrap()
.child(
v_flex()
.gap_0p5()
.max_w_5_6()
.child(Label::new("Play sound when finished generating"))
.child(
Label::new(
"Hear a notification sound when the agent is done generating changes or needs your input.",
)
.color(Color::Muted),
),
)
.child(
Switch::new("play-sound-notification-switch", play_sound_when_agent_done.into())
.color(SwitchColor::Accent)
.on_click({
let fs = self.fs.clone();
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _| {
settings.set_play_sound_when_agent_done(allow);
},
);
}
}),
)
}
fn render_general_settings_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
@@ -337,6 +375,7 @@ impl AgentConfiguration {
.child(Headline::new("General Settings"))
.child(self.render_command_permission(cx))
.child(self.render_single_file_review(cx))
.child(self.render_sound_notification(cx))
}
fn render_context_servers_section(

View File

@@ -2,7 +2,7 @@ mod profile_modal_header;
use std::sync::Arc;
use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings, builtin_profiles};
use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profiles};
use assistant_tool::ToolWorkingSet;
use convert_case::{Case, Casing as _};
use editor::Editor;
@@ -42,7 +42,7 @@ enum Mode {
impl Mode {
pub fn choose_profile(_window: &mut Window, cx: &mut Context<ManageProfilesModal>) -> Self {
let settings = AssistantSettings::get_global(cx);
let settings = AgentSettings::get_global(cx);
let mut builtin_profiles = Vec::new();
let mut custom_profiles = Vec::new();
@@ -196,7 +196,7 @@ impl ManageProfilesModal {
window: &mut Window,
cx: &mut Context<Self>,
) {
let settings = AssistantSettings::get_global(cx);
let settings = AgentSettings::get_global(cx);
let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
return;
};
@@ -234,7 +234,7 @@ impl ManageProfilesModal {
window: &mut Window,
cx: &mut Context<Self>,
) {
let settings = AssistantSettings::get_global(cx);
let settings = AgentSettings::get_global(cx);
let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
return;
};
@@ -270,7 +270,7 @@ impl ManageProfilesModal {
match &self.mode {
Mode::ChooseProfile { .. } => {}
Mode::NewProfile(mode) => {
let settings = AssistantSettings::get_global(cx);
let settings = AgentSettings::get_global(cx);
let base_profile = mode
.base_profile_id
@@ -332,7 +332,7 @@ impl ManageProfilesModal {
profile: AgentProfile,
cx: &mut Context<Self>,
) {
update_settings_file::<AssistantSettings>(self.fs.clone(), cx, {
update_settings_file::<AgentSettings>(self.fs.clone(), cx, {
move |settings, _cx| {
settings.create_profile(profile_id, profile).log_err();
}
@@ -485,7 +485,7 @@ impl ManageProfilesModal {
_window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let settings = AssistantSettings::get_global(cx);
let settings = AgentSettings::get_global(cx);
let base_profile_name = mode.base_profile_id.as_ref().map(|base_profile_id| {
settings
@@ -518,7 +518,7 @@ impl ManageProfilesModal {
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let settings = AssistantSettings::get_global(cx);
let settings = AgentSettings::get_global(cx);
let profile_id = &settings.default_profile;
let profile_name = settings
@@ -712,7 +712,7 @@ impl ManageProfilesModal {
impl Render for ManageProfilesModal {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let settings = AssistantSettings::get_global(cx);
let settings = AgentSettings::get_global(cx);
let go_back_item = div()
.id("cancel-item")

View File

@@ -1,7 +1,7 @@
use std::{collections::BTreeMap, sync::Arc};
use assistant_settings::{
AgentProfile, AgentProfileContent, AgentProfileId, AssistantSettings, AssistantSettingsContent,
use agent_settings::{
AgentProfile, AgentProfileContent, AgentProfileId, AgentSettings, AgentSettingsContent,
ContextServerPresetContent,
};
use assistant_tool::{ToolSource, ToolWorkingSet};
@@ -259,7 +259,7 @@ impl PickerDelegate for ToolPickerDelegate {
is_enabled
};
let active_profile_id = &AssistantSettings::get_global(cx).default_profile;
let active_profile_id = &AgentSettings::get_global(cx).default_profile;
if active_profile_id == &self.profile_id {
self.thread_store
.update(cx, |this, cx| {
@@ -268,12 +268,12 @@ impl PickerDelegate for ToolPickerDelegate {
.log_err();
}
update_settings_file::<AssistantSettings>(self.fs.clone(), cx, {
update_settings_file::<AgentSettings>(self.fs.clone(), cx, {
let profile_id = self.profile_id.clone();
let default_profile = self.profile.clone();
let server_id = server_id.clone();
let tool_name = tool_name.clone();
move |settings: &mut AssistantSettingsContent, _cx| {
move |settings: &mut AgentSettingsContent, _cx| {
settings
.v2_setting(|v2_settings| {
let profiles = v2_settings.profiles.get_or_insert_default();

View File

@@ -1,6 +1,6 @@
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll, Thread, ThreadEvent};
use agent_settings::AgentSettings;
use anyhow::Result;
use assistant_settings::AssistantSettings;
use buffer_diff::DiffHunkStatus;
use collections::{HashMap, HashSet};
use editor::{
@@ -699,7 +699,7 @@ fn render_diff_hunk_controls(
.rounded_b_md()
.bg(cx.theme().colors().editor_background)
.gap_1()
.occlude()
.block_mouse_except_scroll()
.shadow_md()
.children(vec![
Button::new(("reject", row as u64), "Reject")
@@ -1086,7 +1086,7 @@ impl Render for AgentDiffToolbar {
.child(vertical_divider())
.when_some(editor.read(cx).workspace(), |this, _workspace| {
this.child(
IconButton::new("review", IconName::ListCollapse)
IconButton::new("review", IconName::ListTodo)
.icon_size(IconSize::Small)
.tooltip(Tooltip::for_action_title_in(
"Review All Files",
@@ -1116,8 +1116,13 @@ impl Render for AgentDiffToolbar {
return Empty.into_any();
};
let is_generating = agent_diff.read(cx).thread.read(cx).is_generating();
if is_generating {
let has_pending_edit_tool_use = agent_diff
.read(cx)
.thread
.read(cx)
.has_pending_edit_tool_uses();
if has_pending_edit_tool_use {
return div().px_2().child(spinner_icon).into_any();
}
@@ -1253,9 +1258,9 @@ impl AgentDiff {
let settings_subscription = cx.observe_global_in::<SettingsStore>(window, {
let workspace = workspace.clone();
let mut was_active = AssistantSettings::get_global(cx).single_file_review;
let mut was_active = AgentSettings::get_global(cx).single_file_review;
move |this, window, cx| {
let is_active = AssistantSettings::get_global(cx).single_file_review;
let is_active = AgentSettings::get_global(cx).single_file_review;
if was_active != is_active {
was_active = is_active;
this.update_reviewing_editors(&workspace, window, cx);
@@ -1348,6 +1353,7 @@ impl AgentDiff {
ThreadEvent::NewRequest
| ThreadEvent::Stopped(Ok(StopReason::EndTurn))
| ThreadEvent::Stopped(Ok(StopReason::MaxTokens))
| ThreadEvent::Stopped(Ok(StopReason::Refusal))
| ThreadEvent::Stopped(Err(_))
| ThreadEvent::ShowError(_)
| ThreadEvent::CompletionCanceled => {
@@ -1371,6 +1377,7 @@ impl AgentDiff {
| ThreadEvent::ToolFinished { .. }
| ThreadEvent::CheckpointChanged
| ThreadEvent::ToolConfirmationNeeded
| ThreadEvent::ToolUseLimitReached
| ThreadEvent::CancelEditing => {}
}
}
@@ -1460,10 +1467,13 @@ impl AgentDiff {
window: &mut Window,
cx: &mut Context<Self>,
) {
if !AssistantSettings::get_global(cx).single_file_review {
if !AgentSettings::get_global(cx).single_file_review {
for (editor, _) in self.reviewing_editors.drain() {
editor
.update(cx, |editor, cx| editor.end_temporary_diff_override(cx))
.update(cx, |editor, cx| {
editor.end_temporary_diff_override(cx);
editor.unregister_addon::<EditorAgentDiffAddon>();
})
.ok();
}
return;
@@ -1502,7 +1512,7 @@ impl AgentDiff {
multibuffer.add_diff(diff_handle.clone(), cx);
});
let new_state = if thread.read(cx).is_generating() {
let new_state = if thread.read(cx).has_pending_edit_tool_uses() {
EditorState::Generating
} else {
EditorState::Reviewing
@@ -1559,7 +1569,10 @@ impl AgentDiff {
if in_workspace {
editor
.update(cx, |editor, cx| editor.end_temporary_diff_override(cx))
.update(cx, |editor, cx| {
editor.end_temporary_diff_override(cx);
editor.unregister_addon::<EditorAgentDiffAddon>();
})
.ok();
self.reviewing_editors.remove(&editor);
}
@@ -1735,7 +1748,7 @@ impl editor::Addon for EditorAgentDiffAddon {
mod tests {
use super::*;
use crate::{Keep, ThreadStore, thread_store};
use assistant_settings::AssistantSettings;
use agent_settings::AgentSettings;
use assistant_tool::ToolWorkingSet;
use editor::EditorSettings;
use gpui::{TestAppContext, UpdateGlobal, VisualTestContext};
@@ -1754,7 +1767,7 @@ mod tests {
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
AssistantSettings::register(cx);
AgentSettings::register(cx);
prompt_store::init(cx);
thread_store::init(cx);
workspace::init_settings(cx);
@@ -1910,7 +1923,7 @@ mod tests {
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
AssistantSettings::register(cx);
AgentSettings::register(cx);
prompt_store::init(cx);
thread_store::init(cx);
workspace::init_settings(cx);

View File

@@ -1,22 +1,17 @@
use assistant_settings::AssistantSettings;
use agent_settings::AgentSettings;
use fs::Fs;
use gpui::{Entity, FocusHandle, SharedString};
use picker::popover_menu::PickerPopoverMenu;
use crate::Thread;
use crate::ModelUsageContext;
use assistant_context_editor::language_model_selector::{
LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
LanguageModelSelector, ToggleModelSelector, language_model_selector,
};
use language_model::{ConfiguredModel, LanguageModelRegistry};
use settings::update_settings_file;
use std::sync::Arc;
use ui::{PopoverMenuHandle, Tooltip, prelude::*};
#[derive(Clone)]
pub enum ModelType {
Default(Entity<Thread>),
InlineAssistant,
}
pub struct AgentModelSelector {
selector: Entity<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
@@ -28,28 +23,23 @@ impl AgentModelSelector {
fs: Arc<dyn Fs>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle,
model_type: ModelType,
model_usage_context: ModelUsageContext,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
Self {
selector: cx.new(move |cx| {
let fs = fs.clone();
LanguageModelSelector::new(
language_model_selector(
{
let model_type = model_type.clone();
move |cx| match &model_type {
ModelType::Default(thread) => thread.read(cx).configured_model(),
ModelType::InlineAssistant => {
LanguageModelRegistry::read_global(cx).inline_assistant_model()
}
}
let model_context = model_usage_context.clone();
move |cx| model_context.configured_model(cx)
},
move |model, cx| {
let provider = model.provider_id().0.to_string();
let model_id = model.id().0.to_string();
match &model_type {
ModelType::Default(thread) => {
match &model_usage_context {
ModelUsageContext::Thread(thread) => {
thread.update(cx, |thread, cx| {
let registry = LanguageModelRegistry::read_global(cx);
if let Some(provider) = registry.provider(&model.provider_id())
@@ -63,7 +53,7 @@ impl AgentModelSelector {
);
}
});
update_settings_file::<AssistantSettings>(
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _cx| {
@@ -71,8 +61,8 @@ impl AgentModelSelector {
},
);
}
ModelType::InlineAssistant => {
update_settings_file::<AssistantSettings>(
ModelUsageContext::InlineAssistant => {
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _cx| {
@@ -100,15 +90,14 @@ impl AgentModelSelector {
}
impl Render for AgentModelSelector {
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 {
let focus_handle = self.focus_handle.clone();
let model = self.selector.read(cx).active_model(cx);
let model = self.selector.read(cx).delegate.active_model(cx);
let model_name = model
.map(|model| model.model.name().0)
.unwrap_or_else(|| SharedString::from("No model selected"));
LanguageModelSelectorPopoverMenu::new(
PickerPopoverMenu::new(
self.selector.clone(),
Button::new("active-model", model_name)
.label_size(LabelSize::Small)
@@ -127,7 +116,9 @@ impl Render for AgentModelSelector {
)
},
gpui::Corner::BottomRight,
cx,
)
.with_handle(self.menu_handle.clone())
.render(window, cx)
}
}

View File

@@ -1,19 +1,19 @@
use std::ops::Range;
use std::path::Path;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Duration;
use db::kvp::{Dismissable, KEY_VALUE_STORE};
use markdown::Markdown;
use serde::{Deserialize, Serialize};
use agent_settings::{AgentDockPosition, AgentSettings, CompletionMode, DefaultView};
use anyhow::{Result, anyhow};
use assistant_context_editor::{
AgentPanelDelegate, AssistantContext, ConfigurationError, ContextEditor, ContextEvent,
ContextSummary, SlashCommandCompletionProvider, humanize_token_count,
make_lsp_adapter_delegate, render_remaining_tokens,
};
use assistant_settings::{AssistantDockPosition, AssistantSettings};
use assistant_slash_command::SlashCommandWorkingSet;
use assistant_tool::ToolWorkingSet;
@@ -41,8 +41,8 @@ use theme::ThemeSettings;
use time::UtcOffset;
use ui::utils::WithRemSize;
use ui::{
Banner, CheckboxWithLabel, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle,
ProgressBar, Tab, Tooltip, Vector, VectorName, prelude::*,
Banner, CheckboxWithLabel, ContextMenu, ElevationIndex, KeyBinding, PopoverMenu,
PopoverMenuHandle, ProgressBar, Tab, Tooltip, Vector, VectorName, prelude::*,
};
use util::{ResultExt as _, maybe};
use workspace::dock::{DockPosition, Panel, PanelEvent};
@@ -52,7 +52,7 @@ use workspace::{
use zed_actions::agent::{OpenConfiguration, OpenOnboardingModal, ResetOnboarding};
use zed_actions::assistant::{OpenRulesLibrary, ToggleFocus};
use zed_actions::{DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize};
use zed_llm_client::UsageLimit;
use zed_llm_client::{CompletionIntent, UsageLimit};
use crate::active_thread::{self, ActiveThread, ActiveThreadEvent};
use crate::agent_configuration::{AgentConfiguration, AssistantConfigurationEvent};
@@ -64,10 +64,11 @@ use crate::thread_history::{HistoryEntryElement, ThreadHistory};
use crate::thread_store::ThreadStore;
use crate::ui::AgentOnboardingModal;
use crate::{
AddContextServer, AgentDiffPane, ContextStore, DeleteRecentlyOpenThread, ExpandMessageEditor,
Follow, InlineAssistant, NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff,
OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell, TextThreadStore, ThreadEvent,
ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
AddContextServer, AgentDiffPane, ContextStore, ContinueThread, ContinueWithBurnMode,
DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
ResetTrialUpsell, TextThreadStore, ThreadEvent, ToggleBurnMode, ToggleContextPicker,
ToggleNavigationMenu, ToggleOptionsMenu,
};
const AGENT_PANEL_KEY: &str = "agent_panel";
@@ -157,7 +158,7 @@ pub fn init(cx: &mut App) {
window.refresh();
})
.register_action(|_workspace, _: &ResetTrialUpsell, _window, cx| {
TrialUpsell::set_dismissed(false, cx);
Upsell::set_dismissed(false, cx);
})
.register_action(|_workspace, _: &ResetTrialEndUpsell, _window, cx| {
TrialEndUpsell::set_dismissed(false, cx);
@@ -173,7 +174,7 @@ enum ActiveView {
thread: WeakEntity<Thread>,
_subscriptions: Vec<gpui::Subscription>,
},
PromptEditor {
TextThread {
context_editor: Entity<ContextEditor>,
title_editor: Entity<Editor>,
buffer_search_bar: Entity<BufferSearchBar>,
@@ -193,7 +194,7 @@ impl ActiveView {
pub fn which_font_size_used(&self) -> WhichFontSize {
match self {
ActiveView::Thread { .. } | ActiveView::History => WhichFontSize::AgentFont,
ActiveView::PromptEditor { .. } => WhichFontSize::BufferFont,
ActiveView::TextThread { .. } => WhichFontSize::BufferFont,
ActiveView::Configuration => WhichFontSize::None,
}
}
@@ -332,7 +333,7 @@ impl ActiveView {
buffer_search_bar.set_active_pane_item(Some(&context_editor), window, cx)
});
Self::PromptEditor {
Self::TextThread {
context_editor,
title_editor: editor,
buffer_search_bar,
@@ -370,8 +371,7 @@ pub struct AgentPanel {
height: Option<Pixels>,
zoomed: bool,
pending_serialization: Option<Task<Result<()>>>,
hide_trial_upsell: bool,
_trial_markdown: Entity<Markdown>,
hide_upsell: bool,
}
impl AgentPanel {
@@ -524,7 +524,30 @@ impl AgentPanel {
cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
let active_view = ActiveView::thread(thread.clone(), window, cx);
let panel_type = AgentSettings::get_global(cx).default_view;
let active_view = match panel_type {
DefaultView::Thread => ActiveView::thread(thread.clone(), window, cx),
DefaultView::TextThread => {
let context =
context_store.update(cx, |context_store, cx| context_store.create(cx));
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project.clone(), cx).unwrap();
let context_editor = cx.new(|cx| {
let mut editor = ContextEditor::for_context(
context,
fs.clone(),
workspace.clone(),
project.clone(),
lsp_adapter_delegate,
window,
cx,
);
editor.insert_default_prompt(window, cx);
editor
});
ActiveView::prompt_editor(context_editor, language_registry.clone(), window, cx)
}
};
let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
if let ThreadEvent::MessageAdded(_) = &event {
// needed to leave empty state
@@ -676,15 +699,6 @@ impl AgentPanel {
},
);
let trial_markdown = cx.new(|cx| {
Markdown::new(
include_str!("trial_markdown.md").into(),
Some(language_registry.clone()),
None,
cx,
)
});
Self {
active_view,
workspace,
@@ -721,8 +735,7 @@ impl AgentPanel {
height: None,
zoomed: false,
pending_serialization: None,
hide_trial_upsell: false,
_trial_markdown: trial_markdown,
hide_upsell: false,
}
}
@@ -904,8 +917,8 @@ impl AgentPanel {
open_rules_library(
self.language_registry.clone(),
Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
Arc::new(|| {
Box::new(SlashCommandCompletionProvider::new(
Rc::new(|| {
Rc::new(SlashCommandCompletionProvider::new(
Arc::new(SlashCommandWorkingSet::default()),
None,
None,
@@ -1071,9 +1084,23 @@ impl AgentPanel {
pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
match self.active_view {
ActiveView::Configuration | ActiveView::History => {
self.active_view =
ActiveView::thread(self.thread.read(cx).thread().clone(), window, cx);
self.message_editor.focus_handle(cx).focus(window);
if let Some(previous_view) = self.previous_view.take() {
self.active_view = previous_view;
match &self.active_view {
ActiveView::Thread { .. } => {
self.message_editor.focus_handle(cx).focus(window);
}
ActiveView::TextThread { context_editor, .. } => {
context_editor.focus_handle(cx).focus(window);
}
_ => {}
}
} else {
self.active_view =
ActiveView::thread(self.thread.read(cx).thread().clone(), window, cx);
self.message_editor.focus_handle(cx).focus(window);
}
cx.notify();
}
_ => {}
@@ -1238,7 +1265,7 @@ impl AgentPanel {
.map_or(true, |model| model.provider.id() != provider.id())
{
if let Some(model) = provider.default_model(cx) {
update_settings_file::<AssistantSettings>(
update_settings_file::<AgentSettings>(
self.fs.clone(),
cx,
move |settings, _| settings.set_model(model),
@@ -1271,9 +1298,52 @@ impl AgentPanel {
matches!(self.active_view, ActiveView::Thread { .. })
}
fn continue_conversation(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let thread_state = self.thread.read(cx).thread().read(cx);
if !thread_state.tool_use_limit_reached() {
return;
}
let model = thread_state.configured_model().map(|cm| cm.model.clone());
if let Some(model) = model {
self.thread.update(cx, |active_thread, cx| {
active_thread.thread().update(cx, |thread, cx| {
thread.insert_invisible_continue_message(cx);
thread.advance_prompt_id();
thread.send_to_model(
model,
CompletionIntent::UserPrompt,
Some(window.window_handle()),
cx,
);
});
});
} else {
log::warn!("No configured model available for continuation");
}
}
fn toggle_burn_mode(
&mut self,
_: &ToggleBurnMode,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.thread.update(cx, |active_thread, cx| {
active_thread.thread().update(cx, |thread, _cx| {
let current_mode = thread.completion_mode();
thread.set_completion_mode(match current_mode {
CompletionMode::Burn => CompletionMode::Normal,
CompletionMode::Normal => CompletionMode::Burn,
});
});
});
}
pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
match &self.active_view {
ActiveView::PromptEditor { context_editor, .. } => Some(context_editor.clone()),
ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
_ => None,
}
}
@@ -1296,6 +1366,12 @@ impl AgentPanel {
let current_is_history = matches!(self.active_view, ActiveView::History);
let new_is_history = matches!(new_view, ActiveView::History);
let current_is_config = matches!(self.active_view, ActiveView::Configuration);
let new_is_config = matches!(new_view, ActiveView::Configuration);
let current_is_special = current_is_history || current_is_config;
let new_is_special = new_is_history || new_is_config;
match &self.active_view {
ActiveView::Thread { thread, .. } => {
if let Some(thread) = thread.upgrade() {
@@ -1307,7 +1383,7 @@ impl AgentPanel {
}
}
}
ActiveView::PromptEditor { context_editor, .. } => {
ActiveView::TextThread { context_editor, .. } => {
let context = context_editor.read(cx).context();
// When switching away from an unsaved text thread, delete its entry.
if context.read(cx).path().is_none() {
@@ -1327,7 +1403,7 @@ impl AgentPanel {
store.push_recently_opened_entry(RecentEntry::Thread(id, thread), cx);
}
}),
ActiveView::PromptEditor { context_editor, .. } => {
ActiveView::TextThread { context_editor, .. } => {
self.history_store.update(cx, |store, cx| {
let context = context_editor.read(cx).context().clone();
store.push_recently_opened_entry(RecentEntry::Context(context), cx)
@@ -1336,12 +1412,12 @@ impl AgentPanel {
_ => {}
}
if current_is_history && !new_is_history {
if current_is_special && !new_is_special {
self.active_view = new_view;
} else if !current_is_history && new_is_history {
} else if !current_is_special && new_is_special {
self.previous_view = Some(std::mem::replace(&mut self.active_view, new_view));
} else {
if !new_is_history {
if !new_is_special {
self.previous_view = None;
}
self.active_view = new_view;
@@ -1356,7 +1432,7 @@ impl Focusable for AgentPanel {
match &self.active_view {
ActiveView::Thread { .. } => self.message_editor.focus_handle(cx),
ActiveView::History => self.history.focus_handle(cx),
ActiveView::PromptEditor { context_editor, .. } => context_editor.focus_handle(cx),
ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
ActiveView::Configuration => {
if let Some(configuration) = self.configuration.as_ref() {
configuration.focus_handle(cx)
@@ -1369,10 +1445,10 @@ impl Focusable for AgentPanel {
}
fn agent_panel_dock_position(cx: &App) -> DockPosition {
match AssistantSettings::get_global(cx).dock {
AssistantDockPosition::Left => DockPosition::Left,
AssistantDockPosition::Bottom => DockPosition::Bottom,
AssistantDockPosition::Right => DockPosition::Right,
match AgentSettings::get_global(cx).dock {
AgentDockPosition::Left => DockPosition::Left,
AgentDockPosition::Bottom => DockPosition::Bottom,
AgentDockPosition::Right => DockPosition::Right,
}
}
@@ -1392,22 +1468,18 @@ impl Panel for AgentPanel {
}
fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
settings::update_settings_file::<AssistantSettings>(
self.fs.clone(),
cx,
move |settings, _| {
let dock = match position {
DockPosition::Left => AssistantDockPosition::Left,
DockPosition::Bottom => AssistantDockPosition::Bottom,
DockPosition::Right => AssistantDockPosition::Right,
};
settings.set_dock(dock);
},
);
settings::update_settings_file::<AgentSettings>(self.fs.clone(), cx, move |settings, _| {
let dock = match position {
DockPosition::Left => AgentDockPosition::Left,
DockPosition::Bottom => AgentDockPosition::Bottom,
DockPosition::Right => AgentDockPosition::Right,
};
settings.set_dock(dock);
});
}
fn size(&self, window: &Window, cx: &App) -> Pixels {
let settings = AssistantSettings::get_global(cx);
let settings = AgentSettings::get_global(cx);
match self.position(window, cx) {
DockPosition::Left | DockPosition::Right => {
self.width.unwrap_or(settings.default_width)
@@ -1432,8 +1504,7 @@ impl Panel for AgentPanel {
}
fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
(self.enabled(cx) && AssistantSettings::get_global(cx).button)
.then_some(IconName::ZedAssistant)
(self.enabled(cx) && AgentSettings::get_global(cx).button).then_some(IconName::ZedAssistant)
}
fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
@@ -1449,7 +1520,7 @@ impl Panel for AgentPanel {
}
fn enabled(&self, cx: &App) -> bool {
AssistantSettings::get_global(cx).enabled
AgentSettings::get_global(cx).enabled
}
fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
@@ -1513,7 +1584,7 @@ impl AgentPanel {
.into_any_element(),
}
}
ActiveView::PromptEditor {
ActiveView::TextThread {
title_editor,
context_editor,
..
@@ -1605,7 +1676,7 @@ impl AgentPanel {
let show_token_count = match &self.active_view {
ActiveView::Thread { .. } => !is_empty || !editor_empty,
ActiveView::PromptEditor { .. } => true,
ActiveView::TextThread { .. } => true,
_ => false,
};
@@ -1921,7 +1992,7 @@ impl AgentPanel {
Some(token_count)
}
ActiveView::PromptEditor { context_editor, .. } => {
ActiveView::TextThread { context_editor, .. } => {
let element = render_remaining_tokens(context_editor, cx)?;
Some(element.into_any_element())
@@ -1946,7 +2017,7 @@ impl AgentPanel {
return false;
}
if self.hide_trial_upsell || TrialUpsell::dismissed() {
if self.hide_upsell || Upsell::dismissed() {
return false;
}
@@ -1976,7 +2047,7 @@ impl AgentPanel {
true
}
fn render_trial_upsell(
fn render_upsell(
&self,
_window: &mut Window,
cx: &mut Context<Self>,
@@ -1985,6 +2056,14 @@ impl AgentPanel {
return None;
}
if self.user_store.read(cx).account_too_young() {
Some(self.render_young_account_upsell(cx).into_any_element())
} else {
Some(self.render_trial_upsell(cx).into_any_element())
}
}
fn render_young_account_upsell(&self, cx: &mut Context<Self>) -> impl IntoElement {
let checkbox = CheckboxWithLabel::new(
"dont-show-again",
Label::new("Don't show again").color(Color::Muted),
@@ -1992,7 +2071,70 @@ impl AgentPanel {
move |toggle_state, _window, cx| {
let toggle_state_bool = toggle_state.selected();
TrialUpsell::set_dismissed(toggle_state_bool, cx);
Upsell::set_dismissed(toggle_state_bool, cx);
},
);
let contents = div()
.size_full()
.gap_2()
.flex()
.flex_col()
.child(Headline::new("Build better with Zed Pro").size(HeadlineSize::Small))
.child(
Label::new("Your GitHub account was created less than 30 days ago, so we can't offer you a free trial.")
.size(LabelSize::Small),
)
.child(
Label::new(
"Use your own API keys, upgrade to Zed Pro or send an email to billing-support@zed.dev.",
)
.color(Color::Muted),
)
.child(
h_flex()
.w_full()
.px_neg_1()
.justify_between()
.items_center()
.child(h_flex().items_center().gap_1().child(checkbox))
.child(
h_flex()
.gap_2()
.child(
Button::new("dismiss-button", "Not Now")
.style(ButtonStyle::Transparent)
.color(Color::Muted)
.on_click({
let agent_panel = cx.entity();
move |_, _, cx| {
agent_panel.update(cx, |this, cx| {
this.hide_upsell = true;
cx.notify();
});
}
}),
)
.child(
Button::new("cta-button", "Upgrade to Zed Pro")
.style(ButtonStyle::Transparent)
.on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))),
),
),
);
self.render_upsell_container(cx, contents)
}
fn render_trial_upsell(&self, cx: &mut Context<Self>) -> impl IntoElement {
let checkbox = CheckboxWithLabel::new(
"dont-show-again",
Label::new("Don't show again").color(Color::Muted),
ToggleState::Unselected,
move |toggle_state, _window, cx| {
let toggle_state_bool = toggle_state.selected();
Upsell::set_dismissed(toggle_state_bool, cx);
},
);
@@ -2030,7 +2172,7 @@ impl AgentPanel {
let agent_panel = cx.entity();
move |_, _, cx| {
agent_panel.update(cx, |this, cx| {
this.hide_trial_upsell = true;
this.hide_upsell = true;
cx.notify();
});
}
@@ -2044,7 +2186,7 @@ impl AgentPanel {
),
);
Some(self.render_upsell_container(cx, contents))
self.render_upsell_container(cx, contents)
}
fn render_trial_end_upsell(
@@ -2496,7 +2638,11 @@ impl AgentPanel {
})
}
fn render_tool_use_limit_reached(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
fn render_tool_use_limit_reached(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<AnyElement> {
let tool_use_limit_reached = self
.thread
.read(cx)
@@ -2515,17 +2661,59 @@ impl AgentPanel {
.configured_model()?
.model;
let max_mode_upsell = if model.supports_max_mode() {
" Enable max mode for unlimited tool use."
} else {
""
};
let focus_handle = self.focus_handle(cx);
let banner = Banner::new()
.severity(ui::Severity::Info)
.child(h_flex().child(Label::new(format!(
"Consecutive tool use limit reached.{max_mode_upsell}"
))));
.child(Label::new("Consecutive tool use limit reached.").size(LabelSize::Small))
.action_slot(
h_flex()
.gap_1()
.child(
Button::new("continue-conversation", "Continue")
.layer(ElevationIndex::ModalSurface)
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(
&ContinueThread,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(|this, _, window, cx| {
this.continue_conversation(window, cx);
})),
)
.when(model.supports_max_mode(), |this| {
this.child(
Button::new("continue-burn-mode", "Continue with Burn Mode")
.style(ButtonStyle::Filled)
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
.layer(ElevationIndex::ModalSurface)
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(
&ContinueWithBurnMode,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
)
.tooltip(Tooltip::text("Enable Burn Mode for unlimited tool use."))
.on_click(cx.listener(|this, _, window, cx| {
this.thread.update(cx, |active_thread, cx| {
active_thread.thread().update(cx, |thread, _cx| {
thread.set_completion_mode(CompletionMode::Burn);
});
});
this.continue_conversation(window, cx);
})),
)
}),
);
Some(div().px_2().pb_2().child(banner).into_any_element())
}
@@ -2722,7 +2910,7 @@ impl AgentPanel {
) -> Div {
let mut registrar = buffer_search::DivRegistrar::new(
|this, _, _cx| match &this.active_view {
ActiveView::PromptEditor {
ActiveView::TextThread {
buffer_search_bar, ..
} => Some(buffer_search_bar.clone()),
_ => None,
@@ -2840,7 +3028,7 @@ impl AgentPanel {
.detach();
});
}
ActiveView::PromptEditor { context_editor, .. } => {
ActiveView::TextThread { context_editor, .. } => {
context_editor.update(cx, |context_editor, cx| {
ContextEditor::insert_dragged_files(
context_editor,
@@ -2867,7 +3055,7 @@ impl AgentPanel {
fn key_context(&self) -> KeyContext {
let mut key_context = KeyContext::new_with_defaults();
key_context.add("AgentPanel");
if matches!(self.active_view, ActiveView::PromptEditor { .. }) {
if matches!(self.active_view, ActiveView::TextThread { .. }) {
key_context.add("prompt_editor");
}
key_context
@@ -2880,9 +3068,9 @@ impl Render for AgentPanel {
// non-obvious implications to the layout of children.
//
// If you need to change it, please confirm:
// - The message editor expands (esc) correctly
// - The message editor expands (cmd-option-esc) correctly
// - When expanded, the buttons at the bottom of the panel are displayed correctly
// - Font size works as expected and can be changed with ⌘+/⌘-
// - Font size works as expected and can be changed with cmd-+/cmd-
// - Scrolling in all views works as expected
// - Files can be dropped into the panel
let content = v_flex()
@@ -2909,19 +3097,31 @@ impl Render for AgentPanel {
.on_action(cx.listener(Self::decrease_font_size))
.on_action(cx.listener(Self::reset_font_size))
.on_action(cx.listener(Self::toggle_zoom))
.on_action(cx.listener(|this, _: &ContinueThread, window, cx| {
this.continue_conversation(window, cx);
}))
.on_action(cx.listener(|this, _: &ContinueWithBurnMode, window, cx| {
this.thread.update(cx, |active_thread, cx| {
active_thread.thread().update(cx, |thread, _cx| {
thread.set_completion_mode(CompletionMode::Burn);
});
});
this.continue_conversation(window, cx);
}))
.on_action(cx.listener(Self::toggle_burn_mode))
.child(self.render_toolbar(window, cx))
.children(self.render_trial_upsell(window, cx))
.children(self.render_upsell(window, cx))
.children(self.render_trial_end_upsell(window, cx))
.map(|parent| match &self.active_view {
ActiveView::Thread { .. } => parent
.relative()
.child(self.render_active_thread_or_empty_state(window, cx))
.children(self.render_tool_use_limit_reached(cx))
.children(self.render_tool_use_limit_reached(window, cx))
.child(h_flex().child(self.message_editor.clone()))
.children(self.render_last_error(cx))
.child(self.render_drag_target(cx)),
ActiveView::History => parent.child(self.history.clone()),
ActiveView::PromptEditor {
ActiveView::TextThread {
context_editor,
buffer_search_bar,
..
@@ -3099,9 +3299,9 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
}
}
struct TrialUpsell;
struct Upsell;
impl Dismissable for TrialUpsell {
impl Dismissable for Upsell {
const KEY: &'static str = "dismissed-trial-upsell";
}

View File

@@ -1,8 +1,8 @@
use crate::context::ContextLoadResult;
use crate::inline_prompt_editor::CodegenStatus;
use crate::{context::load_context, context_store::ContextStore};
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
use assistant_settings::AssistantSettings;
use client::telemetry::Telemetry;
use collections::HashSet;
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
@@ -34,6 +34,7 @@ use std::{
};
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
use zed_llm_client::CompletionIntent;
pub struct BufferCodegen {
alternatives: Vec<Entity<CodegenAlternative>>,
@@ -443,7 +444,7 @@ impl CodegenAlternative {
}
});
let temperature = AssistantSettings::temperature_for_model(&model, cx);
let temperature = AgentSettings::temperature_for_model(&model, cx);
Ok(cx.spawn(async move |_cx| {
let mut request_message = LanguageModelRequestMessage {
@@ -464,6 +465,7 @@ impl CodegenAlternative {
LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: Some(CompletionIntent::InlineAssist),
mode: None,
tools: Vec::new(),
tool_choice: None,

View File

@@ -734,6 +734,7 @@ impl Display for RulesContext {
#[derive(Debug, Clone)]
pub struct ImageContext {
pub project_path: Option<ProjectPath>,
pub full_path: Option<Arc<Path>>,
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`.
@@ -744,6 +745,7 @@ pub struct ImageContext {
pub enum ImageStatus {
Loading,
Error,
Warning,
Ready,
}
@@ -760,11 +762,17 @@ impl ImageContext {
self.image_task.clone().now_or_never().flatten()
}
pub fn status(&self) -> ImageStatus {
pub fn status(&self, model: Option<&Arc<dyn language_model::LanguageModel>>) -> ImageStatus {
match self.image_task.clone().now_or_never() {
None => ImageStatus::Loading,
Some(None) => ImageStatus::Error,
Some(Some(_)) => ImageStatus::Ready,
Some(Some(_)) => {
if model.is_some_and(|model| !model.supports_images()) {
ImageStatus::Warning
} else {
ImageStatus::Ready
}
}
}
}

View File

@@ -766,6 +766,7 @@ pub(crate) fn insert_crease_for_mention(
let ids = editor.insert_creases(vec![crease.clone()], cx);
editor.fold_creases(vec![crease], false, window, cx);
Some(ids[0])
})
}

View File

@@ -14,7 +14,7 @@ use http_client::HttpClientWithUrl;
use itertools::Itertools;
use language::{Buffer, CodeLabel, HighlightId};
use lsp::CompletionContext;
use project::{Completion, CompletionIntent, ProjectPath, Symbol, WorktreeId};
use project::{Completion, CompletionIntent, CompletionResponse, ProjectPath, Symbol, WorktreeId};
use prompt_store::PromptStore;
use rope::Point;
use text::{Anchor, OffsetRangeExt, ToPoint};
@@ -322,7 +322,10 @@ impl ContextPickerCompletionProvider {
})
.collect::<Vec<_>>();
let new_text = selection_infos.iter().map(|(_, link, _)| link).join(" ");
let new_text = format!(
"{} ",
selection_infos.iter().map(|(_, link, _)| link).join(" ")
);
let callback = Arc::new({
let context_store = context_store.clone();
@@ -420,7 +423,7 @@ impl ContextPickerCompletionProvider {
} else {
IconName::MessageBubbles
};
let new_text = MentionLink::for_thread(&thread_entry);
let new_text = format!("{} ", MentionLink::for_thread(&thread_entry));
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
@@ -435,7 +438,7 @@ impl ContextPickerCompletionProvider {
thread_entry.title().clone(),
excerpt_id,
source_range.start,
new_text_len,
new_text_len - 1,
editor.clone(),
context_store.clone(),
move |window, cx| match &thread_entry {
@@ -489,7 +492,7 @@ impl ContextPickerCompletionProvider {
editor: Entity<Editor>,
context_store: Entity<ContextStore>,
) -> Completion {
let new_text = MentionLink::for_rule(&rules);
let new_text = format!("{} ", MentionLink::for_rule(&rules));
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
@@ -504,7 +507,7 @@ impl ContextPickerCompletionProvider {
rules.title.clone(),
excerpt_id,
source_range.start,
new_text_len,
new_text_len - 1,
editor.clone(),
context_store.clone(),
move |_, cx| {
@@ -526,7 +529,7 @@ impl ContextPickerCompletionProvider {
context_store: Entity<ContextStore>,
http_client: Arc<HttpClientWithUrl>,
) -> Completion {
let new_text = MentionLink::for_fetch(&url_to_fetch);
let new_text = format!("{} ", MentionLink::for_fetch(&url_to_fetch));
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
@@ -541,7 +544,7 @@ impl ContextPickerCompletionProvider {
url_to_fetch.clone(),
excerpt_id,
source_range.start,
new_text_len,
new_text_len - 1,
editor.clone(),
context_store.clone(),
move |_, cx| {
@@ -550,7 +553,7 @@ impl ContextPickerCompletionProvider {
let url_to_fetch = url_to_fetch.clone();
cx.spawn(async move |cx| {
if let Some(context) = context_store
.update(cx, |context_store, _| {
.read_with(cx, |context_store, _| {
context_store.get_url_context(url_to_fetch.clone())
})
.ok()?
@@ -611,7 +614,7 @@ impl ContextPickerCompletionProvider {
crease_icon_path.clone()
};
let new_text = MentionLink::for_file(&file_name, &full_path);
let new_text = format!("{} ", MentionLink::for_file(&file_name, &full_path));
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
@@ -626,7 +629,7 @@ impl ContextPickerCompletionProvider {
file_name,
excerpt_id,
source_range.start,
new_text_len,
new_text_len - 1,
editor,
context_store.clone(),
move |_, cx| {
@@ -682,7 +685,7 @@ impl ContextPickerCompletionProvider {
label.push_str(" ", None);
label.push_str(&file_name, comment_id);
let new_text = MentionLink::for_symbol(&symbol.name, &full_path);
let new_text = format!("{} ", MentionLink::for_symbol(&symbol.name, &full_path));
let new_text_len = new_text.len();
Some(Completion {
replace_range: source_range.clone(),
@@ -697,7 +700,7 @@ impl ContextPickerCompletionProvider {
symbol.name.clone().into(),
excerpt_id,
source_range.start,
new_text_len,
new_text_len - 1,
editor.clone(),
context_store.clone(),
move |_, cx| {
@@ -743,7 +746,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
_trigger: CompletionContext,
_window: &mut Window,
cx: &mut Context<Editor>,
) -> Task<Result<Option<Vec<Completion>>>> {
) -> Task<Result<Vec<CompletionResponse>>> {
let state = buffer.update(cx, |buffer, _cx| {
let position = buffer_position.to_point(buffer);
let line_start = Point::new(position.row, 0);
@@ -753,13 +756,13 @@ impl CompletionProvider for ContextPickerCompletionProvider {
MentionCompletion::try_parse(line, offset_to_line)
});
let Some(state) = state else {
return Task::ready(Ok(None));
return Task::ready(Ok(Vec::new()));
};
let Some((workspace, context_store)) =
self.workspace.upgrade().zip(self.context_store.upgrade())
else {
return Task::ready(Ok(None));
return Task::ready(Ok(Vec::new()));
};
let snapshot = buffer.read(cx).snapshot();
@@ -812,10 +815,10 @@ impl CompletionProvider for ContextPickerCompletionProvider {
cx.spawn(async move |_, cx| {
let matches = search_task.await;
let Some(editor) = editor.upgrade() else {
return Ok(None);
return Ok(Vec::new());
};
Ok(Some(cx.update(|cx| {
let completions = cx.update(|cx| {
matches
.into_iter()
.filter_map(|mat| match mat {
@@ -898,7 +901,14 @@ impl CompletionProvider for ContextPickerCompletionProvider {
),
})
.collect()
})?))
})?;
Ok(vec![CompletionResponse {
completions,
// Since this does its own filtering (see `filter_completions()` returns false),
// there is no benefit to computing whether this set of completions is incomplete.
is_incomplete: true,
}])
})
}
@@ -916,8 +926,9 @@ impl CompletionProvider for ContextPickerCompletionProvider {
&self,
buffer: &Entity<language::Buffer>,
position: language::Anchor,
_: &str,
_: bool,
_text: &str,
_trigger_in_words: bool,
_menu_is_open: bool,
cx: &mut Context<Editor>,
) -> bool {
let buffer = buffer.read(cx);
@@ -1213,7 +1224,7 @@ mod tests {
assert_eq!(worktrees.len(), 1);
worktrees.pop().unwrap()
});
let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
@@ -1286,7 +1297,7 @@ mod tests {
.map(Entity::downgrade)
});
window.focus(&editor.focus_handle(cx));
editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
workspace.downgrade(),
context_store.downgrade(),
None,
@@ -1353,7 +1364,7 @@ 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), "Lorem [@one.txt](@file:dir/a/one.txt) ");
assert!(!editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
@@ -1364,7 +1375,7 @@ 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), "Lorem [@one.txt](@file:dir/a/one.txt) ");
assert!(!editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
@@ -1377,7 +1388,7 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum ",
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum ",
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
@@ -1391,7 +1402,7 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum @file ",
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum @file ",
);
assert!(editor.has_visible_completions_menu());
assert_eq!(
@@ -1409,14 +1420,14 @@ 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)"
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) "
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
vec![
Point::new(0, 6)..Point::new(0, 37),
Point::new(0, 44)..Point::new(0, 79)
Point::new(0, 45)..Point::new(0, 80)
]
);
});
@@ -1426,14 +1437,14 @@ 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@"
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n@"
);
assert!(editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
vec![
Point::new(0, 6)..Point::new(0, 37),
Point::new(0, 44)..Point::new(0, 79)
Point::new(0, 45)..Point::new(0, 80)
]
);
});
@@ -1447,14 +1458,14 @@ 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)"
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n[@six.txt](@file:dir/b/six.txt) "
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
vec![
Point::new(0, 6)..Point::new(0, 37),
Point::new(0, 44)..Point::new(0, 79),
Point::new(0, 45)..Point::new(0, 80),
Point::new(1, 0)..Point::new(1, 31)
]
);

View File

@@ -51,6 +51,10 @@ impl Tool for ContextServerTool {
true
}
fn may_perform_edits(&self) -> bool {
true
}
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
let mut schema = self.tool.input_schema.clone();
assistant_tool::adapt_schema_to_format(&mut schema, format)?;

View File

@@ -7,7 +7,7 @@ use assistant_context_editor::AssistantContext;
use collections::{HashSet, IndexSet};
use futures::{self, FutureExt};
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
use language::Buffer;
use language::{Buffer, File as _};
use language_model::LanguageModelImage;
use project::image_store::is_image_file;
use project::{Project, ProjectItem, ProjectPath, Symbol};
@@ -58,9 +58,10 @@ impl ContextStore {
self.context_set.iter().map(|entry| entry.as_ref())
}
pub fn clear(&mut self) {
pub fn clear(&mut self, cx: &mut Context<Self>) {
self.context_set.clear();
self.context_thread_ids.clear();
cx.notify();
}
pub fn new_context_for_thread(
@@ -303,11 +304,13 @@ impl ContextStore {
project.open_image(project_path.clone(), cx)
})?;
let image_item = open_image_task.await?;
let image = image_item.read_with(cx, |image_item, _| image_item.image.clone())?;
this.update(cx, |this, cx| {
let item = image_item.read(cx);
this.insert_image(
Some(image_item.read(cx).project_path(cx)),
image,
Some(item.project_path(cx)),
Some(item.file.full_path(cx).into()),
item.image.clone(),
remove_if_exists,
cx,
)
@@ -316,12 +319,13 @@ impl ContextStore {
}
pub fn add_image_instance(&mut self, image: Arc<Image>, cx: &mut Context<ContextStore>) {
self.insert_image(None, image, false, cx);
self.insert_image(None, None, image, false, cx);
}
fn insert_image(
&mut self,
project_path: Option<ProjectPath>,
full_path: Option<Arc<Path>>,
image: Arc<Image>,
remove_if_exists: bool,
cx: &mut Context<ContextStore>,
@@ -329,6 +333,7 @@ impl ContextStore {
let image_task = LanguageModelImage::from_image(image.clone(), cx).shared();
let context = AgentContextHandle::Image(ImageContext {
project_path,
full_path,
original_image: image,
image_task,
context_id: self.next_context_id.post_inc(),

View File

@@ -23,7 +23,7 @@ use crate::thread_store::{TextThreadStore, ThreadStore};
use crate::ui::{AddedContext, ContextPill};
use crate::{
AcceptSuggestedContext, AgentPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
ModelUsageContext, RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
};
pub struct ContextStrip {
@@ -37,6 +37,7 @@ pub struct ContextStrip {
_subscriptions: Vec<Subscription>,
focused_index: Option<usize>,
children_bounds: Option<Vec<Bounds<Pixels>>>,
model_usage_context: ModelUsageContext,
}
impl ContextStrip {
@@ -47,6 +48,7 @@ impl ContextStrip {
text_thread_store: Option<WeakEntity<TextThreadStore>>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
suggest_context_kind: SuggestContextKind,
model_usage_context: ModelUsageContext,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
@@ -81,6 +83,7 @@ impl ContextStrip {
_subscriptions: subscriptions,
focused_index: None,
children_bounds: None,
model_usage_context,
}
}
@@ -98,11 +101,20 @@ impl ContextStrip {
.as_ref()
.and_then(|thread_store| thread_store.upgrade())
.and_then(|thread_store| thread_store.read(cx).prompt_store().as_ref());
let current_model = self.model_usage_context.language_model(cx);
self.context_store
.read(cx)
.context()
.flat_map(|context| {
AddedContext::new_pending(context.clone(), prompt_store, project, cx)
AddedContext::new_pending(
context.clone(),
prompt_store,
project,
current_model.as_ref(),
cx,
)
})
.collect::<Vec<_>>()
} else {

View File

@@ -152,7 +152,7 @@ impl HistoryStore {
let entries = join_all(entries)
.await
.into_iter()
.filter_map(|result| result.log_err())
.filter_map(|result| result.log_with_level(log::Level::Debug))
.collect::<VecDeque<_>>();
this.update(cx, |this, _| {

View File

@@ -4,8 +4,8 @@ use std::ops::Range;
use std::rc::Rc;
use std::sync::Arc;
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
use assistant_settings::AssistantSettings;
use client::telemetry::Telemetry;
use collections::{HashMap, HashSet, VecDeque, hash_map};
use editor::display_map::EditorMargins;
@@ -134,7 +134,7 @@ impl InlineAssistant {
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
return;
};
let enabled = AssistantSettings::get_global(cx).enabled;
let enabled = AgentSettings::get_global(cx).enabled;
terminal_panel.update(cx, |terminal_panel, cx| {
terminal_panel.set_assistant_enabled(enabled, cx)
});
@@ -219,7 +219,7 @@ impl InlineAssistant {
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let settings = AssistantSettings::get_global(cx);
let settings = AgentSettings::get_global(cx);
if !settings.enabled {
return;
}
@@ -1445,7 +1445,7 @@ impl InlineAssistant {
style: BlockStyle::Flex,
render: Arc::new(move |cx| {
div()
.block_mouse_down()
.block_mouse_except_scroll()
.bg(cx.theme().status().deleted_background)
.size_full()
.h(height as f32 * cx.window.line_height())
@@ -1771,7 +1771,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
_: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<CodeAction>>> {
if !AssistantSettings::get_global(cx).enabled {
if !AgentSettings::get_global(cx).enabled {
return Task::ready(Ok(Vec::new()));
}

View File

@@ -1,4 +1,4 @@
use crate::agent_model_selector::{AgentModelSelector, ModelType};
use crate::agent_model_selector::AgentModelSelector;
use crate::buffer_codegen::BufferCodegen;
use crate::context::ContextCreasesAddon;
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider};
@@ -7,12 +7,13 @@ use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
use crate::message_editor::{extract_message_creases, insert_message_creases};
use crate::terminal_codegen::TerminalCodegen;
use crate::thread_store::{TextThreadStore, ThreadStore};
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist, ModelUsageContext};
use crate::{RemoveAllContext, ToggleContextPicker};
use assistant_context_editor::language_model_selector::ToggleModelSelector;
use client::ErrorExt;
use collections::VecDeque;
use db::kvp::Dismissable;
use editor::actions::Paste;
use editor::display_map::EditorMargins;
use editor::{
ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
@@ -28,6 +29,7 @@ use language_model::{LanguageModel, LanguageModelRegistry};
use parking_lot::Mutex;
use settings::Settings;
use std::cmp;
use std::rc::Rc;
use std::sync::Arc;
use theme::ThemeSettings;
use ui::utils::WithRemSize;
@@ -98,8 +100,9 @@ impl<T: 'static> Render for PromptEditor<T> {
v_flex()
.key_context("PromptEditor")
.capture_action(cx.listener(Self::paste))
.bg(cx.theme().colors().editor_background)
.block_mouse_down()
.block_mouse_except_scroll()
.gap_0p5()
.border_y_1()
.border_color(cx.theme().status().info_border)
@@ -302,6 +305,10 @@ impl<T: 'static> PromptEditor<T> {
self.editor.read(cx).text(cx)
}
fn paste(&mut self, _: &Paste, _window: &mut Window, cx: &mut Context<Self>) {
crate::active_thread::attach_pasted_images_as_context(&self.context_store, cx);
}
fn toggle_rate_limit_notice(
&mut self,
_: &ClickEvent,
@@ -326,9 +333,7 @@ impl<T: 'static> PromptEditor<T> {
EditorEvent::Edited { .. } => {
if let Some(workspace) = window.root::<Workspace>().flatten() {
workspace.update(cx, |workspace, cx| {
let is_via_ssh = workspace
.project()
.update(cx, |project, _| project.is_via_ssh());
let is_via_ssh = workspace.project().read(cx).is_via_ssh();
workspace
.client()
@@ -373,7 +378,7 @@ impl<T: 'static> PromptEditor<T> {
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.context_store.update(cx, |store, _cx| store.clear());
self.context_store.update(cx, |store, cx| store.clear(cx));
cx.notify();
}
@@ -892,7 +897,7 @@ impl PromptEditor<BufferCodegen> {
let prompt_editor_entity = prompt_editor.downgrade();
prompt_editor.update(cx, |editor, _| {
editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
workspace.clone(),
context_store.downgrade(),
thread_store.clone(),
@@ -913,6 +918,7 @@ impl PromptEditor<BufferCodegen> {
text_thread_store.clone(),
context_picker_menu_handle.clone(),
SuggestContextKind::Thread,
ModelUsageContext::InlineAssistant,
window,
cx,
)
@@ -931,7 +937,7 @@ impl PromptEditor<BufferCodegen> {
fs,
model_selector_menu_handle,
prompt_editor.focus_handle(cx),
ModelType::InlineAssistant,
ModelUsageContext::InlineAssistant,
window,
cx,
)
@@ -1063,7 +1069,7 @@ impl PromptEditor<TerminalCodegen> {
let prompt_editor_entity = prompt_editor.downgrade();
prompt_editor.update(cx, |editor, _| {
editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
workspace.clone(),
context_store.downgrade(),
thread_store.clone(),
@@ -1084,6 +1090,7 @@ impl PromptEditor<TerminalCodegen> {
text_thread_store.clone(),
context_picker_menu_handle.clone(),
SuggestContextKind::Thread,
ModelUsageContext::InlineAssistant,
window,
cx,
)
@@ -1102,7 +1109,7 @@ impl PromptEditor<TerminalCodegen> {
fs,
model_selector_menu_handle.clone(),
prompt_editor.focus_handle(cx),
ModelType::InlineAssistant,
ModelUsageContext::InlineAssistant,
window,
cx,
)

View File

@@ -1,15 +1,16 @@
use std::collections::BTreeMap;
use std::rc::Rc;
use std::sync::Arc;
use crate::agent_model_selector::{AgentModelSelector, ModelType};
use crate::agent_model_selector::AgentModelSelector;
use crate::context::{AgentContextKey, ContextCreasesAddon, ContextLoadResult, load_context};
use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip};
use crate::ui::{
AnimatedLabel, MaxModeTooltip,
MaxModeTooltip,
preview::{AgentPreview, UsageCallout},
};
use agent_settings::{AgentSettings, CompletionMode};
use assistant_context_editor::language_model_selector::ToggleModelSelector;
use assistant_settings::{AssistantSettings, CompletionMode};
use buffer_diff::BufferDiff;
use client::UserStore;
use collections::{HashMap, HashSet};
@@ -23,10 +24,10 @@ use fs::Fs;
use futures::future::Shared;
use futures::{FutureExt as _, future};
use gpui::{
Animation, AnimationExt, App, ClipboardEntry, Entity, EventEmitter, Focusable, Subscription,
Task, TextStyle, WeakEntity, linear_color_stop, linear_gradient, point, pulsating_between,
Animation, AnimationExt, App, Entity, EventEmitter, Focusable, Subscription, Task, TextStyle,
WeakEntity, linear_color_stop, linear_gradient, point, pulsating_between,
};
use language::{Buffer, Language};
use language::{Buffer, Language, Point};
use language_model::{
ConfiguredModel, LanguageModelRequestMessage, MessageContent, RequestUsage,
ZED_CLOUD_PROVIDER_ID,
@@ -41,6 +42,7 @@ use theme::ThemeSettings;
use ui::{Disclosure, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
use util::{ResultExt as _, maybe};
use workspace::{CollaboratorId, Workspace};
use zed_llm_client::CompletionIntent;
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
use crate::context_store::ContextStore;
@@ -49,8 +51,9 @@ use crate::profile_selector::ProfileSelector;
use crate::thread::{MessageCrease, Thread, TokenUsageRatio};
use crate::thread_store::{TextThreadStore, ThreadStore};
use crate::{
ActiveThread, AgentDiffPane, Chat, ExpandMessageEditor, Follow, NewThread, OpenAgentDiff,
RemoveAllContext, ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
ActiveThread, AgentDiffPane, Chat, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll,
ModelUsageContext, NewThread, OpenAgentDiff, RejectAll, RemoveAllContext, ToggleBurnMode,
ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
};
#[derive(RegisterComponent)]
@@ -109,6 +112,7 @@ pub(crate) fn create_editor(
editor.set_placeholder_text("Message the agent @ to include context", cx);
editor.set_show_indent_guides(false, cx);
editor.set_soft_wrap();
editor.set_use_modal_editing(true);
editor.set_context_menu_options(ContextMenuOptions {
min_entries_visible: 12,
max_entries_visible: 12,
@@ -120,7 +124,7 @@ pub(crate) fn create_editor(
let editor_entity = editor.downgrade();
editor.update(cx, |editor, _| {
editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
workspace,
context_store,
Some(thread_store),
@@ -165,6 +169,7 @@ impl MessageEditor {
Some(text_thread_store.clone()),
context_picker_menu_handle.clone(),
SuggestContextKind::File,
ModelUsageContext::Thread(thread.clone()),
window,
cx,
)
@@ -193,7 +198,7 @@ impl MessageEditor {
fs.clone(),
model_selector_menu_handle,
editor.focus_handle(cx),
ModelType::Default(thread.clone()),
ModelUsageContext::Thread(thread.clone()),
window,
cx,
)
@@ -278,7 +283,7 @@ impl MessageEditor {
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.context_store.update(cx, |store, _cx| store.clear());
self.context_store.update(cx, |store, cx| store.clear(cx));
cx.notify();
}
@@ -302,6 +307,21 @@ impl MessageEditor {
cx.notify();
}
fn chat_with_follow(
&mut self,
_: &ChatWithFollow,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.workspace
.update(cx, |this, cx| {
this.follow(CollaboratorId::Agent, window, cx)
})
.log_err();
self.chat(&Chat, window, cx);
}
fn is_editor_empty(&self, cx: &App) -> bool {
self.editor.read(cx).text(cx).trim().is_empty()
}
@@ -358,7 +378,12 @@ impl MessageEditor {
thread
.update(cx, |thread, cx| {
thread.advance_prompt_id();
thread.send_to_model(model, Some(window_handle), cx);
thread.send_to_model(
model,
CompletionIntent::UserPrompt,
Some(window_handle),
cx,
);
})
.log_err();
})
@@ -407,39 +432,24 @@ impl MessageEditor {
}
fn paste(&mut self, _: &Paste, _: &mut Window, cx: &mut Context<Self>) {
let images = cx
.read_from_clipboard()
.map(|item| {
item.into_entries()
.filter_map(|entry| {
if let ClipboardEntry::Image(image) = entry {
Some(image)
} else {
None
}
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
if images.is_empty() {
return;
}
cx.stop_propagation();
self.context_store.update(cx, |store, cx| {
for image in images {
store.add_image_instance(Arc::new(image), cx);
}
});
crate::active_thread::attach_pasted_images_as_context(&self.context_store, cx);
}
fn handle_review_click(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if self.thread.read(cx).has_pending_edit_tool_uses() {
return;
}
self.edits_expanded = true;
AgentDiffPane::deploy(self.thread.clone(), self.workspace.clone(), window, cx).log_err();
cx.notify();
}
fn handle_edit_bar_expand(&mut self, cx: &mut Context<Self>) {
self.edits_expanded = !self.edits_expanded;
cx.notify();
}
fn handle_file_click(
&self,
buffer: Entity<Buffer>,
@@ -454,6 +464,56 @@ impl MessageEditor {
}
}
pub fn toggle_burn_mode(
&mut self,
_: &ToggleBurnMode,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.thread.update(cx, |thread, _cx| {
let active_completion_mode = thread.completion_mode();
thread.set_completion_mode(match active_completion_mode {
CompletionMode::Burn => CompletionMode::Normal,
CompletionMode::Normal => CompletionMode::Burn,
});
});
}
fn handle_accept_all(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
if self.thread.read(cx).has_pending_edit_tool_uses() {
return;
}
self.thread.update(cx, |thread, cx| {
thread.keep_all_edits(cx);
});
cx.notify();
}
fn handle_reject_all(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
if self.thread.read(cx).has_pending_edit_tool_uses() {
return;
}
// Since there's no reject_all_edits method in the thread API,
// we need to iterate through all buffers and reject their edits
let action_log = self.thread.read(cx).action_log().clone();
let changed_buffers = action_log.read(cx).changed_buffers(cx);
for (buffer, _) in changed_buffers {
self.thread.update(cx, |thread, cx| {
let buffer_snapshot = buffer.read(cx);
let start = buffer_snapshot.anchor_before(Point::new(0, 0));
let end = buffer_snapshot.anchor_after(buffer_snapshot.max_point());
thread
.reject_edits_in_ranges(buffer, vec![start..end], cx)
.detach();
});
}
cx.notify();
}
fn render_max_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let thread = self.thread.read(cx);
let model = thread.configured_model();
@@ -462,27 +522,24 @@ impl MessageEditor {
}
let active_completion_mode = thread.completion_mode();
let max_mode_enabled = active_completion_mode == CompletionMode::Max;
let burn_mode_enabled = active_completion_mode == CompletionMode::Burn;
let icon = if burn_mode_enabled {
IconName::ZedBurnModeOn
} else {
IconName::ZedBurnMode
};
Some(
Button::new("max-mode", "Max Mode")
.label_size(LabelSize::Small)
.color(Color::Muted)
.icon(IconName::ZedMaxMode)
IconButton::new("burn-mode", icon)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.toggle_state(max_mode_enabled)
.on_click(cx.listener(move |this, _event, _window, cx| {
this.thread.update(cx, |thread, _cx| {
thread.set_completion_mode(match active_completion_mode {
CompletionMode::Max => CompletionMode::Normal,
CompletionMode::Normal => CompletionMode::Max,
});
});
.toggle_state(burn_mode_enabled)
.selected_icon_color(Color::Error)
.on_click(cx.listener(|this, _event, window, cx| {
this.toggle_burn_mode(&ToggleBurnMode, window, cx);
}))
.tooltip(move |_window, cx| {
cx.new(|_| MaxModeTooltip::new().selected(max_mode_enabled))
cx.new(|_| MaxModeTooltip::new().selected(burn_mode_enabled))
.into()
})
.into_any_element(),
@@ -562,6 +619,7 @@ impl MessageEditor {
v_flex()
.key_context("MessageEditor")
.on_action(cx.listener(Self::chat))
.on_action(cx.listener(Self::chat_with_follow))
.on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| {
this.profile_selector
.read(cx)
@@ -576,6 +634,13 @@ impl MessageEditor {
.on_action(cx.listener(Self::remove_all_context))
.on_action(cx.listener(Self::move_up))
.on_action(cx.listener(Self::expand_message_editor))
.on_action(cx.listener(Self::toggle_burn_mode))
.on_action(
cx.listener(|this, _: &KeepAll, window, cx| this.handle_accept_all(window, cx)),
)
.on_action(
cx.listener(|this, _: &RejectAll, window, cx| this.handle_reject_all(window, cx)),
)
.capture_action(cx.listener(Self::paste))
.gap_2()
.p_2()
@@ -668,7 +733,6 @@ impl MessageEditor {
.justify_between()
.child(
h_flex()
.gap_1()
.child(self.render_follow_toggle(cx))
.children(self.render_max_mode_toggle(cx)),
)
@@ -832,7 +896,10 @@ impl MessageEditor {
let bg_edit_files_disclosure = editor_bg_color.blend(active_color.opacity(0.3));
let is_edit_changes_expanded = self.edits_expanded;
let is_generating = self.thread.read(cx).is_generating();
let thread = self.thread.read(cx);
let pending_edits = thread.has_pending_edit_tool_uses();
const EDIT_NOT_READY_TOOLTIP_LABEL: &str = "Wait until file edits are complete.";
v_flex()
.mt_1()
@@ -842,7 +909,7 @@ impl MessageEditor {
.border_b_0()
.border_color(border_color)
.rounded_t_md()
.shadow(smallvec::smallvec![gpui::BoxShadow {
.shadow(vec![gpui::BoxShadow {
color: gpui::black().opacity(0.15),
offset: point(px(1.), px(-1.)),
blur_radius: px(3.),
@@ -850,31 +917,28 @@ impl MessageEditor {
}])
.child(
h_flex()
.id("edits-container")
.cursor_pointer()
.p_1p5()
.p_1()
.justify_between()
.when(is_edit_changes_expanded, |this| {
this.border_b_1().border_color(border_color)
})
.on_click(
cx.listener(|this, _, window, cx| this.handle_review_click(window, cx)),
)
.child(
h_flex()
.id("edits-container")
.cursor_pointer()
.w_full()
.gap_1()
.child(
Disclosure::new("edits-disclosure", is_edit_changes_expanded)
.on_click(cx.listener(|this, _ev, _window, cx| {
this.edits_expanded = !this.edits_expanded;
cx.notify();
.on_click(cx.listener(|this, _, _, cx| {
this.handle_edit_bar_expand(cx)
})),
)
.map(|this| {
if is_generating {
if pending_edits {
this.child(
AnimatedLabel::new(format!(
"Editing {} {}",
Label::new(format!(
"Editing {} {}",
changed_buffers.len(),
if changed_buffers.len() == 1 {
"file"
@@ -882,7 +946,15 @@ impl MessageEditor {
"files"
}
))
.size(LabelSize::Small),
.color(Color::Muted)
.size(LabelSize::Small)
.with_animation(
"edit-label",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(pulsating_between(0.3, 0.7)),
|label, delta| label.alpha(delta),
),
)
} else {
this.child(
@@ -907,23 +979,74 @@ impl MessageEditor {
.color(Color::Muted),
)
}
}),
})
.on_click(
cx.listener(|this, _, _, cx| this.handle_edit_bar_expand(cx)),
),
)
.child(
Button::new("review", "Review Changes")
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(
&OpenAgentDiff,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
h_flex()
.gap_1()
.child(
IconButton::new("review-changes", IconName::ListTodo)
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Review Changes",
&OpenAgentDiff,
&focus_handle,
window,
cx,
)
}
})
.on_click(cx.listener(|this, _, window, cx| {
this.handle_review_click(window, cx)
})),
)
.on_click(cx.listener(|this, _, window, cx| {
this.handle_review_click(window, cx)
})),
.child(ui::Divider::vertical().color(ui::DividerColor::Border))
.child(
Button::new("reject-all-changes", "Reject All")
.label_size(LabelSize::Small)
.disabled(pending_edits)
.when(pending_edits, |this| {
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
})
.key_binding(
KeyBinding::for_action_in(
&RejectAll,
&focus_handle.clone(),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(|this, _, window, cx| {
this.handle_reject_all(window, cx)
})),
)
.child(
Button::new("accept-all-changes", "Accept All")
.label_size(LabelSize::Small)
.disabled(pending_edits)
.when(pending_edits, |this| {
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
})
.key_binding(
KeyBinding::for_action_in(
&KeepAll,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(|this, _, window, cx| {
this.handle_accept_all(window, cx)
})),
),
),
)
.when(is_edit_changes_expanded, |parent| {
@@ -1167,9 +1290,10 @@ impl MessageEditor {
fn reload_context(&mut self, cx: &mut Context<Self>) -> Task<Option<ContextLoadResult>> {
let load_task = cx.spawn(async move |this, cx| {
let Ok(load_task) = this.update(cx, |this, cx| {
let new_context = this.context_store.read_with(cx, |context_store, cx| {
context_store.new_context_for_thread(this.thread.read(cx), None)
});
let new_context = this
.context_store
.read(cx)
.new_context_for_thread(this.thread.read(cx), None);
load_context(new_context, &this.project, &this.prompt_store, cx)
}) else {
return;
@@ -1248,12 +1372,13 @@ impl MessageEditor {
let request = language_model::LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: None,
mode: None,
messages: vec![request_message],
tools: vec![],
tool_choice: None,
stop: vec![],
temperature: AssistantSettings::temperature_for_model(&model.model, cx),
temperature: AgentSettings::temperature_for_model(&model.model, cx),
};
Some(model.model.count_tokens(request, cx))

View File

@@ -1,11 +1,11 @@
use std::sync::Arc;
use assistant_settings::{
AgentProfile, AgentProfileId, AssistantDockPosition, AssistantSettings, GroupedAgentProfiles,
use agent_settings::{
AgentDockPosition, AgentProfile, AgentProfileId, AgentSettings, GroupedAgentProfiles,
builtin_profiles,
};
use fs::Fs;
use gpui::{Action, Entity, FocusHandle, Subscription, WeakEntity, prelude::*};
use gpui::{Action, Empty, Entity, FocusHandle, Subscription, WeakEntity, prelude::*};
use language_model::LanguageModelRegistry;
use settings::{Settings as _, SettingsStore, update_settings_file};
use ui::{
@@ -39,7 +39,7 @@ impl ProfileSelector {
});
Self {
profiles: GroupedAgentProfiles::from_settings(AssistantSettings::get_global(cx)),
profiles: GroupedAgentProfiles::from_settings(AgentSettings::get_global(cx)),
fs,
thread,
thread_store,
@@ -54,7 +54,7 @@ impl ProfileSelector {
}
fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
self.profiles = GroupedAgentProfiles::from_settings(AssistantSettings::get_global(cx));
self.profiles = GroupedAgentProfiles::from_settings(AgentSettings::get_global(cx));
}
fn build_context_menu(
@@ -63,7 +63,7 @@ impl ProfileSelector {
cx: &mut Context<Self>,
) -> Entity<ContextMenu> {
ContextMenu::build(window, cx, |mut menu, _window, cx| {
let settings = AssistantSettings::get_global(cx);
let settings = AgentSettings::get_global(cx);
for (profile_id, profile) in self.profiles.builtin.iter() {
menu = menu.item(self.menu_entry_for_profile(
profile_id.clone(),
@@ -100,7 +100,7 @@ impl ProfileSelector {
&self,
profile_id: AgentProfileId,
profile: &AgentProfile,
settings: &AssistantSettings,
settings: &AgentSettings,
_cx: &App,
) -> ContextMenuEntry {
let documentation = match profile.name.to_lowercase().as_str() {
@@ -126,7 +126,7 @@ impl ProfileSelector {
let thread_store = self.thread_store.clone();
let profile_id = profile_id.clone();
move |_window, cx| {
update_settings_file::<AssistantSettings>(fs.clone(), cx, {
update_settings_file::<AgentSettings>(fs.clone(), cx, {
let profile_id = profile_id.clone();
move |settings, _cx| {
settings.set_profile(profile_id.clone());
@@ -145,7 +145,7 @@ impl ProfileSelector {
impl Render for ProfileSelector {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let settings = AssistantSettings::get_global(cx);
let settings = AgentSettings::get_global(cx);
let profile_id = &settings.default_profile;
let profile = settings.profiles.get(profile_id);
@@ -153,17 +153,15 @@ impl Render for ProfileSelector {
.map(|profile| profile.name.clone())
.unwrap_or_else(|| "Unknown".into());
let configured_model = self
.thread
.read_with(cx, |thread, _cx| thread.configured_model())
.or_else(|| {
let model_registry = LanguageModelRegistry::read_global(cx);
model_registry.default_model()
});
let supports_tools =
configured_model.map_or(false, |default| default.model.supports_tools());
let configured_model = self.thread.read(cx).configured_model().or_else(|| {
let model_registry = LanguageModelRegistry::read_global(cx);
model_registry.default_model()
});
let Some(configured_model) = configured_model else {
return Empty.into_any_element();
};
if supports_tools {
if configured_model.model.supports_tools() {
let this = cx.entity().clone();
let focus_handle = self.focus_handle.clone();
let trigger_button = Button::new("profile-selector-model", selected_profile)
@@ -210,10 +208,10 @@ impl Render for ProfileSelector {
}
}
fn documentation_side(position: AssistantDockPosition) -> DocumentationSide {
fn documentation_side(position: AgentDockPosition) -> DocumentationSide {
match position {
AssistantDockPosition::Left => DocumentationSide::Right,
AssistantDockPosition::Bottom => DocumentationSide::Left,
AssistantDockPosition::Right => DocumentationSide::Left,
AgentDockPosition::Left => DocumentationSide::Right,
AgentDockPosition::Bottom => DocumentationSide::Left,
AgentDockPosition::Right => DocumentationSide::Left,
}
}

View File

@@ -0,0 +1 @@
These files changed since last read:

View File

@@ -0,0 +1,6 @@
Generate a detailed summary of this conversation. Include:
1. A brief overview of what was discussed
2. Key facts or information discovered
3. Outcomes or conclusions reached
4. Any action items or next steps if any
Format it in Markdown with headings and bullet points.

View File

@@ -0,0 +1,4 @@
Generate a concise 3-7 word title for this conversation, omitting punctuation.
Go straight to the title, without any preamble and prefix like `Here's a concise suggestion:...` or `Title:`.
If the conversation is about a specific subject, include it in the title.
Be descriptive. DO NOT speak in the first person.

View File

@@ -179,21 +179,21 @@ impl TerminalTransaction {
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
let input = Self::sanitize_input(hunk);
self.terminal
.update(cx, |terminal, _| terminal.input(input));
.update(cx, |terminal, _| terminal.input(input.into_bytes()));
}
pub fn undo(&self, cx: &mut App) {
self.terminal
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.as_bytes()));
}
pub fn complete(&self, cx: &mut App) {
self.terminal.update(cx, |terminal, _| {
terminal.input(CARRIAGE_RETURN.to_string())
});
self.terminal
.update(cx, |terminal, _| terminal.input(CARRIAGE_RETURN.as_bytes()));
}
fn sanitize_input(input: String) -> String {
input.replace(['\r', '\n'], "")
fn sanitize_input(mut input: String) -> String {
input.retain(|c| c != '\r' && c != '\n');
input
}
}

View File

@@ -5,8 +5,8 @@ use crate::inline_prompt_editor::{
};
use crate::terminal_codegen::{CLEAR_INPUT, CodegenEvent, TerminalCodegen};
use crate::thread_store::{TextThreadStore, ThreadStore};
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
use assistant_settings::AssistantSettings;
use client::telemetry::Telemetry;
use collections::{HashMap, VecDeque};
use editor::{MultiBuffer, actions::SelectAll};
@@ -25,6 +25,7 @@ use terminal_view::TerminalView;
use ui::prelude::*;
use util::ResultExt;
use workspace::{Toast, Workspace, notifications::NotificationId};
use zed_llm_client::CompletionIntent;
pub fn init(
fs: Arc<dyn Fs>,
@@ -105,7 +106,7 @@ impl TerminalInlineAssistant {
});
let prompt_editor_render = prompt_editor.clone();
let block = terminal_view::BlockProperties {
height: 2,
height: 4,
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
};
terminal_view.update(cx, |terminal_view, cx| {
@@ -201,7 +202,7 @@ impl TerminalInlineAssistant {
.update(cx, |terminal, cx| {
terminal
.terminal()
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.as_bytes()));
})
.log_err();
@@ -271,7 +272,7 @@ impl TerminalInlineAssistant {
.inline_assistant_model()
.context("No inline assistant model")?;
let temperature = AssistantSettings::temperature_for_model(&model, cx);
let temperature = AgentSettings::temperature_for_model(&model, cx);
Ok(cx.background_spawn(async move {
let mut request_message = LanguageModelRequestMessage {
@@ -291,6 +292,7 @@ impl TerminalInlineAssistant {
thread_id: None,
prompt_id: None,
mode: None,
intent: Some(CompletionIntent::TerminalInlineAssist),
messages: vec![request_message],
tools: Vec::new(),
tool_choice: None,

View File

@@ -4,8 +4,8 @@ use std::ops::Range;
use std::sync::Arc;
use std::time::Instant;
use agent_settings::{AgentSettings, CompletionMode};
use anyhow::{Result, anyhow};
use assistant_settings::{AssistantSettings, CompletionMode};
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet};
use chrono::{DateTime, Utc};
use collections::HashMap;
@@ -24,7 +24,7 @@ use language_model::{
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult,
LanguageModelToolResultContent, LanguageModelToolUseId, MessageContent,
ModelRequestLimitReachedError, PaymentRequiredError, RequestUsage, Role, SelectedModel,
StopReason, TokenUsage, WrappedTextContent,
StopReason, TokenUsage,
};
use postage::stream::Stream as _;
use project::Project;
@@ -38,7 +38,7 @@ use thiserror::Error;
use ui::Window;
use util::{ResultExt as _, post_inc};
use uuid::Uuid;
use zed_llm_client::CompletionRequestStatus;
use zed_llm_client::{CompletionIntent, CompletionRequestStatus};
use crate::ThreadStore;
use crate::context::{AgentContext, AgentContextHandle, ContextLoadResult, LoadedContext};
@@ -115,6 +115,7 @@ pub struct Message {
pub segments: Vec<MessageSegment>,
pub loaded_context: LoadedContext,
pub creases: Vec<MessageCrease>,
pub is_hidden: bool,
}
impl Message {
@@ -329,7 +330,7 @@ pub struct Thread {
detailed_summary_task: Task<Option<()>>,
detailed_summary_tx: postage::watch::Sender<DetailedSummaryState>,
detailed_summary_rx: postage::watch::Receiver<DetailedSummaryState>,
completion_mode: assistant_settings::CompletionMode,
completion_mode: agent_settings::CompletionMode,
messages: Vec<Message>,
next_message_id: MessageId,
last_prompt_id: PromptId,
@@ -415,7 +416,7 @@ impl Thread {
detailed_summary_task: Task::ready(None),
detailed_summary_tx,
detailed_summary_rx,
completion_mode: AssistantSettings::get_global(cx).preferred_completion_mode,
completion_mode: AgentSettings::get_global(cx).preferred_completion_mode,
messages: Vec::new(),
next_message_id: MessageId(0),
last_prompt_id: PromptId::new(),
@@ -493,7 +494,7 @@ impl Thread {
let completion_mode = serialized
.completion_mode
.unwrap_or_else(|| AssistantSettings::get_global(cx).preferred_completion_mode);
.unwrap_or_else(|| AgentSettings::get_global(cx).preferred_completion_mode);
Self {
id,
@@ -540,6 +541,7 @@ impl Thread {
context: None,
})
.collect(),
is_hidden: message.is_hidden,
})
.collect(),
next_message_id,
@@ -560,7 +562,7 @@ impl Thread {
cumulative_token_usage: serialized.cumulative_token_usage,
exceeded_window_error: None,
last_usage: None,
tool_use_limit_reached: false,
tool_use_limit_reached: serialized.tool_use_limit_reached,
feedback: None,
message_feedback: HashMap::default(),
last_auto_capture_at: None,
@@ -757,6 +759,14 @@ impl Thread {
return;
};
self.finalize_checkpoint(pending_checkpoint, cx);
}
fn finalize_checkpoint(
&mut self,
pending_checkpoint: ThreadCheckpoint,
cx: &mut Context<Self>,
) {
let git_store = self.project.read(cx).git_store().clone();
let final_checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
cx.spawn(async move |this, cx| match final_checkpoint.await {
@@ -841,7 +851,7 @@ impl Thread {
.get(ix + 1)
.and_then(|message| {
self.message(message.id)
.map(|next_message| next_message.role == Role::User)
.map(|next_message| next_message.role == Role::User && !next_message.is_hidden)
})
.unwrap_or(false)
}
@@ -861,7 +871,16 @@ impl Thread {
self.tool_use
.pending_tool_uses()
.iter()
.all(|tool_use| tool_use.status.is_error())
.all(|pending_tool_use| pending_tool_use.status.is_error())
}
/// Returns whether any pending tool uses may perform edits
pub fn has_pending_edit_tool_uses(&self) -> bool {
self.tool_use
.pending_tool_uses()
.iter()
.filter(|pending_tool_use| !pending_tool_use.status.is_error())
.any(|pending_tool_use| pending_tool_use.may_perform_edits)
}
pub fn tool_uses_for_message(&self, id: MessageId, cx: &App) -> Vec<ToolUse> {
@@ -881,10 +900,7 @@ impl Thread {
pub fn output_for_tool(&self, id: &LanguageModelToolUseId) -> Option<&Arc<str>> {
match &self.tool_use.tool_result(id)?.content {
LanguageModelToolResultContent::Text(text)
| LanguageModelToolResultContent::WrappedText(WrappedTextContent { text, .. }) => {
Some(text)
}
LanguageModelToolResultContent::Text(text) => Some(text),
LanguageModelToolResultContent::Image(_) => {
// TODO: We should display image
None
@@ -943,6 +959,7 @@ impl Thread {
vec![MessageSegment::Text(text.into())],
loaded_context.loaded_context,
creases,
false,
cx,
);
@@ -958,6 +975,20 @@ impl Thread {
message_id
}
pub fn insert_invisible_continue_message(&mut self, cx: &mut Context<Self>) -> MessageId {
let id = self.insert_message(
Role::User,
vec![MessageSegment::Text("Continue where you left off".into())],
LoadedContext::default(),
vec![],
true,
cx,
);
self.pending_checkpoint = None;
id
}
pub fn insert_assistant_message(
&mut self,
segments: Vec<MessageSegment>,
@@ -968,6 +999,7 @@ impl Thread {
segments,
LoadedContext::default(),
Vec::new(),
false,
cx,
)
}
@@ -978,6 +1010,7 @@ impl Thread {
segments: Vec<MessageSegment>,
loaded_context: LoadedContext,
creases: Vec<MessageCrease>,
is_hidden: bool,
cx: &mut Context<Self>,
) -> MessageId {
let id = self.next_message_id.post_inc();
@@ -987,6 +1020,7 @@ impl Thread {
segments,
loaded_context,
creases,
is_hidden,
});
self.touch_updated_at();
cx.emit(ThreadEvent::MessageAdded(id));
@@ -998,6 +1032,7 @@ impl Thread {
id: MessageId,
new_role: Role,
new_segments: Vec<MessageSegment>,
creases: Vec<MessageCrease>,
loaded_context: Option<LoadedContext>,
checkpoint: Option<GitStoreCheckpoint>,
cx: &mut Context<Self>,
@@ -1007,6 +1042,7 @@ impl Thread {
};
message.role = new_role;
message.segments = new_segments;
message.creases = creases;
if let Some(context) = loaded_context {
message.loaded_context = context;
}
@@ -1127,6 +1163,7 @@ impl Thread {
label: crease.metadata.label.clone(),
})
.collect(),
is_hidden: message.is_hidden,
})
.collect(),
initial_project_snapshot,
@@ -1142,6 +1179,7 @@ impl Thread {
model: model.model.id().0.to_string(),
}),
completion_mode: Some(this.completion_mode),
tool_use_limit_reached: this.tool_use_limit_reached,
})
})
}
@@ -1157,6 +1195,7 @@ impl Thread {
pub fn send_to_model(
&mut self,
model: Arc<dyn LanguageModel>,
intent: CompletionIntent,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
@@ -1166,7 +1205,7 @@ impl Thread {
self.remaining_turns -= 1;
let request = self.to_completion_request(model.clone(), cx);
let request = self.to_completion_request(model.clone(), intent, cx);
self.stream_completion(request, model, window, cx);
}
@@ -1186,17 +1225,19 @@ impl Thread {
pub fn to_completion_request(
&self,
model: Arc<dyn LanguageModel>,
intent: CompletionIntent,
cx: &mut Context<Self>,
) -> LanguageModelRequest {
let mut request = LanguageModelRequest {
thread_id: Some(self.id.to_string()),
prompt_id: Some(self.last_prompt_id.to_string()),
intent: Some(intent),
mode: None,
messages: vec![],
tools: Vec::new(),
tool_choice: None,
stop: Vec::new(),
temperature: AssistantSettings::temperature_for_model(&model, cx),
temperature: AgentSettings::temperature_for_model(&model, cx),
};
let available_tools = self.available_tools(cx, model.clone());
@@ -1344,18 +1385,20 @@ impl Thread {
fn to_summarize_request(
&self,
model: &Arc<dyn LanguageModel>,
intent: CompletionIntent,
added_user_message: String,
cx: &App,
) -> LanguageModelRequest {
let mut request = LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: Some(intent),
mode: None,
messages: vec![],
tools: Vec::new(),
tool_choice: None,
stop: Vec::new(),
temperature: AssistantSettings::temperature_for_model(model, cx),
temperature: AgentSettings::temperature_for_model(model, cx),
};
for message in &self.messages {
@@ -1396,7 +1439,7 @@ impl Thread {
messages: &mut Vec<LanguageModelRequestMessage>,
cx: &App,
) {
const STALE_FILES_HEADER: &str = "These files changed since last read:";
const STALE_FILES_HEADER: &str = include_str!("./prompts/stale_files_prompt_header.txt");
let mut stale_message = String::new();
@@ -1408,7 +1451,7 @@ impl Thread {
};
if stale_message.is_empty() {
write!(&mut stale_message, "{}\n", STALE_FILES_HEADER).ok();
write!(&mut stale_message, "{}\n", STALE_FILES_HEADER.trim()).ok();
}
writeln!(&mut stale_message, "- {}", file.path().display()).ok();
@@ -1641,6 +1684,7 @@ impl Thread {
}
CompletionRequestStatus::ToolUseLimitReached => {
thread.tool_use_limit_reached = true;
cx.emit(ThreadEvent::ToolUseLimitReached);
}
}
}
@@ -1693,6 +1737,43 @@ impl Thread {
project.set_agent_location(None, cx);
});
}
StopReason::Refusal => {
thread.project.update(cx, |project, cx| {
project.set_agent_location(None, cx);
});
// Remove the turn that was refused.
//
// https://docs.anthropic.com/en/docs/test-and-evaluate/strengthen-guardrails/handle-streaming-refusals#reset-context-after-refusal
{
let mut messages_to_remove = Vec::new();
for (ix, message) in thread.messages.iter().enumerate().rev() {
messages_to_remove.push(message.id);
if message.role == Role::User {
if ix == 0 {
break;
}
if let Some(prev_message) = thread.messages.get(ix - 1) {
if prev_message.role == Role::Assistant {
break;
}
}
}
}
for message_id in messages_to_remove {
thread.delete_message(message_id, cx);
}
}
cx.emit(ThreadEvent::ShowError(ThreadError::Message {
header: "Language model refusal".into(),
message: "Model refused to generate content for safety reasons.".into(),
}));
}
},
Err(error) => {
thread.project.update(cx, |project, cx| {
@@ -1736,6 +1817,7 @@ impl Thread {
thread.cancel_last_completion(window, cx);
}
}
cx.emit(ThreadEvent::Stopped(result.map_err(Arc::new)));
if let Some((request_callback, (request, response_events))) = thread
@@ -1784,12 +1866,14 @@ impl Thread {
return;
}
let added_user_message = "Generate a concise 3-7 word title for this conversation, omitting punctuation. \
Go straight to the title, without any preamble and prefix like `Here's a concise suggestion:...` or `Title:`. \
If the conversation is about a specific subject, include it in the title. \
Be descriptive. DO NOT speak in the first person.";
let added_user_message = include_str!("./prompts/summarize_thread_prompt.txt");
let request = self.to_summarize_request(&model.model, added_user_message.into(), cx);
let request = self.to_summarize_request(
&model.model,
CompletionIntent::ThreadSummarization,
added_user_message.into(),
cx,
);
self.summary = ThreadSummary::Generating;
@@ -1883,14 +1967,14 @@ impl Thread {
return;
}
let added_user_message = "Generate a detailed summary of this conversation. Include:\n\
1. A brief overview of what was discussed\n\
2. Key facts or information discovered\n\
3. Outcomes or conclusions reached\n\
4. Any action items or next steps if any\n\
Format it in Markdown with headings and bullet points.";
let added_user_message = include_str!("./prompts/summarize_thread_detailed_prompt.txt");
let request = self.to_summarize_request(&model, added_user_message.into(), cx);
let request = self.to_summarize_request(
&model,
CompletionIntent::ThreadContextSummarization,
added_user_message.into(),
cx,
);
*self.detailed_summary_tx.borrow_mut() = DetailedSummaryState::Generating {
message_id: last_message_id,
@@ -1982,7 +2066,8 @@ impl Thread {
model: Arc<dyn LanguageModel>,
) -> Vec<PendingToolUse> {
self.auto_capture_telemetry(cx);
let request = Arc::new(self.to_completion_request(model.clone(), cx));
let request =
Arc::new(self.to_completion_request(model.clone(), CompletionIntent::ToolResults, cx));
let pending_tool_uses = self
.tool_use
.pending_tool_uses()
@@ -1994,7 +2079,7 @@ impl Thread {
for tool_use in pending_tool_uses.iter() {
if let Some(tool) = self.tools.read(cx).tool(&tool_use.name, cx) {
if tool.needs_confirmation(&tool_use.input, cx)
&& !AssistantSettings::get_global(cx).always_allow_tool_actions
&& !AgentSettings::get_global(cx).always_allow_tool_actions
{
self.tool_use.confirm_tool_use(
tool_use.id.clone(),
@@ -2178,7 +2263,7 @@ impl Thread {
if self.all_tools_finished() {
if let Some(ConfiguredModel { model, .. }) = self.configured_model.as_ref() {
if !canceled {
self.send_to_model(model.clone(), window, cx);
self.send_to_model(model.clone(), CompletionIntent::ToolResults, window, cx);
}
self.auto_capture_telemetry(cx);
}
@@ -2211,10 +2296,17 @@ impl Thread {
);
}
self.finalize_pending_checkpoint(cx);
if canceled {
cx.emit(ThreadEvent::CompletionCanceled);
// When canceled, we always want to insert the checkpoint.
// (We skip over finalize_pending_checkpoint, because it
// would conclude we didn't have anything to insert here.)
if let Some(checkpoint) = self.pending_checkpoint.take() {
self.insert_checkpoint(checkpoint, cx);
}
} else {
self.finalize_pending_checkpoint(cx);
}
canceled
@@ -2518,11 +2610,7 @@ impl Thread {
writeln!(markdown, "**\n")?;
match &tool_result.content {
LanguageModelToolResultContent::Text(text)
| LanguageModelToolResultContent::WrappedText(WrappedTextContent {
text,
..
}) => {
LanguageModelToolResultContent::Text(text) => {
writeln!(markdown, "{text}")?;
}
LanguageModelToolResultContent::Image(image) => {
@@ -2767,6 +2855,7 @@ pub enum ThreadEvent {
},
CheckpointChanged,
ToolConfirmationNeeded,
ToolUseLimitReached,
CancelEditing,
CompletionCanceled,
}
@@ -2783,7 +2872,7 @@ struct PendingCompletion {
mod tests {
use super::*;
use crate::{ThreadStore, context::load_context, context_store::ContextStore, thread_store};
use assistant_settings::{AssistantSettings, LanguageModelParameters};
use agent_settings::{AgentSettings, LanguageModelParameters};
use assistant_tool::ToolRegistry;
use editor::EditorSettings;
use gpui::TestAppContext;
@@ -2814,7 +2903,8 @@ mod tests {
.await
.unwrap();
let context = context_store.update(cx, |store, _| store.context().next().cloned().unwrap());
let context =
context_store.read_with(cx, |store, _| store.context().next().cloned().unwrap());
let loaded_context = cx
.update(|cx| load_context(vec![context], &project, &None, cx))
.await;
@@ -2865,7 +2955,7 @@ fn main() {{
// Check message in request
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.messages.len(), 2);
@@ -2960,7 +3050,7 @@ fn main() {{
// Check entire request to make sure all contexts are properly included
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
// The request should contain all 3 messages
@@ -3067,7 +3157,7 @@ fn main() {{
// Check message in request
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.messages.len(), 2);
@@ -3093,7 +3183,7 @@ fn main() {{
// Check that both messages appear in the request
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.messages.len(), 3);
@@ -3125,7 +3215,8 @@ fn main() {{
.await
.unwrap();
let context = context_store.update(cx, |store, _| store.context().next().cloned().unwrap());
let context =
context_store.read_with(cx, |store, _| store.context().next().cloned().unwrap());
let loaded_context = cx
.update(|cx| load_context(vec![context], &project, &None, cx))
.await;
@@ -3137,7 +3228,7 @@ fn main() {{
// Create a request and check that it doesn't have a stale buffer warning yet
let initial_request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
// Make sure we don't have a stale file warning yet
@@ -3173,7 +3264,7 @@ fn main() {{
// Create a new request and check for the stale buffer warning
let new_request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
// We should have a stale file warning as the last message
@@ -3209,81 +3300,81 @@ fn main() {{
// Both model and provider
cx.update(|cx| {
AssistantSettings::override_global(
AssistantSettings {
AgentSettings::override_global(
AgentSettings {
model_parameters: vec![LanguageModelParameters {
provider: Some(model.provider_id().0.to_string().into()),
model: Some(model.id().0.clone()),
temperature: Some(0.66),
}],
..AssistantSettings::get_global(cx).clone()
..AgentSettings::get_global(cx).clone()
},
cx,
);
});
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.temperature, Some(0.66));
// Only model
cx.update(|cx| {
AssistantSettings::override_global(
AssistantSettings {
AgentSettings::override_global(
AgentSettings {
model_parameters: vec![LanguageModelParameters {
provider: None,
model: Some(model.id().0.clone()),
temperature: Some(0.66),
}],
..AssistantSettings::get_global(cx).clone()
..AgentSettings::get_global(cx).clone()
},
cx,
);
});
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.temperature, Some(0.66));
// Only provider
cx.update(|cx| {
AssistantSettings::override_global(
AssistantSettings {
AgentSettings::override_global(
AgentSettings {
model_parameters: vec![LanguageModelParameters {
provider: Some(model.provider_id().0.to_string().into()),
model: None,
temperature: Some(0.66),
}],
..AssistantSettings::get_global(cx).clone()
..AgentSettings::get_global(cx).clone()
},
cx,
);
});
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.temperature, Some(0.66));
// Same model name, different provider
cx.update(|cx| {
AssistantSettings::override_global(
AssistantSettings {
AgentSettings::override_global(
AgentSettings {
model_parameters: vec![LanguageModelParameters {
provider: Some("anthropic".into()),
model: Some(model.id().0.clone()),
temperature: Some(0.66),
}],
..AssistantSettings::get_global(cx).clone()
..AgentSettings::get_global(cx).clone()
},
cx,
);
});
let request = thread.update(cx, |thread, cx| {
thread.to_completion_request(model.clone(), cx)
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx)
});
assert_eq!(request.temperature, None);
}
@@ -3315,7 +3406,12 @@ fn main() {{
// Send a message
thread.update(cx, |thread, cx| {
thread.insert_user_message("Hi!", ContextLoadResult::default(), None, vec![], cx);
thread.send_to_model(model.clone(), None, cx);
thread.send_to_model(
model.clone(),
CompletionIntent::ThreadSummarization,
None,
cx,
);
});
let fake_model = model.as_fake();
@@ -3337,8 +3433,8 @@ fn main() {{
});
cx.run_until_parked();
fake_model.stream_last_completion_response("Brief".into());
fake_model.stream_last_completion_response(" Introduction".into());
fake_model.stream_last_completion_response("Brief");
fake_model.stream_last_completion_response(" Introduction");
fake_model.end_last_completion_stream();
cx.run_until_parked();
@@ -3410,7 +3506,7 @@ fn main() {{
vec![],
cx,
);
thread.send_to_model(model.clone(), None, cx);
thread.send_to_model(model.clone(), CompletionIntent::UserPrompt, None, cx);
});
let fake_model = model.as_fake();
@@ -3431,7 +3527,7 @@ fn main() {{
});
cx.run_until_parked();
fake_model.stream_last_completion_response("A successful summary".into());
fake_model.stream_last_completion_response("A successful summary");
fake_model.end_last_completion_stream();
cx.run_until_parked();
@@ -3448,7 +3544,12 @@ fn main() {{
) {
thread.update(cx, |thread, cx| {
thread.insert_user_message("Hi!", ContextLoadResult::default(), None, vec![], cx);
thread.send_to_model(model.clone(), None, cx);
thread.send_to_model(
model.clone(),
CompletionIntent::ThreadSummarization,
None,
cx,
);
});
let fake_model = model.as_fake();
@@ -3473,7 +3574,7 @@ fn main() {{
fn simulate_successful_response(fake_model: &FakeLanguageModel, cx: &mut TestAppContext) {
cx.run_until_parked();
fake_model.stream_last_completion_response("Assistant response".into());
fake_model.stream_last_completion_response("Assistant response");
fake_model.end_last_completion_stream();
cx.run_until_parked();
}
@@ -3484,7 +3585,7 @@ fn main() {{
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
AssistantSettings::register(cx);
AgentSettings::register(cx);
prompt_store::init(cx);
thread_store::init(cx);
workspace::init_settings(cx);

View File

@@ -1,11 +1,10 @@
use std::borrow::Cow;
use std::cell::{Ref, RefCell};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, CompletionMode};
use anyhow::{Context as _, Result, anyhow};
use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings, CompletionMode};
use assistant_tool::{ToolId, ToolSource, ToolWorkingSet};
use chrono::{DateTime, Utc};
use collections::HashMap;
@@ -17,8 +16,7 @@ use gpui::{
App, BackgroundExecutor, Context, Entity, EventEmitter, Global, ReadGlobal, SharedString,
Subscription, Task, prelude::*,
};
use heed::Database;
use heed::types::SerdeBincode;
use language_model::{LanguageModelToolResultContent, LanguageModelToolUseId, Role, TokenUsage};
use project::context_server_store::{ContextServerStatus, ContextServerStore};
use project::{Project, ProjectItem, ProjectPath, Worktree};
@@ -35,14 +33,52 @@ use crate::context_server_tool::ContextServerTool;
use crate::thread::{
DetailedSummaryState, ExceededWindowError, MessageId, ProjectSnapshot, Thread, ThreadId,
};
use indoc::indoc;
use sqlez::{
bindable::{Bind, Column},
connection::Connection,
statement::Statement,
};
const RULES_FILE_NAMES: [&'static str; 6] = [
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DataType {
#[serde(rename = "json")]
Json,
#[serde(rename = "zstd")]
Zstd,
}
impl Bind for DataType {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
let value = match self {
DataType::Json => "json",
DataType::Zstd => "zstd",
};
value.bind(statement, start_index)
}
}
impl Column for DataType {
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
let (value, next_index) = String::column(statement, start_index)?;
let data_type = match value.as_str() {
"json" => DataType::Json,
"zstd" => DataType::Zstd,
_ => anyhow::bail!("Unknown data type: {}", value),
};
Ok((data_type, next_index))
}
}
const RULES_FILE_NAMES: [&'static str; 8] = [
".rules",
".cursorrules",
".windsurfrules",
".clinerules",
".github/copilot-instructions.md",
"CLAUDE.md",
"AGENT.md",
"AGENTS.md",
];
pub fn init(cx: &mut App) {
@@ -485,13 +521,13 @@ impl ThreadStore {
}
fn load_default_profile(&self, cx: &mut Context<Self>) {
let assistant_settings = AssistantSettings::get_global(cx);
let assistant_settings = AgentSettings::get_global(cx);
self.load_profile_by_id(assistant_settings.default_profile.clone(), cx);
}
pub fn load_profile_by_id(&self, profile_id: AgentProfileId, cx: &mut Context<Self>) {
let assistant_settings = AssistantSettings::get_global(cx);
let assistant_settings = AgentSettings::get_global(cx);
if let Some(profile) = assistant_settings.profiles.get(&profile_id) {
self.load_profile(profile.clone(), cx);
@@ -676,6 +712,8 @@ pub struct SerializedThread {
pub model: Option<SerializedLanguageModel>,
#[serde(default)]
pub completion_mode: Option<CompletionMode>,
#[serde(default)]
pub tool_use_limit_reached: bool,
}
#[derive(Serialize, Deserialize, Debug)]
@@ -757,6 +795,8 @@ pub struct SerializedMessage {
pub context: String,
#[serde(default)]
pub creases: Vec<SerializedCrease>,
#[serde(default)]
pub is_hidden: bool,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -815,6 +855,7 @@ impl LegacySerializedThread {
exceeded_window_error: None,
model: None,
completion_mode: None,
tool_use_limit_reached: false,
}
}
}
@@ -840,6 +881,7 @@ impl LegacySerializedMessage {
tool_results: self.tool_results,
context: String::new(),
creases: Vec::new(),
is_hidden: false,
}
}
}
@@ -860,25 +902,27 @@ impl Global for GlobalThreadsDatabase {}
pub(crate) struct ThreadsDatabase {
executor: BackgroundExecutor,
env: heed::Env,
threads: Database<SerdeBincode<ThreadId>, SerializedThread>,
connection: Arc<Mutex<Connection>>,
}
impl heed::BytesEncode<'_> for SerializedThread {
type EItem = SerializedThread;
impl ThreadsDatabase {
fn connection(&self) -> Arc<Mutex<Connection>> {
self.connection.clone()
}
fn bytes_encode(item: &Self::EItem) -> Result<Cow<[u8]>, heed::BoxedError> {
serde_json::to_vec(item).map(Cow::Owned).map_err(Into::into)
const COMPRESSION_LEVEL: i32 = 3;
}
impl Bind for ThreadId {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
self.to_string().bind(statement, start_index)
}
}
impl<'a> heed::BytesDecode<'a> for SerializedThread {
type DItem = SerializedThread;
fn bytes_decode(bytes: &'a [u8]) -> Result<Self::DItem, heed::BoxedError> {
// We implement this type manually because we want to call `SerializedThread::from_json`,
// instead of the Deserialize trait implementation for `SerializedThread`.
SerializedThread::from_json(bytes).map_err(Into::into)
impl Column for ThreadId {
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
let (id_str, next_index) = String::column(statement, start_index)?;
Ok((ThreadId::from(id_str.as_str()), next_index))
}
}
@@ -894,8 +938,8 @@ impl ThreadsDatabase {
let database_future = executor
.spawn({
let executor = executor.clone();
let database_path = paths::data_dir().join("threads/threads-db.1.mdb");
async move { ThreadsDatabase::new(database_path, executor) }
let threads_dir = paths::data_dir().join("threads");
async move { ThreadsDatabase::new(threads_dir, executor) }
})
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
.boxed()
@@ -904,41 +948,144 @@ impl ThreadsDatabase {
cx.set_global(GlobalThreadsDatabase(database_future));
}
pub fn new(path: PathBuf, executor: BackgroundExecutor) -> Result<Self> {
std::fs::create_dir_all(&path)?;
pub fn new(threads_dir: PathBuf, executor: BackgroundExecutor) -> Result<Self> {
std::fs::create_dir_all(&threads_dir)?;
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 connection = Connection::open_file(&sqlite_path.to_string_lossy());
connection.exec(indoc! {"
CREATE TABLE IF NOT EXISTS threads (
id TEXT PRIMARY KEY,
summary TEXT NOT NULL,
updated_at TEXT NOT NULL,
data_type TEXT NOT NULL,
data BLOB NOT NULL
)
"})?()
.map_err(|e| anyhow!("Failed to create threads table: {}", e))?;
let db = Self {
executor: executor.clone(),
connection: Arc::new(Mutex::new(connection)),
};
if needs_migration_from_heed {
let db_connection = db.connection();
let executor_clone = executor.clone();
executor
.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)?;
log::info!("threads.db migrated to sqlite");
Ok::<(), anyhow::Error>(())
})
.detach();
}
Ok(db)
}
// Remove this migration after 2025-09-01
fn migrate_from_heed(
mdb_path: &Path,
connection: Arc<Mutex<Connection>>,
_executor: BackgroundExecutor,
) -> Result<()> {
use heed::types::SerdeBincode;
struct SerializedThreadHeed(SerializedThread);
impl heed::BytesEncode<'_> for SerializedThreadHeed {
type EItem = SerializedThreadHeed;
fn bytes_encode(
item: &Self::EItem,
) -> Result<std::borrow::Cow<[u8]>, heed::BoxedError> {
serde_json::to_vec(&item.0)
.map(std::borrow::Cow::Owned)
.map_err(Into::into)
}
}
impl<'a> heed::BytesDecode<'a> for SerializedThreadHeed {
type DItem = SerializedThreadHeed;
fn bytes_decode(bytes: &'a [u8]) -> Result<Self::DItem, heed::BoxedError> {
SerializedThread::from_json(bytes)
.map(SerializedThreadHeed)
.map_err(Into::into)
}
}
const ONE_GB_IN_BYTES: usize = 1024 * 1024 * 1024;
let env = unsafe {
heed::EnvOpenOptions::new()
.map_size(ONE_GB_IN_BYTES)
.max_dbs(1)
.open(path)?
.open(mdb_path)?
};
let mut txn = env.write_txn()?;
let threads = env.create_database(&mut txn, Some("threads"))?;
txn.commit()?;
let txn = env.write_txn()?;
let threads: heed::Database<SerdeBincode<ThreadId>, SerializedThreadHeed> = env
.open_database(&txn, Some("threads"))?
.ok_or_else(|| anyhow!("threads database not found"))?;
Ok(Self {
executor,
env,
threads,
})
for result in threads.iter(&txn)? {
let (thread_id, thread_heed) = result?;
Self::save_thread_sync(&connection, thread_id, thread_heed.0)?;
}
Ok(())
}
fn save_thread_sync(
connection: &Arc<Mutex<Connection>>,
id: ThreadId,
thread: SerializedThread,
) -> Result<()> {
let json_data = serde_json::to_string(&thread)?;
let summary = thread.summary.to_string();
let updated_at = thread.updated_at.to_rfc3339();
let connection = connection.lock().unwrap();
let compressed = zstd::encode_all(json_data.as_bytes(), Self::COMPRESSION_LEVEL)?;
let data_type = DataType::Zstd;
let data = compressed;
let mut insert = connection.exec_bound::<(ThreadId, String, String, DataType, Vec<u8>)>(indoc! {"
INSERT OR REPLACE INTO threads (id, summary, updated_at, data_type, data) VALUES (?, ?, ?, ?, ?)
"})?;
insert((id, summary, updated_at, data_type, data))?;
Ok(())
}
pub fn list_threads(&self) -> Task<Result<Vec<SerializedThreadMetadata>>> {
let env = self.env.clone();
let threads = self.threads;
let connection = self.connection.clone();
self.executor.spawn(async move {
let txn = env.read_txn()?;
let mut iter = threads.iter(&txn)?;
let connection = connection.lock().unwrap();
let mut select =
connection.select_bound::<(), (ThreadId, String, String)>(indoc! {"
SELECT id, summary, updated_at FROM threads ORDER BY updated_at DESC
"})?;
let rows = select(())?;
let mut threads = Vec::new();
while let Some((key, value)) = iter.next().transpose()? {
for (id, summary, updated_at) in rows {
threads.push(SerializedThreadMetadata {
id: key,
summary: value.summary,
updated_at: value.updated_at,
id,
summary: summary.into(),
updated_at: DateTime::parse_from_rfc3339(&updated_at)?.with_timezone(&Utc),
});
}
@@ -947,36 +1094,51 @@ impl ThreadsDatabase {
}
pub fn try_find_thread(&self, id: ThreadId) -> Task<Result<Option<SerializedThread>>> {
let env = self.env.clone();
let threads = self.threads;
let connection = self.connection.clone();
self.executor.spawn(async move {
let txn = env.read_txn()?;
let thread = threads.get(&txn, &id)?;
Ok(thread)
let connection = connection.lock().unwrap();
let mut select = connection.select_bound::<ThreadId, (DataType, Vec<u8>)>(indoc! {"
SELECT data_type, data FROM threads WHERE id = ? LIMIT 1
"})?;
let rows = select(id)?;
if let Some((data_type, data)) = rows.into_iter().next() {
let json_data = match data_type {
DataType::Zstd => {
let decompressed = zstd::decode_all(&data[..])?;
String::from_utf8(decompressed)?
}
DataType::Json => String::from_utf8(data)?,
};
let thread = SerializedThread::from_json(json_data.as_bytes())?;
Ok(Some(thread))
} else {
Ok(None)
}
})
}
pub fn save_thread(&self, id: ThreadId, thread: SerializedThread) -> Task<Result<()>> {
let env = self.env.clone();
let threads = self.threads;
let connection = self.connection.clone();
self.executor.spawn(async move {
let mut txn = env.write_txn()?;
threads.put(&mut txn, &id, &thread)?;
txn.commit()?;
Ok(())
})
self.executor
.spawn(async move { Self::save_thread_sync(&connection, id, thread) })
}
pub fn delete_thread(&self, id: ThreadId) -> Task<Result<()>> {
let env = self.env.clone();
let threads = self.threads;
let connection = self.connection.clone();
self.executor.spawn(async move {
let mut txn = env.write_txn()?;
threads.delete(&mut txn, &id)?;
txn.commit()?;
let connection = connection.lock().unwrap();
let mut delete = connection.exec_bound::<ThreadId>(indoc! {"
DELETE FROM threads WHERE id = ?
"})?;
delete(id)?;
Ok(())
})
}

View File

@@ -337,6 +337,12 @@ impl ToolUseState {
)
.into();
let may_perform_edits = self
.tools
.read(cx)
.tool(&tool_use.name, cx)
.is_some_and(|tool| tool.may_perform_edits());
self.pending_tool_uses_by_id.insert(
tool_use.id.clone(),
PendingToolUse {
@@ -345,6 +351,7 @@ impl ToolUseState {
name: tool_use.name.clone(),
ui_text: ui_text.clone(),
input: tool_use.input,
may_perform_edits,
status,
},
);
@@ -518,6 +525,7 @@ pub struct PendingToolUse {
pub ui_text: Arc<str>,
pub input: serde_json::Value,
pub status: PendingToolUseStatus,
pub may_perform_edits: bool,
}
#[derive(Debug, Clone)]

View File

@@ -1,3 +0,0 @@
# Build better with Zed Pro
Try [Zed Pro](https://zed.dev/pricing) for free for 14 days - no credit card required. Only $20/month afterward. Cancel anytime.

View File

@@ -93,20 +93,9 @@ impl ContextPill {
Self::Suggested {
icon_path: Some(icon_path),
..
}
| Self::Added {
context:
AddedContext {
icon_path: Some(icon_path),
..
},
..
} => Icon::from_path(icon_path),
Self::Suggested { kind, .. }
| Self::Added {
context: AddedContext { kind, .. },
..
} => Icon::new(kind.icon()),
Self::Suggested { kind, .. } => Icon::new(kind.icon()),
Self::Added { context, .. } => context.icon(),
}
}
}
@@ -133,6 +122,7 @@ impl RenderOnce for ContextPill {
on_click,
} => {
let status_is_error = matches!(context.status, ContextStatus::Error { .. });
let status_is_warning = matches!(context.status, ContextStatus::Warning { .. });
base_pill
.pr(if on_remove.is_some() { px(2.) } else { px(4.) })
@@ -140,6 +130,9 @@ impl RenderOnce for ContextPill {
if status_is_error {
pill.bg(cx.theme().status().error_background)
.border_color(cx.theme().status().error_border)
} else if status_is_warning {
pill.bg(cx.theme().status().warning_background)
.border_color(cx.theme().status().warning_border)
} else if *focused {
pill.bg(color.element_background)
.border_color(color.border_focused)
@@ -195,7 +188,8 @@ impl RenderOnce for ContextPill {
|label, delta| label.opacity(delta),
)
.into_any_element(),
ContextStatus::Error { message } => element
ContextStatus::Warning { message }
| ContextStatus::Error { message } => element
.tooltip(ui::Tooltip::text(message.clone()))
.into_any_element(),
}),
@@ -270,6 +264,7 @@ pub enum ContextStatus {
Ready,
Loading { message: SharedString },
Error { message: SharedString },
Warning { message: SharedString },
}
#[derive(RegisterComponent)]
@@ -285,6 +280,19 @@ pub struct AddedContext {
}
impl AddedContext {
pub fn icon(&self) -> Icon {
match &self.status {
ContextStatus::Warning { .. } => Icon::new(IconName::Warning).color(Color::Warning),
ContextStatus::Error { .. } => Icon::new(IconName::XCircle).color(Color::Error),
_ => {
if let Some(icon_path) = &self.icon_path {
Icon::from_path(icon_path)
} else {
Icon::new(self.kind.icon())
}
}
}
}
/// Creates an `AddedContext` by retrieving relevant details of `AgentContext`. This returns a
/// `None` if `DirectoryContext` or `RulesContext` no longer exist.
///
@@ -293,6 +301,7 @@ impl AddedContext {
handle: AgentContextHandle,
prompt_store: Option<&Entity<PromptStore>>,
project: &Project,
model: Option<&Arc<dyn language_model::LanguageModel>>,
cx: &App,
) -> Option<AddedContext> {
match handle {
@@ -304,11 +313,15 @@ impl AddedContext {
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)),
AgentContextHandle::Image(handle) => Some(Self::image(handle, model, cx)),
}
}
pub fn new_attached(context: &AgentContext, cx: &App) -> AddedContext {
pub fn new_attached(
context: &AgentContext,
model: Option<&Arc<dyn language_model::LanguageModel>>,
cx: &App,
) -> AddedContext {
match context {
AgentContext::File(context) => Self::attached_file(context, cx),
AgentContext::Directory(context) => Self::attached_directory(context),
@@ -318,7 +331,7 @@ impl AddedContext {
AgentContext::Thread(context) => Self::attached_thread(context),
AgentContext::TextThread(context) => Self::attached_text_thread(context),
AgentContext::Rules(context) => Self::attached_rules(context),
AgentContext::Image(context) => Self::image(context.clone()),
AgentContext::Image(context) => Self::image(context.clone(), model, cx),
}
}
@@ -333,14 +346,8 @@ impl AddedContext {
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 = full_path
.file_name()
.map(|n| n.to_string_lossy().into_owned().into())
.unwrap_or_else(|| full_path_string.clone());
let parent = full_path
.parent()
.and_then(|p| p.file_name())
.map(|n| n.to_string_lossy().into_owned().into());
let (name, parent) =
extract_file_name_and_directory_from_full_path(full_path, &full_path_string);
AddedContext {
kind: ContextKind::File,
name,
@@ -370,14 +377,8 @@ impl AddedContext {
fn directory(handle: DirectoryContextHandle, full_path: &Path) -> AddedContext {
let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
let name = full_path
.file_name()
.map(|n| n.to_string_lossy().into_owned().into())
.unwrap_or_else(|| full_path_string.clone());
let parent = full_path
.parent()
.and_then(|p| p.file_name())
.map(|n| n.to_string_lossy().into_owned().into());
let (name, parent) =
extract_file_name_and_directory_from_full_path(full_path, &full_path_string);
AddedContext {
kind: ContextKind::Directory,
name,
@@ -605,22 +606,45 @@ impl AddedContext {
}
}
fn image(context: ImageContext) -> AddedContext {
fn image(
context: ImageContext,
model: Option<&Arc<dyn language_model::LanguageModel>>,
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);
(name, parent, icon_path)
} else {
("Image".into(), None, None)
};
let status = match context.status(model) {
ImageStatus::Loading => ContextStatus::Loading {
message: "Loading…".into(),
},
ImageStatus::Error => ContextStatus::Error {
message: "Failed to load Image".into(),
},
ImageStatus::Warning => ContextStatus::Warning {
message: format!(
"{} doesn't support attaching Images as Context",
model.map(|m| m.name().0).unwrap_or_else(|| "Model".into())
)
.into(),
},
ImageStatus::Ready => ContextStatus::Ready,
};
AddedContext {
kind: ContextKind::Image,
name: "Image".into(),
parent: None,
name,
parent,
tooltip: None,
icon_path: None,
status: match context.status() {
ImageStatus::Loading => ContextStatus::Loading {
message: "Loading…".into(),
},
ImageStatus::Error => ContextStatus::Error {
message: "Failed to load image".into(),
},
ImageStatus::Ready => ContextStatus::Ready,
},
icon_path,
status,
render_hover: Some(Rc::new({
let image = context.original_image.clone();
move |_, cx| {
@@ -639,6 +663,22 @@ impl AddedContext {
}
}
fn extract_file_name_and_directory_from_full_path(
path: &Path,
name_fallback: &SharedString,
) -> (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)
}
#[derive(Debug, Clone)]
struct ContextFileExcerpt {
pub file_name_and_range: SharedString,
@@ -765,37 +805,52 @@ impl Component for AddedContext {
let mut next_context_id = ContextId::zero();
let image_ready = (
"Ready",
AddedContext::image(ImageContext {
context_id: next_context_id.post_inc(),
project_path: None,
original_image: Arc::new(Image::empty()),
image_task: Task::ready(Some(LanguageModelImage::empty())).shared(),
}),
AddedContext::image(
ImageContext {
context_id: next_context_id.post_inc(),
project_path: None,
full_path: None,
original_image: Arc::new(Image::empty()),
image_task: Task::ready(Some(LanguageModelImage::empty())).shared(),
},
None,
cx,
),
);
let image_loading = (
"Loading",
AddedContext::image(ImageContext {
context_id: next_context_id.post_inc(),
project_path: None,
original_image: Arc::new(Image::empty()),
image_task: cx
.background_spawn(async move {
smol::Timer::after(Duration::from_secs(60 * 5)).await;
Some(LanguageModelImage::empty())
})
.shared(),
}),
AddedContext::image(
ImageContext {
context_id: next_context_id.post_inc(),
project_path: None,
full_path: None,
original_image: Arc::new(Image::empty()),
image_task: cx
.background_spawn(async move {
smol::Timer::after(Duration::from_secs(60 * 5)).await;
Some(LanguageModelImage::empty())
})
.shared(),
},
None,
cx,
),
);
let image_error = (
"Error",
AddedContext::image(ImageContext {
context_id: next_context_id.post_inc(),
project_path: None,
original_image: Arc::new(Image::empty()),
image_task: Task::ready(None).shared(),
}),
AddedContext::image(
ImageContext {
context_id: next_context_id.post_inc(),
project_path: None,
full_path: None,
original_image: Arc::new(Image::empty()),
image_task: Task::ready(None).shared(),
},
None,
cx,
),
);
Some(
@@ -815,3 +870,60 @@ impl Component for AddedContext {
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use gpui::App;
use language_model::{LanguageModel, fake_provider::FakeLanguageModel};
use std::sync::Arc;
#[gpui::test]
fn test_image_context_warning_for_unsupported_model(cx: &mut App) {
let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel::default());
assert!(!model.supports_images());
let image_context = ImageContext {
context_id: ContextId::zero(),
project_path: None,
original_image: Arc::new(Image::empty()),
image_task: Task::ready(Some(LanguageModelImage::empty())).shared(),
full_path: None,
};
let added_context = AddedContext::image(image_context, Some(&model), cx);
assert!(matches!(
added_context.status,
ContextStatus::Warning { .. }
));
assert!(matches!(added_context.kind, ContextKind::Image));
assert_eq!(added_context.name.as_ref(), "Image");
assert!(added_context.parent.is_none());
assert!(added_context.icon_path.is_none());
}
#[gpui::test]
fn test_image_context_ready_for_no_model(cx: &mut App) {
let image_context = ImageContext {
context_id: ContextId::zero(),
project_path: None,
original_image: Arc::new(Image::empty()),
image_task: Task::ready(Some(LanguageModelImage::empty())).shared(),
full_path: None,
};
let added_context = AddedContext::image(image_context, None, cx);
assert!(
matches!(added_context.status, ContextStatus::Ready),
"Expected ready status when no model provided"
);
assert!(matches!(added_context.kind, ContextKind::Image));
assert_eq!(added_context.name.as_ref(), "Image");
assert!(added_context.parent.is_none());
assert!(added_context.icon_path.is_none());
}
}

View File

@@ -1,5 +1,6 @@
use gpui::{Context, IntoElement, Render, Window};
use ui::{prelude::*, tooltip_container};
use crate::ToggleBurnMode;
use gpui::{Context, FontWeight, IntoElement, Render, Window};
use ui::{KeyBinding, prelude::*, tooltip_container};
pub struct MaxModeTooltip {
selected: bool,
@@ -18,38 +19,48 @@ impl MaxModeTooltip {
impl Render for MaxModeTooltip {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (icon, color) = if self.selected {
(IconName::ZedBurnModeOn, Color::Error)
} else {
(IconName::ZedBurnMode, Color::Default)
};
let turned_on = h_flex()
.h_4()
.px_1()
.border_1()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().text_accent.opacity(0.1))
.rounded_sm()
.child(
Label::new("ON")
.size(LabelSize::XSmall)
.weight(FontWeight::SEMIBOLD)
.color(Color::Accent),
);
let title = h_flex()
.gap_1p5()
.child(Icon::new(icon).size(IconSize::Small).color(color))
.child(Label::new("Burn Mode"))
.when(self.selected, |title| title.child(turned_on));
let keybinding = KeyBinding::for_action(&ToggleBurnMode, window, cx)
.map(|kb| kb.size(rems_from_px(12.)));
tooltip_container(window, cx, |this, _, _| {
this.gap_1()
.map(|header| if self.selected {
header.child(
h_flex()
.justify_between()
.child(
h_flex()
.gap_1p5()
.child(Icon::new(IconName::ZedMaxMode).size(IconSize::Small).color(Color::Accent))
.child(Label::new("Zed's Max Mode"))
)
.child(
h_flex()
.gap_0p5()
.child(Icon::new(IconName::Check).size(IconSize::XSmall).color(Color::Accent))
.child(Label::new("Turned On").size(LabelSize::XSmall).color(Color::Accent))
)
)
} else {
header.child(
h_flex()
.gap_1p5()
.child(Icon::new(IconName::ZedMaxMode).size(IconSize::Small))
.child(Label::new("Zed's Max Mode"))
)
})
this
.child(
h_flex()
.justify_between()
.child(title)
.children(keybinding)
)
.child(
div()
.max_w_72()
.max_w_64()
.child(
Label::new("This mode enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning, offering an unfettered agentic experience.")
Label::new("Enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning.")
.size(LabelSize::Small)
.color(Color::Muted)
)

View File

@@ -1,5 +1,5 @@
[package]
name = "assistant_settings"
name = "agent_settings"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
@@ -9,7 +9,7 @@ license = "GPL-3.0-or-later"
workspace = true
[lib]
path = "src/assistant_settings.rs"
path = "src/agent_settings.rs"
[dependencies]
anthropic = { workspace = true, features = ["schemars"] }

View File

@@ -24,7 +24,7 @@ pub struct GroupedAgentProfiles {
}
impl GroupedAgentProfiles {
pub fn from_settings(settings: &crate::AssistantSettings) -> Self {
pub fn from_settings(settings: &crate::AgentSettings) -> Self {
let mut builtin = IndexMap::default();
let mut custom = IndexMap::default();

View File

@@ -8,7 +8,7 @@ use anyhow::{Result, bail};
use collections::IndexMap;
use deepseek::Model as DeepseekModel;
use gpui::{App, Pixels, SharedString};
use language_model::{CloudModel, LanguageModel};
use language_model::LanguageModel;
use lmstudio::Model as LmStudioModel;
use mistral::Model as MistralModel;
use ollama::Model as OllamaModel;
@@ -19,18 +19,26 @@ use settings::{Settings, SettingsSources};
pub use crate::agent_profile::*;
pub fn init(cx: &mut App) {
AssistantSettings::register(cx);
AgentSettings::register(cx);
}
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AssistantDockPosition {
pub enum AgentDockPosition {
Left,
#[default]
Right,
Bottom,
}
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DefaultView {
#[default]
Thread,
TextThread,
}
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum NotifyWhenAgentWaiting {
@@ -43,9 +51,9 @@ pub enum NotifyWhenAgentWaiting {
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(tag = "name", rename_all = "snake_case")]
#[schemars(deny_unknown_fields)]
pub enum AssistantProviderContentV1 {
pub enum AgentProviderContentV1 {
#[serde(rename = "zed.dev")]
ZedDotDev { default_model: Option<CloudModel> },
ZedDotDev { default_model: Option<String> },
#[serde(rename = "openai")]
OpenAi {
default_model: Option<OpenAiModel>,
@@ -80,10 +88,10 @@ pub enum AssistantProviderContentV1 {
}
#[derive(Default, Clone, Debug)]
pub struct AssistantSettings {
pub struct AgentSettings {
pub enabled: bool,
pub button: bool,
pub dock: AssistantDockPosition,
pub dock: AgentDockPosition,
pub default_width: Pixels,
pub default_height: Pixels,
pub default_model: LanguageModelSelection,
@@ -93,9 +101,11 @@ pub struct AssistantSettings {
pub inline_alternatives: Vec<LanguageModelSelection>,
pub using_outdated_settings_version: bool,
pub default_profile: AgentProfileId,
pub default_view: DefaultView,
pub profiles: IndexMap<AgentProfileId, AgentProfile>,
pub always_allow_tool_actions: bool,
pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
pub play_sound_when_agent_done: bool,
pub stream_edits: bool,
pub single_file_review: bool,
pub model_parameters: Vec<LanguageModelParameters>,
@@ -103,7 +113,7 @@ pub struct AssistantSettings {
pub enable_feedback: bool,
}
impl AssistantSettings {
impl AgentSettings {
pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
let settings = Self::get_global(cx);
settings
@@ -158,58 +168,56 @@ impl LanguageModelParameters {
}
}
/// Assistant panel settings
/// Agent panel settings
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
pub struct AssistantSettingsContent {
pub struct AgentSettingsContent {
#[serde(flatten)]
pub inner: Option<AssistantSettingsContentInner>,
pub inner: Option<AgentSettingsContentInner>,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum AssistantSettingsContentInner {
Versioned(Box<VersionedAssistantSettingsContent>),
Legacy(LegacyAssistantSettingsContent),
pub enum AgentSettingsContentInner {
Versioned(Box<VersionedAgentSettingsContent>),
Legacy(LegacyAgentSettingsContent),
}
impl AssistantSettingsContentInner {
fn for_v2(content: AssistantSettingsContentV2) -> Self {
AssistantSettingsContentInner::Versioned(Box::new(VersionedAssistantSettingsContent::V2(
content,
)))
impl AgentSettingsContentInner {
fn for_v2(content: AgentSettingsContentV2) -> Self {
AgentSettingsContentInner::Versioned(Box::new(VersionedAgentSettingsContent::V2(content)))
}
}
impl JsonSchema for AssistantSettingsContent {
impl JsonSchema for AgentSettingsContent {
fn schema_name() -> String {
VersionedAssistantSettingsContent::schema_name()
VersionedAgentSettingsContent::schema_name()
}
fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> Schema {
VersionedAssistantSettingsContent::json_schema(r#gen)
VersionedAgentSettingsContent::json_schema(r#gen)
}
fn is_referenceable() -> bool {
VersionedAssistantSettingsContent::is_referenceable()
VersionedAgentSettingsContent::is_referenceable()
}
}
impl AssistantSettingsContent {
impl AgentSettingsContent {
pub fn is_version_outdated(&self) -> bool {
match &self.inner {
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
VersionedAssistantSettingsContent::V1(_) => true,
VersionedAssistantSettingsContent::V2(_) => false,
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
VersionedAgentSettingsContent::V1(_) => true,
VersionedAgentSettingsContent::V2(_) => false,
},
Some(AssistantSettingsContentInner::Legacy(_)) => true,
Some(AgentSettingsContentInner::Legacy(_)) => true,
None => false,
}
}
fn upgrade(&self) -> AssistantSettingsContentV2 {
fn upgrade(&self) -> AgentSettingsContentV2 {
match &self.inner {
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
VersionedAssistantSettingsContent::V1(ref settings) => AssistantSettingsContentV2 {
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
VersionedAgentSettingsContent::V1(ref settings) => AgentSettingsContentV2 {
enabled: settings.enabled,
button: settings.button,
dock: settings.dock,
@@ -219,54 +227,49 @@ impl AssistantSettingsContent {
.provider
.clone()
.and_then(|provider| match provider {
AssistantProviderContentV1::ZedDotDev { default_model } => {
default_model.map(|model| LanguageModelSelection {
AgentProviderContentV1::ZedDotDev { default_model } => default_model
.map(|model| LanguageModelSelection {
provider: "zed.dev".into(),
model: model.id().to_string(),
})
}
AssistantProviderContentV1::OpenAi { default_model, .. } => {
default_model.map(|model| LanguageModelSelection {
model,
}),
AgentProviderContentV1::OpenAi { default_model, .. } => default_model
.map(|model| LanguageModelSelection {
provider: "openai".into(),
model: model.id().to_string(),
})
}
AssistantProviderContentV1::Anthropic { default_model, .. } => {
}),
AgentProviderContentV1::Anthropic { default_model, .. } => {
default_model.map(|model| LanguageModelSelection {
provider: "anthropic".into(),
model: model.id().to_string(),
})
}
AssistantProviderContentV1::Ollama { default_model, .. } => {
default_model.map(|model| LanguageModelSelection {
AgentProviderContentV1::Ollama { default_model, .. } => default_model
.map(|model| LanguageModelSelection {
provider: "ollama".into(),
model: model.id().to_string(),
})
}
AssistantProviderContentV1::LmStudio { default_model, .. } => {
default_model.map(|model| LanguageModelSelection {
}),
AgentProviderContentV1::LmStudio { default_model, .. } => default_model
.map(|model| LanguageModelSelection {
provider: "lmstudio".into(),
model: model.id().to_string(),
})
}
AssistantProviderContentV1::DeepSeek { default_model, .. } => {
default_model.map(|model| LanguageModelSelection {
}),
AgentProviderContentV1::DeepSeek { default_model, .. } => default_model
.map(|model| LanguageModelSelection {
provider: "deepseek".into(),
model: model.id().to_string(),
})
}
AssistantProviderContentV1::Mistral { default_model, .. } => {
default_model.map(|model| LanguageModelSelection {
}),
AgentProviderContentV1::Mistral { default_model, .. } => default_model
.map(|model| LanguageModelSelection {
provider: "mistral".into(),
model: model.id().to_string(),
})
}
}),
}),
inline_assistant_model: None,
commit_message_model: None,
thread_summary_model: None,
inline_alternatives: None,
default_profile: None,
default_view: None,
profiles: None,
always_allow_tool_actions: None,
notify_when_agent_waiting: None,
@@ -275,10 +278,11 @@ impl AssistantSettingsContent {
model_parameters: Vec::new(),
preferred_completion_mode: None,
enable_feedback: None,
play_sound_when_agent_done: None,
},
VersionedAssistantSettingsContent::V2(ref settings) => settings.clone(),
VersionedAgentSettingsContent::V2(ref settings) => settings.clone(),
},
Some(AssistantSettingsContentInner::Legacy(settings)) => AssistantSettingsContentV2 {
Some(AgentSettingsContentInner::Legacy(settings)) => AgentSettingsContentV2 {
enabled: None,
button: settings.button,
dock: settings.dock,
@@ -298,6 +302,7 @@ impl AssistantSettingsContent {
thread_summary_model: None,
inline_alternatives: None,
default_profile: None,
default_view: None,
profiles: None,
always_allow_tool_actions: None,
notify_when_agent_waiting: None,
@@ -306,31 +311,30 @@ impl AssistantSettingsContent {
model_parameters: Vec::new(),
preferred_completion_mode: None,
enable_feedback: None,
play_sound_when_agent_done: None,
},
None => AssistantSettingsContentV2::default(),
None => AgentSettingsContentV2::default(),
}
}
pub fn set_dock(&mut self, dock: AssistantDockPosition) {
pub fn set_dock(&mut self, dock: AgentDockPosition) {
match &mut self.inner {
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
VersionedAssistantSettingsContent::V1(ref mut settings) => {
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
VersionedAgentSettingsContent::V1(ref mut settings) => {
settings.dock = Some(dock);
}
VersionedAssistantSettingsContent::V2(ref mut settings) => {
VersionedAgentSettingsContent::V2(ref mut settings) => {
settings.dock = Some(dock);
}
},
Some(AssistantSettingsContentInner::Legacy(settings)) => {
Some(AgentSettingsContentInner::Legacy(settings)) => {
settings.dock = Some(dock);
}
None => {
self.inner = Some(AssistantSettingsContentInner::for_v2(
AssistantSettingsContentV2 {
dock: Some(dock),
..Default::default()
},
))
self.inner = Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
dock: Some(dock),
..Default::default()
}))
}
}
}
@@ -340,105 +344,101 @@ impl AssistantSettingsContent {
let provider = language_model.provider_id().0.to_string();
match &mut self.inner {
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
VersionedAssistantSettingsContent::V1(ref mut settings) => {
match provider.as_ref() {
"zed.dev" => {
log::warn!("attempted to set zed.dev model on outdated settings");
}
"anthropic" => {
let api_url = match &settings.provider {
Some(AssistantProviderContentV1::Anthropic { api_url, .. }) => {
api_url.clone()
}
_ => None,
};
settings.provider = Some(AssistantProviderContentV1::Anthropic {
default_model: AnthropicModel::from_id(&model).ok(),
api_url,
});
}
"ollama" => {
let api_url = match &settings.provider {
Some(AssistantProviderContentV1::Ollama { api_url, .. }) => {
api_url.clone()
}
_ => None,
};
settings.provider = Some(AssistantProviderContentV1::Ollama {
default_model: Some(ollama::Model::new(
&model,
None,
None,
Some(language_model.supports_tools()),
)),
api_url,
});
}
"lmstudio" => {
let api_url = match &settings.provider {
Some(AssistantProviderContentV1::LmStudio { api_url, .. }) => {
api_url.clone()
}
_ => None,
};
settings.provider = Some(AssistantProviderContentV1::LmStudio {
default_model: Some(lmstudio::Model::new(&model, None, None)),
api_url,
});
}
"openai" => {
let (api_url, available_models) = match &settings.provider {
Some(AssistantProviderContentV1::OpenAi {
api_url,
available_models,
..
}) => (api_url.clone(), available_models.clone()),
_ => (None, None),
};
settings.provider = Some(AssistantProviderContentV1::OpenAi {
default_model: OpenAiModel::from_id(&model).ok(),
Some(AgentSettingsContentInner::Versioned(settings)) => match **settings {
VersionedAgentSettingsContent::V1(ref mut settings) => match provider.as_ref() {
"zed.dev" => {
log::warn!("attempted to set zed.dev model on outdated settings");
}
"anthropic" => {
let api_url = match &settings.provider {
Some(AgentProviderContentV1::Anthropic { api_url, .. }) => {
api_url.clone()
}
_ => None,
};
settings.provider = Some(AgentProviderContentV1::Anthropic {
default_model: AnthropicModel::from_id(&model).ok(),
api_url,
});
}
"ollama" => {
let api_url = match &settings.provider {
Some(AgentProviderContentV1::Ollama { api_url, .. }) => api_url.clone(),
_ => None,
};
settings.provider = Some(AgentProviderContentV1::Ollama {
default_model: Some(ollama::Model::new(
&model,
None,
None,
Some(language_model.supports_tools()),
Some(language_model.supports_images()),
None,
)),
api_url,
});
}
"lmstudio" => {
let api_url = match &settings.provider {
Some(AgentProviderContentV1::LmStudio { api_url, .. }) => {
api_url.clone()
}
_ => None,
};
settings.provider = Some(AgentProviderContentV1::LmStudio {
default_model: Some(lmstudio::Model::new(&model, None, None, false)),
api_url,
});
}
"openai" => {
let (api_url, available_models) = match &settings.provider {
Some(AgentProviderContentV1::OpenAi {
api_url,
available_models,
});
}
"deepseek" => {
let api_url = match &settings.provider {
Some(AssistantProviderContentV1::DeepSeek { api_url, .. }) => {
api_url.clone()
}
_ => None,
};
settings.provider = Some(AssistantProviderContentV1::DeepSeek {
default_model: DeepseekModel::from_id(&model).ok(),
api_url,
});
}
_ => {}
..
}) => (api_url.clone(), available_models.clone()),
_ => (None, None),
};
settings.provider = Some(AgentProviderContentV1::OpenAi {
default_model: OpenAiModel::from_id(&model).ok(),
api_url,
available_models,
});
}
}
VersionedAssistantSettingsContent::V2(ref mut settings) => {
"deepseek" => {
let api_url = match &settings.provider {
Some(AgentProviderContentV1::DeepSeek { api_url, .. }) => {
api_url.clone()
}
_ => None,
};
settings.provider = Some(AgentProviderContentV1::DeepSeek {
default_model: DeepseekModel::from_id(&model).ok(),
api_url,
});
}
_ => {}
},
VersionedAgentSettingsContent::V2(ref mut settings) => {
settings.default_model = Some(LanguageModelSelection {
provider: provider.into(),
model,
});
}
},
Some(AssistantSettingsContentInner::Legacy(settings)) => {
Some(AgentSettingsContentInner::Legacy(settings)) => {
if let Ok(model) = OpenAiModel::from_id(&language_model.id().0) {
settings.default_open_ai_model = Some(model);
}
}
None => {
self.inner = Some(AssistantSettingsContentInner::for_v2(
AssistantSettingsContentV2 {
default_model: Some(LanguageModelSelection {
provider: provider.into(),
model,
}),
..Default::default()
},
));
self.inner = Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
default_model: Some(LanguageModelSelection {
provider: provider.into(),
model,
}),
..Default::default()
}));
}
}
}
@@ -467,15 +467,15 @@ impl AssistantSettingsContent {
pub fn v2_setting(
&mut self,
f: impl FnOnce(&mut AssistantSettingsContentV2) -> anyhow::Result<()>,
f: impl FnOnce(&mut AgentSettingsContentV2) -> anyhow::Result<()>,
) -> anyhow::Result<()> {
match self.inner.get_or_insert_with(|| {
AssistantSettingsContentInner::for_v2(AssistantSettingsContentV2 {
AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
..Default::default()
})
}) {
AssistantSettingsContentInner::Versioned(boxed) => {
if let VersionedAssistantSettingsContent::V2(ref mut settings) = **boxed {
AgentSettingsContentInner::Versioned(boxed) => {
if let VersionedAgentSettingsContent::V2(ref mut settings) = **boxed {
f(settings)
} else {
Ok(())
@@ -504,6 +504,14 @@ impl AssistantSettingsContent {
.ok();
}
pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
self.v2_setting(|setting| {
setting.play_sound_when_agent_done = Some(allow);
Ok(())
})
.ok();
}
pub fn set_single_file_review(&mut self, allow: bool) {
self.v2_setting(|setting| {
setting.single_file_review = Some(allow);
@@ -560,16 +568,16 @@ impl AssistantSettingsContent {
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
#[serde(tag = "version")]
#[schemars(deny_unknown_fields)]
pub enum VersionedAssistantSettingsContent {
pub enum VersionedAgentSettingsContent {
#[serde(rename = "1")]
V1(AssistantSettingsContentV1),
V1(AgentSettingsContentV1),
#[serde(rename = "2")]
V2(AssistantSettingsContentV2),
V2(AgentSettingsContentV2),
}
impl Default for VersionedAssistantSettingsContent {
impl Default for VersionedAgentSettingsContent {
fn default() -> Self {
Self::V2(AssistantSettingsContentV2 {
Self::V2(AgentSettingsContentV2 {
enabled: None,
button: None,
dock: None,
@@ -581,6 +589,7 @@ impl Default for VersionedAssistantSettingsContent {
thread_summary_model: None,
inline_alternatives: None,
default_profile: None,
default_view: None,
profiles: None,
always_allow_tool_actions: None,
notify_when_agent_waiting: None,
@@ -589,30 +598,31 @@ impl Default for VersionedAssistantSettingsContent {
model_parameters: Vec::new(),
preferred_completion_mode: None,
enable_feedback: None,
play_sound_when_agent_done: None,
})
}
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
#[schemars(deny_unknown_fields)]
pub struct AssistantSettingsContentV2 {
/// Whether the Assistant is enabled.
pub struct AgentSettingsContentV2 {
/// Whether the Agent is enabled.
///
/// Default: true
enabled: Option<bool>,
/// Whether to show the assistant panel button in the status bar.
/// Whether to show the agent panel button in the status bar.
///
/// Default: true
button: Option<bool>,
/// Where to dock the assistant.
/// Where to dock the agent panel.
///
/// Default: right
dock: Option<AssistantDockPosition>,
/// Default width in pixels when the assistant is docked to the left or right.
dock: Option<AgentDockPosition>,
/// Default width in pixels when the agent panel is docked to the left or right.
///
/// Default: 640
default_width: Option<f32>,
/// Default height in pixels when the assistant is docked to the bottom.
/// Default height in pixels when the agent panel is docked to the bottom.
///
/// Default: 320
default_height: Option<f32>,
@@ -630,6 +640,10 @@ pub struct AssistantSettingsContentV2 {
///
/// Default: write
default_profile: Option<AgentProfileId>,
/// Which view type to show by default in the agent panel.
///
/// Default: "thread"
default_view: Option<DefaultView>,
/// The available agent profiles.
pub profiles: Option<IndexMap<AgentProfileId, AgentProfileContent>>,
/// Whenever a tool action would normally wait for your confirmation
@@ -641,6 +655,10 @@ pub struct AssistantSettingsContentV2 {
///
/// Default: "primary_screen"
notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
/// Whether to play a sound when the agent has either completed its response, or needs user input.
///
/// Default: false
play_sound_when_agent_done: Option<bool>,
/// Whether to stream edits from the agent as they are received.
///
/// Default: false
@@ -658,7 +676,6 @@ pub struct AssistantSettingsContentV2 {
/// Default: []
#[serde(default)]
model_parameters: Vec<LanguageModelParameters>,
/// What completion mode to enable for new threads
///
/// Default: normal
@@ -674,14 +691,15 @@ pub struct AssistantSettingsContentV2 {
pub enum CompletionMode {
#[default]
Normal,
Max,
#[serde(alias = "max")]
Burn,
}
impl From<CompletionMode> for zed_llm_client::CompletionMode {
fn from(value: CompletionMode) -> Self {
match value {
CompletionMode::Normal => zed_llm_client::CompletionMode::Normal,
CompletionMode::Max => zed_llm_client::CompletionMode::Max,
CompletionMode::Burn => zed_llm_client::CompletionMode::Max,
}
}
}
@@ -712,6 +730,7 @@ impl JsonSchema for LanguageModelProviderSetting {
"zed.dev".into(),
"copilot_chat".into(),
"deepseek".into(),
"openrouter".into(),
"mistral".into(),
]),
..Default::default()
@@ -759,50 +778,50 @@ pub struct ContextServerPresetContent {
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
#[schemars(deny_unknown_fields)]
pub struct AssistantSettingsContentV1 {
/// Whether the Assistant is enabled.
pub struct AgentSettingsContentV1 {
/// Whether the Agent is enabled.
///
/// Default: true
enabled: Option<bool>,
/// Whether to show the assistant panel button in the status bar.
/// Whether to show the Agent panel button in the status bar.
///
/// Default: true
button: Option<bool>,
/// Where to dock the assistant.
/// Where to dock the Agent.
///
/// Default: right
dock: Option<AssistantDockPosition>,
/// Default width in pixels when the assistant is docked to the left or right.
dock: Option<AgentDockPosition>,
/// Default width in pixels when the Agent is docked to the left or right.
///
/// Default: 640
default_width: Option<f32>,
/// Default height in pixels when the assistant is docked to the bottom.
/// Default height in pixels when the Agent is docked to the bottom.
///
/// Default: 320
default_height: Option<f32>,
/// The provider of the assistant service.
/// The provider of the Agent service.
///
/// This can be "openai", "anthropic", "ollama", "lmstudio", "deepseek", "zed.dev"
/// each with their respective default models and configurations.
provider: Option<AssistantProviderContentV1>,
provider: Option<AgentProviderContentV1>,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
#[schemars(deny_unknown_fields)]
pub struct LegacyAssistantSettingsContent {
/// Whether to show the assistant panel button in the status bar.
pub struct LegacyAgentSettingsContent {
/// Whether to show the Agent panel button in the status bar.
///
/// Default: true
pub button: Option<bool>,
/// Where to dock the assistant.
/// Where to dock the Agent.
///
/// Default: right
pub dock: Option<AssistantDockPosition>,
/// Default width in pixels when the assistant is docked to the left or right.
pub dock: Option<AgentDockPosition>,
/// Default width in pixels when the Agent is docked to the left or right.
///
/// Default: 640
pub default_width: Option<f32>,
/// Default height in pixels when the assistant is docked to the bottom.
/// Default height in pixels when the Agent is docked to the bottom.
///
/// Default: 320
pub default_height: Option<f32>,
@@ -816,20 +835,20 @@ pub struct LegacyAssistantSettingsContent {
pub openai_api_url: Option<String>,
}
impl Settings for AssistantSettings {
impl Settings for AgentSettings {
const KEY: Option<&'static str> = Some("agent");
const FALLBACK_KEY: Option<&'static str> = Some("assistant");
const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
type FileContent = AssistantSettingsContent;
type FileContent = AgentSettingsContent;
fn load(
sources: SettingsSources<Self::FileContent>,
_: &mut gpui::App,
) -> anyhow::Result<Self> {
let mut settings = AssistantSettings::default();
let mut settings = AgentSettings::default();
for value in sources.defaults_and_customizations() {
if value.is_version_outdated() {
@@ -867,9 +886,14 @@ impl Settings for AssistantSettings {
&mut settings.notify_when_agent_waiting,
value.notify_when_agent_waiting,
);
merge(
&mut settings.play_sound_when_agent_done,
value.play_sound_when_agent_done,
);
merge(&mut settings.stream_edits, value.stream_edits);
merge(&mut settings.single_file_review, value.single_file_review);
merge(&mut settings.default_profile, value.default_profile);
merge(&mut settings.default_view, value.default_view);
merge(
&mut settings.preferred_completion_mode,
value.preferred_completion_mode,
@@ -919,28 +943,25 @@ impl Settings for AssistantSettings {
.and_then(|b| b.as_bool())
{
match &mut current.inner {
Some(AssistantSettingsContentInner::Versioned(versioned)) => {
match versioned.as_mut() {
VersionedAssistantSettingsContent::V1(setting) => {
setting.enabled = Some(b);
setting.button = Some(b);
}
VersionedAssistantSettingsContent::V2(setting) => {
setting.enabled = Some(b);
setting.button = Some(b);
}
Some(AgentSettingsContentInner::Versioned(versioned)) => match versioned.as_mut() {
VersionedAgentSettingsContent::V1(setting) => {
setting.enabled = Some(b);
setting.button = Some(b);
}
}
Some(AssistantSettingsContentInner::Legacy(setting)) => setting.button = Some(b),
VersionedAgentSettingsContent::V2(setting) => {
setting.enabled = Some(b);
setting.button = Some(b);
}
},
Some(AgentSettingsContentInner::Legacy(setting)) => setting.button = Some(b),
None => {
current.inner = Some(AssistantSettingsContentInner::for_v2(
AssistantSettingsContentV2 {
current.inner =
Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
enabled: Some(b),
button: Some(b),
..Default::default()
},
));
}));
}
}
}
@@ -962,7 +983,7 @@ mod tests {
use super::*;
#[gpui::test]
async fn test_deserialize_assistant_settings_with_version(cx: &mut TestAppContext) {
async fn test_deserialize_agent_settings_with_version(cx: &mut TestAppContext) {
let fs = fs::FakeFs::new(cx.executor().clone());
fs.create_dir(paths::settings_file().parent().unwrap())
.await
@@ -971,51 +992,51 @@ mod tests {
cx.update(|cx| {
let test_settings = settings::SettingsStore::test(cx);
cx.set_global(test_settings);
AssistantSettings::register(cx);
AgentSettings::register(cx);
});
cx.update(|cx| {
assert!(!AssistantSettings::get_global(cx).using_outdated_settings_version);
assert!(!AgentSettings::get_global(cx).using_outdated_settings_version);
assert_eq!(
AssistantSettings::get_global(cx).default_model,
AgentSettings::get_global(cx).default_model,
LanguageModelSelection {
provider: "zed.dev".into(),
model: "claude-3-7-sonnet-latest".into(),
model: "claude-sonnet-4".into(),
}
);
});
cx.update(|cx| {
settings::SettingsStore::global(cx).update_settings_file::<AssistantSettings>(
settings::SettingsStore::global(cx).update_settings_file::<AgentSettings>(
fs.clone(),
|settings, _| {
*settings = AssistantSettingsContent {
inner: Some(AssistantSettingsContentInner::for_v2(
AssistantSettingsContentV2 {
default_model: Some(LanguageModelSelection {
provider: "test-provider".into(),
model: "gpt-99".into(),
}),
inline_assistant_model: None,
commit_message_model: None,
thread_summary_model: None,
inline_alternatives: None,
enabled: None,
button: None,
dock: None,
default_width: None,
default_height: None,
default_profile: None,
profiles: None,
always_allow_tool_actions: None,
notify_when_agent_waiting: None,
stream_edits: None,
single_file_review: None,
enable_feedback: None,
model_parameters: Vec::new(),
preferred_completion_mode: None,
},
)),
*settings = AgentSettingsContent {
inner: Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
default_model: Some(LanguageModelSelection {
provider: "test-provider".into(),
model: "gpt-99".into(),
}),
inline_assistant_model: None,
commit_message_model: None,
thread_summary_model: None,
inline_alternatives: None,
enabled: None,
button: None,
dock: None,
default_width: None,
default_height: None,
default_profile: None,
default_view: None,
profiles: None,
always_allow_tool_actions: None,
play_sound_when_agent_done: None,
notify_when_agent_waiting: None,
stream_edits: None,
single_file_review: None,
enable_feedback: None,
model_parameters: Vec::new(),
preferred_completion_mode: None,
})),
}
},
);
@@ -1027,14 +1048,14 @@ mod tests {
assert!(raw_settings_value.contains(r#""version": "2""#));
#[derive(Debug, Deserialize)]
struct AssistantSettingsTest {
agent: AssistantSettingsContent,
struct AgentSettingsTest {
agent: AgentSettingsContent,
}
let assistant_settings: AssistantSettingsTest =
let agent_settings: AgentSettingsTest =
serde_json_lenient::from_str(&raw_settings_value).unwrap();
assert!(!assistant_settings.agent.is_version_outdated());
assert!(!agent_settings.agent.is_version_outdated());
}
#[gpui::test]
@@ -1059,29 +1080,27 @@ mod tests {
.set_user_settings(user_settings_content, cx)
.unwrap();
cx.set_global(test_settings);
AssistantSettings::register(cx);
AgentSettings::register(cx);
});
cx.run_until_parked();
let assistant_settings = cx.update(|cx| AssistantSettings::get_global(cx).clone());
assert!(assistant_settings.enabled);
assert!(!assistant_settings.using_outdated_settings_version);
assert_eq!(assistant_settings.default_model.model, "gpt-99");
let agent_settings = cx.update(|cx| AgentSettings::get_global(cx).clone());
assert!(agent_settings.enabled);
assert!(!agent_settings.using_outdated_settings_version);
assert_eq!(agent_settings.default_model.model, "gpt-99");
cx.update_global::<SettingsStore, _>(|settings_store, cx| {
settings_store.update_user_settings::<AssistantSettings>(cx, |settings| {
*settings = AssistantSettingsContent {
inner: Some(AssistantSettingsContentInner::for_v2(
AssistantSettingsContentV2 {
enabled: Some(false),
default_model: Some(LanguageModelSelection {
provider: "xai".to_owned().into(),
model: "grok".to_owned(),
}),
..Default::default()
},
)),
settings_store.update_user_settings::<AgentSettings>(cx, |settings| {
*settings = AgentSettingsContent {
inner: Some(AgentSettingsContentInner::for_v2(AgentSettingsContentV2 {
enabled: Some(false),
default_model: Some(LanguageModelSelection {
provider: "xai".to_owned().into(),
model: "grok".to_owned(),
}),
..Default::default()
})),
};
});
});
@@ -1091,12 +1110,12 @@ mod tests {
let settings = cx.update(|cx| SettingsStore::global(cx).raw_user_settings().clone());
#[derive(Debug, Deserialize)]
struct AssistantSettingsTest {
assistant: AssistantSettingsContent,
struct AgentSettingsTest {
assistant: AgentSettingsContent,
agent: Option<serde_json_lenient::Value>,
}
let assistant_settings: AssistantSettingsTest = serde_json::from_value(settings).unwrap();
assert!(assistant_settings.agent.is_none());
let agent_settings: AgentSettingsTest = serde_json::from_value(settings).unwrap();
assert!(agent_settings.agent.is_none());
}
}

View File

@@ -34,7 +34,6 @@ pub enum AnthropicModelMode {
pub enum Model {
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
Claude3_5Sonnet,
#[default]
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
Claude3_7Sonnet,
#[serde(
@@ -42,6 +41,21 @@ pub enum Model {
alias = "claude-3-7-sonnet-thinking-latest"
)]
Claude3_7SonnetThinking,
#[serde(rename = "claude-opus-4", alias = "claude-opus-4-latest")]
ClaudeOpus4,
#[serde(
rename = "claude-opus-4-thinking",
alias = "claude-opus-4-thinking-latest"
)]
ClaudeOpus4Thinking,
#[default]
#[serde(rename = "claude-sonnet-4", alias = "claude-sonnet-4-latest")]
ClaudeSonnet4,
#[serde(
rename = "claude-sonnet-4-thinking",
alias = "claude-sonnet-4-thinking-latest"
)]
ClaudeSonnet4Thinking,
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
Claude3_5Haiku,
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
@@ -89,6 +103,14 @@ impl Model {
Ok(Self::Claude3Sonnet)
} else if id.starts_with("claude-3-haiku") {
Ok(Self::Claude3Haiku)
} else if id.starts_with("claude-opus-4-thinking") {
Ok(Self::ClaudeOpus4Thinking)
} else if id.starts_with("claude-opus-4") {
Ok(Self::ClaudeOpus4)
} else if id.starts_with("claude-sonnet-4-thinking") {
Ok(Self::ClaudeSonnet4Thinking)
} else if id.starts_with("claude-sonnet-4") {
Ok(Self::ClaudeSonnet4)
} else {
anyhow::bail!("invalid model id {id}");
}
@@ -96,6 +118,10 @@ impl Model {
pub fn id(&self) -> &str {
match self {
Model::ClaudeOpus4 => "claude-opus-4-latest",
Model::ClaudeOpus4Thinking => "claude-opus-4-thinking-latest",
Model::ClaudeSonnet4 => "claude-sonnet-4-latest",
Model::ClaudeSonnet4Thinking => "claude-sonnet-4-thinking-latest",
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
Model::Claude3_7Sonnet => "claude-3-7-sonnet-latest",
Model::Claude3_7SonnetThinking => "claude-3-7-sonnet-thinking-latest",
@@ -110,6 +136,8 @@ impl Model {
/// The id of the model that should be used for making API requests
pub fn request_id(&self) -> &str {
match self {
Model::ClaudeOpus4 | Model::ClaudeOpus4Thinking => "claude-opus-4-20250514",
Model::ClaudeSonnet4 | Model::ClaudeSonnet4Thinking => "claude-sonnet-4-20250514",
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
Model::Claude3_7Sonnet | Model::Claude3_7SonnetThinking => "claude-3-7-sonnet-latest",
Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
@@ -122,6 +150,10 @@ impl Model {
pub fn display_name(&self) -> &str {
match self {
Model::ClaudeOpus4 => "Claude Opus 4",
Model::ClaudeOpus4Thinking => "Claude Opus 4 Thinking",
Model::ClaudeSonnet4 => "Claude Sonnet 4",
Model::ClaudeSonnet4Thinking => "Claude Sonnet 4 Thinking",
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
Self::Claude3_7SonnetThinking => "Claude 3.7 Sonnet Thinking",
@@ -137,7 +169,11 @@ impl Model {
pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> {
match self {
Self::Claude3_5Sonnet
Self::ClaudeOpus4
| Self::ClaudeOpus4Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::Claude3_5Sonnet
| Self::Claude3_5Haiku
| Self::Claude3_7Sonnet
| Self::Claude3_7SonnetThinking
@@ -156,7 +192,11 @@ impl Model {
pub fn max_token_count(&self) -> usize {
match self {
Self::Claude3_5Sonnet
Self::ClaudeOpus4
| Self::ClaudeOpus4Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::Claude3_5Sonnet
| Self::Claude3_5Haiku
| Self::Claude3_7Sonnet
| Self::Claude3_7SonnetThinking
@@ -173,7 +213,11 @@ impl Model {
Self::Claude3_5Sonnet
| Self::Claude3_7Sonnet
| Self::Claude3_7SonnetThinking
| Self::Claude3_5Haiku => 8_192,
| Self::Claude3_5Haiku
| Self::ClaudeOpus4
| Self::ClaudeOpus4Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking => 8_192,
Self::Custom {
max_output_tokens, ..
} => max_output_tokens.unwrap_or(4_096),
@@ -182,7 +226,11 @@ impl Model {
pub fn default_temperature(&self) -> f32 {
match self {
Self::Claude3_5Sonnet
Self::ClaudeOpus4
| Self::ClaudeOpus4Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::Claude3_5Sonnet
| Self::Claude3_7Sonnet
| Self::Claude3_7SonnetThinking
| Self::Claude3_5Haiku
@@ -201,10 +249,14 @@ impl Model {
Self::Claude3_5Sonnet
| Self::Claude3_7Sonnet
| Self::Claude3_5Haiku
| Self::ClaudeOpus4
| Self::ClaudeSonnet4
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3Haiku => AnthropicModelMode::Default,
Self::Claude3_7SonnetThinking => AnthropicModelMode::Thinking {
Self::Claude3_7SonnetThinking
| Self::ClaudeOpus4Thinking
| Self::ClaudeSonnet4Thinking => AnthropicModelMode::Thinking {
budget_tokens: Some(4_096),
},
Self::Custom { mode, .. } => mode.clone(),

View File

@@ -12,8 +12,8 @@ workspace = true
path = "src/assistant_context_editor.rs"
[dependencies]
agent_settings.workspace = true
anyhow.workspace = true
assistant_settings.workspace = true
assistant_slash_command.workspace = true
assistant_slash_commands.workspace = true
chrono.workspace = true
@@ -57,8 +57,10 @@ uuid.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
indoc.workspace = true
language_model = { workspace = true, features = ["test-support"] }
languages = { workspace = true, features = ["test-support"] }
pretty_assertions.workspace = true

View File

@@ -3,6 +3,7 @@ mod context_editor;
mod context_history;
mod context_store;
pub mod language_model_selector;
mod max_mode_tooltip;
mod slash_command;
mod slash_command_picker;

View File

@@ -1,8 +1,8 @@
#[cfg(test)]
mod context_tests;
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result, bail};
use assistant_settings::AssistantSettings;
use assistant_slash_command::{
SlashCommandContent, SlashCommandEvent, SlashCommandLine, SlashCommandOutputSection,
SlashCommandResult, SlashCommandWorkingSet,
@@ -29,6 +29,7 @@ use paths::contexts_dir;
use project::Project;
use prompt_store::PromptBuilder;
use serde::{Deserialize, Serialize};
use settings::Settings;
use smallvec::SmallVec;
use std::{
cmp::{Ordering, max},
@@ -44,6 +45,7 @@ use text::{BufferSnapshot, ToPoint};
use ui::IconName;
use util::{ResultExt, TryFutureExt, post_inc};
use uuid::Uuid;
use zed_llm_client::CompletionIntent;
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ContextId(String);
@@ -682,6 +684,7 @@ pub struct AssistantContext {
language_registry: Arc<LanguageRegistry>,
project: Option<Entity<Project>>,
prompt_builder: Arc<PromptBuilder>,
completion_mode: agent_settings::CompletionMode,
}
trait ContextAnnotation {
@@ -718,6 +721,14 @@ impl AssistantContext {
)
}
pub fn completion_mode(&self) -> agent_settings::CompletionMode {
self.completion_mode
}
pub fn set_completion_mode(&mut self, completion_mode: agent_settings::CompletionMode) {
self.completion_mode = completion_mode;
}
pub fn new(
id: ContextId,
replica_id: ReplicaId,
@@ -764,6 +775,7 @@ impl AssistantContext {
pending_cache_warming_task: Task::ready(None),
_subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
pending_save: Task::ready(Ok(())),
completion_mode: AgentSettings::get_global(cx).preferred_completion_mode,
path: None,
buffer,
telemetry,
@@ -1730,9 +1742,8 @@ impl AssistantContext {
merge_same_roles,
} => {
if !merge_same_roles && Some(role) != last_role {
let offset = this.buffer.read_with(cx, |buffer, _cx| {
insert_position.to_offset(buffer)
});
let buffer = this.buffer.read(cx);
let offset = insert_position.to_offset(buffer);
this.insert_message_at_offset(
offset,
role,
@@ -2204,6 +2215,7 @@ impl AssistantContext {
StopReason::ToolUse => {}
StopReason::EndTurn => {}
StopReason::MaxTokens => {}
StopReason::Refusal => {}
}
}
})
@@ -2261,13 +2273,13 @@ impl AssistantContext {
let mut completion_request = LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: Some(CompletionIntent::UserPrompt),
mode: None,
messages: Vec::new(),
tools: Vec::new(),
tool_choice: None,
stop: Vec::new(),
temperature: model
.and_then(|model| AssistantSettings::temperature_for_model(model, cx)),
temperature: model.and_then(|model| AgentSettings::temperature_for_model(model, cx)),
};
for message in self.messages(cx) {
if message.status != MessageStatus::Done {
@@ -2322,7 +2334,15 @@ impl AssistantContext {
completion_request.messages.push(request_message);
}
}
let supports_max_mode = if let Some(model) = model {
model.supports_max_mode()
} else {
false
};
if supports_max_mode {
completion_request.mode = Some(self.completion_mode.into());
}
completion_request
}

View File

@@ -1210,8 +1210,8 @@ async fn test_summarization(cx: &mut TestAppContext) {
});
cx.run_until_parked();
fake_model.stream_last_completion_response("Brief".into());
fake_model.stream_last_completion_response(" Introduction".into());
fake_model.stream_last_completion_response("Brief");
fake_model.stream_last_completion_response(" Introduction");
fake_model.end_last_completion_stream();
cx.run_until_parked();
@@ -1274,7 +1274,7 @@ async fn test_thread_summary_error_retry(cx: &mut TestAppContext) {
});
cx.run_until_parked();
fake_model.stream_last_completion_response("A successful summary".into());
fake_model.stream_last_completion_response("A successful summary");
fake_model.end_last_completion_stream();
cx.run_until_parked();
@@ -1356,7 +1356,7 @@ fn setup_context_editor_with_fake_model(
fn simulate_successful_response(fake_model: &FakeLanguageModel, cx: &mut TestAppContext) {
cx.run_until_parked();
fake_model.stream_last_completion_response("Assistant response".into());
fake_model.stream_last_completion_response("Assistant response");
fake_model.end_last_completion_stream();
cx.run_until_parked();
}
@@ -1386,7 +1386,7 @@ fn init_test(cx: &mut App) {
LanguageModelRegistry::test(cx);
cx.set_global(settings_store);
language::init(cx);
assistant_settings::init(cx);
agent_settings::init(cx);
Project::init_settings(cx);
}

View File

@@ -1,8 +1,11 @@
use crate::language_model_selector::{
LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
use crate::{
language_model_selector::{
LanguageModelSelector, ToggleModelSelector, language_model_selector,
},
max_mode_tooltip::MaxModeTooltip,
};
use agent_settings::{AgentSettings, CompletionMode};
use anyhow::Result;
use assistant_settings::AssistantSettings;
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};
use assistant_slash_commands::{
DefaultSlashCommand, DocsSlashCommand, DocsSlashCommandArgs, FileSlashCommand,
@@ -40,7 +43,7 @@ use language_model::{
Role,
};
use multi_buffer::MultiBufferRow;
use picker::Picker;
use picker::{Picker, popover_menu::PickerPopoverMenu};
use project::{Project, Worktree};
use project::{ProjectPath, lsp_store::LocalLspAdapterDelegate};
use rope::Point;
@@ -51,6 +54,7 @@ use std::{
cmp,
ops::Range,
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
time::Duration,
};
@@ -234,7 +238,7 @@ impl ContextEditor {
editor.set_show_breakpoints(false, cx);
editor.set_show_wrap_guides(false, cx);
editor.set_show_indent_guides(false, cx);
editor.set_completion_provider(Some(Box::new(completion_provider)));
editor.set_completion_provider(Some(Rc::new(completion_provider)));
editor.set_menu_inline_completions_policy(MenuInlineCompletionsPolicy::Never);
editor.set_collaboration_hub(Box::new(project.clone()));
@@ -279,10 +283,10 @@ impl ContextEditor {
slash_menu_handle: Default::default(),
dragged_file_worktrees: Vec::new(),
language_model_selector: cx.new(|cx| {
LanguageModelSelector::new(
language_model_selector(
|cx| LanguageModelRegistry::read_global(cx).default_model(),
move |model, cx| {
update_settings_file::<AssistantSettings>(
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _| settings.set_model(model.clone()),
@@ -1642,34 +1646,35 @@ impl ContextEditor {
let context = self.context.read(cx);
let mut text = String::new();
for message in context.messages(cx) {
if message.offset_range.start >= selection.range().end {
break;
} else if message.offset_range.end >= selection.range().start {
let range = cmp::max(message.offset_range.start, selection.range().start)
..cmp::min(message.offset_range.end, selection.range().end);
if range.is_empty() {
let snapshot = context.buffer().read(cx).snapshot();
let point = snapshot.offset_to_point(range.start);
selection.start = snapshot.point_to_offset(Point::new(point.row, 0));
selection.end = snapshot.point_to_offset(cmp::min(
Point::new(point.row + 1, 0),
snapshot.max_point(),
));
for chunk in context.buffer().read(cx).text_for_range(selection.range()) {
text.push_str(chunk);
}
} else {
for chunk in context.buffer().read(cx).text_for_range(range) {
text.push_str(chunk);
}
if message.offset_range.end < selection.range().end {
text.push('\n');
// If selection is empty, we want to copy the entire line
if selection.range().is_empty() {
let snapshot = context.buffer().read(cx).snapshot();
let point = snapshot.offset_to_point(selection.range().start);
selection.start = snapshot.point_to_offset(Point::new(point.row, 0));
selection.end = snapshot
.point_to_offset(cmp::min(Point::new(point.row + 1, 0), snapshot.max_point()));
for chunk in context.buffer().read(cx).text_for_range(selection.range()) {
text.push_str(chunk);
}
} else {
for message in context.messages(cx) {
if message.offset_range.start >= selection.range().end {
break;
} else if message.offset_range.end >= selection.range().start {
let range = cmp::max(message.offset_range.start, selection.range().start)
..cmp::min(message.offset_range.end, selection.range().end);
if !range.is_empty() {
for chunk in context.buffer().read(cx).text_for_range(range) {
text.push_str(chunk);
}
if message.offset_range.end < selection.range().end {
text.push('\n');
}
}
}
}
}
(text, CopyMetadata { creases }, vec![selection])
}
@@ -1902,7 +1907,7 @@ impl ContextEditor {
.on_click(cx.listener(|this, _event, _window, cx| {
let client = this
.workspace
.update(cx, |workspace, _| workspace.client().clone())
.read_with(cx, |workspace, _| workspace.client().clone())
.log_err();
if let Some(client) = client {
@@ -2007,17 +2012,17 @@ impl ContextEditor {
None => (ButtonStyle::Filled, None),
};
ButtonLike::new("send_button")
Button::new("send_button", "Send")
.label_size(LabelSize::Small)
.disabled(self.sending_disabled(cx))
.style(style)
.when_some(tooltip, |button, tooltip| {
button.tooltip(move |_, _| tooltip.clone())
})
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Send"))
.children(
.key_binding(
KeyBinding::for_action_in(&Assist, &focus_handle, window, cx)
.map(|binding| binding.into_any_element()),
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(move |_event, window, cx| {
focus_handle.dispatch_action(&Assist, window, cx);
@@ -2057,7 +2062,50 @@ impl ContextEditor {
)
}
fn render_language_model_selector(&self, cx: &mut Context<Self>) -> impl IntoElement {
fn render_max_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let context = self.context().read(cx);
let active_model = LanguageModelRegistry::read_global(cx)
.default_model()
.map(|default| default.model)?;
if !active_model.supports_max_mode() {
return None;
}
let active_completion_mode = context.completion_mode();
let burn_mode_enabled = active_completion_mode == CompletionMode::Burn;
let icon = if burn_mode_enabled {
IconName::ZedBurnModeOn
} else {
IconName::ZedBurnMode
};
Some(
IconButton::new("burn-mode", icon)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.toggle_state(burn_mode_enabled)
.selected_icon_color(Color::Error)
.on_click(cx.listener(move |this, _event, _window, cx| {
this.context().update(cx, |context, _cx| {
context.set_completion_mode(match active_completion_mode {
CompletionMode::Burn => CompletionMode::Normal,
CompletionMode::Normal => CompletionMode::Burn,
});
});
}))
.tooltip(move |_window, cx| {
cx.new(|_| MaxModeTooltip::new().selected(burn_mode_enabled))
.into()
})
.into_any_element(),
)
}
fn render_language_model_selector(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx)
.default_model()
.map(|default| default.model);
@@ -2067,7 +2115,7 @@ impl ContextEditor {
None => SharedString::from("No model selected"),
};
LanguageModelSelectorPopoverMenu::new(
PickerPopoverMenu::new(
self.language_model_selector.clone(),
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
@@ -2095,8 +2143,10 @@ impl ContextEditor {
)
},
gpui::Corner::BottomLeft,
cx,
)
.with_handle(self.language_model_selector_menu_handle.clone())
.render(window, cx)
}
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
@@ -2502,6 +2552,7 @@ impl Render for ContextEditor {
let provider = LanguageModelRegistry::read_global(cx)
.default_model()
.map(|default| default.provider);
let accept_terms = if self.show_accept_terms {
provider.as_ref().and_then(|provider| {
provider.render_accept_terms(LanguageModelProviderTosView::PromptEditorPopup, cx)
@@ -2511,6 +2562,8 @@ impl Render for ContextEditor {
};
let language_model_selector = self.language_model_selector_menu_handle.clone();
let max_mode_toggle = self.render_max_mode_toggle(cx);
v_flex()
.key_context("ContextEditor")
.capture_action(cx.listener(ContextEditor::cancel))
@@ -2550,31 +2603,28 @@ impl Render for ContextEditor {
})
.children(self.render_last_error(cx))
.child(
h_flex().w_full().relative().child(
h_flex()
.p_2()
.w_full()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.bg(cx.theme().colors().editor_background)
.child(
h_flex()
.gap_1()
.child(self.render_inject_context_menu(cx))
.child(ui::Divider::vertical())
.child(
div()
.pl_0p5()
.child(self.render_language_model_selector(cx)),
),
)
.child(
h_flex()
.w_full()
.justify_end()
.child(self.render_send_button(window, cx)),
),
),
h_flex()
.relative()
.py_2()
.pl_1p5()
.pr_2()
.w_full()
.justify_between()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.bg(cx.theme().colors().editor_background)
.child(
h_flex()
.gap_0p5()
.child(self.render_inject_context_menu(cx))
.when_some(max_mode_toggle, |this, element| this.child(element)),
)
.child(
h_flex()
.gap_1()
.child(self.render_language_model_selector(window, cx))
.child(self.render_send_button(window, cx)),
),
)
}
}
@@ -3215,74 +3265,92 @@ mod tests {
use super::*;
use fs::FakeFs;
use gpui::{App, TestAppContext, VisualTestContext};
use indoc::indoc;
use language::{Buffer, LanguageRegistry};
use pretty_assertions::assert_eq;
use prompt_store::PromptBuilder;
use text::OffsetRangeExt;
use unindent::Unindent;
use util::path;
#[gpui::test]
async fn test_copy_paste_whole_message(cx: &mut TestAppContext) {
let (context, context_editor, mut cx) = setup_context_editor_text(vec![
(Role::User, "What is the Zed editor?"),
(
Role::Assistant,
"Zed is a modern, high-performance code editor designed from the ground up for speed and collaboration.",
),
(Role::User, ""),
],cx).await;
// Select & Copy whole user message
assert_copy_paste_context_editor(
&context_editor,
message_range(&context, 0, &mut cx),
indoc! {"
What is the Zed editor?
Zed is a modern, high-performance code editor designed from the ground up for speed and collaboration.
What is the Zed editor?
"},
&mut cx,
);
// Select & Copy whole assistant message
assert_copy_paste_context_editor(
&context_editor,
message_range(&context, 1, &mut cx),
indoc! {"
What is the Zed editor?
Zed is a modern, high-performance code editor designed from the ground up for speed and collaboration.
What is the Zed editor?
Zed is a modern, high-performance code editor designed from the ground up for speed and collaboration.
"},
&mut cx,
);
}
#[gpui::test]
async fn test_copy_paste_no_selection(cx: &mut TestAppContext) {
cx.update(init_test);
let (context, context_editor, mut cx) = setup_context_editor_text(
vec![
(Role::User, "user1"),
(Role::Assistant, "assistant1"),
(Role::Assistant, "assistant2"),
(Role::User, ""),
],
cx,
)
.await;
let fs = FakeFs::new(cx.executor());
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new(|cx| {
AssistantContext::local(
registry,
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
cx,
)
});
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let workspace = window.root(cx).unwrap();
let cx = &mut VisualTestContext::from_window(*window, cx);
// Copy and paste first assistant message
let message_2_range = message_range(&context, 1, &mut cx);
assert_copy_paste_context_editor(
&context_editor,
message_2_range.start..message_2_range.start,
indoc! {"
user1
assistant1
assistant2
assistant1
"},
&mut cx,
);
let context_editor = window
.update(cx, |_, window, cx| {
cx.new(|cx| {
ContextEditor::for_context(
context,
fs,
workspace.downgrade(),
project,
None,
window,
cx,
)
})
})
.unwrap();
context_editor.update_in(cx, |context_editor, window, cx| {
context_editor.editor.update(cx, |editor, cx| {
editor.set_text("abc\ndef\nghi", window, cx);
editor.move_to_beginning(&Default::default(), window, cx);
})
});
context_editor.update_in(cx, |context_editor, window, cx| {
context_editor.editor.update(cx, |editor, cx| {
editor.copy(&Default::default(), window, cx);
editor.paste(&Default::default(), window, cx);
assert_eq!(editor.text(cx), "abc\nabc\ndef\nghi");
})
});
context_editor.update_in(cx, |context_editor, window, cx| {
context_editor.editor.update(cx, |editor, cx| {
editor.cut(&Default::default(), window, cx);
assert_eq!(editor.text(cx), "abc\ndef\nghi");
editor.paste(&Default::default(), window, cx);
assert_eq!(editor.text(cx), "abc\nabc\ndef\nghi");
})
});
// Copy and cut second assistant message
let message_3_range = message_range(&context, 2, &mut cx);
assert_copy_paste_context_editor(
&context_editor,
message_3_range.start..message_3_range.start,
indoc! {"
user1
assistant1
assistant2
assistant1
assistant2
"},
&mut cx,
);
}
#[gpui::test]
@@ -3359,13 +3427,136 @@ mod tests {
}
}
async fn setup_context_editor_text(
messages: Vec<(Role, &str)>,
cx: &mut TestAppContext,
) -> (
Entity<AssistantContext>,
Entity<ContextEditor>,
VisualTestContext,
) {
cx.update(init_test);
let fs = FakeFs::new(cx.executor());
let context = create_context_with_messages(messages, cx);
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let workspace = window.root(cx).unwrap();
let mut cx = VisualTestContext::from_window(*window, cx);
let context_editor = window
.update(&mut cx, |_, window, cx| {
cx.new(|cx| {
let editor = ContextEditor::for_context(
context.clone(),
fs,
workspace.downgrade(),
project,
None,
window,
cx,
);
editor
})
})
.unwrap();
(context, context_editor, cx)
}
fn message_range(
context: &Entity<AssistantContext>,
message_ix: usize,
cx: &mut TestAppContext,
) -> Range<usize> {
context.update(cx, |context, cx| {
context
.messages(cx)
.nth(message_ix)
.unwrap()
.anchor_range
.to_offset(&context.buffer().read(cx).snapshot())
})
}
fn assert_copy_paste_context_editor<T: editor::ToOffset>(
context_editor: &Entity<ContextEditor>,
range: Range<T>,
expected_text: &str,
cx: &mut VisualTestContext,
) {
context_editor.update_in(cx, |context_editor, window, cx| {
context_editor.editor.update(cx, |editor, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([range]));
});
context_editor.copy(&Default::default(), window, cx);
context_editor.editor.update(cx, |editor, cx| {
editor.move_to_end(&Default::default(), window, cx);
});
context_editor.paste(&Default::default(), window, cx);
context_editor.editor.update(cx, |editor, cx| {
assert_eq!(editor.text(cx), expected_text);
});
});
}
fn create_context_with_messages(
mut messages: Vec<(Role, &str)>,
cx: &mut TestAppContext,
) -> Entity<AssistantContext> {
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
cx.new(|cx| {
let mut context = AssistantContext::local(
registry,
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
cx,
);
let mut message_1 = context.messages(cx).next().unwrap();
let (role, text) = messages.remove(0);
loop {
if role == message_1.role {
context.buffer().update(cx, |buffer, cx| {
buffer.edit([(message_1.offset_range, text)], None, cx);
});
break;
}
let mut ids = HashSet::default();
ids.insert(message_1.id);
context.cycle_message_roles(ids, cx);
message_1 = context.messages(cx).next().unwrap();
}
let mut last_message_id = message_1.id;
for (role, text) in messages {
context.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
let message = context.messages(cx).last().unwrap();
last_message_id = message.id;
context.buffer().update(cx, |buffer, cx| {
buffer.edit([(message.offset_range, text)], None, cx);
})
}
context
})
}
fn init_test(cx: &mut App) {
let settings_store = SettingsStore::test(cx);
prompt_store::init(cx);
LanguageModelRegistry::test(cx);
cx.set_global(settings_store);
language::init(cx);
assistant_settings::init(cx);
agent_settings::init(cx);
Project::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
workspace::init_settings(cx);

View File

@@ -4,8 +4,7 @@ use collections::{HashSet, IndexMap};
use feature_flags::ZedProFeatureFlag;
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
use gpui::{
Action, AnyElement, AnyView, App, BackgroundExecutor, Corner, DismissEvent, Entity,
EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
Action, AnyElement, App, BackgroundExecutor, DismissEvent, Subscription, Task,
action_with_deprecated_aliases,
};
use language_model::{
@@ -15,7 +14,7 @@ use language_model::{
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate};
use proto::Plan;
use ui::{ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger, prelude::*};
use ui::{ListItem, ListItemSpacing, prelude::*};
action_with_deprecated_aliases!(
agent,
@@ -31,77 +30,128 @@ const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &mut App) + 'static>;
type GetActiveModel = Arc<dyn Fn(&App) -> Option<ConfiguredModel> + 'static>;
pub struct LanguageModelSelector {
picker: Entity<Picker<LanguageModelPickerDelegate>>,
pub type LanguageModelSelector = Picker<LanguageModelPickerDelegate>;
pub fn language_model_selector(
get_active_model: impl Fn(&App) -> Option<ConfiguredModel> + 'static,
on_model_changed: impl Fn(Arc<dyn LanguageModel>, &mut App) + 'static,
window: &mut Window,
cx: &mut Context<LanguageModelSelector>,
) -> LanguageModelSelector {
let delegate = LanguageModelPickerDelegate::new(get_active_model, on_model_changed, window, cx);
Picker::list(delegate, window, cx)
.show_scrollbar(true)
.width(rems(20.))
.max_height(Some(rems(20.).into()))
}
fn all_models(cx: &App) -> GroupedModels {
let providers = LanguageModelRegistry::global(cx).read(cx).providers();
let recommended = providers
.iter()
.flat_map(|provider| {
provider
.recommended_models(cx)
.into_iter()
.map(|model| ModelInfo {
model,
icon: provider.icon(),
})
})
.collect();
let other = providers
.iter()
.flat_map(|provider| {
provider
.provided_models(cx)
.into_iter()
.map(|model| ModelInfo {
model,
icon: provider.icon(),
})
})
.collect();
GroupedModels::new(other, recommended)
}
#[derive(Clone)]
struct ModelInfo {
model: Arc<dyn LanguageModel>,
icon: IconName,
}
pub struct LanguageModelPickerDelegate {
on_model_changed: OnModelChanged,
get_active_model: GetActiveModel,
all_models: Arc<GroupedModels>,
filtered_entries: Vec<LanguageModelPickerEntry>,
selected_index: usize,
_authenticate_all_providers_task: Task<()>,
_subscriptions: Vec<Subscription>,
}
impl LanguageModelSelector {
pub fn new(
impl LanguageModelPickerDelegate {
fn new(
get_active_model: impl Fn(&App) -> Option<ConfiguredModel> + 'static,
on_model_changed: impl Fn(Arc<dyn LanguageModel>, &mut App) + 'static,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut Context<Picker<Self>>,
) -> Self {
let on_model_changed = Arc::new(on_model_changed);
let models = all_models(cx);
let entries = models.entries();
let all_models = Self::all_models(cx);
let entries = all_models.entries();
let delegate = LanguageModelPickerDelegate {
language_model_selector: cx.entity().downgrade(),
Self {
on_model_changed: on_model_changed.clone(),
all_models: Arc::new(all_models),
all_models: Arc::new(models),
selected_index: Self::get_active_model_index(&entries, get_active_model(cx)),
filtered_entries: entries,
get_active_model: Arc::new(get_active_model),
};
let picker = cx.new(|cx| {
Picker::list(delegate, window, cx)
.show_scrollbar(true)
.width(rems(20.))
.max_height(Some(rems(20.).into()))
});
let subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent));
LanguageModelSelector {
picker,
_authenticate_all_providers_task: Self::authenticate_all_providers(cx),
_subscriptions: vec![
cx.subscribe_in(
&LanguageModelRegistry::global(cx),
window,
Self::handle_language_model_registry_event,
),
subscription,
],
_subscriptions: vec![cx.subscribe_in(
&LanguageModelRegistry::global(cx),
window,
|picker, _, event, window, cx| {
match event {
language_model::Event::ProviderStateChanged
| language_model::Event::AddedProvider(_)
| language_model::Event::RemovedProvider(_) => {
let query = picker.query(cx);
picker.delegate.all_models = Arc::new(all_models(cx));
// Update matches will automatically drop the previous task
// if we get a provider event again
picker.update_matches(query, window, cx)
}
_ => {}
}
},
)],
}
}
fn handle_language_model_registry_event(
&mut self,
_registry: &Entity<LanguageModelRegistry>,
event: &language_model::Event,
window: &mut Window,
cx: &mut Context<Self>,
) {
match event {
language_model::Event::ProviderStateChanged
| language_model::Event::AddedProvider(_)
| language_model::Event::RemovedProvider(_) => {
self.picker.update(cx, |this, cx| {
let query = this.query(cx);
this.delegate.all_models = Arc::new(Self::all_models(cx));
// Update matches will automatically drop the previous task
// if we get a provider event again
this.update_matches(query, window, cx)
});
}
_ => {}
}
fn get_active_model_index(
entries: &[LanguageModelPickerEntry],
active_model: Option<ConfiguredModel>,
) -> usize {
entries
.iter()
.position(|entry| {
if let LanguageModelPickerEntry::Model(model) = entry {
active_model
.as_ref()
.map(|active_model| {
active_model.model.id() == model.model.id()
&& active_model.provider.id() == model.model.provider_id()
})
.unwrap_or_default()
} else {
false
}
})
.unwrap_or(0)
}
/// Authenticates all providers in the [`LanguageModelRegistry`].
@@ -154,169 +204,9 @@ impl LanguageModelSelector {
})
}
fn all_models(cx: &App) -> GroupedModels {
let mut recommended = Vec::new();
let mut recommended_set = HashSet::default();
for provider in LanguageModelRegistry::global(cx)
.read(cx)
.providers()
.iter()
{
let models = provider.recommended_models(cx);
recommended_set.extend(models.iter().map(|model| (model.provider_id(), model.id())));
recommended.extend(
provider
.recommended_models(cx)
.into_iter()
.map(move |model| ModelInfo {
model: model.clone(),
icon: provider.icon(),
}),
);
}
let other_models = LanguageModelRegistry::global(cx)
.read(cx)
.providers()
.iter()
.map(|provider| {
(
provider.id(),
provider
.provided_models(cx)
.into_iter()
.filter_map(|model| {
let not_included =
!recommended_set.contains(&(model.provider_id(), model.id()));
not_included.then(|| ModelInfo {
model: model.clone(),
icon: provider.icon(),
})
})
.collect::<Vec<_>>(),
)
})
.collect::<IndexMap<_, _>>();
GroupedModels {
recommended,
other: other_models,
}
}
pub fn active_model(&self, cx: &App) -> Option<ConfiguredModel> {
(self.picker.read(cx).delegate.get_active_model)(cx)
(self.get_active_model)(cx)
}
fn get_active_model_index(
entries: &[LanguageModelPickerEntry],
active_model: Option<ConfiguredModel>,
) -> usize {
entries
.iter()
.position(|entry| {
if let LanguageModelPickerEntry::Model(model) = entry {
active_model
.as_ref()
.map(|active_model| {
active_model.model.id() == model.model.id()
&& active_model.provider.id() == model.model.provider_id()
})
.unwrap_or_default()
} else {
false
}
})
.unwrap_or(0)
}
}
impl EventEmitter<DismissEvent> for LanguageModelSelector {}
impl Focusable for LanguageModelSelector {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for LanguageModelSelector {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
self.picker.clone()
}
}
#[derive(IntoElement)]
pub struct LanguageModelSelectorPopoverMenu<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
language_model_selector: Entity<LanguageModelSelector>,
trigger: T,
tooltip: TT,
handle: Option<PopoverMenuHandle<LanguageModelSelector>>,
anchor: Corner,
}
impl<T, TT> LanguageModelSelectorPopoverMenu<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
pub fn new(
language_model_selector: Entity<LanguageModelSelector>,
trigger: T,
tooltip: TT,
anchor: Corner,
) -> Self {
Self {
language_model_selector,
trigger,
tooltip,
handle: None,
anchor,
}
}
pub fn with_handle(mut self, handle: PopoverMenuHandle<LanguageModelSelector>) -> Self {
self.handle = Some(handle);
self
}
}
impl<T, TT> RenderOnce for LanguageModelSelectorPopoverMenu<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let language_model_selector = self.language_model_selector.clone();
PopoverMenu::new("model-switcher")
.menu(move |_window, _cx| Some(language_model_selector.clone()))
.trigger_with_tooltip(self.trigger, self.tooltip)
.anchor(self.anchor)
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
.offset(gpui::Point {
x: px(0.0),
y: px(-2.0),
})
}
}
#[derive(Clone)]
struct ModelInfo {
model: Arc<dyn LanguageModel>,
icon: IconName,
}
pub struct LanguageModelPickerDelegate {
language_model_selector: WeakEntity<LanguageModelSelector>,
on_model_changed: OnModelChanged,
get_active_model: GetActiveModel,
all_models: Arc<GroupedModels>,
filtered_entries: Vec<LanguageModelPickerEntry>,
selected_index: usize,
}
struct GroupedModels {
@@ -326,11 +216,14 @@ struct GroupedModels {
impl GroupedModels {
pub fn new(other: Vec<ModelInfo>, recommended: Vec<ModelInfo>) -> Self {
let recommended_ids: HashSet<_> = recommended.iter().map(|info| info.model.id()).collect();
let recommended_ids = recommended
.iter()
.map(|info| (info.model.provider_id(), info.model.id()))
.collect::<HashSet<_>>();
let mut other_by_provider: IndexMap<_, Vec<ModelInfo>> = IndexMap::default();
for model in other {
if recommended_ids.contains(&model.model.id()) {
if recommended_ids.contains(&(model.model.provider_id(), model.model.id())) {
continue;
}
@@ -577,9 +470,7 @@ impl PickerDelegate for LanguageModelPickerDelegate {
}
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
self.language_model_selector
.update(cx, |_this, cx| cx.emit(DismissEvent))
.ok();
cx.emit(DismissEvent);
}
fn render_match(
@@ -917,4 +808,26 @@ mod tests {
// Recommended models should not appear in "other"
assert_models_eq(actual_other_models, vec!["zed/gemini", "copilot/o3"]);
}
#[gpui::test]
fn test_dont_exclude_models_from_other_providers(_cx: &mut TestAppContext) {
let recommended_models = create_models(vec![("zed", "claude")]);
let all_models = create_models(vec![
("zed", "claude"), // Should be filtered out from "other"
("zed", "gemini"),
("copilot", "claude"), // Should not be filtered out from "other"
]);
let grouped_models = GroupedModels::new(all_models, recommended_models);
let actual_other_models = grouped_models
.other
.values()
.flatten()
.cloned()
.collect::<Vec<_>>();
// Recommended models should not appear in "other"
assert_models_eq(actual_other_models, vec!["zed/gemini", "copilot/claude"]);
}
}

View File

@@ -0,0 +1,61 @@
use gpui::{Context, FontWeight, IntoElement, Render, Window};
use ui::{prelude::*, tooltip_container};
pub struct MaxModeTooltip {
selected: bool,
}
impl MaxModeTooltip {
pub fn new() -> Self {
Self { selected: false }
}
pub fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
}
impl Render for MaxModeTooltip {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (icon, color) = if self.selected {
(IconName::ZedBurnModeOn, Color::Error)
} else {
(IconName::ZedBurnMode, Color::Default)
};
let turned_on = h_flex()
.h_4()
.px_1()
.border_1()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().text_accent.opacity(0.1))
.rounded_sm()
.child(
Label::new("ON")
.size(LabelSize::XSmall)
.weight(FontWeight::SEMIBOLD)
.color(Color::Accent),
);
let title = h_flex()
.gap_1p5()
.child(Icon::new(icon).size(IconSize::Small).color(color))
.child(Label::new("Burn Mode"))
.when(self.selected, |title| title.child(turned_on));
tooltip_container(window, cx, |this, _, _| {
this
.child(title)
.child(
div()
.max_w_64()
.child(
Label::new("Enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning.")
.size(LabelSize::Small)
.color(Color::Muted)
)
)
})
}
}

View File

@@ -48,7 +48,7 @@ impl SlashCommandCompletionProvider {
name_range: Range<Anchor>,
window: &mut Window,
cx: &mut App,
) -> Task<Result<Option<Vec<project::Completion>>>> {
) -> Task<Result<Vec<project::CompletionResponse>>> {
let slash_commands = self.slash_commands.clone();
let candidates = slash_commands
.command_names(cx)
@@ -71,28 +71,27 @@ impl SlashCommandCompletionProvider {
.await;
cx.update(|_, cx| {
Some(
matches
.into_iter()
.filter_map(|mat| {
let command = slash_commands.command(&mat.string, cx)?;
let mut new_text = mat.string.clone();
let requires_argument = command.requires_argument();
let accepts_arguments = command.accepts_arguments();
if requires_argument || accepts_arguments {
new_text.push(' ');
}
let completions = matches
.into_iter()
.filter_map(|mat| {
let command = slash_commands.command(&mat.string, cx)?;
let mut new_text = mat.string.clone();
let requires_argument = command.requires_argument();
let accepts_arguments = command.accepts_arguments();
if requires_argument || accepts_arguments {
new_text.push(' ');
}
let confirm =
editor
.clone()
.zip(workspace.clone())
.map(|(editor, workspace)| {
let command_name = mat.string.clone();
let command_range = command_range.clone();
let editor = editor.clone();
let workspace = workspace.clone();
Arc::new(
let confirm =
editor
.clone()
.zip(workspace.clone())
.map(|(editor, workspace)| {
let command_name = mat.string.clone();
let command_range = command_range.clone();
let editor = editor.clone();
let workspace = workspace.clone();
Arc::new(
move |intent: CompletionIntent,
window: &mut Window,
cx: &mut App| {
@@ -118,22 +117,27 @@ impl SlashCommandCompletionProvider {
}
},
) as Arc<_>
});
Some(project::Completion {
replace_range: name_range.clone(),
documentation: Some(CompletionDocumentation::SingleLine(
command.description().into(),
)),
new_text,
label: command.label(cx),
icon_path: None,
insert_text_mode: None,
confirm,
source: CompletionSource::Custom,
})
});
Some(project::Completion {
replace_range: name_range.clone(),
documentation: Some(CompletionDocumentation::SingleLine(
command.description().into(),
)),
new_text,
label: command.label(cx),
icon_path: None,
insert_text_mode: None,
confirm,
source: CompletionSource::Custom,
})
.collect(),
)
})
.collect();
vec![project::CompletionResponse {
completions,
is_incomplete: false,
}]
})
})
}
@@ -147,7 +151,7 @@ impl SlashCommandCompletionProvider {
last_argument_range: Range<Anchor>,
window: &mut Window,
cx: &mut App,
) -> Task<Result<Option<Vec<project::Completion>>>> {
) -> Task<Result<Vec<project::CompletionResponse>>> {
let new_cancel_flag = Arc::new(AtomicBool::new(false));
let mut flag = self.cancel_flag.lock();
flag.store(true, SeqCst);
@@ -165,28 +169,27 @@ impl SlashCommandCompletionProvider {
let workspace = self.workspace.clone();
let arguments = arguments.to_vec();
cx.background_spawn(async move {
Ok(Some(
completions
.await?
.into_iter()
.map(|new_argument| {
let confirm =
editor
.clone()
.zip(workspace.clone())
.map(|(editor, workspace)| {
Arc::new({
let mut completed_arguments = arguments.clone();
if new_argument.replace_previous_arguments {
completed_arguments.clear();
} else {
completed_arguments.pop();
}
completed_arguments.push(new_argument.new_text.clone());
let completions = completions
.await?
.into_iter()
.map(|new_argument| {
let confirm =
editor
.clone()
.zip(workspace.clone())
.map(|(editor, workspace)| {
Arc::new({
let mut completed_arguments = arguments.clone();
if new_argument.replace_previous_arguments {
completed_arguments.clear();
} else {
completed_arguments.pop();
}
completed_arguments.push(new_argument.new_text.clone());
let command_range = command_range.clone();
let command_name = command_name.clone();
move |intent: CompletionIntent,
let command_range = command_range.clone();
let command_name = command_name.clone();
move |intent: CompletionIntent,
window: &mut Window,
cx: &mut App| {
if new_argument.after_completion.run()
@@ -210,34 +213,41 @@ impl SlashCommandCompletionProvider {
!new_argument.after_completion.run()
}
}
}) as Arc<_>
});
}) as Arc<_>
});
let mut new_text = new_argument.new_text.clone();
if new_argument.after_completion == AfterCompletion::Continue {
new_text.push(' ');
}
let mut new_text = new_argument.new_text.clone();
if new_argument.after_completion == AfterCompletion::Continue {
new_text.push(' ');
}
project::Completion {
replace_range: if new_argument.replace_previous_arguments {
argument_range.clone()
} else {
last_argument_range.clone()
},
label: new_argument.label,
icon_path: None,
new_text,
documentation: None,
confirm,
insert_text_mode: None,
source: CompletionSource::Custom,
}
})
.collect(),
))
project::Completion {
replace_range: if new_argument.replace_previous_arguments {
argument_range.clone()
} else {
last_argument_range.clone()
},
label: new_argument.label,
icon_path: None,
new_text,
documentation: None,
confirm,
insert_text_mode: None,
source: CompletionSource::Custom,
}
})
.collect();
Ok(vec![project::CompletionResponse {
completions,
is_incomplete: false,
}])
})
} else {
Task::ready(Ok(Some(Vec::new())))
Task::ready(Ok(vec![project::CompletionResponse {
completions: Vec::new(),
is_incomplete: false,
}]))
}
}
}
@@ -251,7 +261,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
_: editor::CompletionContext,
window: &mut Window,
cx: &mut Context<Editor>,
) -> Task<Result<Option<Vec<project::Completion>>>> {
) -> Task<Result<Vec<project::CompletionResponse>>> {
let Some((name, arguments, command_range, last_argument_range)) =
buffer.update(cx, |buffer, _cx| {
let position = buffer_position.to_point(buffer);
@@ -295,7 +305,10 @@ impl CompletionProvider for SlashCommandCompletionProvider {
Some((name, arguments, command_range, last_argument_range))
})
else {
return Task::ready(Ok(Some(Vec::new())));
return Task::ready(Ok(vec![project::CompletionResponse {
completions: Vec::new(),
is_incomplete: false,
}]));
};
if let Some((arguments, argument_range)) = arguments {
@@ -329,6 +342,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
position: language::Anchor,
_text: &str,
_trigger_in_words: bool,
_menu_is_open: bool,
cx: &mut Context<Editor>,
) -> bool {
let buffer = buffer.read(cx);

View File

@@ -338,7 +338,7 @@ where
let handle = self
.active_context_editor
.update(cx, |this, _| this.slash_menu_handle.clone())
.read_with(cx, |this, _| this.slash_menu_handle.clone())
.ok();
PopoverMenu::new("model-switcher")
.menu(move |_window, _cx| Some(picker_view.clone()))

View File

@@ -44,6 +44,6 @@ worktree.workspace = true
workspace-hack.workspace = true
[dev-dependencies]
env_logger.workspace = true
pretty_assertions.workspace = true
settings.workspace = true
zlog.workspace = true

View File

@@ -587,9 +587,7 @@ mod test {
use super::collect_files;
pub fn init_test(cx: &mut gpui::TestAppContext) {
if std::env::var("RUST_LOG").is_ok() {
env_logger::try_init().ok();
}
zlog::init_test();
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);

View File

@@ -13,6 +13,7 @@ path = "src/assistant_tool.rs"
[dependencies]
anyhow.workspace = true
async-watch.workspace = true
buffer_diff.workspace = true
clock.workspace = true
collections.workspace = true
@@ -37,7 +38,6 @@ buffer_diff = { workspace = true, features = ["test-support"] }
collections = { workspace = true, features = ["test-support"] }
clock = { workspace = true, features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
language_model = { workspace = true, features = ["test-support"] }
@@ -48,3 +48,4 @@ rand.workspace = true
settings = { workspace = true, features = ["test-support"] }
text = { workspace = true, features = ["test-support"] }
util = { workspace = true, features = ["test-support"] }
zlog.workspace = true

View File

@@ -1,7 +1,7 @@
use anyhow::{Context as _, Result};
use buffer_diff::BufferDiff;
use collections::BTreeMap;
use futures::{StreamExt, channel::mpsc};
use futures::{FutureExt, StreamExt, channel::mpsc};
use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task, WeakEntity};
use language::{Anchor, Buffer, BufferEvent, DiskState, Point, ToPoint};
use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle};
@@ -92,21 +92,21 @@ impl ActionLog {
let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
let (diff_update_tx, diff_update_rx) = mpsc::unbounded();
let diff_base;
let unreviewed_changes;
let unreviewed_edits;
if is_created {
diff_base = Rope::default();
unreviewed_changes = Patch::new(vec![Edit {
unreviewed_edits = Patch::new(vec![Edit {
old: 0..1,
new: 0..text_snapshot.max_point().row + 1,
}])
} else {
diff_base = buffer.read(cx).as_rope().clone();
unreviewed_changes = Patch::default();
unreviewed_edits = Patch::default();
}
TrackedBuffer {
buffer: buffer.clone(),
diff_base,
unreviewed_changes,
unreviewed_edits: unreviewed_edits,
snapshot: text_snapshot.clone(),
status,
version: buffer.read(cx).version(),
@@ -175,7 +175,7 @@ impl ActionLog {
.map_or(false, |file| file.disk_state() != DiskState::Deleted)
{
// If the buffer had been deleted by a tool, but it got
// resurrected externally, we want to clear the changes we
// resurrected externally, we want to clear the edits we
// were tracking and reset the buffer's state.
self.tracked_buffers.remove(&buffer);
self.track_buffer_internal(buffer, false, cx);
@@ -188,108 +188,274 @@ impl ActionLog {
async fn maintain_diff(
this: WeakEntity<Self>,
buffer: Entity<Buffer>,
mut diff_update: mpsc::UnboundedReceiver<(ChangeAuthor, text::BufferSnapshot)>,
mut buffer_updates: mpsc::UnboundedReceiver<(ChangeAuthor, text::BufferSnapshot)>,
cx: &mut AsyncApp,
) -> Result<()> {
while let Some((author, buffer_snapshot)) = diff_update.next().await {
let (rebase, diff, language, language_registry) =
this.read_with(cx, |this, cx| {
let tracked_buffer = this
.tracked_buffers
.get(&buffer)
.context("buffer not tracked")?;
let git_store = this.read_with(cx, |this, cx| this.project.read(cx).git_store().clone())?;
let git_diff = this
.update(cx, |this, cx| {
this.project.update(cx, |project, cx| {
project.open_uncommitted_diff(buffer.clone(), cx)
})
})?
.await
.ok();
let buffer_repo = git_store.read_with(cx, |git_store, cx| {
git_store.repository_and_path_for_buffer_id(buffer.read(cx).remote_id(), cx)
})?;
let rebase = cx.background_spawn({
let mut base_text = tracked_buffer.diff_base.clone();
let old_snapshot = tracked_buffer.snapshot.clone();
let new_snapshot = buffer_snapshot.clone();
let unreviewed_changes = tracked_buffer.unreviewed_changes.clone();
async move {
let edits = diff_snapshots(&old_snapshot, &new_snapshot);
if let ChangeAuthor::User = author {
apply_non_conflicting_edits(
&unreviewed_changes,
edits,
&mut base_text,
new_snapshot.as_rope(),
);
let (git_diff_updates_tx, mut git_diff_updates_rx) = async_watch::channel(());
let _repo_subscription =
if let Some((git_diff, (buffer_repo, _))) = git_diff.as_ref().zip(buffer_repo) {
cx.update(|cx| {
let mut old_head = buffer_repo.read(cx).head_commit.clone();
Some(cx.subscribe(git_diff, move |_, event, cx| match event {
buffer_diff::BufferDiffEvent::DiffChanged { .. } => {
let new_head = buffer_repo.read(cx).head_commit.clone();
if new_head != old_head {
old_head = new_head;
git_diff_updates_tx.send(()).ok();
}
(Arc::new(base_text.to_string()), base_text)
}
});
_ => {}
}))
})?
} else {
None
};
anyhow::Ok((
rebase,
tracked_buffer.diff.clone(),
tracked_buffer.buffer.read(cx).language().cloned(),
tracked_buffer.buffer.read(cx).language_registry(),
))
})??;
let (new_base_text, new_diff_base) = rebase.await;
let diff_snapshot = BufferDiff::update_diff(
diff.clone(),
buffer_snapshot.clone(),
Some(new_base_text),
true,
false,
language,
language_registry,
cx,
)
.await;
let mut unreviewed_changes = Patch::default();
if let Ok(diff_snapshot) = diff_snapshot {
unreviewed_changes = cx
.background_spawn({
let diff_snapshot = diff_snapshot.clone();
let buffer_snapshot = buffer_snapshot.clone();
let new_diff_base = new_diff_base.clone();
async move {
let mut unreviewed_changes = Patch::default();
for hunk in diff_snapshot.hunks_intersecting_range(
Anchor::MIN..Anchor::MAX,
&buffer_snapshot,
) {
let old_range = new_diff_base
.offset_to_point(hunk.diff_base_byte_range.start)
..new_diff_base.offset_to_point(hunk.diff_base_byte_range.end);
let new_range = hunk.range.start..hunk.range.end;
unreviewed_changes.push(point_to_row_edit(
Edit {
old: old_range,
new: new_range,
},
&new_diff_base,
&buffer_snapshot.as_rope(),
));
}
unreviewed_changes
}
})
.await;
diff.update(cx, |diff, cx| {
diff.set_snapshot(diff_snapshot, &buffer_snapshot, cx)
})?;
loop {
futures::select_biased! {
buffer_update = buffer_updates.next() => {
if let Some((author, buffer_snapshot)) = buffer_update {
Self::track_edits(&this, &buffer, author, buffer_snapshot, cx).await?;
} else {
break;
}
}
_ = git_diff_updates_rx.changed().fuse() => {
if let Some(git_diff) = git_diff.as_ref() {
Self::keep_committed_edits(&this, &buffer, &git_diff, cx).await?;
}
}
}
this.update(cx, |this, cx| {
let tracked_buffer = this
.tracked_buffers
.get_mut(&buffer)
.context("buffer not tracked")?;
tracked_buffer.diff_base = new_diff_base;
tracked_buffer.snapshot = buffer_snapshot;
tracked_buffer.unreviewed_changes = unreviewed_changes;
cx.notify();
anyhow::Ok(())
})??;
}
Ok(())
}
async fn track_edits(
this: &WeakEntity<ActionLog>,
buffer: &Entity<Buffer>,
author: ChangeAuthor,
buffer_snapshot: text::BufferSnapshot,
cx: &mut AsyncApp,
) -> Result<()> {
let rebase = this.read_with(cx, |this, cx| {
let tracked_buffer = this
.tracked_buffers
.get(buffer)
.context("buffer not tracked")?;
let rebase = cx.background_spawn({
let mut base_text = tracked_buffer.diff_base.clone();
let old_snapshot = tracked_buffer.snapshot.clone();
let new_snapshot = buffer_snapshot.clone();
let unreviewed_edits = tracked_buffer.unreviewed_edits.clone();
async move {
let edits = diff_snapshots(&old_snapshot, &new_snapshot);
if let ChangeAuthor::User = author {
apply_non_conflicting_edits(
&unreviewed_edits,
edits,
&mut base_text,
new_snapshot.as_rope(),
);
}
(Arc::new(base_text.to_string()), base_text)
}
});
anyhow::Ok(rebase)
})??;
let (new_base_text, new_diff_base) = rebase.await;
Self::update_diff(
this,
buffer,
buffer_snapshot,
new_base_text,
new_diff_base,
cx,
)
.await
}
async fn keep_committed_edits(
this: &WeakEntity<ActionLog>,
buffer: &Entity<Buffer>,
git_diff: &Entity<BufferDiff>,
cx: &mut AsyncApp,
) -> Result<()> {
let buffer_snapshot = this.read_with(cx, |this, _cx| {
let tracked_buffer = this
.tracked_buffers
.get(buffer)
.context("buffer not tracked")?;
anyhow::Ok(tracked_buffer.snapshot.clone())
})??;
let (new_base_text, new_diff_base) = this
.read_with(cx, |this, cx| {
let tracked_buffer = this
.tracked_buffers
.get(buffer)
.context("buffer not tracked")?;
let old_unreviewed_edits = tracked_buffer.unreviewed_edits.clone();
let agent_diff_base = tracked_buffer.diff_base.clone();
let git_diff_base = git_diff.read(cx).base_text().as_rope().clone();
let buffer_text = tracked_buffer.snapshot.as_rope().clone();
anyhow::Ok(cx.background_spawn(async move {
let mut old_unreviewed_edits = old_unreviewed_edits.into_iter().peekable();
let committed_edits = language::line_diff(
&agent_diff_base.to_string(),
&git_diff_base.to_string(),
)
.into_iter()
.map(|(old, new)| Edit { old, new });
let mut new_agent_diff_base = agent_diff_base.clone();
let mut row_delta = 0i32;
for committed in committed_edits {
while let Some(unreviewed) = old_unreviewed_edits.peek() {
// If the committed edit matches the unreviewed
// edit, assume the user wants to keep it.
if committed.old == unreviewed.old {
let unreviewed_new =
buffer_text.slice_rows(unreviewed.new.clone()).to_string();
let committed_new =
git_diff_base.slice_rows(committed.new.clone()).to_string();
if unreviewed_new == committed_new {
let old_byte_start =
new_agent_diff_base.point_to_offset(Point::new(
(unreviewed.old.start as i32 + row_delta) as u32,
0,
));
let old_byte_end =
new_agent_diff_base.point_to_offset(cmp::min(
Point::new(
(unreviewed.old.end as i32 + row_delta) as u32,
0,
),
new_agent_diff_base.max_point(),
));
new_agent_diff_base
.replace(old_byte_start..old_byte_end, &unreviewed_new);
row_delta +=
unreviewed.new_len() as i32 - unreviewed.old_len() as i32;
}
} else if unreviewed.old.start >= committed.old.end {
break;
}
old_unreviewed_edits.next().unwrap();
}
}
(
Arc::new(new_agent_diff_base.to_string()),
new_agent_diff_base,
)
}))
})??
.await;
Self::update_diff(
this,
buffer,
buffer_snapshot,
new_base_text,
new_diff_base,
cx,
)
.await
}
async fn update_diff(
this: &WeakEntity<ActionLog>,
buffer: &Entity<Buffer>,
buffer_snapshot: text::BufferSnapshot,
new_base_text: Arc<String>,
new_diff_base: Rope,
cx: &mut AsyncApp,
) -> Result<()> {
let (diff, language, language_registry) = this.read_with(cx, |this, cx| {
let tracked_buffer = this
.tracked_buffers
.get(buffer)
.context("buffer not tracked")?;
anyhow::Ok((
tracked_buffer.diff.clone(),
buffer.read(cx).language().cloned(),
buffer.read(cx).language_registry().clone(),
))
})??;
let diff_snapshot = BufferDiff::update_diff(
diff.clone(),
buffer_snapshot.clone(),
Some(new_base_text),
true,
false,
language,
language_registry,
cx,
)
.await;
let mut unreviewed_edits = Patch::default();
if let Ok(diff_snapshot) = diff_snapshot {
unreviewed_edits = cx
.background_spawn({
let diff_snapshot = diff_snapshot.clone();
let buffer_snapshot = buffer_snapshot.clone();
let new_diff_base = new_diff_base.clone();
async move {
let mut unreviewed_edits = Patch::default();
for hunk in diff_snapshot
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer_snapshot)
{
let old_range = new_diff_base
.offset_to_point(hunk.diff_base_byte_range.start)
..new_diff_base.offset_to_point(hunk.diff_base_byte_range.end);
let new_range = hunk.range.start..hunk.range.end;
unreviewed_edits.push(point_to_row_edit(
Edit {
old: old_range,
new: new_range,
},
&new_diff_base,
&buffer_snapshot.as_rope(),
));
}
unreviewed_edits
}
})
.await;
diff.update(cx, |diff, cx| {
diff.set_snapshot(diff_snapshot, &buffer_snapshot, cx);
})?;
}
this.update(cx, |this, cx| {
let tracked_buffer = this
.tracked_buffers
.get_mut(buffer)
.context("buffer not tracked")?;
tracked_buffer.diff_base = new_diff_base;
tracked_buffer.snapshot = buffer_snapshot;
tracked_buffer.unreviewed_edits = unreviewed_edits;
cx.notify();
anyhow::Ok(())
})?
}
/// Track a buffer as read, so we can notify the model about user edits.
pub fn buffer_read(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {
self.track_buffer_internal(buffer, false, cx);
@@ -350,7 +516,7 @@ impl ActionLog {
buffer_range.start.to_point(buffer)..buffer_range.end.to_point(buffer);
let mut delta = 0i32;
tracked_buffer.unreviewed_changes.retain_mut(|edit| {
tracked_buffer.unreviewed_edits.retain_mut(|edit| {
edit.old.start = (edit.old.start as i32 + delta) as u32;
edit.old.end = (edit.old.end as i32 + delta) as u32;
@@ -415,14 +581,38 @@ impl ActionLog {
self.project
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
} else {
buffer
.read(cx)
.entry_id(cx)
.and_then(|entry_id| {
self.project
.update(cx, |project, cx| project.delete_entry(entry_id, false, cx))
})
.unwrap_or(Task::ready(Ok(())))
// For a file created by AI with no pre-existing content,
// only delete the file if we're certain it contains only AI content
// with no edits from the user.
let initial_version = tracked_buffer.version.clone();
let current_version = buffer.read(cx).version();
let current_content = buffer.read(cx).text();
let tracked_content = tracked_buffer.snapshot.text();
let is_ai_only_content =
initial_version == current_version && current_content == tracked_content;
if is_ai_only_content {
buffer
.read(cx)
.entry_id(cx)
.and_then(|entry_id| {
self.project.update(cx, |project, cx| {
project.delete_entry(entry_id, false, cx)
})
})
.unwrap_or(Task::ready(Ok(())))
} else {
// Not sure how to disentangle edits made by the user
// from edits made by the AI at this point.
// For now, preserve both to avoid data loss.
//
// TODO: Better solution (disable "Reject" after user makes some
// edit or find a way to differentiate between AI and user edits)
Task::ready(Ok(()))
}
};
self.tracked_buffers.remove(&buffer);
@@ -437,7 +627,7 @@ impl ActionLog {
.project
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx));
// Clear all tracked changes for this buffer and start over as if we just read it.
// Clear all tracked edits for this buffer and start over as if we just read it.
self.tracked_buffers.remove(&buffer);
self.buffer_read(buffer.clone(), cx);
cx.notify();
@@ -453,7 +643,7 @@ impl ActionLog {
.peekable();
let mut edits_to_revert = Vec::new();
for edit in tracked_buffer.unreviewed_changes.edits() {
for edit in tracked_buffer.unreviewed_edits.edits() {
let new_range = tracked_buffer
.snapshot
.anchor_before(Point::new(edit.new.start, 0))
@@ -505,7 +695,7 @@ impl ActionLog {
.retain(|_buffer, tracked_buffer| match tracked_buffer.status {
TrackedBufferStatus::Deleted => false,
_ => {
tracked_buffer.unreviewed_changes.clear();
tracked_buffer.unreviewed_edits.clear();
tracked_buffer.diff_base = tracked_buffer.snapshot.as_rope().clone();
tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx);
true
@@ -514,11 +704,11 @@ impl ActionLog {
cx.notify();
}
/// Returns the set of buffers that contain changes that haven't been reviewed by the user.
/// Returns the set of buffers that contain edits that haven't been reviewed by the user.
pub fn changed_buffers(&self, cx: &App) -> BTreeMap<Entity<Buffer>, Entity<BufferDiff>> {
self.tracked_buffers
.iter()
.filter(|(_, tracked)| tracked.has_changes(cx))
.filter(|(_, tracked)| tracked.has_edits(cx))
.map(|(buffer, tracked)| (buffer.clone(), tracked.diff.clone()))
.collect()
}
@@ -638,11 +828,7 @@ fn point_to_row_edit(edit: Edit<Point>, old_text: &Rope, new_text: &Rope) -> Edi
old: edit.old.start.row + 1..edit.old.end.row + 1,
new: edit.new.start.row + 1..edit.new.end.row + 1,
}
} else if edit.old.start.column == 0
&& edit.old.end.column == 0
&& edit.new.end.column == 0
&& edit.old.end != old_text.max_point()
{
} else if edit.old.start.column == 0 && edit.old.end.column == 0 && edit.new.end.column == 0 {
Edit {
old: edit.old.start.row..edit.old.end.row,
new: edit.new.start.row..edit.new.end.row,
@@ -670,7 +856,7 @@ enum TrackedBufferStatus {
struct TrackedBuffer {
buffer: Entity<Buffer>,
diff_base: Rope,
unreviewed_changes: Patch<u32>,
unreviewed_edits: Patch<u32>,
status: TrackedBufferStatus,
version: clock::Global,
diff: Entity<BufferDiff>,
@@ -682,7 +868,7 @@ struct TrackedBuffer {
}
impl TrackedBuffer {
fn has_changes(&self, cx: &App) -> bool {
fn has_edits(&self, cx: &App) -> bool {
self.diff
.read(cx)
.hunks(&self.buffer.read(cx), cx)
@@ -703,8 +889,6 @@ pub struct ChangedBuffer {
#[cfg(test)]
mod tests {
use std::env;
use super::*;
use buffer_diff::DiffHunkStatusKind;
use gpui::TestAppContext;
@@ -713,13 +897,12 @@ mod tests {
use rand::prelude::*;
use serde_json::json;
use settings::SettingsStore;
use std::env;
use util::{RandomCharIter, path};
#[ctor::ctor]
fn init_logger() {
if std::env::var("RUST_LOG").is_ok() {
env_logger::init();
}
zlog::init_test();
}
fn init_test(cx: &mut TestAppContext) {
@@ -1578,7 +1761,6 @@ mod tests {
project.find_project_path("dir/new_file", cx)
})
.unwrap();
let buffer = project
.update(cx, |project, cx| project.open_buffer(file_path, cx))
.await
@@ -1621,6 +1803,72 @@ mod tests {
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
}
#[gpui::test]
async fn test_reject_created_file_with_user_edits(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let file_path = project
.read_with(cx, |project, cx| {
project.find_project_path("dir/new_file", cx)
})
.unwrap();
let buffer = project
.update(cx, |project, cx| project.open_buffer(file_path, cx))
.await
.unwrap();
// AI creates file with initial content
cx.update(|cx| {
action_log.update(cx, |log, cx| log.buffer_created(buffer.clone(), cx));
buffer.update(cx, |buffer, cx| buffer.set_text("ai content", cx));
action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
});
project
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
.await
.unwrap();
cx.run_until_parked();
// User makes additional edits
cx.update(|cx| {
buffer.update(cx, |buffer, cx| {
buffer.edit([(10..10, "\nuser added this line")], None, cx);
});
});
project
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
.await
.unwrap();
assert!(fs.is_file(path!("/dir/new_file").as_ref()).await);
// Reject all
action_log
.update(cx, |log, cx| {
log.reject_edits_in_ranges(
buffer.clone(),
vec![Point::new(0, 0)..Point::new(100, 0)],
cx,
)
})
.await
.unwrap();
cx.run_until_parked();
// File should still contain all the content
assert!(fs.is_file(path!("/dir/new_file").as_ref()).await);
let content = buffer.read_with(cx, |buffer, _| buffer.text());
assert_eq!(content, "ai content\nuser added this line");
}
#[gpui::test(iterations = 100)]
async fn test_random_diffs(mut rng: StdRng, cx: &mut TestAppContext) {
init_test(cx);
@@ -1664,15 +1912,15 @@ mod tests {
.unwrap();
}
_ => {
let is_agent_change = rng.gen_bool(0.5);
if is_agent_change {
let is_agent_edit = rng.gen_bool(0.5);
if is_agent_edit {
log::info!("agent edit");
} else {
log::info!("user edit");
}
cx.update(|cx| {
buffer.update(cx, |buffer, cx| buffer.randomly_edit(&mut rng, 1, cx));
if is_agent_change {
if is_agent_edit {
action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
}
});
@@ -1697,7 +1945,7 @@ mod tests {
let tracked_buffer = log.tracked_buffers.get(&buffer).unwrap();
let mut old_text = tracked_buffer.diff_base.clone();
let new_text = buffer.read(cx).as_rope();
for edit in tracked_buffer.unreviewed_changes.edits() {
for edit in tracked_buffer.unreviewed_edits.edits() {
let old_start = old_text.point_to_offset(Point::new(edit.new.start, 0));
let old_end = old_text.point_to_offset(cmp::min(
Point::new(edit.new.start + edit.old_len(), 0),
@@ -1713,6 +1961,171 @@ mod tests {
}
}
#[gpui::test]
async fn test_keep_edits_on_commit(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
path!("/project"),
json!({
".git": {},
"file.txt": "a\nb\nc\nd\ne\nf\ng\nh\ni\nj",
}),
)
.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())],
"0000000",
);
cx.run_until_parked();
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let file_path = project
.read_with(cx, |project, cx| {
project.find_project_path(path!("/project/file.txt"), cx)
})
.unwrap();
let buffer = project
.update(cx, |project, cx| project.open_buffer(file_path, cx))
.await
.unwrap();
cx.update(|cx| {
action_log.update(cx, |log, cx| log.buffer_read(buffer.clone(), cx));
buffer.update(cx, |buffer, cx| {
buffer.edit(
[
// Edit at the very start: a -> A
(Point::new(0, 0)..Point::new(0, 1), "A"),
// Deletion in the middle: remove lines d and e
(Point::new(3, 0)..Point::new(5, 0), ""),
// Modification: g -> GGG
(Point::new(6, 0)..Point::new(6, 1), "GGG"),
// Addition: insert new line after h
(Point::new(7, 1)..Point::new(7, 1), "\nNEW"),
// Edit the very last character: j -> J
(Point::new(9, 0)..Point::new(9, 1), "J"),
],
None,
cx,
);
});
action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
});
cx.run_until_parked();
assert_eq!(
unreviewed_hunks(&action_log, cx),
vec![(
buffer.clone(),
vec![
HunkStatus {
range: Point::new(0, 0)..Point::new(1, 0),
diff_status: DiffHunkStatusKind::Modified,
old_text: "a\n".into()
},
HunkStatus {
range: Point::new(3, 0)..Point::new(3, 0),
diff_status: DiffHunkStatusKind::Deleted,
old_text: "d\ne\n".into()
},
HunkStatus {
range: Point::new(4, 0)..Point::new(5, 0),
diff_status: DiffHunkStatusKind::Modified,
old_text: "g\n".into()
},
HunkStatus {
range: Point::new(6, 0)..Point::new(7, 0),
diff_status: DiffHunkStatusKind::Added,
old_text: "".into()
},
HunkStatus {
range: Point::new(8, 0)..Point::new(8, 1),
diff_status: DiffHunkStatusKind::Modified,
old_text: "j".into()
}
]
)]
);
// Simulate a git commit that matches some edits but not others:
// - Accepts the first edit (a -> A)
// - Accepts the deletion (remove d and e)
// - Makes a different change to g (g -> G instead of GGG)
// - Ignores the NEW line addition
// - 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())],
"0000001",
);
cx.run_until_parked();
assert_eq!(
unreviewed_hunks(&action_log, cx),
vec![(
buffer.clone(),
vec![
HunkStatus {
range: Point::new(4, 0)..Point::new(5, 0),
diff_status: DiffHunkStatusKind::Modified,
old_text: "g\n".into()
},
HunkStatus {
range: Point::new(6, 0)..Point::new(7, 0),
diff_status: DiffHunkStatusKind::Added,
old_text: "".into()
},
HunkStatus {
range: Point::new(8, 0)..Point::new(8, 1),
diff_status: DiffHunkStatusKind::Modified,
old_text: "j".into()
}
]
)]
);
// 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(),
)],
"0000002",
);
cx.run_until_parked();
assert_eq!(
unreviewed_hunks(&action_log, cx),
vec![(
buffer.clone(),
vec![
HunkStatus {
range: Point::new(6, 0)..Point::new(7, 0),
diff_status: DiffHunkStatusKind::Added,
old_text: "".into()
},
HunkStatus {
range: Point::new(8, 0)..Point::new(8, 1),
diff_status: DiffHunkStatusKind::Modified,
old_text: "j".into()
}
]
)]
);
// 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())],
"0000003",
);
cx.run_until_parked();
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct HunkStatus {
range: Range<Point>,

View File

@@ -218,6 +218,9 @@ pub trait Tool: 'static + Send + Sync {
/// before having permission to run.
fn needs_confirmation(&self, input: &serde_json::Value, cx: &App) -> bool;
/// Returns true if the tool may perform edits.
fn may_perform_edits(&self) -> bool;
/// Returns the JSON schema that describes the tool's input.
fn input_schema(&self, _: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
Ok(serde_json::Value::Object(serde_json::Map::default()))

View File

@@ -16,11 +16,24 @@ pub fn adapt_schema_to_format(
}
match format {
LanguageModelToolSchemaFormat::JsonSchema => Ok(()),
LanguageModelToolSchemaFormat::JsonSchema => preprocess_json_schema(json),
LanguageModelToolSchemaFormat::JsonSchemaSubset => adapt_to_json_schema_subset(json),
}
}
fn preprocess_json_schema(json: &mut Value) -> Result<()> {
// `additionalProperties` defaults to `false` unless explicitly specified.
// This prevents models from hallucinating tool parameters.
if let Value::Object(obj) = json {
if let Some(Value::String(type_str)) = obj.get("type") {
if type_str == "object" && !obj.contains_key("additionalProperties") {
obj.insert("additionalProperties".to_string(), Value::Bool(false));
}
}
}
Ok(())
}
/// Tries to adapt the json schema so that it is compatible with https://ai.google.dev/api/caching#Schema
fn adapt_to_json_schema_subset(json: &mut Value) -> Result<()> {
if let Value::Object(obj) = json {
@@ -237,4 +250,59 @@ mod tests {
assert!(adapt_to_json_schema_subset(&mut json).is_err());
}
#[test]
fn test_preprocess_json_schema_adds_additional_properties() {
let mut json = json!({
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
});
preprocess_json_schema(&mut json).unwrap();
assert_eq!(
json,
json!({
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"additionalProperties": false
})
);
}
#[test]
fn test_preprocess_json_schema_preserves_additional_properties() {
let mut json = json!({
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"additionalProperties": true
});
preprocess_json_schema(&mut json).unwrap();
assert_eq!(
json,
json!({
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"additionalProperties": true
})
);
}
}

View File

@@ -15,10 +15,10 @@ path = "src/assistant_tools.rs"
eval = []
[dependencies]
aho-corasick.workspace = true
agent_settings.workspace = true
anyhow.workspace = true
assistant_settings.workspace = true
assistant_tool.workspace = true
async-watch.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
collections.workspace = true
@@ -36,6 +36,7 @@ itertools.workspace = true
language.workspace = true
language_model.workspace = true
log.workspace = true
lsp.workspace = true
markdown.workspace = true
open.workspace = true
paths.workspace = true
@@ -64,6 +65,7 @@ workspace.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
lsp = { workspace = true, features = ["test-support"] }
client = { workspace = true, features = ["test-support"] }
clock = { workspace = true, features = ["test-support"] }
collections = { workspace = true, features = ["test-support"] }

View File

@@ -37,13 +37,13 @@ use crate::diagnostics_tool::DiagnosticsTool;
use crate::edit_file_tool::EditFileTool;
use crate::fetch_tool::FetchTool;
use crate::find_path_tool::FindPathTool;
use crate::grep_tool::GrepTool;
use crate::list_directory_tool::ListDirectoryTool;
use crate::now_tool::NowTool;
use crate::thinking_tool::ThinkingTool;
pub use edit_file_tool::{EditFileMode, EditFileToolInput};
pub use find_path_tool::FindPathToolInput;
pub use grep_tool::{GrepTool, GrepToolInput};
pub use open_tool::OpenTool;
pub use read_file_tool::{ReadFileTool, ReadFileToolInput};
pub use terminal_tool::TerminalTool;
@@ -96,7 +96,7 @@ fn register_web_search_tool(registry: &Entity<LanguageModelRegistry>, cx: &mut A
#[cfg(test)]
mod tests {
use super::*;
use assistant_settings::AssistantSettings;
use agent_settings::AgentSettings;
use client::Client;
use clock::FakeSystemClock;
use http_client::FakeHttpClient;
@@ -126,6 +126,7 @@ mod tests {
}
},
"required": ["location"],
"additionalProperties": false
})
);
}
@@ -133,7 +134,7 @@ mod tests {
#[gpui::test]
fn test_builtin_tool_schema_compatibility(cx: &mut App) {
settings::init(cx);
AssistantSettings::register(cx);
AgentSettings::register(cx);
let client = Client::new(
Arc::new(FakeSystemClock::new()),

View File

@@ -1,9 +0,0 @@
Invoke multiple other tool calls either sequentially or concurrently.
This tool is useful when you need to perform several operations at once, improving efficiency by reducing the number of back-and-forth interactions needed to complete complex tasks.
If the tool calls are set to be run sequentially, then each tool call within the batch is executed in the order provided. If it's set to run concurrently, then they may run in a different order. Regardless, all tool calls will have the same permissions and context as if they were called individually.
This tool should never be used to run a total of one tool. Instead, just run that one tool directly. You can run batches within batches if desired, which is a way you can mix concurrent and sequential tool call execution.
When it's possible to run tools in a batch, you should run as many as possible in the batch, up to a maximum of 32. For example, don't run multiple consecutive batches of 10 when you could instead run one batch of 30.

View File

@@ -1,19 +0,0 @@
A tool for applying code actions to specific sections of your code. It uses language servers to provide refactoring capabilities similar to what you'd find in an IDE.
This tool can:
- List all available code actions for a selected text range
- Execute a specific code action on that range
- Rename symbols across your codebase. This tool is the preferred way to rename things, and you should always prefer to rename code symbols using this tool rather than using textual find/replace when both are available.
Use this tool when you want to:
- Discover what code actions are available for a piece of code
- Apply automatic fixes and code transformations
- Rename variables, functions, or other symbols consistently throughout your project
- Clean up imports, implement interfaces, or perform other language-specific operations
- If unsure what actions are available, call the tool without specifying an action to get a list
- For common operations, you can directly specify actions like "quickfix.all" or "source.organizeImports"
- For renaming, use the special "textDocument/rename" action and provide the new name in the arguments field
- Be specific with your text range and context to ensure the tool identifies the correct code location
The tool will automatically save any changes it makes to your files.

View File

@@ -1,39 +0,0 @@
Returns either an outline of the public code symbols in the entire project (grouped by file) or else an outline of both the public and private code symbols within a particular file.
When a path is provided, this tool returns a hierarchical outline of code symbols for that specific file.
When no path is provided, it returns a list of all public code symbols in the project, organized by file.
You can also provide an optional regular expression which filters the output by only showing code symbols which match that regex.
Results are paginated with 2000 entries per page. Use the optional 'offset' parameter to request subsequent pages.
Markdown headings indicate the structure of the output; just like
with markdown headings, the more # symbols there are at the beginning of a line,
the deeper it is in the hierarchy.
Each code symbol entry ends with a line number or range, which tells you what portion of the
underlying source code file corresponds to that part of the outline. You can use
that line information with other tools, to strategically read portions of the source code.
For example, you can use this tool to find a relevant symbol in the project, then get the outline of the file which contains that symbol, then use the line number information from that file's outline to read different sections of that file, without having to read the entire file all at once (which can be slow, or use a lot of tokens).
<example>
# class Foo [L123-136]
## method do_something(arg1, arg2) [L124-126]
## method process_data(data) [L128-135]
# class Bar [L145-161]
## method initialize() [L146-149]
## method update_state(new_state) [L160]
## private method _validate_state(state) [L161-162]
</example>
This example shows how tree-sitter outlines the structure of source code:
1. `class Foo` is defined on lines 123-136
- It contains a method `do_something` spanning lines 124-126
- It also has a method `process_data` spanning lines 128-135
2. `class Bar` is defined on lines 145-161
- It has an `initialize` method spanning lines 146-149
- It has an `update_state` method on line 160
- It has a private method `_validate_state` spanning lines 161-162

View File

@@ -1,9 +0,0 @@
Reads the contents of a path on the filesystem.
If the path is a directory, this lists all files and directories within that path.
If the path is a file, this returns the file's contents.
When reading a file, if the file is too big and no line range is specified, an outline of the file's code symbols is listed instead, which can be used to request specific line ranges in a subsequent call.
Similarly, if a directory has too many entries to show at once, a subset of entries will be shown,
and subsequent requests can use starting and ending line numbers to get other subsets.

View File

@@ -48,6 +48,10 @@ impl Tool for CopyPathTool {
false
}
fn may_perform_edits(&self) -> bool {
true
}
fn description(&self) -> String {
include_str!("./copy_path_tool/description.md").into()
}

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