Compare commits

..

147 Commits

Author SHA1 Message Date
Smit Barmase
b428f421d4 experiment with initial idea 2025-05-28 18:01:30 +05:30
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
447 changed files with 14202 additions and 4896 deletions

View File

@@ -524,7 +524,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 +610,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 +667,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 +714,12 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
nix-build:
timeout-minutes: 60
name: Nix Build
continue-on-error: true
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

65
.github/workflows/nix.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
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 }}"
- 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

@@ -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,10 @@ jobs:
- name: Upload Zed Nightly
run: script/upload-nightly linux-targz
bundle-nix:
needs: tests
uses: ./.github/workflows/nix.yml
update-nightly-tag:
name: Update nightly tag
if: github.repository_owner == 'zed-industries'

233
Cargo.lock generated
View File

@@ -14,6 +14,7 @@ dependencies = [
"gpui",
"language",
"project",
"release_channel",
"smallvec",
"ui",
"util",
@@ -52,13 +53,14 @@ dependencies = [
name = "agent"
version = "0.1.0"
dependencies = [
"agent_settings",
"anyhow",
"assistant_context_editor",
"assistant_settings",
"assistant_slash_command",
"assistant_slash_commands",
"assistant_tool",
"async-watch",
"audio",
"buffer_diff",
"chrono",
"client",
@@ -111,7 +113,6 @@ dependencies = [
"serde_json",
"serde_json_lenient",
"settings",
"smallvec",
"smol",
"streaming_diff",
"telemetry",
@@ -134,6 +135,33 @@ dependencies = [
"zed_llm_client",
]
[[package]]
name = "agent_settings"
version = "0.1.0"
dependencies = [
"anthropic",
"anyhow",
"collections",
"deepseek",
"fs",
"gpui",
"indexmap",
"language_model",
"lmstudio",
"log",
"mistral",
"ollama",
"open_ai",
"paths",
"schemars",
"serde",
"serde_json",
"serde_json_lenient",
"settings",
"workspace-hack",
"zed_llm_client",
]
[[package]]
name = "ahash"
version = "0.7.8"
@@ -481,8 +509,8 @@ dependencies = [
name = "assistant_context_editor"
version = "0.1.0"
dependencies = [
"agent_settings",
"anyhow",
"assistant_settings",
"assistant_slash_command",
"assistant_slash_commands",
"chrono",
@@ -533,33 +561,6 @@ dependencies = [
"zed_actions",
]
[[package]]
name = "assistant_settings"
version = "0.1.0"
dependencies = [
"anthropic",
"anyhow",
"collections",
"deepseek",
"fs",
"gpui",
"indexmap",
"language_model",
"lmstudio",
"log",
"mistral",
"ollama",
"open_ai",
"paths",
"schemars",
"serde",
"serde_json",
"serde_json_lenient",
"settings",
"workspace-hack",
"zed_llm_client",
]
[[package]]
name = "assistant_slash_command"
version = "0.1.0"
@@ -593,7 +594,6 @@ dependencies = [
"collections",
"context_server",
"editor",
"env_logger 0.11.8",
"feature_flags",
"fs",
"futures 0.3.31",
@@ -619,6 +619,7 @@ dependencies = [
"workspace",
"workspace-hack",
"worktree",
"zlog",
]
[[package]]
@@ -631,7 +632,6 @@ dependencies = [
"collections",
"ctor",
"derive_more",
"env_logger 0.11.8",
"futures 0.3.31",
"gpui",
"icons",
@@ -650,15 +650,16 @@ dependencies = [
"util",
"workspace",
"workspace-hack",
"zlog",
]
[[package]]
name = "assistant_tools"
version = "0.1.0"
dependencies = [
"agent_settings",
"aho-corasick",
"anyhow",
"assistant_settings",
"assistant_tool",
"buffer_diff",
"chrono",
@@ -2222,7 +2223,6 @@ dependencies = [
"anyhow",
"clock",
"ctor",
"env_logger 0.11.8",
"futures 0.3.31",
"git2",
"gpui",
@@ -2237,6 +2237,7 @@ dependencies = [
"unindent",
"util",
"workspace-hack",
"zlog",
]
[[package]]
@@ -2975,9 +2976,9 @@ dependencies = [
name = "collab"
version = "0.44.0"
dependencies = [
"agent_settings",
"anyhow",
"assistant_context_editor",
"assistant_settings",
"assistant_slash_command",
"assistant_tool",
"async-stripe",
@@ -3007,7 +3008,6 @@ dependencies = [
"debugger_ui",
"derive_more",
"editor",
"env_logger 0.11.8",
"envy",
"extension",
"file_finder",
@@ -3082,6 +3082,7 @@ dependencies = [
"workspace-hack",
"worktree",
"zed_llm_client",
"zlog",
]
[[package]]
@@ -3336,7 +3337,6 @@ dependencies = [
"command_palette_hooks",
"ctor",
"editor",
"env_logger 0.11.8",
"fs",
"futures 0.3.31",
"gpui",
@@ -3362,6 +3362,7 @@ dependencies = [
"util",
"workspace",
"workspace-hack",
"zlog",
]
[[package]]
@@ -4012,7 +4013,6 @@ dependencies = [
"client",
"collections",
"dap-types",
"env_logger 0.11.8",
"fs",
"futures 0.3.31",
"gpui",
@@ -4031,14 +4031,17 @@ dependencies = [
"smol",
"task",
"telemetry",
"tree-sitter",
"tree-sitter-go",
"util",
"workspace-hack",
"zlog",
]
[[package]]
name = "dap-types"
version = "0.0.1"
source = "git+https://github.com/zed-industries/dap-types?rev=be69a016ba710191b9fdded28c8b042af4b617f7#be69a016ba710191b9fdded28c8b042af4b617f7"
source = "git+https://github.com/zed-industries/dap-types?rev=68516de327fa1be15214133a0a2e52a12982ce75#68516de327fa1be15214133a0a2e52a12982ce75"
dependencies = [
"schemars",
"serde",
@@ -4056,10 +4059,10 @@ dependencies = [
"gpui",
"json_dotpath",
"language",
"log",
"paths",
"serde",
"serde_json",
"smol",
"task",
"util",
"workspace-hack",
@@ -4219,7 +4222,6 @@ dependencies = [
"db",
"debugger_tools",
"editor",
"env_logger 0.11.8",
"feature_flags",
"file_icons",
"futures 0.3.31",
@@ -4248,6 +4250,8 @@ dependencies = [
"util",
"workspace",
"workspace-hack",
"zed_actions",
"zlog",
]
[[package]]
@@ -4350,7 +4354,7 @@ version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.101",
"workspace-hack",
]
@@ -4364,7 +4368,6 @@ dependencies = [
"component",
"ctor",
"editor",
"env_logger 0.11.8",
"futures 0.3.31",
"gpui",
"indoc",
@@ -4385,6 +4388,7 @@ dependencies = [
"util",
"workspace",
"workspace-hack",
"zlog",
]
[[package]]
@@ -4683,7 +4687,6 @@ dependencies = [
"dap",
"db",
"emojis",
"env_logger 0.11.8",
"feature_flags",
"file_icons",
"fs",
@@ -4729,7 +4732,6 @@ dependencies = [
"tree-sitter-rust",
"tree-sitter-typescript",
"ui",
"unicode-script",
"unicode-segmentation",
"unindent",
"url",
@@ -4738,6 +4740,7 @@ dependencies = [
"workspace",
"workspace-hack",
"zed_actions",
"zlog",
]
[[package]]
@@ -4994,8 +4997,8 @@ name = "eval"
version = "0.1.0"
dependencies = [
"agent",
"agent_settings",
"anyhow",
"assistant_settings",
"assistant_tool",
"assistant_tools",
"async-trait",
@@ -5121,10 +5124,8 @@ dependencies = [
"task",
"toml 0.8.20",
"util",
"wasi-preview1-component-adapter-provider",
"wasm-encoder 0.221.3",
"wasmparser 0.221.3",
"wit-component 0.221.3",
"workspace-hack",
]
@@ -5165,7 +5166,6 @@ dependencies = [
"criterion",
"ctor",
"dap",
"env_logger 0.11.8",
"extension",
"fs",
"futures 0.3.31",
@@ -5202,6 +5202,7 @@ dependencies = [
"wasmtime",
"wasmtime-wasi",
"workspace-hack",
"zlog",
]
[[package]]
@@ -5375,7 +5376,6 @@ dependencies = [
"collections",
"ctor",
"editor",
"env_logger 0.11.8",
"file_icons",
"futures 0.3.31",
"fuzzy",
@@ -5383,8 +5383,10 @@ dependencies = [
"language",
"menu",
"picker",
"pretty_assertions",
"project",
"schemars",
"search",
"serde",
"serde_derive",
"serde_json",
@@ -5395,6 +5397,7 @@ dependencies = [
"util",
"workspace",
"workspace-hack",
"zlog",
]
[[package]]
@@ -6098,9 +6101,9 @@ dependencies = [
name = "git_ui"
version = "0.1.0"
dependencies = [
"agent_settings",
"anyhow",
"askpass",
"assistant_settings",
"buffer_diff",
"chrono",
"collections",
@@ -6109,7 +6112,6 @@ dependencies = [
"ctor",
"db",
"editor",
"env_logger 0.11.8",
"futures 0.3.31",
"fuzzy",
"git",
@@ -6145,6 +6147,7 @@ dependencies = [
"workspace",
"workspace-hack",
"zed_actions",
"zlog",
]
[[package]]
@@ -7122,9 +7125,10 @@ name = "gpui_macros"
version = "0.1.0"
dependencies = [
"gpui",
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.101",
"workspace-hack",
]
@@ -8157,6 +8161,27 @@ dependencies = [
"generic-array",
]
[[package]]
name = "inspector_ui"
version = "0.1.0"
dependencies = [
"anyhow",
"command_palette_hooks",
"editor",
"fuzzy",
"gpui",
"language",
"project",
"serde_json",
"serde_json_lenient",
"theme",
"ui",
"util",
"workspace",
"workspace-hack",
"zed_actions",
]
[[package]]
name = "install_cli"
version = "0.1.0"
@@ -8706,7 +8731,6 @@ dependencies = [
"ctor",
"diffy",
"ec4rs",
"env_logger 0.11.8",
"fs",
"futures 0.3.31",
"fuzzy",
@@ -8751,6 +8775,7 @@ dependencies = [
"unindent",
"util",
"workspace-hack",
"zlog",
]
[[package]]
@@ -8782,19 +8807,16 @@ dependencies = [
"client",
"collections",
"futures 0.3.31",
"google_ai",
"gpui",
"http_client",
"icons",
"image",
"open_ai",
"parking_lot",
"proto",
"schemars",
"serde",
"serde_json",
"smol",
"strum 0.27.1",
"telemetry_events",
"thiserror 2.0.12",
"util",
@@ -8818,7 +8840,6 @@ dependencies = [
"credentials_provider",
"deepseek",
"editor",
"feature_flags",
"fs",
"futures 0.3.31",
"google_ai",
@@ -8881,7 +8902,6 @@ dependencies = [
"collections",
"copilot",
"editor",
"env_logger 0.11.8",
"futures 0.3.31",
"gpui",
"itertools 0.14.0",
@@ -8898,6 +8918,7 @@ dependencies = [
"workspace",
"workspace-hack",
"zed_actions",
"zlog",
]
[[package]]
@@ -8930,8 +8951,10 @@ dependencies = [
"regex",
"rope",
"rust-embed",
"schemars",
"serde",
"serde_json",
"serde_json_lenient",
"settings",
"smol",
"snippet_provider",
@@ -9418,7 +9441,6 @@ dependencies = [
"async-pipe",
"collections",
"ctor",
"env_logger 0.11.8",
"futures 0.3.31",
"gpui",
"log",
@@ -9432,6 +9454,7 @@ dependencies = [
"smol",
"util",
"workspace-hack",
"zlog",
]
[[package]]
@@ -9543,10 +9566,10 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
name = "markdown"
version = "0.1.0"
dependencies = [
"anyhow",
"assets",
"base64 0.22.1",
"env_logger 0.11.8",
"futures 0.3.31",
"gpui",
"language",
"languages",
@@ -9928,7 +9951,6 @@ dependencies = [
"clock",
"collections",
"ctor",
"env_logger 0.11.8",
"gpui",
"indoc",
"itertools 0.14.0",
@@ -9949,6 +9971,7 @@ dependencies = [
"tree-sitter",
"util",
"workspace-hack",
"zlog",
]
[[package]]
@@ -12015,7 +12038,6 @@ dependencies = [
"context_server",
"dap",
"dap_adapters",
"env_logger 0.11.8",
"extension",
"fancy-regex 0.14.0",
"fs",
@@ -13000,6 +13022,7 @@ dependencies = [
"unindent",
"util",
"worktree",
"zlog",
]
[[package]]
@@ -13341,7 +13364,6 @@ dependencies = [
"arrayvec",
"criterion",
"ctor",
"env_logger 0.11.8",
"gpui",
"log",
"rand 0.8.5",
@@ -13351,6 +13373,7 @@ dependencies = [
"unicode-segmentation",
"util",
"workspace-hack",
"zlog",
]
[[package]]
@@ -13368,7 +13391,6 @@ dependencies = [
"base64 0.22.1",
"chrono",
"collections",
"env_logger 0.11.8",
"futures 0.3.31",
"gpui",
"parking_lot",
@@ -13382,6 +13404,7 @@ dependencies = [
"tracing",
"util",
"workspace-hack",
"zlog",
"zstd",
]
@@ -14097,7 +14120,6 @@ dependencies = [
"client",
"clock",
"collections",
"env_logger 0.11.8",
"feature_flags",
"fs",
"futures 0.3.31",
@@ -14128,6 +14150,7 @@ dependencies = [
"workspace",
"workspace-hack",
"worktree",
"zlog",
]
[[package]]
@@ -14658,11 +14681,14 @@ dependencies = [
name = "snippets_ui"
version = "0.1.0"
dependencies = [
"file_finder",
"file_icons",
"fuzzy",
"gpui",
"language",
"paths",
"picker",
"settings",
"ui",
"util",
"workspace",
@@ -14756,7 +14782,7 @@ version = "0.1.0"
dependencies = [
"sqlez",
"sqlformat",
"syn 1.0.109",
"syn 2.0.101",
"workspace-hack",
]
@@ -15157,11 +15183,11 @@ version = "0.1.0"
dependencies = [
"arrayvec",
"ctor",
"env_logger 0.11.8",
"log",
"rand 0.8.5",
"rayon",
"workspace-hack",
"zlog",
]
[[package]]
@@ -15472,7 +15498,6 @@ dependencies = [
"collections",
"ctor",
"editor",
"env_logger 0.11.8",
"fuzzy",
"gpui",
"language",
@@ -15489,6 +15514,7 @@ dependencies = [
"util",
"workspace",
"workspace-hack",
"zlog",
]
[[package]]
@@ -15734,7 +15760,6 @@ dependencies = [
"clock",
"collections",
"ctor",
"env_logger 0.11.8",
"gpui",
"http_client",
"log",
@@ -15747,6 +15772,7 @@ dependencies = [
"sum_tree",
"util",
"workspace-hack",
"zlog",
]
[[package]]
@@ -16631,8 +16657,6 @@ dependencies = [
[[package]]
name = "tree-sitter-python"
version = "0.23.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d065aaa27f3aaceaf60c1f0e0ac09e1cb9eb8ed28e7bcdaa52129cffc7f4b04"
dependencies = [
"cc",
"tree-sitter-language",
@@ -16808,6 +16832,7 @@ dependencies = [
"component",
"documented",
"gpui",
"gpui_macros",
"icons",
"itertools 0.14.0",
"menu",
@@ -16841,7 +16866,7 @@ name = "ui_macros"
version = "0.1.0"
dependencies = [
"quote",
"syn 1.0.109",
"syn 2.0.101",
"workspace-hack",
]
@@ -17088,6 +17113,8 @@ dependencies = [
"tempfile",
"tendril",
"unicase",
"unicode-script",
"unicode-segmentation",
"util_macros",
"walkdir",
"workspace-hack",
@@ -17098,7 +17125,7 @@ name = "util_macros"
version = "0.1.0"
dependencies = [
"quote",
"syn 1.0.109",
"syn 2.0.101",
"workspace-hack",
]
@@ -17375,12 +17402,6 @@ dependencies = [
"wit-bindgen-rt 0.39.0",
]
[[package]]
name = "wasi-preview1-component-adapter-provider"
version = "29.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcd9f21bbde82ba59e415a8725e6ad0d0d7e9e460b1a3ccbca5bdee952c1a324"
[[package]]
name = "wasite"
version = "0.1.0"
@@ -17503,22 +17524,6 @@ dependencies = [
"wasmparser 0.201.0",
]
[[package]]
name = "wasm-metadata"
version = "0.221.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11f4ef50d17e103a88774cd4aa5d06bfb1ae44036a8f3f1325e0e9b3e3417ac4"
dependencies = [
"anyhow",
"indexmap",
"serde",
"serde_derive",
"serde_json",
"spdx",
"wasm-encoder 0.221.3",
"wasmparser 0.221.3",
]
[[package]]
name = "wasm-metadata"
version = "0.227.1"
@@ -19015,25 +19020,6 @@ dependencies = [
"wit-parser 0.201.0",
]
[[package]]
name = "wit-component"
version = "0.221.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66c55ca8772d2b270e28066caed50ce4e53a28c3ac10e01efbd90e5be31e448b"
dependencies = [
"anyhow",
"bitflags 2.9.0",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder 0.221.3",
"wasm-metadata 0.221.3",
"wasmparser 0.221.3",
"wit-parser 0.221.3",
]
[[package]]
name = "wit-component"
version = "0.227.1"
@@ -19134,7 +19120,6 @@ dependencies = [
"component",
"dap",
"db",
"env_logger 0.11.8",
"fs",
"futures 0.3.31",
"gpui",
@@ -19166,6 +19151,7 @@ dependencies = [
"windows 0.61.1",
"workspace-hack",
"zed_actions",
"zlog",
]
[[package]]
@@ -19340,7 +19326,6 @@ dependencies = [
"unicode-properties",
"url",
"uuid",
"wasm-encoder 0.221.3",
"wasmparser 0.221.3",
"wasmtime",
"wasmtime-cranelift",
@@ -19363,7 +19348,6 @@ dependencies = [
"anyhow",
"clock",
"collections",
"env_logger 0.11.8",
"fs",
"futures 0.3.31",
"fuzzy",
@@ -19390,6 +19374,7 @@ dependencies = [
"text",
"util",
"workspace-hack",
"zlog",
]
[[package]]
@@ -19697,12 +19682,12 @@ version = "0.189.0"
dependencies = [
"activity_indicator",
"agent",
"agent_settings",
"anyhow",
"ashpd",
"askpass",
"assets",
"assistant_context_editor",
"assistant_settings",
"assistant_tool",
"assistant_tools",
"async-watch",
@@ -19749,6 +19734,7 @@ dependencies = [
"image_viewer",
"indoc",
"inline_completion_button",
"inspector_ui",
"install_cli",
"jj_ui",
"journal",
@@ -19811,6 +19797,7 @@ dependencies = [
"title_bar",
"toolchain_selector",
"tree-sitter-md",
"tree-sitter-python",
"tree-sitter-rust",
"ui",
"ui_input",
@@ -19888,9 +19875,9 @@ dependencies = [
[[package]]
name = "zed_llm_client"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9be71e2f9b271e1eb8eb3e0d986075e770d1a0a299fb036abc3f1fc13a2fa7eb"
checksum = "22a8b9575b215536ed8ad254ba07171e4e13bd029eda3b54cca4b184d2768050"
dependencies = [
"anyhow",
"serde",
@@ -19930,7 +19917,7 @@ dependencies = [
[[package]]
name = "zed_toml"
version = "0.1.3"
version = "0.1.4"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -20082,7 +20069,6 @@ dependencies = [
"ctor",
"db",
"editor",
"env_logger 0.11.8",
"feature_flags",
"fs",
"futures 0.3.31",
@@ -20121,6 +20107,7 @@ dependencies = [
"worktree",
"zed_actions",
"zed_llm_client",
"zlog",
]
[[package]]

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",
@@ -210,12 +211,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 +280,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" }
@@ -430,7 +432,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 +449,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"] }
@@ -550,7 +553,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"
@@ -586,7 +589,7 @@ tree-sitter-html = "0.23"
tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.24"
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
tree-sitter-python = "0.23"
tree-sitter-python = { path = "../repos/tree-sitter-python"}
tree-sitter-regex = "0.24"
tree-sitter-ruby = "0.23"
tree-sitter-rust = "0.24"
@@ -600,7 +603,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 = [
@@ -614,9 +616,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.3"
zstd = "0.11"
[workspace.dependencies.async-stripe]

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

@@ -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,6 +31,7 @@
"ctrl-,": "zed::OpenSettings",
"ctrl-q": "zed::Quit",
"f4": "debugger::Start",
"alt-f4": "debugger::RerunLastSession",
"f5": "debugger::Continue",
"shift-f5": "debugger::Stop",
"ctrl-shift-f5": "debugger::Restart",
@@ -247,7 +248,9 @@
"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"
}
},
{
@@ -274,6 +277,7 @@
"context": "MessageEditor > Editor",
"bindings": {
"enter": "agent::Chat",
"ctrl-enter": "agent::ChatWithFollow",
"ctrl-i": "agent::ToggleProfileSelector",
"shift-ctrl-r": "agent::OpenAgentDiff"
}
@@ -675,7 +679,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"
}
},
{
@@ -870,6 +875,23 @@
"ctrl-i": "debugger::ToggleSessionPicker"
}
},
{
"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": {

View File

@@ -4,6 +4,7 @@
"use_key_equivalents": true,
"bindings": {
"f4": "debugger::Start",
"alt-f4": "debugger::RerunLastSession",
"f5": "debugger::Continue",
"shift-f5": "debugger::Stop",
"shift-cmd-f5": "debugger::Restart",
@@ -282,7 +283,9 @@
"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"
}
},
{
@@ -311,6 +314,7 @@
"use_key_equivalents": true,
"bindings": {
"enter": "agent::Chat",
"cmd-enter": "agent::ChatWithFollow",
"cmd-i": "agent::ToggleProfileSelector",
"shift-ctrl-r": "agent::OpenAgentDiff"
}
@@ -357,12 +361,6 @@
"ctrl--": "pane::GoBack"
}
},
{
"context": "ThreadHistory",
"bindings": {
"ctrl--": "pane::GoBack"
}
},
{
"context": "ThreadHistory > Editor",
"bindings": {
@@ -741,7 +739,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"
}
},
{
@@ -844,7 +843,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"
}
},
{
@@ -936,6 +938,13 @@
"cmd-i": "debugger::ToggleSessionPicker"
}
},
{
"context": "BreakpointList",
"bindings": {
"space": "debugger::ToggleEnableBreakpoint",
"backspace": "debugger::UnsetBreakpoint"
}
},
{
"context": "CollabPanel && not_editing",
"use_key_equivalents": true,

View File

@@ -213,6 +213,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`
@@ -322,7 +324,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 +475,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,
@@ -721,14 +729,14 @@
// The provider to use.
"provider": "zed.dev",
// The model to use.
"model": "claude-3-7-sonnet-latest"
"model": "claude-sonnet-4"
},
// 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 +756,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 +822,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 +959,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.
@@ -1429,7 +1452,9 @@
"language_servers": ["erlang-ls", "!elp", "..."]
},
"Git Commit": {
"allow_rewrap": "anywhere"
"allow_rewrap": "anywhere",
"preferred_line_length": 72,
"soft_wrap": "bounded"
},
"Go": {
"code_actions_on_format": {

View File

@@ -1,32 +1,30 @@
[
{
"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"
}
]

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;
@@ -508,14 +508,7 @@ impl ActivityIndicator {
};
move |_, _, cx| workspace::reload(&reload, cx)
})),
tooltip_message: Some(format!("Install version: {}", {
match version {
auto_update::VersionCheckType::Sha(sha) => sha.to_string(),
auto_update::VersionCheckType::Semantic(semantic_version) => {
semantic_version.to_string()
}
}
})),
tooltip_message: Some(Self::install_version_tooltip_message(&version)),
}),
AutoUpdateStatus::Errored => Some(Content {
icon: Some(
@@ -555,6 +548,17 @@ impl ActivityIndicator {
None
}
fn install_version_tooltip_message(version: &VersionCheckType) -> String {
format!("Install 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,
@@ -686,3 +690,26 @@ impl StatusItemView for ActivityIndicator {
) {
}
}
#[cfg(test)]
mod tests {
use gpui::SemanticVersion;
use release_channel::AppCommitSha;
use super::*;
#[test]
fn test_install_version_tooltip_message() {
let message = ActivityIndicator::install_version_tooltip_message(
&VersionCheckType::Semantic(SemanticVersion::new(1, 0, 0)),
);
assert_eq!(message, "Install version: 1.0.0");
let message = ActivityIndicator::install_version_tooltip_message(&VersionCheckType::Sha(
AppCommitSha::new("14d9a4189f058d8736339b06ff2340101eaea5af".to_string()),
));
assert_eq!(message, "Install 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
@@ -76,7 +77,6 @@ serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
settings.workspace = true
smallvec.workspace = true
smol.workspace = true
streaming_diff.workspace = true
telemetry.workspace = true

View File

@@ -13,9 +13,10 @@ use crate::tool_use::{PendingToolUseStatus, ToolUse};
use crate::ui::{
AddedContext, AgentNotification, AgentNotificationEvent, AnimatedLabel, ContextPill,
};
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,7 +53,7 @@ use ui::{
};
use util::ResultExt as _;
use util::markdown::MarkdownCodeBlock;
use workspace::Workspace;
use workspace::{CollaboratorId, Workspace};
use zed_actions::assistant::OpenRulesLibrary;
pub struct ActiveThread {
@@ -971,7 +972,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 +997,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(cx);
self.show_notification(
if thread.used_tools_since_last_user_message() {
if used_tools {
"Finished running tools"
} else {
"New message"
@@ -996,6 +1013,7 @@ impl ActiveThread {
_ => {}
},
ThreadEvent::ToolConfirmationNeeded => {
self.play_notification_sound(cx);
self.show_notification("Waiting for tool confirmation", IconName::Info, window, cx);
}
ThreadEvent::StreamedAssistantText(message_id, text) => {
@@ -1018,7 +1036,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 +1150,13 @@ impl ActiveThread {
cx.notify();
}
fn play_notification_sound(&self, cx: &mut App) {
let settings = AgentSettings::get_global(cx);
if settings.play_sound_when_agent_done {
Audio::play_sound(Sound::AgentDone, cx);
}
}
fn show_notification(
&mut self,
caption: impl Into<SharedString>,
@@ -1146,7 +1170,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);
@@ -1417,7 +1441,7 @@ impl ActiveThread {
tools: vec![],
tool_choice: None,
stop: vec![],
temperature: AssistantSettings::temperature_for_model(
temperature: AgentSettings::temperature_for_model(
&configured_model.model,
cx,
),
@@ -1467,7 +1491,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();
}
@@ -1754,6 +1778,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 {
@@ -1874,7 +1903,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 +2011,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),
),
),
)
)
}),
)
@@ -3054,7 +3107,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, _| {
@@ -3593,3 +3646,163 @@ fn open_editor_at_position(
}
})
}
#[cfg(test)]
mod tests {
use assistant_tool::{ToolRegistry, ToolWorkingSet};
use editor::EditorSettings;
use fs::FakeFs;
use gpui::{AppContext, TestAppContext, VisualTestContext};
use language_model::{LanguageModel, fake_provider::FakeLanguageModel};
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(), 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)));
}
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,7 +28,7 @@ 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 _;
@@ -69,6 +69,7 @@ actions!(
AddContextServer,
RemoveSelectedThread,
Chat,
ChatWithFollow,
CycleNextInlineAssist,
CyclePreviousInlineAssist,
FocusUp,
@@ -86,6 +87,8 @@ actions!(
Follow,
ResetTrialUpsell,
ResetTrialEndUpsell,
ContinueThread,
ContinueWithBurnMode,
]
);
@@ -120,7 +123,7 @@ pub fn init(
is_eval: bool,
cx: &mut App,
) {
AssistantSettings::register(cx);
AgentSettings::register(cx);
SlashCommandSettings::register(cx);
assistant_context_editor::init(client.clone(), cx);
@@ -173,7 +176,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(
@@ -598,7 +637,7 @@ impl AgentConfiguration {
.hover(|style| style.bg(cx.theme().colors().element_hover))
.rounded_sm()
.child(
Label::new(tool.name())
Label::new(tool.ui_name())
.buffer_font(cx)
.size(LabelSize::Small),
)

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};
@@ -117,7 +117,7 @@ impl ToolPickerDelegate {
ToolSource::Native => {
if mode == ToolPickerMode::BuiltinTools {
items.extend(tools.into_iter().map(|tool| PickerItem::Tool {
name: tool.name().into(),
name: tool.ui_name().into(),
server_id: None,
}));
}
@@ -129,7 +129,7 @@ impl ToolPickerDelegate {
server_id: server_id.clone(),
});
items.extend(tools.into_iter().map(|tool| PickerItem::Tool {
name: tool.name().into(),
name: tool.ui_name().into(),
server_id: Some(server_id.clone()),
}));
}
@@ -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()
.stop_mouse_events_except_scroll()
.shadow_md()
.children(vec![
Button::new(("reject", row as u64), "Reject")
@@ -1253,9 +1253,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);
@@ -1461,7 +1461,7 @@ 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))
@@ -1736,7 +1736,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};
@@ -1755,7 +1755,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);
@@ -1911,7 +1911,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,4 +1,4 @@
use assistant_settings::AssistantSettings;
use agent_settings::AgentSettings;
use fs::Fs;
use gpui::{Entity, FocusHandle, SharedString};
@@ -63,7 +63,7 @@ impl AgentModelSelector {
);
}
});
update_settings_file::<AssistantSettings>(
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _cx| {
@@ -72,7 +72,7 @@ impl AgentModelSelector {
);
}
ModelType::InlineAssistant => {
update_settings_file::<AssistantSettings>(
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _cx| {

View File

@@ -1,18 +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 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;
@@ -40,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};
@@ -63,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, ToggleContextPicker, ToggleNavigationMenu,
ToggleOptionsMenu,
};
const AGENT_PANEL_KEY: &str = "agent_panel";
@@ -522,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
@@ -892,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,
@@ -1226,7 +1251,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),
@@ -1259,6 +1284,26 @@ 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, Some(window.window_handle()), cx);
});
});
} else {
log::warn!("No configured model available for continuation");
}
}
pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
match &self.active_view {
ActiveView::PromptEditor { context_editor, .. } => Some(context_editor.clone()),
@@ -1357,10 +1402,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,
}
}
@@ -1380,22 +1425,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)
@@ -1420,8 +1461,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> {
@@ -1437,7 +1477,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 {
@@ -1973,7 +2013,7 @@ impl AgentPanel {
return None;
}
if self.user_store.read(cx).current_user_account_too_young() {
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())
@@ -2555,7 +2595,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)
@@ -2574,17 +2618,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::Max);
});
});
this.continue_conversation(window, cx);
})),
)
}),
);
Some(div().px_2().pb_2().child(banner).into_any_element())
}
@@ -2939,9 +3025,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()
@@ -2968,6 +3054,17 @@ 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::Max);
});
});
this.continue_conversation(window, cx);
}))
.child(self.render_toolbar(window, cx))
.children(self.render_upsell(window, cx))
.children(self.render_trial_end_upsell(window, cx))
@@ -2975,7 +3072,7 @@ impl Render for AgentPanel {
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)),

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};
@@ -443,7 +443,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 {

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

@@ -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| {
@@ -1213,7 +1216,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 +1289,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 +1356,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 +1367,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 +1380,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 +1394,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 +1412,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 +1429,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 +1450,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

@@ -30,6 +30,10 @@ impl ContextServerTool {
impl Tool for ContextServerTool {
fn name(&self) -> String {
format!("{}-{}", self.server_id, self.tool.name)
}
fn ui_name(&self) -> String {
self.tool.name.clone()
}

View File

@@ -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(

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;
}
@@ -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

@@ -28,6 +28,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;
@@ -326,9 +327,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 +372,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 +891,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(),
@@ -1063,7 +1062,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(),

View File

@@ -1,4 +1,5 @@
use std::collections::BTreeMap;
use std::rc::Rc;
use std::sync::Arc;
use crate::agent_model_selector::{AgentModelSelector, ModelType};
@@ -8,8 +9,8 @@ use crate::ui::{
AnimatedLabel, 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};
@@ -49,8 +50,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, NewThread,
OpenAgentDiff, RemoveAllContext, ToggleContextPicker, ToggleProfileSelector,
register_agent_preview,
};
#[derive(RegisterComponent)]
@@ -120,7 +122,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),
@@ -278,7 +280,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 +304,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()
}
@@ -463,16 +480,18 @@ impl MessageEditor {
let active_completion_mode = thread.completion_mode();
let max_mode_enabled = active_completion_mode == CompletionMode::Max;
let icon = if max_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)
.selected_icon_color(Color::Error)
.on_click(cx.listener(move |this, _event, _window, cx| {
this.thread.update(cx, |thread, _cx| {
thread.set_completion_mode(match active_completion_mode {
@@ -562,6 +581,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)
@@ -668,7 +688,6 @@ impl MessageEditor {
.justify_between()
.child(
h_flex()
.gap_1()
.child(self.render_follow_toggle(cx))
.children(self.render_max_mode_toggle(cx)),
)
@@ -842,7 +861,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.),
@@ -1167,9 +1186,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;
@@ -1253,7 +1273,7 @@ impl MessageEditor {
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

@@ -193,7 +193,8 @@ impl TerminalTransaction {
});
}
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};
@@ -271,7 +271,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 {

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;
@@ -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)
}
@@ -943,6 +953,7 @@ impl Thread {
vec![MessageSegment::Text(text.into())],
loaded_context.loaded_context,
creases,
false,
cx,
);
@@ -958,6 +969,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 +993,7 @@ impl Thread {
segments,
LoadedContext::default(),
Vec::new(),
false,
cx,
)
}
@@ -978,6 +1004,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 +1014,7 @@ impl Thread {
segments,
loaded_context,
creases,
is_hidden,
});
self.touch_updated_at();
cx.emit(ThreadEvent::MessageAdded(id));
@@ -1127,6 +1155,7 @@ impl Thread {
label: crease.metadata.label.clone(),
})
.collect(),
is_hidden: message.is_hidden,
})
.collect(),
initial_project_snapshot,
@@ -1142,6 +1171,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,
})
})
}
@@ -1196,7 +1226,7 @@ impl Thread {
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());
@@ -1355,7 +1385,7 @@ impl Thread {
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 {
@@ -1773,6 +1803,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
@@ -2031,7 +2062,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(),
@@ -2248,10 +2279,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
@@ -2820,7 +2858,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;
@@ -2851,7 +2889,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;
@@ -3162,7 +3201,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;
@@ -3246,14 +3286,14 @@ 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,
);
@@ -3266,14 +3306,14 @@ fn main() {{
// 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,
);
@@ -3286,14 +3326,14 @@ fn main() {{
// 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,
);
@@ -3306,14 +3346,14 @@ fn main() {{
// 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,
);
@@ -3521,7 +3561,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

@@ -4,8 +4,8 @@ use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::Arc;
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;
@@ -485,13 +485,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 +676,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 +759,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 +819,7 @@ impl LegacySerializedThread {
exceeded_window_error: None,
model: None,
completion_mode: None,
tool_use_limit_reached: false,
}
}
}
@@ -840,6 +845,7 @@ impl LegacySerializedMessage {
tool_results: self.tool_results,
context: String::new(),
creases: Vec::new(),
is_hidden: false,
}
}
}

View File

@@ -18,18 +18,24 @@ impl MaxModeTooltip {
impl Render for MaxModeTooltip {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let icon = if self.selected {
IconName::ZedBurnModeOn
} else {
IconName::ZedBurnMode
};
let title = h_flex()
.gap_1()
.child(Icon::new(icon).size(IconSize::Small))
.child(Label::new("Burn Mode"));
tooltip_container(window, cx, |this, _, _| {
this.gap_1()
this.gap_0p5()
.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(title)
.child(
h_flex()
.gap_0p5()
@@ -38,18 +44,13 @@ impl Render for MaxModeTooltip {
)
)
} else {
header.child(
h_flex()
.gap_1p5()
.child(Icon::new(IconName::ZedMaxMode).size(IconSize::Small))
.child(Label::new("Zed's Max Mode"))
)
header.child(title)
})
.child(
div()
.max_w_72()
.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, offering an unfettered agentic experience.")
.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,99 @@ 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()),
)),
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 +465,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 +502,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 +566,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 +587,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 +596,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 +638,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 +653,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 +674,6 @@ pub struct AssistantSettingsContentV2 {
/// Default: []
#[serde(default)]
model_parameters: Vec<LanguageModelParameters>,
/// What completion mode to enable for new threads
///
/// Default: normal
@@ -759,50 +774,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 +831,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 +882,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 +939,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 +979,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 +988,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 +1044,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 +1076,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 +1106,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

@@ -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

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,
@@ -1730,9 +1730,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,
@@ -2267,8 +2266,7 @@ impl AssistantContext {
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 {

View File

@@ -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,8 @@
use crate::language_model_selector::{
LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
};
use agent_settings::AgentSettings;
use anyhow::Result;
use assistant_settings::AssistantSettings;
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};
use assistant_slash_commands::{
DefaultSlashCommand, DocsSlashCommand, DocsSlashCommandArgs, FileSlashCommand,
@@ -51,6 +51,7 @@ use std::{
cmp,
ops::Range,
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
time::Duration,
};
@@ -234,7 +235,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()));
@@ -282,7 +283,7 @@ impl ContextEditor {
LanguageModelSelector::new(
|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()),
@@ -1902,7 +1903,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 {
@@ -3365,7 +3366,7 @@ mod tests {
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

@@ -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

@@ -37,7 +37,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 +47,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

@@ -717,9 +717,7 @@ mod tests {
#[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) {

View File

@@ -203,6 +203,11 @@ pub trait Tool: 'static + Send + Sync {
/// Returns the name of the tool.
fn name(&self) -> String;
/// Returns the name to be displayed in the UI for this tool.
fn ui_name(&self) -> String {
self.name()
}
/// Returns the description of the tool.
fn description(&self) -> String;

View File

@@ -15,9 +15,9 @@ path = "src/assistant_tools.rs"
eval = []
[dependencies]
agent_settings.workspace = true
aho-corasick.workspace = true
anyhow.workspace = true
assistant_settings.workspace = true
assistant_tool.workspace = true
buffer_diff.workspace = true
chrono.workspace = true

View File

@@ -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;
@@ -133,7 +133,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,3 +1,4 @@
mod create_file_parser;
mod edit_parser;
#[cfg(test)]
mod evals;
@@ -6,6 +7,7 @@ use crate::{Template, Templates};
use aho_corasick::AhoCorasick;
use anyhow::Result;
use assistant_tool::ActionLog;
use create_file_parser::{CreateFileParser, CreateFileParserEvent};
use edit_parser::{EditParser, EditParserEvent, EditParserMetrics};
use futures::{
Stream, StreamExt,
@@ -123,16 +125,16 @@ impl EditAgent {
mpsc::UnboundedReceiver<EditAgentOutputEvent>,
) {
let (output_events_tx, output_events_rx) = mpsc::unbounded();
let (parse_task, parse_rx) = Self::parse_create_file_chunks(edit_chunks, cx);
let this = self.clone();
let task = cx.spawn(async move |cx| {
this.action_log
.update(cx, |log, cx| log.buffer_created(buffer.clone(), cx))?;
let output = this
.overwrite_with_chunks_internal(buffer, edit_chunks, output_events_tx, cx)
.await;
this.overwrite_with_chunks_internal(buffer, parse_rx, output_events_tx, cx)
.await?;
this.project
.update(cx, |project, cx| project.set_agent_location(None, cx))?;
output
parse_task.await
});
(task, output_events_rx)
}
@@ -140,10 +142,10 @@ impl EditAgent {
async fn overwrite_with_chunks_internal(
&self,
buffer: Entity<Buffer>,
edit_chunks: impl 'static + Send + Stream<Item = Result<String, LanguageModelCompletionError>>,
mut parse_rx: UnboundedReceiver<Result<CreateFileParserEvent>>,
output_events_tx: mpsc::UnboundedSender<EditAgentOutputEvent>,
cx: &mut AsyncApp,
) -> Result<EditAgentOutput> {
) -> Result<()> {
cx.update(|cx| {
buffer.update(cx, |buffer, cx| buffer.set_text("", cx));
self.action_log.update(cx, |log, cx| {
@@ -163,34 +165,31 @@ impl EditAgent {
.ok();
})?;
let mut raw_edits = String::new();
pin_mut!(edit_chunks);
while let Some(chunk) = edit_chunks.next().await {
let chunk = chunk?;
raw_edits.push_str(&chunk);
cx.update(|cx| {
buffer.update(cx, |buffer, cx| buffer.append(chunk, cx));
self.action_log
.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
self.project.update(cx, |project, cx| {
project.set_agent_location(
Some(AgentLocation {
buffer: buffer.downgrade(),
position: language::Anchor::MAX,
}),
cx,
)
});
})?;
output_events_tx
.unbounded_send(EditAgentOutputEvent::Edited)
.ok();
while let Some(event) = parse_rx.next().await {
match event? {
CreateFileParserEvent::NewTextChunk { chunk } => {
cx.update(|cx| {
buffer.update(cx, |buffer, cx| buffer.append(chunk, cx));
self.action_log
.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
self.project.update(cx, |project, cx| {
project.set_agent_location(
Some(AgentLocation {
buffer: buffer.downgrade(),
position: language::Anchor::MAX,
}),
cx,
)
});
})?;
output_events_tx
.unbounded_send(EditAgentOutputEvent::Edited)
.ok();
}
}
}
Ok(EditAgentOutput {
raw_edits,
parser_metrics: EditParserMetrics::default(),
})
Ok(())
}
pub fn edit(
@@ -435,6 +434,44 @@ impl EditAgent {
(output, rx)
}
fn parse_create_file_chunks(
chunks: impl 'static + Send + Stream<Item = Result<String, LanguageModelCompletionError>>,
cx: &mut AsyncApp,
) -> (
Task<Result<EditAgentOutput>>,
UnboundedReceiver<Result<CreateFileParserEvent>>,
) {
let (tx, rx) = mpsc::unbounded();
let output = cx.background_spawn(async move {
pin_mut!(chunks);
let mut parser = CreateFileParser::new();
let mut raw_edits = String::new();
while let Some(chunk) = chunks.next().await {
match chunk {
Ok(chunk) => {
raw_edits.push_str(&chunk);
for event in parser.push(Some(&chunk)) {
tx.unbounded_send(Ok(event))?;
}
}
Err(error) => {
tx.unbounded_send(Err(error.into()))?;
}
}
}
// Send final events with None to indicate completion
for event in parser.push(None) {
tx.unbounded_send(Ok(event))?;
}
Ok(EditAgentOutput {
raw_edits,
parser_metrics: EditParserMetrics::default(),
})
});
(output, rx)
}
fn reindent_new_text_chunks(
delta: IndentDelta,
mut stream: impl Unpin + Stream<Item = Result<EditParserEvent>>,
@@ -1138,7 +1175,7 @@ mod tests {
})
);
chunks_tx.unbounded_send("jkl\n").unwrap();
chunks_tx.unbounded_send("```\njkl\n").unwrap();
cx.run_until_parked();
assert_eq!(
drain_events(&mut events),
@@ -1146,7 +1183,7 @@ mod tests {
);
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.snapshot().text()),
"jkl\n"
"jkl"
);
assert_eq!(
project.read_with(cx, |project, _| project.agent_location()),
@@ -1164,7 +1201,7 @@ mod tests {
);
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.snapshot().text()),
"jkl\nmno\n"
"jkl\nmno"
);
assert_eq!(
project.read_with(cx, |project, _| project.agent_location()),
@@ -1174,7 +1211,7 @@ mod tests {
})
);
chunks_tx.unbounded_send("pqr").unwrap();
chunks_tx.unbounded_send("pqr\n```").unwrap();
cx.run_until_parked();
assert_eq!(
drain_events(&mut events),

View File

@@ -0,0 +1,218 @@
use regex::Regex;
use smallvec::SmallVec;
use std::cell::LazyCell;
use util::debug_panic;
const START_MARKER: LazyCell<Regex> = LazyCell::new(|| Regex::new(r"\n?```\S*\n").unwrap());
const END_MARKER: LazyCell<Regex> = LazyCell::new(|| Regex::new(r"\n```\s*$").unwrap());
#[derive(Debug)]
pub enum CreateFileParserEvent {
NewTextChunk { chunk: String },
}
#[derive(Debug)]
pub struct CreateFileParser {
state: ParserState,
buffer: String,
}
#[derive(Debug, PartialEq)]
enum ParserState {
Pending,
WithinText,
Finishing,
Finished,
}
impl CreateFileParser {
pub fn new() -> Self {
CreateFileParser {
state: ParserState::Pending,
buffer: String::new(),
}
}
pub fn push(&mut self, chunk: Option<&str>) -> SmallVec<[CreateFileParserEvent; 1]> {
if chunk.is_none() {
self.state = ParserState::Finishing;
}
let chunk = chunk.unwrap_or_default();
self.buffer.push_str(chunk);
let mut edit_events = SmallVec::new();
loop {
match &mut self.state {
ParserState::Pending => {
if let Some(m) = START_MARKER.find(&self.buffer) {
self.buffer.drain(..m.end());
self.state = ParserState::WithinText;
} else {
break;
}
}
ParserState::WithinText => {
let text = self.buffer.trim_end_matches(&['`', '\n', ' ']);
let text_len = text.len();
if text_len > 0 {
edit_events.push(CreateFileParserEvent::NewTextChunk {
chunk: self.buffer.drain(..text_len).collect(),
});
}
break;
}
ParserState::Finishing => {
if let Some(m) = END_MARKER.find(&self.buffer) {
self.buffer.drain(m.start()..);
}
if !self.buffer.is_empty() {
if !self.buffer.ends_with('\n') {
self.buffer.push('\n');
}
edit_events.push(CreateFileParserEvent::NewTextChunk {
chunk: self.buffer.drain(..).collect(),
});
}
self.state = ParserState::Finished;
break;
}
ParserState::Finished => debug_panic!("Can't call parser after finishing"),
}
}
edit_events
}
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
use rand::prelude::*;
use std::cmp;
#[gpui::test(iterations = 100)]
fn test_happy_path(mut rng: StdRng) {
let mut parser = CreateFileParser::new();
assert_eq!(
parse_random_chunks("```\nHello world\n```", &mut parser, &mut rng),
"Hello world".to_string()
);
}
#[gpui::test(iterations = 100)]
fn test_cut_prefix(mut rng: StdRng) {
let mut parser = CreateFileParser::new();
assert_eq!(
parse_random_chunks(
indoc! {"
Let me write this file for you:
```
Hello world
```
"},
&mut parser,
&mut rng
),
"Hello world".to_string()
);
}
#[gpui::test(iterations = 100)]
fn test_language_name_on_fences(mut rng: StdRng) {
let mut parser = CreateFileParser::new();
assert_eq!(
parse_random_chunks(
indoc! {"
```rust
Hello world
```
"},
&mut parser,
&mut rng
),
"Hello world".to_string()
);
}
#[gpui::test(iterations = 100)]
fn test_leave_suffix(mut rng: StdRng) {
let mut parser = CreateFileParser::new();
assert_eq!(
parse_random_chunks(
indoc! {"
Let me write this file for you:
```
Hello world
```
The end
"},
&mut parser,
&mut rng
),
// This output is marlformed, so we're doing our best effort
"Hello world\n```\n\nThe end\n".to_string()
);
}
#[gpui::test(iterations = 100)]
fn test_inner_fences(mut rng: StdRng) {
let mut parser = CreateFileParser::new();
assert_eq!(
parse_random_chunks(
indoc! {"
Let me write this file for you:
```
```
Hello world
```
```
"},
&mut parser,
&mut rng
),
// This output is marlformed, so we're doing our best effort
"```\nHello world\n```\n".to_string()
);
}
fn parse_random_chunks(input: &str, parser: &mut CreateFileParser, rng: &mut StdRng) -> String {
let chunk_count = rng.gen_range(1..=cmp::min(input.len(), 50));
let mut chunk_indices = (0..input.len()).choose_multiple(rng, chunk_count);
chunk_indices.sort();
chunk_indices.push(input.len());
let chunk_indices = chunk_indices
.into_iter()
.map(Some)
.chain(vec![None])
.collect::<Vec<Option<usize>>>();
let mut edit = String::default();
let mut last_ix = 0;
for chunk_ix in chunk_indices {
let mut chunk = None;
if let Some(chunk_ix) = chunk_ix {
chunk = Some(&input[last_ix..chunk_ix]);
last_ix = chunk_ix;
}
for event in parser.push(chunk) {
match event {
CreateFileParserEvent::NewTextChunk { chunk } => {
edit.push_str(&chunk);
}
}
}
}
edit
}
}

View File

@@ -2,12 +2,12 @@ use derive_more::{Add, AddAssign};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::{cmp, mem, ops::Range};
use std::{mem, ops::Range};
const OLD_TEXT_END_TAG: &str = "</old_text>";
const NEW_TEXT_END_TAG: &str = "</new_text>";
const END_TAG_LEN: usize = OLD_TEXT_END_TAG.len();
const _: () = debug_assert!(OLD_TEXT_END_TAG.len() == NEW_TEXT_END_TAG.len());
const EDITS_END_TAG: &str = "</edits>";
const END_TAGS: [&str; 3] = [OLD_TEXT_END_TAG, NEW_TEXT_END_TAG, EDITS_END_TAG];
#[derive(Debug)]
pub enum EditParserEvent {
@@ -115,8 +115,9 @@ impl EditParser {
self.state = EditParserState::Pending;
edit_events.push(EditParserEvent::NewTextChunk { chunk, done: true });
} else {
let mut end_prefixes = (1..END_TAG_LEN)
.flat_map(|i| [&NEW_TEXT_END_TAG[..i], &OLD_TEXT_END_TAG[..i]])
let mut end_prefixes = END_TAGS
.iter()
.flat_map(|tag| (1..tag.len()).map(move |i| &tag[..i]))
.chain(["\n"]);
if end_prefixes.all(|prefix| !self.buffer.ends_with(&prefix)) {
edit_events.push(EditParserEvent::NewTextChunk {
@@ -133,16 +134,11 @@ impl EditParser {
}
fn find_end_tag(&self) -> Option<Range<usize>> {
let old_text_end_tag_ix = self.buffer.find(OLD_TEXT_END_TAG);
let new_text_end_tag_ix = self.buffer.find(NEW_TEXT_END_TAG);
let start_ix = if let Some((old_text_ix, new_text_ix)) =
old_text_end_tag_ix.zip(new_text_end_tag_ix)
{
cmp::min(old_text_ix, new_text_ix)
} else {
old_text_end_tag_ix.or(new_text_end_tag_ix)?
};
Some(start_ix..start_ix + END_TAG_LEN)
let (tag, start_ix) = END_TAGS
.iter()
.flat_map(|tag| Some((tag, self.buffer.find(tag)?)))
.min_by_key(|(_, ix)| *ix)?;
Some(start_ix..start_ix + tag.len())
}
pub fn finish(self) -> EditParserMetrics {
@@ -373,6 +369,35 @@ mod tests {
mismatched_tags: 4
}
);
let mut parser = EditParser::new();
assert_eq!(
parse_random_chunks(
// Reduced from an actual Opus 4 output
indoc! {"
<edits>
<old_text>
Lorem
</old_text>
<new_text>
LOREM
</edits>
"},
&mut parser,
&mut rng
),
vec![Edit {
old_text: "Lorem".to_string(),
new_text: "LOREM".to_string(),
},]
);
assert_eq!(
parser.finish(),
EditParserMetrics {
tags: 2,
mismatched_tags: 1
}
);
}
#[derive(Default, Debug, PartialEq, Eq)]
@@ -407,6 +432,9 @@ mod tests {
}
last_ix = chunk_ix;
}
assert_eq!(pending_edit, Edit::default(), "unfinished edit");
edits
}
}

View File

@@ -163,6 +163,15 @@ fn eval_delete_run_git_blame() {
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_translate_doc_comments() {
// Results for 2025-05-22
//
// Model | Pass rate
// ============================================
//
// claude-3.7-sonnet |
// gemini-2.5-pro-preview-03-25 | 1.0
// gemini-2.5-flash-preview-04-17 |
// gpt-4.1 |
let input_file_path = "root/canvas.rs";
let input_file_content = include_str!("evals/fixtures/translate_doc_comments/before.rs");
let edit_description = "Translate all doc comments to Italian";
@@ -216,6 +225,15 @@ fn eval_translate_doc_comments() {
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_use_wasi_sdk_in_compile_parser_to_wasm() {
// Results for 2025-05-22
//
// Model | Pass rate
// ============================================
//
// claude-3.7-sonnet | 0.98
// gemini-2.5-pro-preview-03-25 | 0.99
// gemini-2.5-flash-preview-04-17 |
// gpt-4.1 |
let input_file_path = "root/lib.rs";
let input_file_content =
include_str!("evals/fixtures/use_wasi_sdk_in_compile_parser_to_wasm/before.rs");
@@ -332,6 +350,15 @@ fn eval_use_wasi_sdk_in_compile_parser_to_wasm() {
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_disable_cursor_blinking() {
// Results for 2025-05-22
//
// Model | Pass rate
// ============================================
//
// claude-3.7-sonnet |
// gemini-2.5-pro-preview-03-25 | 1.0
// gemini-2.5-flash-preview-04-17 |
// gpt-4.1 |
let input_file_path = "root/editor.rs";
let input_file_content = include_str!("evals/fixtures/disable_cursor_blinking/before.rs");
let edit_description = "Comment out the call to `BlinkManager::enable`";
@@ -406,6 +433,15 @@ fn eval_disable_cursor_blinking() {
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_from_pixels_constructor() {
// Results for 2025-05-22
//
// Model | Pass rate
// ============================================
//
// claude-3.7-sonnet |
// gemini-2.5-pro-preview-03-25 | 0.94
// gemini-2.5-flash-preview-04-17 |
// gpt-4.1 |
let input_file_path = "root/canvas.rs";
let input_file_content = include_str!("evals/fixtures/from_pixels_constructor/before.rs");
let edit_description = "Implement from_pixels constructor and add tests.";
@@ -597,11 +633,20 @@ fn eval_from_pixels_constructor() {
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_zode() {
// Results for 2025-05-22
//
// Model | Pass rate
// ============================================
//
// claude-3.7-sonnet | 1.0
// gemini-2.5-pro-preview-03-25 | 1.0
// gemini-2.5-flash-preview-04-17 | 1.0
// gpt-4.1 | 1.0
let input_file_path = "root/zode.py";
let input_content = None;
let edit_description = "Create the main Zode CLI script";
eval(
200,
50,
1.,
EvalInput::from_conversation(
vec![
@@ -694,6 +739,15 @@ fn eval_zode() {
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_add_overwrite_test() {
// Results for 2025-05-22
//
// Model | Pass rate
// ============================================
//
// claude-3.7-sonnet | 0.16
// gemini-2.5-pro-preview-03-25 | 0.35
// gemini-2.5-flash-preview-04-17 |
// gpt-4.1 |
let input_file_path = "root/action_log.rs";
let input_file_content = include_str!("evals/fixtures/add_overwrite_test/before.rs");
let edit_description = "Add a new test for overwriting a file in action_log.rs";
@@ -920,14 +974,11 @@ fn eval_create_empty_file() {
// thoughts into it. This issue is not specific to empty files, but
// it's easier to reproduce with them.
//
// Results for 2025-05-21:
//
// Model | Pass rate
// ============================================
//
// --------------------------------------------
// Prompt version: 2025-05-21
// --------------------------------------------
//
// claude-3.7-sonnet | 1.00
// gemini-2.5-pro-preview-03-25 | 1.00
// gemini-2.5-flash-preview-04-17 | 1.00
@@ -1430,7 +1481,7 @@ impl EditAgentTest {
model.provider_id() == selected_model.provider
&& model.id() == selected_model.model
})
.unwrap();
.expect("Model not found");
let provider = models.provider(&model.provider_id()).unwrap();
(provider, model)
})?;

View File

@@ -676,9 +676,7 @@ mod tests {
#[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) {

View File

@@ -7698,7 +7698,7 @@ impl Editor {
.gap_1()
// Workaround: For some reason, there's a gap if we don't do this
.ml(-BORDER_WIDTH)
.shadow(smallvec![gpui::BoxShadow {
.shadow(vec![gpui::BoxShadow {
color: gpui::black().opacity(0.05),
offset: point(px(1.), px(1.)),
blur_radius: px(2.),

View File

@@ -9,7 +9,7 @@ use assistant_tool::{
ToolUseStatus,
};
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
use editor::{Editor, EditorMode, MultiBuffer, PathKey};
use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey};
use futures::StreamExt;
use gpui::{
Animation, AnimationExt, AnyWindowHandle, App, AppContext, AsyncApp, Entity, EntityId, Task,
@@ -428,7 +428,9 @@ impl EditFileToolCard {
editor.set_show_gutter(false, cx);
editor.disable_inline_diagnostics();
editor.disable_expand_excerpt_buttons(cx);
editor.disable_scrollbars_and_minimap(window, cx);
// Keep horizontal scrollbar so user can scroll horizontally if needed
editor.set_show_vertical_scrollbar(false, cx);
editor.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
editor.set_soft_wrap_mode(SoftWrap::None, cx);
editor.scroll_manager.set_forbid_vertical_scroll(true);
editor.set_show_indent_guides(false, cx);

View File

@@ -125,18 +125,287 @@ impl Tool for ListDirectoryTool {
return Task::ready(Err(anyhow!("{} is not a directory.", input.path))).into();
}
let mut output = String::new();
let mut folders = Vec::new();
let mut files = Vec::new();
for entry in worktree.child_entries(&project_path.path) {
writeln!(
output,
"{}",
Path::new(worktree.root_name()).join(&entry.path).display(),
)
.unwrap();
let full_path = Path::new(worktree.root_name())
.join(&entry.path)
.display()
.to_string();
if entry.is_dir() {
folders.push(full_path);
} else {
files.push(full_path);
}
}
let mut output = String::new();
if !folders.is_empty() {
writeln!(output, "# Folders:\n{}", folders.join("\n")).unwrap();
}
if !files.is_empty() {
writeln!(output, "\n# Files:\n{}", files.join("\n")).unwrap();
}
if output.is_empty() {
return Task::ready(Ok(format!("{} is empty.", input.path).into())).into();
writeln!(output, "{} is empty.", input.path).unwrap();
}
Task::ready(Ok(output.into())).into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use assistant_tool::Tool;
use gpui::{AppContext, TestAppContext};
use indoc::indoc;
use language_model::fake_provider::FakeLanguageModel;
use project::{FakeFs, Project};
use serde_json::json;
use settings::SettingsStore;
use util::path;
fn platform_paths(path_str: &str) -> String {
if cfg!(target_os = "windows") {
path_str.replace("/", "\\")
} else {
path_str.to_string()
}
}
fn init_test(cx: &mut TestAppContext) {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
});
}
#[gpui::test]
async fn test_list_directory_separates_files_and_dirs(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/project",
json!({
"src": {
"main.rs": "fn main() {}",
"lib.rs": "pub fn hello() {}",
"models": {
"user.rs": "struct User {}",
"post.rs": "struct Post {}"
},
"utils": {
"helper.rs": "pub fn help() {}"
}
},
"tests": {
"integration_test.rs": "#[test] fn test() {}"
},
"README.md": "# Project",
"Cargo.toml": "[package]"
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let model = Arc::new(FakeLanguageModel::default());
let tool = Arc::new(ListDirectoryTool);
// Test listing root directory
let input = json!({
"path": "project"
});
let result = cx
.update(|cx| {
tool.clone().run(
input,
Arc::default(),
project.clone(),
action_log.clone(),
model.clone(),
None,
cx,
)
})
.output
.await
.unwrap();
let content = result.content.as_str().unwrap();
assert_eq!(
content,
platform_paths(indoc! {"
# Folders:
project/src
project/tests
# Files:
project/Cargo.toml
project/README.md
"})
);
// Test listing src directory
let input = json!({
"path": "project/src"
});
let result = cx
.update(|cx| {
tool.clone().run(
input,
Arc::default(),
project.clone(),
action_log.clone(),
model.clone(),
None,
cx,
)
})
.output
.await
.unwrap();
let content = result.content.as_str().unwrap();
assert_eq!(
content,
platform_paths(indoc! {"
# Folders:
project/src/models
project/src/utils
# Files:
project/src/lib.rs
project/src/main.rs
"})
);
// Test listing directory with only files
let input = json!({
"path": "project/tests"
});
let result = cx
.update(|cx| {
tool.clone().run(
input,
Arc::default(),
project.clone(),
action_log.clone(),
model.clone(),
None,
cx,
)
})
.output
.await
.unwrap();
let content = result.content.as_str().unwrap();
assert!(!content.contains("# Folders:"));
assert!(content.contains("# Files:"));
assert!(content.contains(&platform_paths("project/tests/integration_test.rs")));
}
#[gpui::test]
async fn test_list_directory_empty_directory(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/project",
json!({
"empty_dir": {}
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let model = Arc::new(FakeLanguageModel::default());
let tool = Arc::new(ListDirectoryTool);
let input = json!({
"path": "project/empty_dir"
});
let result = cx
.update(|cx| tool.run(input, Arc::default(), project, action_log, model, None, cx))
.output
.await
.unwrap();
let content = result.content.as_str().unwrap();
assert_eq!(content, "project/empty_dir is empty.\n");
}
#[gpui::test]
async fn test_list_directory_error_cases(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/project",
json!({
"file.txt": "content"
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let model = Arc::new(FakeLanguageModel::default());
let tool = Arc::new(ListDirectoryTool);
// Test non-existent path
let input = json!({
"path": "project/nonexistent"
});
let result = cx
.update(|cx| {
tool.clone().run(
input,
Arc::default(),
project.clone(),
action_log.clone(),
model.clone(),
None,
cx,
)
})
.output
.await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Path not found"));
// Test trying to list a file instead of directory
let input = json!({
"path": "project/file.txt"
});
let result = cx
.update(|cx| tool.run(input, Arc::default(), project, action_log, model, None, cx))
.output
.await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("is not a directory")
);
}
}

View File

@@ -1,8 +1,10 @@
You are an expert engineer and your task is to write a new file from scratch.
You MUST respond directly with the file's content, without explanations, additional text or triple backticks.
You MUST respond with the file's content wrapped in triple backticks (```).
The backticks should be on their own line.
The text you output will be saved verbatim as the content of the file.
Tool calls have been disabled. You MUST start your response directly with the file's new content.
Tool calls have been disabled.
Start your response with ```.
<file_path>
{{path}}

View File

@@ -43,7 +43,8 @@ NEW TEXT 3 HERE
- Always close all tags properly
{{!-- This example is important for Gemini 2.5 --}}
{{!-- The following example adds almost 10% pass rate for Gemini 2.5.
Claude and gpt-4.1 don't really need it. --}}
<example>
<edits>

View File

@@ -275,7 +275,7 @@ impl Tool for TerminalTool {
let exit_status = terminal
.update(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
.await;
let (content, content_line_count) = terminal.update(cx, |terminal, _| {
let (content, content_line_count) = terminal.read_with(cx, |terminal, _| {
(terminal.get_content(), terminal.total_lines())
})?;
@@ -673,8 +673,7 @@ mod tests {
use super::*;
fn init_test(executor: &BackgroundExecutor, cx: &mut TestAppContext) {
zlog::init();
zlog::init_output_stdout();
zlog::init_test();
executor.allow_parking();
cx.update(|cx| {

View File

@@ -18,6 +18,7 @@ pub enum Sound {
Unmute,
StartScreenshare,
StopScreenshare,
AgentDone,
}
impl Sound {
@@ -29,6 +30,7 @@ impl Sound {
Self::Unmute => "unmute",
Self::StartScreenshare => "start_screenshare",
Self::StopScreenshare => "stop_screenshare",
Self::AgentDone => "agent_done",
}
}
}

View File

@@ -41,7 +41,7 @@ struct UpdateRequestBody {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum VersionCheckType {
Sha(String),
Sha(AppCommitSha),
Semantic(SemanticVersion),
}
@@ -493,7 +493,7 @@ impl AutoUpdater {
async fn update(this: Entity<Self>, mut cx: AsyncApp) -> Result<()> {
let (client, installed_version, previous_status, release_channel) =
this.update(&mut cx, |this, cx| {
this.read_with(&mut cx, |this, cx| {
(
this.http_client.clone(),
this.current_version,
@@ -510,7 +510,7 @@ impl AutoUpdater {
let fetched_release_data =
Self::get_latest_release(&this, "zed", OS, ARCH, release_channel, &mut cx).await?;
let fetched_version = fetched_release_data.clone().version;
let app_commit_sha = cx.update(|cx| AppCommitSha::try_global(cx).map(|sha| sha.0));
let app_commit_sha = cx.update(|cx| AppCommitSha::try_global(cx).map(|sha| sha.full()));
let newer_version = Self::check_for_newer_version(
*RELEASE_CHANNEL,
app_commit_sha,
@@ -569,9 +569,9 @@ impl AutoUpdater {
if let AutoUpdateStatus::Updated { version, .. } = status {
match version {
VersionCheckType::Sha(cached_version) => {
let should_download = fetched_version != cached_version;
let newer_version =
should_download.then(|| VersionCheckType::Sha(fetched_version));
let should_download = fetched_version != cached_version.full();
let newer_version = should_download
.then(|| VersionCheckType::Sha(AppCommitSha::new(fetched_version)));
return Ok(newer_version);
}
VersionCheckType::Semantic(cached_version) => {
@@ -590,7 +590,8 @@ impl AutoUpdater {
.flatten()
.map(|sha| fetched_version != sha)
.unwrap_or(true);
let newer_version = should_download.then(|| VersionCheckType::Sha(fetched_version));
let newer_version = should_download
.then(|| VersionCheckType::Sha(AppCommitSha::new(fetched_version)));
Ok(newer_version)
}
_ => Self::check_for_newer_version_non_nightly(
@@ -1041,7 +1042,7 @@ mod tests {
assert_eq!(
newer_version.unwrap(),
Some(VersionCheckType::Sha(fetched_sha))
Some(VersionCheckType::Sha(AppCommitSha::new(fetched_sha)))
);
}
@@ -1052,7 +1053,7 @@ mod tests {
let installed_version = SemanticVersion::new(1, 0, 0);
let status = AutoUpdateStatus::Updated {
binary_path: PathBuf::new(),
version: VersionCheckType::Sha("b".to_string()),
version: VersionCheckType::Sha(AppCommitSha::new("b".to_string())),
};
let fetched_sha = "b".to_string();
@@ -1074,7 +1075,7 @@ mod tests {
let installed_version = SemanticVersion::new(1, 0, 0);
let status = AutoUpdateStatus::Updated {
binary_path: PathBuf::new(),
version: VersionCheckType::Sha("b".to_string()),
version: VersionCheckType::Sha(AppCommitSha::new("b".to_string())),
};
let fetched_sha = "c".to_string();
@@ -1088,7 +1089,7 @@ mod tests {
assert_eq!(
newer_version.unwrap(),
Some(VersionCheckType::Sha(fetched_sha))
Some(VersionCheckType::Sha(AppCommitSha::new(fetched_sha)))
);
}
@@ -1110,7 +1111,7 @@ mod tests {
assert_eq!(
newer_version.unwrap(),
Some(VersionCheckType::Sha(fetched_sha))
Some(VersionCheckType::Sha(AppCommitSha::new(fetched_sha)))
);
}
@@ -1122,7 +1123,7 @@ mod tests {
let installed_version = SemanticVersion::new(1, 0, 0);
let status = AutoUpdateStatus::Updated {
binary_path: PathBuf::new(),
version: VersionCheckType::Sha("b".to_string()),
version: VersionCheckType::Sha(AppCommitSha::new("b".to_string())),
};
let fetched_sha = "b".to_string();
@@ -1145,7 +1146,7 @@ mod tests {
let installed_version = SemanticVersion::new(1, 0, 0);
let status = AutoUpdateStatus::Updated {
binary_path: PathBuf::new(),
version: VersionCheckType::Sha("b".to_string()),
version: VersionCheckType::Sha(AppCommitSha::new("b".to_string())),
};
let fetched_sha = "c".to_string();
@@ -1159,7 +1160,7 @@ mod tests {
assert_eq!(
newer_version.unwrap(),
Some(VersionCheckType::Sha(fetched_sha))
Some(VersionCheckType::Sha(AppCommitSha::new(fetched_sha)))
);
}
}

View File

@@ -15,6 +15,7 @@ pub enum BedrockModelMode {
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
pub enum Model {
// Anthropic models (already included)
#[default]
#[serde(rename = "claude-sonnet-4", alias = "claude-sonnet-4-latest")]
ClaudeSonnet4,
#[serde(
@@ -29,7 +30,6 @@ pub enum Model {
alias = "claude-opus-4-thinking-latest"
)]
ClaudeOpus4Thinking,
#[default]
#[serde(rename = "claude-3-5-sonnet-v2", alias = "claude-3-5-sonnet-latest")]
Claude3_5SonnetV2,
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]

View File

@@ -31,9 +31,9 @@ workspace-hack.workspace = true
[dev-dependencies]
ctor.workspace = true
env_logger.workspace = true
gpui = { workspace = true, features = ["test-support"] }
rand.workspace = true
serde_json.workspace = true
text = { workspace = true, features = ["test-support"] }
unindent.workspace = true
zlog.workspace = true

View File

@@ -1346,9 +1346,7 @@ mod tests {
#[ctor::ctor]
fn init_logger() {
if std::env::var("RUST_LOG").is_ok() {
env_logger::init();
}
zlog::init_test();
}
#[gpui::test]

View File

@@ -116,7 +116,7 @@ impl ActiveCall {
envelope: TypedEnvelope<proto::IncomingCall>,
mut cx: AsyncApp,
) -> Result<proto::Ack> {
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
let user_store = this.read_with(&mut cx, |this, _| this.user_store.clone())?;
let call = IncomingCall {
room_id: envelope.payload.room_id,
participants: user_store

View File

@@ -387,7 +387,7 @@ impl ChannelChat {
let loaded_messages = messages_from_proto(proto_messages, &user_store, cx).await?;
let first_loaded_message_id = loaded_messages.first().map(|m| m.id);
let loaded_message_ids = this.update(cx, |this, _| {
let loaded_message_ids = this.read_with(cx, |this, _| {
let mut loaded_message_ids: HashSet<u64> = HashSet::default();
for message in loaded_messages.iter() {
if let Some(saved_message_id) = message.id.into() {
@@ -457,7 +457,7 @@ impl ChannelChat {
)
.await?;
let pending_messages = this.update(cx, |this, _| {
let pending_messages = this.read_with(cx, |this, _| {
this.pending_messages().cloned().collect::<Vec<_>>()
})?;
@@ -531,7 +531,7 @@ impl ChannelChat {
message: TypedEnvelope<proto::ChannelMessageSent>,
mut cx: AsyncApp,
) -> Result<()> {
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
let user_store = this.read_with(&mut cx, |this, _| this.user_store.clone())?;
let message = message.payload.message.context("empty message")?;
let message_id = message.id;
@@ -563,7 +563,7 @@ impl ChannelChat {
message: TypedEnvelope<proto::ChannelMessageUpdate>,
mut cx: AsyncApp,
) -> Result<()> {
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
let user_store = this.read_with(&mut cx, |this, _| this.user_store.clone())?;
let message = message.payload.message.context("empty message")?;
let message = ChannelMessage::from_proto(message, &user_store, &mut cx).await?;

View File

@@ -333,7 +333,7 @@ impl ChannelStore {
if let Some(request) = request {
let response = request.await?;
let this = this.upgrade().context("channel store dropped")?;
let user_store = this.update(cx, |this, _| this.user_store.clone())?;
let user_store = this.read_with(cx, |this, _| this.user_store.clone())?;
ChannelMessage::from_proto_vec(response.messages, &user_store, cx).await
} else {
Ok(Vec::new())
@@ -478,7 +478,7 @@ impl ChannelStore {
hash_map::Entry::Vacant(e) => {
let task = cx
.spawn(async move |this, cx| {
let channel = this.update(cx, |this, _| {
let channel = this.read_with(cx, |this, _| {
this.channel_for_id(channel_id).cloned().ok_or_else(|| {
Arc::new(anyhow!("no channel for id: {channel_id}"))
})
@@ -848,7 +848,7 @@ impl ChannelStore {
message: TypedEnvelope<proto::UpdateChannels>,
mut cx: AsyncApp,
) -> Result<()> {
this.update(&mut cx, |this, _| {
this.read_with(&mut cx, |this, _| {
this.update_channels_tx
.unbounded_send(message.payload)
.unwrap();

View File

@@ -137,7 +137,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
let user_id = 5;
let channel_id = 5;
let channel_store = cx.update(init_test);
let client = channel_store.update(cx, |s, _| s.client());
let client = channel_store.read_with(cx, |s, _| s.client());
let server = FakeServer::for_client(user_id, &client, cx).await;
// Get the available channels.

View File

@@ -905,7 +905,15 @@ impl Client {
}
futures::select_biased! {
result = self.set_connection(conn, cx).fuse() => ConnectionResult::Result(result.context("client auth and connect")),
result = self.set_connection(conn, cx).fuse() => {
match result.context("client auth and connect") {
Ok(()) => ConnectionResult::Result(Ok(())),
Err(err) => {
self.set_status(Status::ConnectionError, cx);
ConnectionResult::Result(Err(err))
},
}
},
_ = timeout => {
self.set_status(Status::ConnectionError, cx);
ConnectionResult::Timeout
@@ -1842,7 +1850,7 @@ mod tests {
let (done_tx2, done_rx2) = smol::channel::unbounded();
AnyProtoClient::from(client.clone()).add_entity_message_handler(
move |entity: Entity<TestEntity>, _: TypedEnvelope<proto::JoinProject>, mut cx| {
match entity.update(&mut cx, |entity, _| entity.id).unwrap() {
match entity.read_with(&mut cx, |entity, _| entity.id).unwrap() {
1 => done_tx1.try_send(()).unwrap(),
2 => done_tx2.try_send(()).unwrap(),
_ => unreachable!(),

View File

@@ -39,7 +39,7 @@ enum ProxyType<'t> {
HttpProxy(HttpProxyType<'t>),
}
fn parse_proxy_type<'t>(proxy: &'t Url) -> Option<((String, u16), ProxyType<'t>)> {
fn parse_proxy_type(proxy: &Url) -> Option<((String, u16), ProxyType)> {
let scheme = proxy.scheme();
let host = proxy.host()?.to_string();
let port = proxy.port_or_known_default()?;

View File

@@ -109,6 +109,7 @@ pub struct UserStore {
edit_predictions_usage_limit: Option<proto::UsageLimit>,
is_usage_based_billing_enabled: Option<bool>,
account_too_young: Option<bool>,
has_overdue_invoices: Option<bool>,
current_user: watch::Receiver<Option<Arc<User>>>,
accepted_tos_at: Option<Option<DateTime<Utc>>>,
contacts: Vec<Arc<Contact>>,
@@ -176,6 +177,7 @@ impl UserStore {
edit_predictions_usage_limit: None,
is_usage_based_billing_enabled: None,
account_too_young: None,
has_overdue_invoices: None,
accepted_tos_at: None,
contacts: Default::default(),
incoming_contact_requests: Default::default(),
@@ -322,7 +324,7 @@ impl UserStore {
message: TypedEnvelope<proto::UpdateContacts>,
mut cx: AsyncApp,
) -> Result<()> {
this.update(&mut cx, |this, _| {
this.read_with(&mut cx, |this, _| {
this.update_contacts_tx
.unbounded_send(UpdateContacts::Update(message.payload))
.unwrap();
@@ -350,6 +352,7 @@ impl UserStore {
.and_then(|trial_started_at| DateTime::from_timestamp(trial_started_at as i64, 0));
this.is_usage_based_billing_enabled = message.payload.is_usage_based_billing_enabled;
this.account_too_young = message.payload.account_too_young;
this.has_overdue_invoices = message.payload.has_overdue_invoices;
if let Some(usage) = message.payload.usage {
this.model_request_usage_amount = Some(usage.model_requests_usage_amount);
@@ -657,7 +660,7 @@ impl UserStore {
.await?;
}
this.update(cx, |this, _| {
this.read_with(cx, |this, _| {
user_ids
.iter()
.map(|user_id| {
@@ -700,7 +703,7 @@ impl UserStore {
let load_users = self.get_users(vec![user_id], cx);
cx.spawn(async move |this, cx| {
load_users.await?;
this.update(cx, |this, _| {
this.read_with(cx, |this, _| {
this.users
.get(&user_id)
.cloned()
@@ -755,11 +758,16 @@ impl UserStore {
self.current_user.clone()
}
/// Check if the current user's account is too new to use the service
pub fn current_user_account_too_young(&self) -> bool {
/// Returns whether the user's account is too new to use the service.
pub fn account_too_young(&self) -> bool {
self.account_too_young.unwrap_or(false)
}
/// Returns whether the current user has overdue invoices and usage should be blocked.
pub fn has_overdue_invoices(&self) -> bool {
self.has_overdue_invoices.unwrap_or(false)
}
pub fn current_user_has_accepted_terms(&self) -> Option<bool> {
self.accepted_tos_at
.map(|accepted_tos_at| accepted_tos_at.is_some())

View File

@@ -76,8 +76,8 @@ workspace-hack.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
agent_settings.workspace = true
assistant_context_editor.workspace = true
assistant_settings.workspace = true
assistant_slash_command.workspace = true
assistant_tool.workspace = true
async-trait.workspace = true
@@ -95,7 +95,6 @@ dap = { workspace = true, features = ["test-support"] }
dap_adapters = { workspace = true, features = ["test-support"] }
debugger_ui = { workspace = true, features = ["test-support"] }
editor = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
extension.workspace = true
file_finder.workspace = true
fs = { workspace = true, features = ["test-support"] }
@@ -133,6 +132,7 @@ unindent.workspace = true
util.workspace = true
workspace = { workspace = true, features = ["test-support"] }
worktree = { workspace = true, features = ["test-support"] }
zlog.workspace = true
[package.metadata.cargo-machete]
ignored = ["async-stripe"]

View File

@@ -2,5 +2,5 @@ ZED_ENVIRONMENT=production
RUST_LOG=info
INVITE_LINK_PREFIX=https://zed.dev/invites/
AUTO_JOIN_CHANNEL_ID=283
DATABASE_MAX_CONNECTIONS=85
DATABASE_MAX_CONNECTIONS=250
LLM_DATABASE_MAX_CONNECTIONS=25

View File

@@ -269,7 +269,8 @@ async fn list_billing_subscriptions(
.and_utc()
.to_rfc3339_opts(SecondsFormat::Millis, true)
}),
is_cancelable: subscription.stripe_subscription_status.is_cancelable()
is_cancelable: subscription.kind != Some(SubscriptionKind::ZedFree)
&& subscription.stripe_subscription_status.is_cancelable()
&& subscription.stripe_cancel_at.is_none(),
})
.collect(),
@@ -591,23 +592,32 @@ async fn manage_billing_subscription(
}),
..Default::default()
}),
ManageSubscriptionIntent::Cancel => Some(CreateBillingPortalSessionFlowData {
type_: CreateBillingPortalSessionFlowDataType::SubscriptionCancel,
after_completion: Some(CreateBillingPortalSessionFlowDataAfterCompletion {
type_: stripe::CreateBillingPortalSessionFlowDataAfterCompletionType::Redirect,
redirect: Some(CreateBillingPortalSessionFlowDataAfterCompletionRedirect {
return_url: format!("{}/account", app.config.zed_dot_dev_url()),
ManageSubscriptionIntent::Cancel => {
if subscription.kind == Some(SubscriptionKind::ZedFree) {
return Err(Error::http(
StatusCode::BAD_REQUEST,
"free subscription cannot be canceled".into(),
));
}
Some(CreateBillingPortalSessionFlowData {
type_: CreateBillingPortalSessionFlowDataType::SubscriptionCancel,
after_completion: Some(CreateBillingPortalSessionFlowDataAfterCompletion {
type_: stripe::CreateBillingPortalSessionFlowDataAfterCompletionType::Redirect,
redirect: Some(CreateBillingPortalSessionFlowDataAfterCompletionRedirect {
return_url: format!("{}/account", app.config.zed_dot_dev_url()),
}),
..Default::default()
}),
subscription_cancel: Some(
stripe::CreateBillingPortalSessionFlowDataSubscriptionCancel {
subscription: subscription.stripe_subscription_id,
retention: None,
},
),
..Default::default()
}),
subscription_cancel: Some(
stripe::CreateBillingPortalSessionFlowDataSubscriptionCancel {
subscription: subscription.stripe_subscription_id,
retention: None,
},
),
..Default::default()
}),
})
}
ManageSubscriptionIntent::StopCancellation => unreachable!(),
};
@@ -1505,6 +1515,12 @@ async fn sync_model_request_usage_with_stripe(
let claude_sonnet_4_max = stripe_billing
.find_price_by_lookup_key("claude-sonnet-4-requests-max")
.await?;
let claude_opus_4 = stripe_billing
.find_price_by_lookup_key("claude-opus-4-requests")
.await?;
let claude_opus_4_max = stripe_billing
.find_price_by_lookup_key("claude-opus-4-requests-max")
.await?;
let claude_3_5_sonnet = stripe_billing
.find_price_by_lookup_key("claude-3-5-sonnet-requests")
.await?;
@@ -1538,6 +1554,10 @@ async fn sync_model_request_usage_with_stripe(
let model = llm_db.model_by_id(usage_meter.model_id)?;
let (price, meter_event_name) = match model.name.as_str() {
"claude-opus-4" => match usage_meter.mode {
CompletionMode::Normal => (&claude_opus_4, "claude_opus_4/requests"),
CompletionMode::Max => (&claude_opus_4_max, "claude_opus_4/requests/max"),
},
"claude-sonnet-4" => match usage_meter.mode {
CompletionMode::Normal => (&claude_sonnet_4, "claude_sonnet_4/requests"),
CompletionMode::Max => (&claude_sonnet_4_max, "claude_sonnet_4/requests/max"),

View File

@@ -56,6 +56,12 @@ pub use sea_orm::ConnectOptions;
pub use tables::user::Model as User;
pub use tables::*;
#[cfg(test)]
pub struct DatabaseTestOptions {
pub runtime: tokio::runtime::Runtime,
pub query_failure_probability: parking_lot::Mutex<f64>,
}
/// Database gives you a handle that lets you access the database.
/// It handles pooling internally.
pub struct Database {
@@ -68,7 +74,7 @@ pub struct Database {
notification_kinds_by_id: HashMap<NotificationKindId, &'static str>,
notification_kinds_by_name: HashMap<String, NotificationKindId>,
#[cfg(test)]
runtime: Option<tokio::runtime::Runtime>,
test_options: Option<DatabaseTestOptions>,
}
// The `Database` type has so many methods that its impl blocks are split into
@@ -87,7 +93,7 @@ impl Database {
notification_kinds_by_name: HashMap::default(),
executor,
#[cfg(test)]
runtime: None,
test_options: None,
})
}
@@ -355,11 +361,16 @@ impl Database {
{
#[cfg(test)]
{
let test_options = self.test_options.as_ref().unwrap();
if let Executor::Deterministic(executor) = &self.executor {
executor.simulate_random_delay().await;
let fail_probability = *test_options.query_failure_probability.lock();
if executor.rng().gen_bool(fail_probability) {
return Err(anyhow!("simulated query failure"))?;
}
}
self.runtime.as_ref().unwrap().block_on(future)
test_options.runtime.block_on(future)
}
#[cfg(not(test))]

View File

@@ -20,7 +20,7 @@ impl Database {
&self,
params: &CreateBillingCustomerParams,
) -> Result<billing_customer::Model> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let customer = billing_customer::Entity::insert(billing_customer::ActiveModel {
user_id: ActiveValue::set(params.user_id),
stripe_customer_id: ActiveValue::set(params.stripe_customer_id.clone()),
@@ -40,7 +40,7 @@ impl Database {
id: BillingCustomerId,
params: &UpdateBillingCustomerParams,
) -> Result<()> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
billing_customer::Entity::update(billing_customer::ActiveModel {
id: ActiveValue::set(id),
user_id: params.user_id.clone(),
@@ -61,7 +61,7 @@ impl Database {
&self,
id: BillingCustomerId,
) -> Result<Option<billing_customer::Model>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
Ok(billing_customer::Entity::find()
.filter(billing_customer::Column::Id.eq(id))
.one(&*tx)
@@ -75,7 +75,7 @@ impl Database {
&self,
user_id: UserId,
) -> Result<Option<billing_customer::Model>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
Ok(billing_customer::Entity::find()
.filter(billing_customer::Column::UserId.eq(user_id))
.one(&*tx)
@@ -89,7 +89,7 @@ impl Database {
&self,
stripe_customer_id: &str,
) -> Result<Option<billing_customer::Model>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
Ok(billing_customer::Entity::find()
.filter(billing_customer::Column::StripeCustomerId.eq(stripe_customer_id))
.one(&*tx)

View File

@@ -22,7 +22,7 @@ impl Database {
&self,
user_id: UserId,
) -> Result<Option<billing_preference::Model>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
Ok(billing_preference::Entity::find()
.filter(billing_preference::Column::UserId.eq(user_id))
.one(&*tx)
@@ -37,7 +37,7 @@ impl Database {
user_id: UserId,
params: &CreateBillingPreferencesParams,
) -> Result<billing_preference::Model> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let preferences = billing_preference::Entity::insert(billing_preference::ActiveModel {
user_id: ActiveValue::set(user_id),
max_monthly_llm_usage_spending_in_cents: ActiveValue::set(
@@ -65,7 +65,7 @@ impl Database {
user_id: UserId,
params: &UpdateBillingPreferencesParams,
) -> Result<billing_preference::Model> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let preferences = billing_preference::Entity::update_many()
.set(billing_preference::ActiveModel {
max_monthly_llm_usage_spending_in_cents: params

View File

@@ -35,7 +35,7 @@ impl Database {
&self,
params: &CreateBillingSubscriptionParams,
) -> Result<billing_subscription::Model> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let id = billing_subscription::Entity::insert(billing_subscription::ActiveModel {
billing_customer_id: ActiveValue::set(params.billing_customer_id),
kind: ActiveValue::set(params.kind),
@@ -64,7 +64,7 @@ impl Database {
id: BillingSubscriptionId,
params: &UpdateBillingSubscriptionParams,
) -> Result<()> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
billing_subscription::Entity::update(billing_subscription::ActiveModel {
id: ActiveValue::set(id),
billing_customer_id: params.billing_customer_id.clone(),
@@ -90,7 +90,7 @@ impl Database {
&self,
id: BillingSubscriptionId,
) -> Result<Option<billing_subscription::Model>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
Ok(billing_subscription::Entity::find_by_id(id)
.one(&*tx)
.await?)
@@ -103,7 +103,7 @@ impl Database {
&self,
stripe_subscription_id: &str,
) -> Result<Option<billing_subscription::Model>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
Ok(billing_subscription::Entity::find()
.filter(
billing_subscription::Column::StripeSubscriptionId.eq(stripe_subscription_id),
@@ -118,7 +118,7 @@ impl Database {
&self,
user_id: UserId,
) -> Result<Option<billing_subscription::Model>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
Ok(billing_subscription::Entity::find()
.inner_join(billing_customer::Entity)
.filter(billing_customer::Column::UserId.eq(user_id))
@@ -152,7 +152,7 @@ impl Database {
&self,
user_id: UserId,
) -> Result<Vec<billing_subscription::Model>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let subscriptions = billing_subscription::Entity::find()
.inner_join(billing_customer::Entity)
.filter(billing_customer::Column::UserId.eq(user_id))
@@ -169,7 +169,7 @@ impl Database {
&self,
user_ids: HashSet<UserId>,
) -> Result<HashMap<UserId, (billing_customer::Model, billing_subscription::Model)>> {
self.transaction(|tx| {
self.weak_transaction(|tx| {
let user_ids = user_ids.clone();
async move {
let mut rows = billing_subscription::Entity::find()
@@ -201,7 +201,7 @@ impl Database {
&self,
user_ids: HashSet<UserId>,
) -> Result<HashMap<UserId, (billing_customer::Model, billing_subscription::Model)>> {
self.transaction(|tx| {
self.weak_transaction(|tx| {
let user_ids = user_ids.clone();
async move {
let mut rows = billing_subscription::Entity::find()
@@ -236,7 +236,7 @@ impl Database {
/// Returns the count of the active billing subscriptions for the user with the specified ID.
pub async fn count_active_billing_subscriptions(&self, user_id: UserId) -> Result<usize> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let count = billing_subscription::Entity::find()
.inner_join(billing_customer::Entity)
.filter(

View File

@@ -9,7 +9,7 @@ pub enum ContributorSelector {
impl Database {
/// Retrieves the GitHub logins of all users who have signed the CLA.
pub async fn get_contributors(&self) -> Result<Vec<String>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryGithubLogin {
GithubLogin,
@@ -32,7 +32,7 @@ impl Database {
&self,
selector: &ContributorSelector,
) -> Result<Option<DateTime>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let condition = match selector {
ContributorSelector::GitHubUserId { github_user_id } => {
user::Column::GithubUserId.eq(*github_user_id)
@@ -69,7 +69,7 @@ impl Database {
github_user_created_at: DateTimeUtc,
initial_channel_id: Option<ChannelId>,
) -> Result<()> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let user = self
.get_or_create_user_by_github_account_tx(
github_login,

View File

@@ -15,7 +15,7 @@ impl Database {
max_schema_version: i32,
limit: usize,
) -> Result<Vec<ExtensionMetadata>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let mut condition = Condition::all()
.add(
extension::Column::LatestVersion
@@ -43,7 +43,7 @@ impl Database {
ids: &[&str],
constraints: Option<&ExtensionVersionConstraints>,
) -> Result<Vec<ExtensionMetadata>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let extensions = extension::Entity::find()
.filter(extension::Column::ExternalId.is_in(ids.iter().copied()))
.all(&*tx)
@@ -123,7 +123,7 @@ impl Database {
&self,
extension_id: &str,
) -> Result<Vec<ExtensionMetadata>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let condition = extension::Column::ExternalId
.eq(extension_id)
.into_condition();
@@ -162,7 +162,7 @@ impl Database {
extension_id: &str,
constraints: Option<&ExtensionVersionConstraints>,
) -> Result<Option<ExtensionMetadata>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let extension = extension::Entity::find()
.filter(extension::Column::ExternalId.eq(extension_id))
.one(&*tx)
@@ -187,7 +187,7 @@ impl Database {
extension_id: &str,
version: &str,
) -> Result<Option<ExtensionMetadata>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let extension = extension::Entity::find()
.filter(extension::Column::ExternalId.eq(extension_id))
.filter(extension_version::Column::Version.eq(version))
@@ -204,7 +204,7 @@ impl Database {
}
pub async fn get_known_extension_versions(&self) -> Result<HashMap<String, Vec<String>>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let mut extension_external_ids_by_id = HashMap::default();
let mut rows = extension::Entity::find().stream(&*tx).await?;
@@ -242,7 +242,7 @@ impl Database {
&self,
versions_by_extension_id: &HashMap<&str, Vec<NewExtensionVersion>>,
) -> Result<()> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
for (external_id, versions) in versions_by_extension_id {
if versions.is_empty() {
continue;
@@ -346,7 +346,7 @@ impl Database {
}
pub async fn record_extension_download(&self, extension: &str, version: &str) -> Result<bool> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryId {
Id,

View File

@@ -13,7 +13,7 @@ impl Database {
&self,
params: &CreateProcessedStripeEventParams,
) -> Result<()> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
processed_stripe_event::Entity::insert(processed_stripe_event::ActiveModel {
stripe_event_id: ActiveValue::set(params.stripe_event_id.clone()),
stripe_event_type: ActiveValue::set(params.stripe_event_type.clone()),
@@ -35,7 +35,7 @@ impl Database {
&self,
event_id: &str,
) -> Result<Option<processed_stripe_event::Model>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
Ok(processed_stripe_event::Entity::find_by_id(event_id)
.one(&*tx)
.await?)
@@ -48,7 +48,7 @@ impl Database {
&self,
event_ids: &[&str],
) -> Result<Vec<processed_stripe_event::Model>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
Ok(processed_stripe_event::Entity::find()
.filter(
processed_stripe_event::Column::StripeEventId.is_in(event_ids.iter().copied()),

View File

@@ -382,7 +382,7 @@ impl Database {
/// Returns the active flags for the user.
pub async fn get_user_flags(&self, user: UserId) -> Result<Vec<String>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryAs {
Flag,

View File

@@ -55,6 +55,11 @@ impl Model {
account_created_at
}
/// Returns the age of the user's account.
pub fn account_age(&self) -> chrono::Duration {
chrono::Utc::now().naive_utc() - self.account_created_at()
}
}
impl Related<super::access_token::Entity> for Entity {

View File

@@ -30,7 +30,7 @@ pub struct TestDb {
}
impl TestDb {
pub fn sqlite(background: BackgroundExecutor) -> Self {
pub fn sqlite(executor: BackgroundExecutor) -> Self {
let url = "sqlite::memory:";
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_io()
@@ -41,7 +41,7 @@ impl TestDb {
let mut db = runtime.block_on(async {
let mut options = ConnectOptions::new(url);
options.max_connections(5);
let mut db = Database::new(options, Executor::Deterministic(background))
let mut db = Database::new(options, Executor::Deterministic(executor.clone()))
.await
.unwrap();
let sql = include_str!(concat!(
@@ -59,7 +59,10 @@ impl TestDb {
db
});
db.runtime = Some(runtime);
db.test_options = Some(DatabaseTestOptions {
runtime,
query_failure_probability: parking_lot::Mutex::new(0.0),
});
Self {
db: Some(Arc::new(db)),
@@ -67,7 +70,7 @@ impl TestDb {
}
}
pub fn postgres(background: BackgroundExecutor) -> Self {
pub fn postgres(executor: BackgroundExecutor) -> Self {
static LOCK: Mutex<()> = Mutex::new(());
let _guard = LOCK.lock();
@@ -90,7 +93,7 @@ impl TestDb {
options
.max_connections(5)
.idle_timeout(Duration::from_secs(0));
let mut db = Database::new(options, Executor::Deterministic(background))
let mut db = Database::new(options, Executor::Deterministic(executor.clone()))
.await
.unwrap();
let migrations_path = concat!(env!("CARGO_MANIFEST_DIR"), "/migrations");
@@ -101,7 +104,10 @@ impl TestDb {
db
});
db.runtime = Some(runtime);
db.test_options = Some(DatabaseTestOptions {
runtime,
query_failure_probability: parking_lot::Mutex::new(0.0),
});
Self {
db: Some(Arc::new(db)),
@@ -112,6 +118,12 @@ impl TestDb {
pub fn db(&self) -> &Arc<Database> {
self.db.as_ref().unwrap()
}
pub fn set_query_failure_probability(&self, probability: f64) {
let database = self.db.as_ref().unwrap();
let test_options = database.test_options.as_ref().unwrap();
*test_options.query_failure_probability.lock() = probability;
}
}
#[macro_export]
@@ -136,7 +148,7 @@ impl Drop for TestDb {
fn drop(&mut self) {
let db = self.db.take().unwrap();
if let sea_orm::DatabaseBackend::Postgres = db.pool.get_database_backend() {
db.runtime.as_ref().unwrap().block_on(async {
db.test_options.as_ref().unwrap().runtime.block_on(async {
use util::ResultExt;
let query = "
SELECT pg_terminate_backend(pg_stat_activity.pid)

View File

@@ -1,5 +1,5 @@
use crate::db::billing_subscription::SubscriptionKind;
use crate::db::{billing_subscription, user};
use crate::db::{billing_customer, billing_subscription, user};
use crate::llm::AGENT_EXTENDED_TRIAL_FEATURE_FLAG;
use crate::{Config, db::billing_preference};
use anyhow::{Context as _, Result};
@@ -32,6 +32,8 @@ pub struct LlmTokenClaims {
pub enable_model_request_overages: bool,
pub model_request_overages_spend_limit_in_cents: u32,
pub can_use_web_search_tool: bool,
#[serde(default)]
pub has_overdue_invoices: bool,
}
const LLM_TOKEN_LIFETIME: Duration = Duration::from_secs(60 * 60);
@@ -40,6 +42,7 @@ impl LlmTokenClaims {
pub fn create(
user: &user::Model,
is_staff: bool,
billing_customer: billing_customer::Model,
billing_preferences: Option<billing_preference::Model>,
feature_flags: &Vec<String>,
subscription: billing_subscription::Model,
@@ -99,6 +102,7 @@ impl LlmTokenClaims {
.map_or(0, |preferences| {
preferences.model_request_overages_spend_limit_in_cents as u32
}),
has_overdue_invoices: billing_customer.has_overdue_invoices,
};
Ok(jsonwebtoken::encode(

View File

@@ -110,6 +110,13 @@ pub enum Principal {
}
impl Principal {
fn user(&self) -> &User {
match self {
Principal::User(user) => user,
Principal::Impersonated { user, .. } => user,
}
}
fn update_span(&self, span: &tracing::Span) {
match &self {
Principal::User(user) => {
@@ -741,7 +748,7 @@ impl Server {
supermaven_client,
};
if let Err(error) = this.send_initial_client_update(connection_id, &principal, zed_version, send_connection_id, &session).await {
if let Err(error) = this.send_initial_client_update(connection_id, zed_version, send_connection_id, &session).await {
tracing::error!(?error, "failed to send initial client update");
return;
}
@@ -825,7 +832,6 @@ impl Server {
async fn send_initial_client_update(
&self,
connection_id: ConnectionId,
principal: &Principal,
zed_version: ZedVersion,
mut send_connection_id: Option<oneshot::Sender<ConnectionId>>,
session: &Session,
@@ -841,7 +847,7 @@ impl Server {
let _ = send_connection_id.send(connection_id);
}
match principal {
match &session.principal {
Principal::User(user) | Principal::Impersonated { user, admin: _ } => {
if !user.connected_once {
self.peer.send(connection_id, proto::ShowContacts {})?;
@@ -851,7 +857,7 @@ impl Server {
.await?;
}
update_user_plan(user.id, session).await?;
update_user_plan(session).await?;
let contacts = self.app_state.db.get_contacts(user.id).await?;
@@ -941,10 +947,10 @@ impl Server {
.context("user not found")?;
let update_user_plan = make_update_user_plan_message(
&user,
user.admin,
&self.app_state.db,
self.app_state.llm_db.clone(),
user_id,
user.admin,
)
.await?;
@@ -2707,26 +2713,25 @@ async fn current_plan(db: &Arc<Database>, user_id: UserId, is_staff: bool) -> Re
}
async fn make_update_user_plan_message(
user: &User,
is_staff: bool,
db: &Arc<Database>,
llm_db: Option<Arc<LlmDatabase>>,
user_id: UserId,
is_staff: bool,
) -> Result<proto::UpdateUserPlan> {
let feature_flags = db.get_user_flags(user_id).await?;
let plan = current_plan(db, user_id, is_staff).await?;
let billing_customer = db.get_billing_customer_by_user_id(user_id).await?;
let billing_preferences = db.get_billing_preferences(user_id).await?;
let user = db.get_user_by_id(user_id).await?;
let feature_flags = db.get_user_flags(user.id).await?;
let plan = current_plan(db, user.id, is_staff).await?;
let billing_customer = db.get_billing_customer_by_user_id(user.id).await?;
let billing_preferences = db.get_billing_preferences(user.id).await?;
let (subscription_period, usage) = if let Some(llm_db) = llm_db {
let subscription = db.get_active_billing_subscription(user_id).await?;
let subscription = db.get_active_billing_subscription(user.id).await?;
let subscription_period =
crate::db::billing_subscription::Model::current_period(subscription, is_staff);
let usage = if let Some((period_start_at, period_end_at)) = subscription_period {
llm_db
.get_subscription_usage_for_period(user_id, period_start_at, period_end_at)
.get_subscription_usage_for_period(user.id, period_start_at, period_end_at)
.await?
} else {
None
@@ -2737,21 +2742,13 @@ async fn make_update_user_plan_message(
(None, None)
};
// Calculate account_too_young
let account_too_young = if matches!(plan, proto::Plan::ZedPro) {
// If they have paid, then we allow them to use all of the features
false
} else if let Some(user) = user {
// If we have access to the profile age, we use that
chrono::Utc::now().naive_utc() - user.account_created_at() < MIN_ACCOUNT_AGE_FOR_LLM_USE
} else {
// Default to false otherwise
false
};
let account_too_young =
!matches!(plan, proto::Plan::ZedPro) && user.account_age() < MIN_ACCOUNT_AGE_FOR_LLM_USE;
Ok(proto::UpdateUserPlan {
plan: plan.into(),
trial_started_at: billing_customer
.as_ref()
.and_then(|billing_customer| billing_customer.trial_started_at)
.map(|trial_started_at| trial_started_at.and_utc().timestamp() as u64),
is_usage_based_billing_enabled: if is_staff {
@@ -2766,6 +2763,8 @@ async fn make_update_user_plan_message(
}
}),
account_too_young: Some(account_too_young),
has_overdue_invoices: billing_customer
.map(|billing_customer| billing_customer.has_overdue_invoices),
usage: usage.map(|usage| {
let plan = match plan {
proto::Plan::Free => zed_llm_client::Plan::ZedFree,
@@ -2822,14 +2821,14 @@ async fn make_update_user_plan_message(
})
}
async fn update_user_plan(user_id: UserId, session: &Session) -> Result<()> {
async fn update_user_plan(session: &Session) -> Result<()> {
let db = session.db().await;
let update_user_plan = make_update_user_plan_message(
session.principal.user(),
session.is_staff(),
&db.0,
session.app_state.llm_db.clone(),
user_id,
session.is_staff(),
)
.await?;
@@ -4081,6 +4080,7 @@ async fn get_llm_api_token(
let token = LlmTokenClaims::create(
&user,
session.is_staff(),
billing_customer,
billing_preferences,
&flags,
billing_subscription,

View File

@@ -177,11 +177,13 @@ impl StripeBilling {
const BILLING_THRESHOLD_IN_CENTS: i64 = 20 * 100;
let price_per_unit = price.unit_amount.unwrap_or_default();
let _units_for_billing_threshold = BILLING_THRESHOLD_IN_CENTS / price_per_unit;
stripe::Subscription::update(
&self.client,
subscription_id,
stripe::UpdateSubscription {
billing_thresholds: Some(stripe::SubscriptionBillingThresholds { amount_gte: Some(BILLING_THRESHOLD_IN_CENTS), ..Default::default() }),
items: Some(vec![stripe::UpdateSubscriptionItems {
price: Some(price.id.to_string()),
..Default::default()

View File

@@ -18,9 +18,7 @@ use workspace::{Workspace, dock::Panel};
use super::{TestClient, TestServer};
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| {
theme::init(theme::LoadThemes::JustBase, cx);

View File

@@ -679,7 +679,7 @@ async fn test_collaborating_with_code_actions(
editor_b.update_in(cx_b, |editor, window, cx| {
editor.toggle_code_actions(
&ToggleCodeActions {
deployed_from_indicator: None,
deployed_from: None,
quick_launch: false,
},
window,

View File

@@ -2066,7 +2066,7 @@ async fn share_workspace(
workspace: &Entity<Workspace>,
cx: &mut VisualTestContext,
) -> anyhow::Result<u64> {
let project = workspace.update(cx, |workspace, _| workspace.project().clone());
let project = workspace.read_with(cx, |workspace, _| workspace.project().clone());
cx.read(ActiveCall::global)
.update(cx, |call, cx| call.share_project(project, cx))
.await

View File

@@ -56,9 +56,36 @@ use workspace::Pane;
#[ctor::ctor]
fn init_logger() {
if std::env::var("RUST_LOG").is_ok() {
env_logger::init();
zlog::init_test();
}
#[gpui::test(iterations = 10)]
async fn test_database_failure_during_client_reconnection(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
let mut server = TestServer::start(executor.clone()).await;
let client = server.create_client(cx, "user_a").await;
// Keep disconnecting the client until a database failure prevents it from
// reconnecting.
server.test_db.set_query_failure_probability(0.3);
loop {
server.disconnect_client(client.peer_id().unwrap());
executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
if !client.status().borrow().is_connected() {
break;
}
}
// Make the database healthy again and ensure the client can finally connect.
server.test_db.set_query_failure_probability(0.);
executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
assert!(
matches!(*client.status().borrow(), client::Status::Connected { .. }),
"status was {:?}",
*client.status().borrow()
);
}
#[gpui::test(iterations = 10)]
@@ -6416,7 +6443,7 @@ async fn test_join_after_restart(cx1: &mut TestAppContext, cx2: &mut TestAppCont
async fn test_preview_tabs(cx: &mut TestAppContext) {
let (_server, client) = TestServer::start1(cx).await;
let (workspace, cx) = client.build_test_workspace(cx).await;
let project = workspace.update(cx, |workspace, _| workspace.project().clone());
let project = workspace.read_with(cx, |workspace, _| workspace.project().clone());
let worktree_id = project.update(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -6435,7 +6462,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
path: Path::new("3.rs").into(),
};
let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let get_path = |pane: &Pane, idx: usize, cx: &App| {
pane.item_for_index(idx).unwrap().project_path(cx).unwrap()
@@ -6588,7 +6615,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
pane.split(workspace::SplitDirection::Right, cx);
});
let right_pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
let right_pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
pane.update(cx, |pane, cx| {
assert_eq!(pane.items_len(), 1);

View File

@@ -589,9 +589,7 @@ async fn test_remote_server_debugger(
cx_a.update(|cx| {
release_channel::init(SemanticVersion::default(), cx);
command_palette_hooks::init(cx);
if std::env::var("RUST_LOG").is_ok() {
env_logger::try_init().ok();
}
zlog::init_test();
dap_adapters::init(cx);
});
server_cx.update(|cx| {

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