Compare commits

...

380 Commits

Author SHA1 Message Date
Zed Bot
d846d45519 Bump to 0.211.5 for @smitbarmase 2025-11-06 15:05:46 +00:00
Smit Barmase
f70f507f61 language: Fix completion menu no longer prioritizes relevant items for Typescript and Python (#42065)
Closes #41672

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

Release Notes:

- Fixed issue where completion menu no longer prioritizes relevant items
for TypeScript and Python.
2025-11-06 13:37:06 +05:30
Anthony Eid
e517719132 AI: Fix Github Copilot edit predictions failing to start (#41934)
Closes #41457 #41806 #41801

Copilot started using `node:sqlite` module which is an experimental
feature between node v22-v23 (stable in v24). The fix was passing in the
experimental flag when Zed starts the copilot LSP.

I tested this with v20.19.5 and v24.11.0. The fix got v20.19 working and
didn't affect v24.11 which was already working.

Release Notes:

- AI: Fix Github Copilot edit predictions failing to start
2025-11-06 13:30:28 +05:30
Richard Feldman
74efd3adbc Run ACP login from same cwd as agent server (#42038)
This makes it possible to do login via things like `cmd: "node", args:
["my-node-file.js", "login"]`

Also, that command will now use Zed's managed `node` instance.

Release Notes:

- ACP extensions can now run terminal login commands using relative
paths
2025-11-06 08:11:16 +01:00
Danilo Leal
9f1a9016b6 agent_ui: Fix how icons from external agents are displayed (#42034)
Release Notes:

- N/A
2025-11-06 08:07:59 +01:00
Danilo Leal
506f333ce1 gpui: Add support for rendering SVG from external files (#42024)
Release Notes:

- N/A

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-11-06 08:06:59 +01:00
Finn Evers
214a0bc116 svg_preview: Update preview on every buffer edit (#41270)
Closes https://github.com/zed-industries/zed/issues/39104

This fixes an issue where the preview would not work for remote buffers
in the process.

Release Notes:

- Fixed an issue where the SVG preview would not work in remote
scenarios.
- The SVG preview will now rerender on every keypress instead of only on
saves.
2025-11-06 08:06:54 +01:00
Conrad Irwin
d461acbc7b Revert "Don't draft release notes"
This reverts commit 62ece18dfe.
2025-11-05 23:51:19 -07:00
zed-zippy[bot]
7acefd50cc Refresh zed.dev releases page after releases (#42060) (cherry-pick to stable) (#42064)
Cherry-pick of #42060 to stable

----
Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-11-06 06:49:23 +00:00
Conrad Irwin
62ece18dfe Don't draft release notes 2025-11-05 10:45:11 -07:00
Joseph T. Lyons
4475689e4c v0.211.x stable 2025-11-05 12:07:10 -05:00
Ben Kunkle
5ea5b5e7a9 Fix integer underflow in autosave mode after delay in the settings (cherry-pick #41898) (#42013)
Closes #41774 

Release Notes:

- settings_ui: Fixed an integer underflow panic when attempting to hit
the `-` sign on settings item that take delays in milliseconds

Co-authored-by: Ignasius <96295999+ignasius-j-s@users.noreply.github.com>
2025-11-05 11:30:12 -05:00
Richard Feldman
d93f528a37 Use our node runtime for ACP extensions (#41955)
Release Notes:

- Now ACP extensions use Zed's managed Node.js runtime

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-11-05 12:24:23 +01:00
Richard Feldman
44140197a6 Add ACP terminal-login via _meta field (#41954)
As discussed with @benbrandt and @mikayla-maki:

* We now tell ACP clients we support the nonstandard `terminal-auth`
`_meta` field for terminal-based authentication
* In the future, we anticipate ACP itself supporting *some* form of
terminal-based authentication, but that hasn't been designed yet or gone
through the RFD process
* For now, this unblocks terminal-based auth

Release Notes:

- Added experimental terminal-based authentication to ACP support
2025-11-05 11:40:11 +01:00
Lukas Wirth
2e746791b1 project: Fetch latest lsp data in deduplicate_range_based_lsp_requests 2025-11-05 09:24:05 +01:00
Lukas Wirth
a3f230f760 zed: Reduce number of rayon threads, spawn with bigger stacks (#41812)
We already do this for the cli and remote server but forgot to do so for
the main binary

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-11-05 09:13:16 +01:00
zed-zippy[bot]
ff0eef98c9 Fetch (just) enough refs in script/cherry-pick (#41949) (cherry-pick to preview) (#41951)
Cherry-pick of #41949 to preview

----
Before this change we'd download all the tagged commits, but none of
their ancestors,
this was slow and made cherry-picking fail.

Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-11-04 19:45:56 -07:00
Zed Bot
712f75ddb4 Bump to 0.211.4 for @ConradIrwin 2025-11-05 00:15:16 +00:00
Conrad Irwin
74031e2243 More tweaks to CI pipeline (#41941)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-11-04 16:59:39 -07:00
zed-zippy[bot]
8ea85828f5 lsp: Fix dynamic registration of document diagnostics (#41929) (cherry-pick to preview) (#41947)
Cherry-pick of #41929 to preview

----
- lsp: Fix dynamic registration of diagnostic capabilities not taking
effect when an initial capability is not specified
Gist of the issue lies within use of .get_mut instead of .entry. If we
had not created any dynamic capability beforehand, we'd miss a
registration, essentially

- **Determine whether to update remote caps in a smarter manner**

Release Notes:

- Fixed document diagnostics with Ty language server.

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-11-04 23:33:47 +00:00
zed-zippy[bot]
7deac6cd2c Improve compare_perf.yml, cherry_pick.yml (#41606) (cherry-pick) (#41946)
Cherry-pick of #41606

----
Release Notes:

- N/A

---------

Co-authored-by: Nia Espera <nia@zed.dev>

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Nia Espera <nia@zed.dev>
2025-11-04 23:01:35 +00:00
zed-zippy[bot]
03a0f9b944 Shell out to real tar in extension builder (#41856) (cherry-pick) (#41945)
Cherry-pick of #41856

----
We see `test_extension_store_with_test_extension` hang in untarring the
WASI SDK some times.

In lieu of trying to debug the problem, let's try shelling out for now
in the hope that the test becomes more reliable.

There's a bit of risk here because we're using async-tar for other
things (but probably not 300Mb tar files...)

Assisted-By: Zed AI

Closes #ISSUE

Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-11-04 22:39:24 +00:00
Conrad Irwin
c374532318 settings_ui: Fix dropdowns after #41036 (#41920) (cherry-pick) (#41930)
Cherry-pick of #41920

----
Closes #41533

Both of the issues in the release notes that are fixed in this PR, were
caused by incorrect usage of the `window.use_state` API.
The first issue was caused by calling `window.use_state` in a render
helper, resulting in the element ID used to share state being the same
across different pages, resulting in the state being re-used when it
should have been re-created. The fix for this was to move the
`window.state` (and rendering logic) into a `impl RenderOnce` component,
so that the IDs are resolved during the render, avoiding the state
conflicts.

The second issue is caused by using a `move` closure in the
`window.use_state` call, resulting in stale closure values when the
window state is re-used.

Release Notes:

- settings_ui: Fixed an issue where some dropdown menus would show
options from a different dropdown when clicked
- settings_ui: Fixed an issue where attempting to change a setting in a
dropdown back to it's original value after changing it would do nothing

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-11-04 14:01:06 -05:00
Zed Bot
a677ecc889 Bump to 0.211.3 for @ConradIrwin 2025-11-04 05:35:12 +00:00
Conrad Irwin
59213b26c9 Fix merge conflict (#41853)
Closes #ISSUE

Release Notes:

- N/A
2025-11-03 22:31:09 -07:00
Piotr Osiewicz
2b901ad3ae ci: Enable namespace caching for Linux workers (#41652)
Release Notes:

- N/A
2025-11-03 22:30:51 -07:00
Conrad Irwin
61dddc4d65 Re-use the existing bundle steps for nightly too (#41699)
One of the reasons we didn't spot that we were missing the telemetry env
vars for the production builds was that nightly (which was working) had
its own set of build steps. This re-uses those and pushes the env vars
down from the workflow to the job.

It also fixes nightly releases to upload all-in-one go so that all
platforms update in sync.

Closes #41655

Release Notes:

- N/A
2025-11-03 22:30:45 -07:00
Ben Kunkle
14281179f2 gh-workflow unit evals (#41637)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-11-03 22:30:35 -07:00
Conrad Irwin
2f3c2082f4 Fix branch diff hunk expansion (#41873)
Closes #ISSUE

Release Notes:

- (preview only) Fixes a bug where hunks were not expanded when viewing
branch diff
2025-11-03 22:30:16 -07:00
Kirill Bulatov
f8979f1ed7 Do not pull diagnostics when those are disabled (#41865)
Based on 

[hang.log](https://github.com/user-attachments/files/23319081/hang.log)


Release Notes:

- N/A
2025-11-04 00:43:07 +02:00
Kirill Bulatov
921be53241 Remove incorrectly added test
During cherry-picking of https://github.com/zed-industries/zed/pull/41859 , one test was incorrectly merged in.
This was only added in https://github.com/zed-industries/zed/pull/41342 which is not cherry-picked, hence the test will fail.
2025-11-04 00:38:38 +02:00
Kirill Bulatov
af2d462bf7 Fix racy inlay hints queries (#41816)
Follow-up of https://github.com/zed-industries/zed/pull/40183

Release Notes:

- (Preview only) Fixed inlay hints duplicating when multiple editors are
open for the same buffer

---------

Co-authored-by: Lukas Wirth <lukas@zed.dev>
2025-11-04 00:17:00 +02:00
Kirill Bulatov
713903fe76 Fix incorrect search ranges when rendering search matches in the outline panel (#41859)
Closes https://github.com/zed-industries/zed/issues/41792

Release Notes:

- Fixed outline panel panicking when rendering certain search matches
2025-11-04 00:16:44 +02:00
Lukas Wirth
f4c077bdcf Remove unused import 2025-11-03 22:13:40 +01:00
Lukas Wirth
48fdd8893f file_finder: Fix highlighting panic in open path prompt (#41808)
Closes https://github.com/zed-industries/zed/issues/41249

Couldn't quite come up with a test case here but verified it works.

Release Notes:

- Fixed a panic in file finder when deleting characters
2025-11-03 22:02:10 +01:00
Lukas Wirth
93495542de editor: Fix refresh_linked_ranges panics due to old snapshot use (#41657)
Fixes ZED-29Z

Release Notes:

- Fixed panic in `refresh_linked_ranges`
2025-11-03 22:01:54 +01:00
Finn Evers
311abd0e1b extension_host: Do not try auto installing suppressed extensions (#41551)
Release Notes:

- Fixed an issue where Zed would try to install extensions specified
under `auto_install_extensions` which were moved into core.
2025-11-03 21:34:28 +01:00
Lukas Wirth
c5e298b5d8 Revert "sum_tree: Replace rayon with futures (#41586)"
This reverts commit f2ce06c7b0.
2025-11-03 20:02:37 +01:00
Zed Bot
ec813e9833 Bump to 0.211.2 for @ConradIrwin 2025-11-01 05:00:53 +00:00
Conrad Irwin
68063eaa45 Fix telemetry in release builds (#41695)
This was inadvertently broken in v0.211.1-pre when we rewrote the
release build

Release Notes:

- N/A
2025-10-31 22:56:18 -06:00
Conrad Irwin
7dadd4a240 Delete old ci.yml (#41668)
The new one is much better

Release Notes:

- N/A
2025-10-31 22:55:54 -06:00
Ben Kunkle
4f8c3782cc Fix release.yml workflow (#41675)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-31 16:31:11 -04:00
Conrad Irwin
6eb3cdbf20 blerg 2025-10-31 13:11:39 -06:00
Conrad Irwin
8677bb57f0 Skip release notes for a hot second 2025-10-31 13:07:53 -06:00
Zed Bot
45accd931e Bump to 0.211.1 for @ConradIrwin 2025-10-31 18:32:04 +00:00
Conrad Irwin
671c4eb133 Delete old ci.yml 2025-10-31 12:27:21 -06:00
Ben Kunkle
011e3c155b gh-workflow release (#41502)
Closes #ISSUE

Rewrite our release pipeline to be generated by `gh-workflow`

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-31 12:26:32 -06:00
claytonrcarter
3eb9d7765a bundle: Restore local install on macOS (#41482)
I just pulled and ran a local build via `script/bundle-mac -l -i` but
found that the resulting bundle wasn't installed as expected. (me:
"ToggleAllDocks!! Wait! Where is it?!") Looking into, it looks like the
`-l` flag was removed in #41392, leaving the `$local_only` var orphaned,
which then left the `-i/$local_install` flag unreachable. I suspect that
this was unintentional, so this PR re-adds the `-l/$local_only` flag to
`script/bundle-mac`.

I ran the build again and confirmed that local install seemed to work as
expected. (ie "ToggleAllDocks!! 🎉")

While here, I also removed the last reference to `$local_arch`, because
all other references to that were removed in #41392.

/cc @osiewicz 

Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-10-31 12:26:19 -06:00
Conrad Irwin
d6c49fbc3c Add a no-op compare_perf workflow (#41605)
Testing PR for @zed-zippy

Release Notes:

- N/A
2025-10-31 12:24:42 -06:00
Conrad Irwin
7bf3f92a0a Don't skip tests in nightly release (#41573)
Release Notes:

- N/A
2025-10-31 12:24:25 -06:00
Conrad Irwin
2751512ae8 Use gh-workflow for tests (take 2) (#41420)
This re-implements the reverted commit 8b051d6cc3.

Closes #ISSUE

Release Notes:

- N/A

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-10-31 12:09:43 -06:00
Conrad Irwin
032ab87cbe Generate dwarf files for builds again (#41651)
Closes #ISSUE

Release Notes:

- N/A
2025-10-31 11:03:48 -06:00
Lukas Wirth
dbabcfcac8 sum_tree: Replace rayon with futures (#41586)
Release Notes:

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

Co-authored by: Kate <kate@zed.dev>
2025-10-31 12:20:42 +01:00
Bennet Fenner
9436818467 agent_ui: Fix agent: Chat with follow not working (#41581)
Release Notes:

- Fixed an issue where `agent: Chat with follow` was not working anymore

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-10-30 17:17:06 +01:00
Lukas Wirth
501f78aacb project: Fix inlay hints duplicatig on chunk start (#41461)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-29 16:23:03 +01:00
Finn Evers
0d5bfaf7b4 Revert "Support relative line number on wrapped lines (#39268)" (#41450)
Closes #41422

This completely broke line numbering as described in the linked issue
and scrolling up does not have the correct numbers any more.

Release Notes:

- NOTE: The `relative_line_numbers` change
(https://github.com/zed-industries/zed/pull/39268) was reverted and did
not make the release cut!
2025-10-29 16:22:00 +01:00
Finn Evers
b1935b95a1 ui: Don't show scrollbar track in too many cases (#41455)
Follow-up to https://github.com/zed-industries/zed/pull/41354 which
introduced a small regression.

Release Notes:

- N/A
2025-10-29 16:22:00 +01:00
Conrad Irwin
f98c12513b v0.211.x preview 2025-10-28 19:43:56 -06:00
Callum Tolley
d0d7b9cdcd Update docs to use == instead of = (#41415)
Closes #41219

Release Notes:

- Updated docs to use `==` instead of `=` in keymap context.

Hopefully I'm not mistaken here, but I think the docs have a bug in them
2025-10-28 19:34:19 -06:00
Ben Kunkle
8b051d6cc3 Revert "Use gh workflow for tests" (#41411)
Reverts zed-industries/zed#41384

The branch-protection rules work much better when there is a Job that
runs every time and can be depended on to pass, we no longer have this.

Release Notes:

- N/A
2025-10-29 00:37:57 +00:00
John Tur
16d84a31ec Adjust Windows fusion manifests (#41408)
- Declare UAC support. This will prevent Windows from flagging
`auto_update_helper.exe` as a legacy setup program that needs to run as
administrator.
- Declare support for Windows 10. This will stop Windows from applying
various application compatibility profiles.

The UAC policy is not really appropriate to apply to all GPUI
applications (e.g. an installer written in GPUI may want to declare
itself as `requireAdministrator` instead of `asInvoker`). I tried
splitting this into a Zed.exe-only manifest and enabling manifest file
merging, but I ran out of my time-box. We can fix this later if this is
flagged by GPUI users.

Release Notes:

- N/A
2025-10-28 20:21:31 -04:00
Ben Kunkle
4adff4aa8a Use gh workflow for tests (#41384)
Follow up for: #41304

Splits CI tests (cherry-picks and PRs only for now) into separate
workflows using `gh-workflow`. Includes a couple restructures to
- run more things in parallel
- remove our previous shell script based checking to filter tests based
on files changed, instead using the builtin `paths:` workflow filters


Splitting the docs/style/rust tests & checks into separate workflows
means we lose the complete summary showing all the tests in one view,
but it's possible to re-add in the future if we go back to checking what
files changed ourselves or always run everything.

Release Notes:

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

---------

Co-authored-by: Conrad <conrad@zed.dev>
2025-10-28 23:31:38 +00:00
Danilo Leal
7de3c67b1d docs: Improve header on mobile (#41404)
Release Notes:

- N/A
2025-10-28 19:34:13 -03:00
Max Brunsfeld
60bd417d8b Allow inspection of zeta2's LLM-based context retrieval (#41340)
Release Notes:

- N/A

---------

Co-authored-by: Agus Zubiaga <agus@zed.dev>
2025-10-28 22:26:48 +00:00
Danilo Leal
d31194dcf8 docs: Fix keybinding display in /configuring-zed (#41402)
Release Notes:

- N/A
2025-10-28 19:20:49 -03:00
Piotr Osiewicz
4cc6d6a398 ci: Notarize in parallel (different flavor) (#41392)
Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Conrad Irwin <conrad@zed.dev>
2025-10-28 16:18:17 -06:00
Piotr Osiewicz
b9eafb80fd extensions: Load extension byte repr in background thread (again) (#41398)
Release Notes:

- N/A
2025-10-28 20:54:35 +00:00
Cameron Mcloughlin
5e7927f628 [WIP] editor: Implement next/prev reference (#41078)
Co-authored-by: Cole <cole@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-10-28 20:41:44 +00:00
Katie Geer
b75736568b docs: Reorganize introduction (#41387)
Release Notes:
- Remove Windows/Linux from Getting Started
- Consolidate download & system into new Installation page
- Move Remote Dev out of Windows and into Remote Development
- Add Uninstall page
- Add updates page
- Remove addl learning materials from intro
2025-10-28 17:39:40 -03:00
Lukas Wirth
360074effb rope: Prevent stack overflows by bumping rayon stack sizes (#41397)
Thread stacks in rust by default have 2 megabytes of stack which for
sumtrees (or ropes in this case) can easily be exceeded depending on the
workload.

Release Notes:

- Fixed stack overflows when constructing large ropes
2025-10-28 20:21:49 +00:00
Conrad Irwin
b1922b7156 Move Nightly release to gh-workflow (#41349)
Follow up to #41304 to move nightly release over

Release Notes:

- N/A

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-10-28 13:57:23 -06:00
Danilo Leal
54fd7ea699 agent_ui: Don't show root project in path prefix in @-mention menu (#41372)
This PR hides the worktree root name from the path prefix displayed when
you @-mention a file or directory in the agent panel. Given the tight UI
real state we have, I believe that having the project name in there is
redundant—the project you're in is already displayed in the title bar.
Not only it was the very first word you'd see after the file's name, but
it also made the path longer than it needs to. A bit of a design clean
up here :)

(PS: We still show the project name if there are more than one in the
same workspace.)

Release Notes:

- N/A
2025-10-28 16:15:53 -03:00
Danilo Leal
8564602c3b command palette: Make footer buttons justified to the right (#41382)
Release Notes:

- N/A
2025-10-28 16:15:44 -03:00
Marshall Bowers
e604ef3af9 Add a setting to prevent sharing projects in public channels (#41395)
This PR adds a setting to prevent projects from being shared in public
channels.

This can be enabled by adding the following to the project settings
(`.zed/settings.json`):

```json
{
  "prevent_sharing_in_public_channels": true
}
```

This will then disable the "Share" button when not in a private channel:

<img width="380" height="115" alt="Screenshot 2025-10-28 at 2 28 10 PM"
src="https://github.com/user-attachments/assets/6761ac34-c0d5-4451-a443-adf7a1c42bcd"
/>

Release Notes:

- collaboration: Added a `prevent_sharing_in_public_channels` project
setting for preventing projects from being shared in public channels.
2025-10-28 18:48:07 +00:00
Bennet Fenner
1f5101d9fd agent_ui: Trim whitespace when submitting message (#41391)
Closes #41017

Release Notes:

- N/A
2025-10-28 19:02:39 +01:00
Christian Durán Carvajal
37540d1ff7 agent: Only include tool guidance in system prompt when profile has tools enabled (#40413)
Was previously sending all of the tools to the LLM on the first message
of a conversation regardless of the selected agent profile. This added
extra context, and tended to create scenarios where the LLM would
attempt to use the tool and it would fail since it was not available.

To reproduce, create a new conversation where you ask the Minimal mode
which tools it has access to, or try to write to a file.

Release Notes:

- Fixed an issue in the agent where all tools would be presented as
available even when using the `Minimal` profile

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-10-28 16:56:39 +00:00
Piotr Osiewicz
d9d24582bb lsp: Fix workspace diagnostics when registered statically (#41386)
Closes #41379

Release Notes:

- Fixed diagnostics for Ruff and Biome
2025-10-28 17:16:42 +01:00
Sushant Mishra
a4a2acfaa5 gpui: Set initial window title on Wayland (#36844)
Closes #36843 

Release Notes:

- Fixed: set the initial window title correctly on startup in Wayland.
2025-10-28 16:52:01 +01:00
Jason Lee
55554a8653 markdown_preview: Improve nested list item prefix style (#39606)
Release Notes:

- Improved nested list item prefix style for Markdown preview.


## Before

<img width="667" height="450" alt="SCR-20251006-rtis"
src="https://github.com/user-attachments/assets/439160c4-7982-463c-9017-268d47c42c0c"
/>

## After

<img width="739" height="440" alt="SCR-20251006-rzlb"
src="https://github.com/user-attachments/assets/f6c237d9-3ff0-4468-ae9c-6853c5c2946a"
/>

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-10-28 16:44:05 +01:00
Paweł Kondzior
d00ad02b05 agent_ui: Add file name and line number to symbol completions (#40508)
Symbol completions in the context picker now show "SymbolName
filename.txt L123" instead of just "SymbolName". This helps distinguish
between symbols with the same name in different files.

## Motivation

I noticed that the message prompt editor only showed symbol names
without any context, making it hard to use in large projects with many
symbols sharing the same name. The inline prompt editor already includes
file context for symbol completions, so I brought that same UX
improvement to the message prompt editor. I've decided to match the
highlighting style with directory completion entries from the message
editor.

## Changes

- Extract file name from both InProject and OutsideProject symbol
locations
- Add `build_symbol_label` helper to format labels with file context
- Update test expectation for new label format

## Screenshots
Inline Prompt Editor
<img width="843" height="334" alt="Screenshot 2025-10-17 at 17 47 52"
src="https://github.com/user-attachments/assets/16752e1a-0b8c-4f58-a0b2-25ae5130f18c"
/>
Old Message Editor:
<img width="466" height="363" alt="Screenshot 2025-10-17 at 17 31 44"
src="https://github.com/user-attachments/assets/900659f3-0632-4dff-bc9a-ab2dad351964"
/>
New Message Editor:
<img width="482" height="359" alt="Screenshot 2025-10-17 at 17 31 23"
src="https://github.com/user-attachments/assets/bf382167-f88b-4f3d-ba3e-1e251a8df962"
/>


Release Notes:

- Added file names and line numbers to symbol completions in the agent
panel
2025-10-28 16:43:48 +01:00
David Kleingeld
233a1eb46e Open keymap editor from command palette entry (#40825)
Release Notes:

- Adds footer to the command palette with buttons to add or change the
selected actions keybinding. Both open the keymap editor though the add
button takes you directly to the modal for recording a new keybind.


https://github.com/user-attachments/assets/0ee6b91e-b1dd-4d7f-ad64-cc79689ceeb2

---------

Co-authored-by: Joseph T. Lyons <JosephTLyons@gmail.com>
Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-10-28 15:14:58 +00:00
Conrad Irwin
c656101862 Use gh-workflow for the run-bundling aspects of CI.yml (#41304)
To help make our GitHub Actions easier to understand, we're planning to
split the existing `ci.yml` into three separate workflows:

* run_bundling.yml (this PR)
* run_tests.yml 
* make_release.yml

To avoid the duplication that this might otherwise cause, we're planning
to write the workflows with gh-workflow, and use rust instead of
encoding logic in YAML conditions.

Release Notes:

- N/A

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-10-28 09:12:04 -06:00
Lionel Henry
3248a05406 Propagate Jupyter client errors (#40886)
Closes #40884

- Make IOPub task return a `Result`
- Create a monitoring task that watches over IOPub, Control, Routing and
Shell tasks.
- If any of these tasks fail, report the error with `kernel_errored()`
(which is already used to report process crashes)


https://github.com/user-attachments/assets/3125f6c7-099a-41ca-b668-fe694ecc68b9

This is not perfect. I did not have time to look into this but:

- When such errors happen, the kernel should be shut down.
- The kernel should no longer appear as online in the UI

But at least the user is getting feedback on what went wrong.

Release Notes:

- Jupyter client errors are now surfaced in the UI (#40884)
2025-10-28 14:45:27 +00:00
Bennet Fenner
baaf87aa23 Fix unit_evals.yml (#41377)
Release Notes:

- N/A
2025-10-28 14:30:47 +00:00
Piotr Osiewicz
8991f58b97 ci: Bump target directory size limit for mac runners (#41375)
Release Notes:

- N/A
2025-10-28 14:12:03 +00:00
Bennet Fenner
2579f86bcd acp_thread: Fix @mention file path format (#41310)
After #38882 we were always including file/directory mentions as
`zed:///agent/file?path=a/b/c.rs`.
However, for most resource links (files/directories/symbols/selections)
we want to use a common format, so that ACP servers don't have to
implement custom handling for parsing `ResourceLink`s coming from Zed.

This is what it looks like now:
```
[@index.js](file:///Users/.../projects/reqwest/examples/wasm_github_fetch/index.js) 
[@wasm](file:///Users/.../projects/reqwest/src/wasm) 
[@Error](file:///Users/.../projects/reqwest/src/async_impl/client.rs?symbol=Error#L2661:2661) 
[@error.rs (23:27)](file:///Users/.../projects/reqwest/src/error.rs#L23:27) 
```

Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-10-28 15:06:19 +01:00
Kirill Bulatov
5423fafc83 Use proper inlay hint range when filtering out hints (#41363)
Follow-up of https://github.com/zed-industries/zed/pull/40183

Release Notes:

- N/A

---------

Co-authored-by: Lukas Wirth <lukas@zed.dev>
2025-10-28 12:58:38 +00:00
Finn Evers
8a01e48339 ui: Properly update scrollbar track color (#41354)
Closes https://github.com/zed-industries/zed/issues/41334

This comes down to a caching issue..

Release Notes:

- Fixed an issue where the scrollbar track color would not update in
case the theme was changed.
2025-10-28 09:30:58 +00:00
Adir Shemesh
1b43217c05 Add a jetbrains-like Toggle All Docks action (#40567)
The current Jetbrains keymap has `ctrl-shift-f12` set to
`CloseAllDocks`. On Jetbrains IDEs this hotkey actually toggles the
docks, which is very convenient: You press it once to hide all docks and
just focus on the code, and then you can press it again to toggle your
docks right back to how they were. Unlike `CloseAllDocks`, a toggle
means the editor needs to remember the previous docks state so this
necessitated some code changes.

Release Notes:

- Added a `Toggle All Docks` editor action and updated the keymaps to
use it
2025-10-28 08:54:05 +00:00
Kirill Bulatov
1b6cde7032 Revert "Fix ESLint linebreak-style errors by preserving line endings in LSP communication (#38773)" (#41355)
This reverts commit 435eab6896.

This caused format on save to scroll down to bottom instead of keeping
the position.

Release Notes:

- N/A
2025-10-28 08:45:02 +00:00
Finn Evers
bd0bcdb0ed Fix line number settings migration (#41351)
Follow-up to https://github.com/zed-industries/zed/pull/39268

Also updates the documentation.

Release Notes:

- N/A
2025-10-28 08:08:49 +00:00
Anthony Eid
2b5699117f editor: Fix calculate relative line number panic (#41352)
### Reproduction steps

1. Turn on relative line numbers
2. Start a debugging session and hit an active debug line
3. minimize Zed so the editor element with the active debug line has
zero visible rows

#### Before 

https://github.com/user-attachments/assets/57cc7a4d-478d-481a-8b70-f14c879bd858
#### After 

https://github.com/user-attachments/assets/19614104-f9aa-4b76-886b-1ad4a5985403

Release Notes:

- debugger: Fix a panic that could occur when minimizing Zed
2025-10-28 08:08:02 +00:00
John Tur
0857ddadc5 Always delete OpenConsole.exe on Windows uninstall (#41348)
By default, the uninstaller will only delete files that were written by
the original installer. When users upgrade Zed, these new
OpenConsole.exe files will have been written by auto_upgrade_helper, not
the installer. Force them to be deleted on uninstall, so they do not
hang around.

Release Notes:

- N/A
2025-10-28 07:14:56 +00:00
Coenen Benjamin
3e3618b3ff debugger: Add horizontal scrollbar for frame item and tooltip for variables (#41261)
Closes #40360 

I first tried to use an horizontal scrollbar also for variables but as
it's a List that can be collapsed it didn't feel natural so I ended up
adding a tooltip to have to full value of the variable when you hover
the item. (cf screenshots).




https://github.com/user-attachments/assets/70c4150d-b967-46b0-8720-82bbad9c9cca




https://github.com/user-attachments/assets/d0b52189-b090-4824-8eb7-2f455fa58b33



Release Notes:

- Added: for debugger UI horizontal scrollbar for frame item and tooltip
for variables.

Signed-off-by: Benjamin <5719034+bnjjj@users.noreply.github.com>
2025-10-28 03:04:21 -04:00
Anthony Eid
2163580b16 Fix tab switcher spacing bug (#41329)
The tab switcher render matches calls each workspace item's
`Item::tab_content` function that can return an element of variable
size. Because the tab switcher was using a uniform list under the hood,
this would cause spacing issues when tab_contents elements had different
sizes.

The fix is by changing the picker to use a material list under the hood.

Release Notes:

- N/A
2025-10-28 03:00:55 -04:00
h-michaelson20
4778d61bdd Fix copy button not working for REPL error output (#40669)
## Description

Fixes the copy button functionality in REPL interactive mode error
output sections.

When executing Python code that produces errors in the REPL (e.g.,
`NameError`), the copy button in the error output section was
unresponsive. The stdout/stderr copy button worked correctly, but the
error traceback section copy button had no effect when clicked.

Fixes #40207

## Changes

Modified the following:
src/outputs.rs: Fixed context issues in render_output_controls by
replacing cx.listener() with simple closures, and added custom button
implementation for ErrorOutput that copies/opens the complete error
(name + message + traceback)
src/outputs/plain.rs: Made full_text() method public to allow access
from button handlers
src/outputs/user_error.rs: Added Clone derive to ErrorView struct and
removed a couple pieces of commented code

## Why This Matters

The copy button was clearly broken and it is useful to have for REPL
workflows. Users could potentially need to copy error messages for a
variety of reasons.

## Testing

See attached demo for proof that the fix is working as intended. (this
is my first ever commit, if there are additional test cases I need to
write or run, please let me know!)


https://github.com/user-attachments/assets/da158205-4119-47eb-a271-196ef8d196e4

Release Notes:

- Fixed copy button not working for REPL error output
2025-10-28 06:52:53 +00:00
Lukas Wirth
46c5d515bf recent_projects: Surface project opening errors to user (#41308)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-28 06:44:44 +00:00
Xiaobo Liu
73bd12ebbe editor: Optimize selection overlap checking (#41281)
Replace the binary search approach with a more efficient partition_point
method for checking selection overlaps. This eliminates the need to
collect and sort selection ranges separately, reducing memory allocation
and improving performance when handling multiple selections.

Release Notes:

- N/A

Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>
2025-10-28 07:40:38 +01:00
versecafe
cc829e7fdb remote: 60 second timeout on initial connection (#41339)
Closes #41316

Release Notes:

- Fixes #41316 

> This keeps the 5 second heartbeat behavior for after the connection is
made
2025-10-28 06:58:35 +01:00
Jakub Konka
fdf5bf7e6a remote: Support building x86_64-linux-musl proxy in nix-darwin (#41291)
This change adds two things to our remote server build
process:
1. It now checks if all required tooling is installed before using it or installing it on demand. This includes checks for `rustup` and `cargo-zigbuild` in your `PATH`.
2. Next, if `ZED_BUILD_REMOTE_SERVER` contains `musl` and `ZED_ZSTD_MUSL_LIB`
is set, we will pass its value (the path) to `cargo-zigbuild` as `-C
link-arg=-L{path}`.

Release Notes:

- N/A
2025-10-28 06:58:09 +01:00
John Tur
c94536a2d6 Fix Windows updater failing to copy OpenConsole.exe (#41338)
Release Notes:

- N/A
2025-10-28 05:10:57 +00:00
John Tur
9db474051b Reland Windows Arm64 builds in CI (#40855)
Release Notes:

- windows: Added builds for Arm64 architecture

---------

Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
2025-10-27 23:59:34 -04:00
Lukas
1d0bb5a7a6 Make 'wrap selections in tag' work with line selection mode (#41030)
The `wrap selections in tag` action currently did not take line_mode
into account, which means when selecting lines with `shift-v`, the
start/end tags would be inserted into the middle of the selection (where
the cursor sits)


https://github.com/user-attachments/assets/a1cbf3da-d52a-42e2-aecf-1a7b6d1dbb32

This PR fixes this behaviour by checking if the selection uses line_mode
and then adjusting start and end points accordingly.

NOTE: I looked into amending the test cases for this, but I am unsure
how to express line mode with range markers. I would appreciate some
guidance on this and then I am happy to add test cases.

After:


https://github.com/user-attachments/assets/a212c41f-b0db-4f50-866f-fced7bc677ca

Release Notes:

- Fixed `Editor: wrap selection in tags` when in vim visual line mode

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-10-28 03:07:41 +00:00
Josh Piasecki
6823847978 Add buffer_search_deployed key context (#41193)
Release Notes:

- Pane key context now includes 'buffer_search_deployed' identifier

The goal of this PR is to add a new identifier in the key context that
will let the user target when the BufferSearchBar is deployed even if
they are not focused on it.

requested in #36930

Same rational as #40454 this will allow users to make more flexible
keybindings, by including some additional information higher up the key
context tree.

i thought adding this context to `Pane` seemed more appropriate than
`Editor` since `Terminal` also has a `BufferSearchBar`; however, I ran
into some import issues between BufferSearchBar, Search, Pane, and
Workspace which made it difficult to implement adding this context
directly inside `Pane`'s render function.

instead i added a new method called `contributes_context` to
`ToolbarItem` which will allow any toolbar item to add additional
context to the `Pane` level, which feels like it might come in handy.

here are some screen shots of the context being displayed in the Editor
and the Terminal

<img width="1653" height="1051" alt="Screenshot 2025-10-25 at 14 34 03"
src="https://github.com/user-attachments/assets/21c5b07a-8d36-4e0b-ad09-378b12d2ea38"
/>

<img width="1444" height="1167" alt="Screenshot 2025-10-25 at 12 32 21"
src="https://github.com/user-attachments/assets/86afe72f-b238-43cd-8230-9cb59fb93b2c"
/>
2025-10-27 20:37:37 -06:00
Conrad Irwin
3a7bdf43f5 Fix unwrap in branch diff (#41330)
Closes #ISSUE

Release Notes:

- N/A
2025-10-28 02:24:54 +00:00
Thomas Heartman
d5e297147f Support relative line number on wrapped lines (#39268)
**Problem:** Current relative line numbering creates a mismatch with
vim-style navigation when soft wrap is enabled. Users must mentally
calculate whether target lines are wrapped segments or logical lines,
making `<n>j/k` navigation unreliable and cognitively demanding.

**How things work today:**
- Real line navigation (`j/k` moves by logical lines): Requires
determining if visible lines are wrapped segments before jumping. Can't
jump to wrapped lines directly.
- Display line navigation (`j/k` moves by display rows): Line numbers
don't correspond to actual row distances for multi-line jumps.

**Proposed solution:** Count and number each display line (including
wrapped segments) for relative numbering. This creates direct
visual-to-navigational correspondence where the relative number shown
always matches the `<n>j/k` distance needed.

**Benefits:**
- Eliminates mental overhead of distinguishing wrapped vs. logical lines
- Makes relative line numbers consistently actionable regardless of wrap
state
- Preserves intuitive "what you see is what you navigate" principle
- Maintains vim workflow efficiency in narrow window scenarios

Also explained an discussed in
https://github.com/zed-industries/zed/discussions/25733.

Release Notes:

Release Notes:

- Added support for counting wrapped lines as relative lines and for
displaying line numbers for wrapped segments. Changes
`relative_line_numbers` from a boolean to an enum: `enabled`,
`disabled`, or `wrapped`.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-10-27 20:20:45 -06:00
Lukas Wirth
1c4923e1c8 gpui: Add a timeout to #[gpui::test] tests (#41303)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-28 01:37:41 +00:00
Agus Zubiaga
ee80ba6693 zeta2: LLM-based context gathering (#41326)
Release Notes:

- N/A

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Max Brunsfeld <max@zed.dev>
2025-10-27 22:54:42 +00:00
Abdelhakim Qbaich
fd306c97f4 Fix default settings entry for basedpyright (#40812)
If you set `{"basedpyright": {"analysis": {"typeCheckingMode":
"off"}}}`, you will notice that it doesn't actually work, but
`{"basedpyright.analysis": {"typeCheckingMode": "off"}}` does.

Made the change on how the default is being set.

Release Notes:

- N/A
2025-10-27 22:57:04 +01:00
Anthony Eid
b3483a157c settings_ui: Fix tabbing in settings UI main page content (#41209)
Tabbing into the main page would move focus to the navigation panel
instead of auto-scrolling. This PR fixes that bug.


Release Notes:

- N/A
2025-10-27 17:30:34 -04:00
Conrad Irwin
ac66e912d5 Don't upload symbols to DO anymore (#41317)
Sentry now symbolicates stack traces, no need to make our builds slower

Release Notes:

- N/A
2025-10-27 15:22:53 -06:00
Anthony Eid
5e37a7b78c Fix shell welcome prompt showing up in Zed's stdout (#41311)
The bug occurred because `smol::process::Command::from(_)` doesn't set
the correct fields for stdio markers. So moving the stdio configuration
after converting to a `smol` command fixed the issue.

I added the `std::process::Command::{stdout, stderr, stdin}` functions
to our disallowed list in clippy to prevent any bugs like this appearing
in the future.

Release Notes:

- N/A
2025-10-27 20:04:36 +00:00
Cameron Mcloughlin
00278f43bb Add Rust convenience Tree-sitter injections for common crates (#41258) 2025-10-27 19:58:04 +00:00
Mikayla Maki
ac3d2a338b Tune the focus-visible heuristics a bit (#41314)
This isn't quite right yet, as a proper solution would remember the
input modality at the moment of focus change, rather than at painting
time. But this gets us close enough for now.

Release Notes:

- N/A
2025-10-27 19:53:53 +00:00
Conrad Irwin
58f07ff709 Try gh-workflow (#41155)
Experimenting with not writing YAML by hand...

Release Notes:

- N/A
2025-10-27 13:39:01 -06:00
Danilo Leal
db0f7a8b23 docs: Mention the settings and keymap UIs more prominently (#41302)
Release Notes:

- N/A
2025-10-27 15:17:11 -03:00
Marshall Bowers
f5ad4c8bd9 Remove PostgREST (#41299)
This PR removes the PostgREST containers and deployments, as we're no
longer using it.

Release Notes:

- N/A
2025-10-27 13:27:59 -04:00
Piotr Osiewicz
172984978f collab: Add 'Copy channel notes link' to right click menu on channels (#41298)
Release Notes:

- Added a "Copy Channel Notes Link" action to right-click menu of Zed
channels.
2025-10-27 17:00:36 +00:00
Mohin Hasin Rabbi
ba26ca4aee docs: Document per-release channel configuration (#40833)
## Summary
- Document the `stable`/`preview`/`nightly` top-level keys that let
users scope settings overrides per release channel.
- Provide an example `settings.json` snippet and call out that overrides
replace array values rather than merging them.
- Mention that UI-driven changes edit the root config so per-channel
blocks might need manual updates.

## Testing
- Not run (docs only).

Fixes #40458.

Release Notes:

- N/A

---------

Co-authored-by: MrSubidubi <finn@zed.dev>
2025-10-27 16:50:32 +00:00
Finn Evers
1ae8e0c53a keymap_editor: Clear action query when showing matching keybindings (#41296)
Closes https://github.com/zed-industries/zed/issues/41050

Release Notes:

- Fixed an issue where showing matching keystrokes in the keybind editor
modal would not clear an active text query.
2025-10-27 17:49:13 +01:00
pedrxd
a70f80df95 Add support for changing the Codestral endpoint (#41116)
```json
  "edit_predictions": {
    "codestral": {
      "api_url": "https://codestral.mistral.ai",
      "model": "codestral-latest",
      "max_tokens": 150
    }
  },
```

Release Notes:

- Added support for changing the Codestral endpoint. This was discussed
at #34371.

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-10-27 16:00:27 +00:00
Dino
821a4880bd cli: Use --wait to prefer focused window (#41051)
Introduce a new `prefer_focused_window` field to the
`workspace::OpenOptions` struct that, when provided, will make it so
that Zed opens the provided path in the currently focused window.

This will now automatically be set to true when the `--wait` flag is
used with the CLI.

Closes #40551 

Release Notes:

- Improved the `--wait` flag in Zed's CLI so as to always open the
provided file in the currently focused window

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-10-27 15:58:41 +00:00
Danilo Leal
7f17d4b61d Add Tailwind CSS and Ruff to built-in features list (#41285)
Closes https://github.com/zed-industries/zed/issues/41168

This PR adds both Tailwind CSS and Ruff (linter for Python) as built-in
features; a banner mentioning this should show up now for these two when
searching for them in the extensions UI.

There will also be a corresponding zed.dev site PR adding a "Ruff is
built-in" card to the zed.dev/extensions page.

Release Notes:

- N/A
2025-10-27 12:38:56 -03:00
Bennet Fenner
ebf4a23b18 Use paths::external_agents_dir (#41286)
We were not actually using `paths::agent_servers` and were manually
constructing the path to the `external_agents` folder in a few places.

Release Notes:

- N/A
2025-10-27 15:24:44 +00:00
Adam Richardson
370d4ce200 rope: Micro optimize the creation of masks (#41132)
Using compiler explorer I saw that the compiler wasn't clever enough to
optimise away the branches in the masking code. I thought the compiler
would have a better chance if we always branched, which [turned out to
be the case](https://godbolt.org/z/PM594Pz18).

Running the benchmarks the biggest benefit I saw was:
```
push/65536              time:   [2.9067 ms 2.9243 ms 2.9417 ms]
                        thrpt:  [21.246 MiB/s 21.373 MiB/s 21.502 MiB/s]
                 change:
                        time:   [-8.3452% -7.2617% -6.2009%] (p = 0.00 < 0.05)
                        thrpt:  [+6.6108% +7.8303% +9.1050%]
                        Performance has improved.
```
But I did also see some regressions:
```
slice/4096              time:   [66.195 µs 66.815 µs 67.448 µs]
                        thrpt:  [57.915 MiB/s 58.464 MiB/s 59.012 MiB/s]
                 change:
                        time:   [+3.7131% +5.1698% +6.6971%] (p = 0.00 < 0.05)
                        thrpt:  [-6.2768% -4.9157% -3.5802%]
                        Performance has regressed.
```

Release Notes:

- N/A
2025-10-27 16:16:16 +01:00
Richard Feldman
2284131bfc Add @rtfeldman to reviewers list (#41127)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-27 11:13:12 -04:00
Ben Kunkle
2f7045f724 settings_ui: Show migration banner (#41112)
Closes #ISSUE

Release Notes:

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

---------

Co-authored-by: Danilo <danilo@zed.dev>
2025-10-27 10:13:26 -04:00
Jakub Konka
5d359ea2f2 remote: Fall back to SCP if SFTP fails (#41255)
Fixes https://github.com/zed-industries/zed/issues/41260

After experimenting and reading through the implementation of OpenSSH
stack on Windows, it looks like batch mode precludes use of passwords.
In the listing
b8c08ef9da/sshconnect2.c (L417),
the last field of each `Authmode` struct is a pointer to the config
value that *disables* that particular mode. In this case, `keyboard`
(interactive) and `password` modes are both disabled if batch mode is
used. We should therefore fall back to `scp` if `sftp` fails rather than
to fail outright.

Release Notes:

- N/A
2025-10-27 15:01:13 +01:00
Ben Kunkle
72c6a74505 keymap_editor: Fix updating empty keymap (#40909)
Closes #40898

Release Notes:

- Fixed an issue where attempting to add or update a key binding in the
keymap editor with an empty `keymap.json` file would fail
2025-10-27 09:56:54 -04:00
Alvaro Parker
941033e373 settings_ui: Add vim motions on navigation menu (#39988)
Closes #ISSUE

Release Notes:

- Added vim motions on settings navigation menu
2025-10-27 09:53:37 -04:00
Piotr Osiewicz
83884ca36f lsp: Support tracking multiple registrations of diagnostic providers (#41096)
Closes #40966
Closes #41195
Closes #40980 

Release Notes:

- Fixed diagnostics not working with basedpyright/pyright beyond an
initial version of the document

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: dino <dinojoaocosta@gmail.com>
2025-10-27 14:52:23 +01:00
Kirill Bulatov
f503c65924 Make "About Zed" menu entry to work when all Zed windows are closed (#41272)
Closes https://github.com/zed-industries/zed/issues/41267

The only downside would be the fact that a Zed window will appear behing
the version modal, but this is a limitation for all "window-less"
actions currently.

Release Notes:

- Made "About Zed" menu entry to work when all Zed windows are closed
2025-10-27 11:33:50 +00:00
Lukas Wirth
c1cd371786 fix: Edit predictions using stale snapshot (#41271)
Release Notes:

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

Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-10-27 11:00:37 +00:00
Ben Brandt
7cb2d83608 acp: Start sending Client Info to the Agent (#41265)
Updates to acp crate 0.7, which allows us to send information about the
client to the Agent.
In the future, we can also use the AgentInfo on the response for
internal metrics.

Release Notes:

- N/A
2025-10-27 10:05:50 +00:00
Lukas Wirth
ae3abf50d8 editor: Fix panics in CursorPosition::update_position (#41237)
Fixes a regression introduced in
https://github.com/zed-industries/zed/pull/39857. As for the exact
reason this causes this issue I am not yet sure will investigate (as per
the todos in code)

Fixes ZED-23R

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-27 08:26:39 +00:00
deltamaya
edf2ec7d4c editor: Make hover popover delay strictly respect hover_popover_delay setting (#41149)
Previously, the hover popover delay was implemented using two
overlapping timers, which caused the minimum delay to always be at least
HOVER_REQUEST_DELAY_MILLIS, regardless of the hover_popover_delay
setting.
This change updates the logic to wait for hover_popover_delay only,
ensuring the total delay is always equals to hover_popover_delay . As a
result, the hover popover now appears after the intended delay, matching
the user's configuration more accurately.

Release Notes:

- Improved hover popover respecting settings delay correctly
2025-10-27 08:01:43 +00:00
Marshall Bowers
2919e1976a docs: Display action names in backticks instead of quotes (#41248)
This PR fixes some instances where we were displaying action names in
quotes instead of in backticks.

Also fixed some mentions of using an action to open the settings file,
as this has changed after the release of the settings UI.

Release Notes:

- N/A
2025-10-27 00:50:31 +00:00
Lukas Wirth
fd3ca0303f workspace: Handle non-cloneable items better (#41215)
When trying to split and clone a non clone-able workspace item we now
attempt split and move instead of doing nothing. Additionally we disable
the split menu buttons if we can't split the active item at all.

Release Notes:

- Improved handling of unsplittable panes
2025-10-26 13:24:26 +00:00
Lukas Wirth
61e4a1d16a settings: Fix out of bounds index (#41227)
Fixes ZED-2J8

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-26 12:57:44 +00:00
Lukas Wirth
2471ae451c Pre-initialize global rayon threadpool (#41226)
We only use it a handful of times and the default amount of threads
(logical cpu core number) its spawns is overkill for this. This also
gives the threads names oppose to being labeled `<unknown>`

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-26 12:44:34 +00:00
Lukas Wirth
d6b31d8932 gpui: Fix TextLayout::layout producing invalid text runs (#41224)
The issues is that the closure supplied to `request_measured_layout`
could be run multiple times with differing `known_dimensions`. This in
turn will mean we truncate the font runs once, inserting a multibyte
char at the end but then in the next iteration use those truncated runs
for possible the original untruncated string which has multibyte
characters overlapping the now truncated run end resulting in a faulty
utf8 boundary index.

Solution to this is simple, truncate a clone of the runs when needed
instead of modifying the original.

Fixes https://github.com/zed-industries/zed/issues/36925
Fixed ZED-2FF
Fixes ZED-2KM
Fixes ZED-2KK
Fixes ZED-1FF
Fixes ZED-255
Fixes ZED-2JD
Fixes ZED-2FX
Fixes ZED-2K2
Fixes ZED-2JX
Fixes ZED-2GE
Fixes ZED-2FC
Fixes ZED-2GD
Fixes ZED-2HY
Fixes ZED-2HR
Fixes ZED-2FN
Fixes ZED-2GT
Fixes ZED-2FK
Fixes ZED-2EY
Fixes ZED-27E
Fixes ZED-272
Fixes ZED-2EM
Fixes ZED-2CC
Fixes ZED-29V
Fixes ZED-25B
Fixes ZED-257
Fixes ZED-24R
Fixes ZED-24Q
Fixes ZED-23Z
Fixes ZED-227

Release Notes:

- Fixed a crash in text shaping when truncating rendered text
2025-10-26 13:00:52 +01:00
Lukas Wirth
95ad7a6cae gpui: Drop unnecessary use of StringIndexConverter (#41221)
Might help with ZED-1FF

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-26 10:35:33 +00:00
Kirill Bulatov
54a7da364c Fix task terminal split (#41218)
Re-lands https://github.com/zed-industries/zed/pull/40824

Release Notes:

- N/A
2025-10-26 10:01:40 +00:00
Lukas Wirth
003a39740a Try working around spurious rebuild bug in cargo (#41015)
We've been seeing a lot of weird constant rebuilds recently with
rust-anaylzer's check, either with cargo thinking build scripts are too
new timestamp wise or all fingerprints having gone missing somehow
(???). Reading through some related bug reports hints at disabling
incremental potentially working around this for now so let's test that
out

cc https://github.com/rust-lang/cargo/issues/16104

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-26 10:38:59 +01:00
Lukas Wirth
33ec545d1f workspace: Make Item::clone_on_split async (#41211)
Split out from https://github.com/zed-industries/zed/pull/40774 to
reduce the size of the reland of that PR (once I figure out the cause of
the issue)

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-26 08:46:37 +00:00
phpjit
b7cc597d28 languages: Add inline values support for JavaScript, TypeScript, and TSX (#40914)
Adds debugger inline values support for JavaScript, TypeScript, and TSX languages. 

Release Notes:

- debugger: Add inline value support for Javascript, TypeScript, and TSX

---------

Co-authored-by: Anthony <anthony@zed.dev>
2025-10-25 22:51:59 +00:00
Anthony Eid
ef306245e0 settings_ui: Add telemetry (#40973)
1. Settings Viewed: Whenever someone opens or refocus the settings ui
via an action
2. Settings Closed: When the settings ui window is closed 
3. Settings Navigation Clicked: The category and subcategory that a user
clicked on
4. Settings Error Shown: Whenever an error banner shows up
5. Settings Changed: The setting a user changed through the UI


cc: @katie-z-geer 

Release Notes:

- N/A
2025-10-25 22:43:16 +00:00
Abdelhakim Qbaich
615d1d7ab4 Avoid menu clipping with debugger welcome panel on resize (#41177)
Before:
<img width="1345" height="58" alt="before"
src="https://github.com/user-attachments/assets/cba79bba-6437-47fd-ad2b-b4ceaf03ef5d"
/>

After:
<img width="1756" height="90" alt="after"
src="https://github.com/user-attachments/assets/3442c7f6-a4dc-4c4c-92c5-2ca5ad9beb0d"
/>


Release Notes:

- N/A
2025-10-25 18:26:13 -04:00
Remco Smits
45983e11e9 markdown: Add support for HTML lists (#39553)
This PR adds support for **HTML** both ordered and unordered lists.

<img width="1441" height="805" alt="Screenshot 2025-10-07 at 21 40 17"
src="https://github.com/user-attachments/assets/8a54aec1-75aa-48fb-bf9f-c153cca48682"
/>

See code example used inside the screenshot:

```html
<ol>
  <li>First item</li>
  <li>Second item</li>
  <li>Third item
    <ol>
      <li>Indented item</li>
      <li>Indented item</li>
    </ol>
  </li>
  <li>Fourth item</li>
</ol>
```

TODO: 
- [x] Add examples
- [x] update description (screenshots, add small description)
- [x] fix displaying of nested lists

cc @bennetbo

Release Notes:

- markdown preview: Added support for HTML lists

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-10-25 20:15:17 +00:00
Bennet Fenner
06e1db54a7 codex: Delete older versions after installing new one (#41191)
Release Notes:

- codex: Fixed an issue where downloading a new version would not delete
older versions
2025-10-25 19:19:57 +00:00
Karl-Erik Enkelmann
8e09256c8c Handle itemDefaults in CompletionList according to spec (#41187)
Closes https://github.com/zed-extensions/java/issues/101

Previously Zed did not handle resolving CompletionList with itemDefaults
correctly according to the
[LSP-Spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#:~:text=/**%0A%09%20*%20The%20edit%20text,%3F%3A%20string%3B).
When a `CompletionList` is provided by the server, that includes ranges
as `itemDefaults`, the field to use for the snippet to insert as
`newText` is in the `textEditText` field, with a fallback to `label` if
that does not exist (`insertText` is ignored).

Release Notes:

- Fixed Java language severs' completion defaults handling on Zed's side
2025-10-25 22:15:32 +03:00
Remco Smits
79ef10bfc3 markdown: Add support for HTML table column align attribute (#41163)
This PR allows you to define `align="right"` for example to change the
default alignment on **HTML** table columns. This PR also refactors
where we store the alignments in order to make it so you can define it
column based instead of only row based.

See that the `Revenue` column is left aligned instead of the default
`centered`.

**Result**

<img width="1161" height="177" alt="Screenshot 2025-10-25 at 11 01 38"
src="https://github.com/user-attachments/assets/94bda4f0-00c1-4726-a3bd-99b3f2573ef5"
/>


**Code example**

```HTML
<table>
    <tr>
        <th rowspan="2">Region</th>
        <th colspan="2" align="left">Revenue</th>
        <th rowspan="2">Growth</th>
    </tr>
    <tr>
        <th>Q2 2024</th>
        <th>Q3 2024</th>
    </tr>
    <tr>
        <td>North America</td>
        <td>$2.8M</td>
        <td>$2.4B</td>
        <td>+85,614%</td>
    </tr>
    <tr>
        <td>Europe</td>
        <td>$1.2M</td>
        <td>$1.9B</td>
        <td>+158,233%</td>
    </tr>
    <tr>
        <td>Asia-Pacific</td>
        <td>$0.5M</td>
        <td>$1.4B</td>
        <td>+279,900%</td>
    </tr>
</table>
```

Release Notes:

- markdown preview: Add support for `HTML` table column `align`
attribute
2025-10-25 20:12:05 +02:00
Remco Smits
986ca19516 markdown: Fix HTML tables with mismatching columns (#41108)
Follow-up: https://github.com/zed-industries/zed/pull/39898

Right now, we don't fill the empty column when the current row count is
less than the max row count. This PR fixes that by filling it with an
empty cell. So the table columns don't flow in the wrong direction, as
you can see inside the first screenshot.

**Before**
<img width="1095" height="182" alt="Screenshot 2025-10-24 at 16 09 02"
src="https://github.com/user-attachments/assets/e3abf24e-c190-4bd7-b43a-39f2f01ecd1c"
/>

**After**
<img width="1165" height="178" alt="Screenshot 2025-10-24 at 16 19 17"
src="https://github.com/user-attachments/assets/427c25f9-82a7-498b-a1a2-d71e4c288fe5"
/>

**Code example**
```html
<table>
    <tr>
        <th rowspan="2">Region</th>
        <th colspan="2">Revenue</th>
        <th rowspan="2">Growth</th>
    </tr>
    <tr>
        <th>Q2 2024</th>
        <th>Q3 2024</th>
    </tr>
    <tr>
        <td>North America</td>
        <td>$2.8M</td>
        <td>$2.4B</td>
        <td>+85,614%</td>
        <td>+99%</td> // extra column here
    </tr>
    <tr>
        <td>Europe</td>
        <td>$1.2M</td>
        <td>$1.9B</td>
        <td>+158,233%</td>
    </tr>
    <tr>
        <td>Asia-Pacific</td>
        <td>$0.5M</td>
        <td>$1.4B</td>
        <td>+279,900%</td>
    </tr>
</table>
```

**Note** there are no release notes, as the previous PR didn't get
released yet.

Release Notes:

- N/A
2025-10-25 20:09:56 +02:00
Douglas Leão
42d8d77938 Update GDScript documentation (#41142)
Some information in the GDScript documentation page was outdated (like
it still treats Zed if it was a macOS exclusive app) or some details
were missing.

- The `zed-gdscript` URL has been updated with the new owner.
- Pre-requisites added (with netcat included).
- Godot installation steps removed, it's now listed in the
pre-requisites.
- Setup steps simplified and reworded.

The note at the bottom was removed as I didn't see the issue in my end.

Release Notes:

- N/A
2025-10-25 15:59:04 +03:00
Julia Ryan
e4c90be58a Add more detailed error for remove_file (#41147)
This should help us debug #40958.

Release Notes:

- N/A
2025-10-25 02:02:06 +00:00
Julia Ryan
7433d85458 Notify on opening WSL paths outside of wsl (#40195)
Closes #27340

Release Notes:

- N/A

---------

Co-authored-by: John Tur <john-tur@outlook.com>
2025-10-25 01:44:32 +00:00
Kirill Bulatov
1dffdea27c Fix duplicate hints in multi buffer excerpts during editing and scrolling (#41143)
Follow-up of https://github.com/zed-industries/zed/pull/40183
Closes https://github.com/zed-industries/zed/issues/24798

Release Notes:

- N/A
2025-10-24 23:08:19 +00:00
Mohin Hasin Rabbi
1f40a3c70e Document global debug.json usage and fix REPL error copy (#40836)
## Overview
- document how to keep a per-user debug.json so global launch tasks show
up everywhere (Fixes #39849)
- sanitize REPL terminal text before copying so error blocks can be
copied and opened in buffers (Fixes #40207)

## Design Decisions
- reused the existing user debug file (paths::debug_scenarios_file) and
pointed docs at the zed::OpenDebugTasks command to stay aligned with the
settings UX
- extract a sanitize helper inside TerminalOutput::full_text to strip
\r/null padding while keeping indentation intact, then join the cleaned
lines so clipboard and buffers get readable text

## Testing
- Not run (cargo is unavailable in this environment)

Fixes #39849.
Fixes #40207.
2025-10-25 01:13:53 +03:00
Somtoo Chukwurah
92c31278ee Add support for GitHub Copilot /responses endpoint (#40762)
Add support for GithubCopilot /responses endpoint. This gives the
copilot chat provider the ability to use the new GPT-5 codex model and
any other model that lacks support for /chat/copmletions endpoint.

Closes #38858 

Release Notes:

- Add support for GithubCopilot /responses endpoint.

# Added
1. copilot_response.rs that has the /response endpoint types
2. uses response endpoint if model does not support /chat/completions.
3. new into_copilot_response() to map LanguageCompletionEvents to
Request.
4. new map_stream() to map response stream event to
LanguageCompletionEvents and tests.
5. Fixed a bug where trying to parse response for non streaming for
/chat/completion was failing

# Notes
There is a pr open - https://github.com/zed-industries/zed/pull/39989
for adding /response support for OpenAi and OpenAi compatible API.
Altough they share some similarities (copilot api seems to mirror openAi
directly) ive simplified some stuff and tried to keep it the same with
the vscode-chat implementation where possible. There might be a case for
code reuse but i think keeping them separate for now should be ok.

# Tool Calls
<img width="716" height="670" alt="Screenshot from 2025-10-15 17-12-30"
src="https://github.com/user-attachments/assets/14e88a52-ba8b-4209-8f78-73d15034b1e0"
/>

# Image
<img width="923" height="494" alt="Screenshot from 2025-10-21 02-02-26"
src="https://github.com/user-attachments/assets/b96ce97c-331e-45cb-b5b1-7aa10ed387b4"
/>
2025-10-24 13:25:58 -06:00
Danilo Leal
a7c5b8d78b Add ResetAllZoom and ResetAgentZoom actions (#41124)
Very often, when I'm testing or playing around with the zoom feature,
including the agent panel, I find myself missing one quick action that
would bring everything back to normal. That's what `ResetAllZoom` does.
If you have customized your zoom level in all areas of Zed, that action
returns everything to its default state. Similarly, if you're playing
around with zoom just in the agent panel, `ResetAgentZoom` does the same
in that context.

Release Notes:

- Added the `ResetAllZoom` and `ResetAgentZoom ` actions, allowing to
return the zoom level across the whole app and/or just in the agent
panel to its default/original value.
2025-10-24 18:11:39 +00:00
Conrad Irwin
d1b28e6431 REVIEWERS.conl (#40533)
Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
Co-authored-by: David Kleingeld <davidsk@zed.dev>
Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Co-authored-by: Lukas Wirth <lukas@zed.dev>
Co-authored-by: dino <dinojoaocosta@gmail.com>
Co-authored-by: John Tur <john-tur@outlook.com>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Kate <work@localcc.cc>
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-10-24 11:50:49 -06:00
Danilo Leal
ecf179df0c agent: Scale the agent UI and buffer font proportionally (#41121)
Closes https://github.com/zed-industries/zed/issues/41094

This PR adds the `agent_buffer_font_size` settings to be scaled in the
same proportion as the agent panel's UI font size, which was the only
setting that was being accounted for. This is something that I forgot to
do when we broke down the agent panel's font size controls into these
two values.

Release Notes:

- agent: Fixed an issue where the agent panel's buffer and UI font size
wouldn't scale proportionally.
2025-10-24 14:45:32 -03:00
Ben Kunkle
e54c9da2a8 Fix include ignored migration rerunning (#41114)
Closes #ISSUE

Release Notes:

- Fixed an issue where having a correct `file_finder.include_ignored`
setting would result in failed to migrate errors
2025-10-24 13:06:29 -04:00
Jakub Konka
bcbc6a330e Use ShellKind::try_quote whenever we need to quote shell args (#41104)
Re-reverts
8f4646d6c3
with fixes

Release Notes:

- N/A
2025-10-24 18:19:53 +02:00
feeiyu
f213f4bcc8 Fix duplicate process entries in WSL debug attach list (#40591)
Closes #40589

Replaced `System::new_all()` with `System::new_with_specifics` to fetch
only essential process information and exclude non-main threads from the
process list

after fix:
<img width="641" height="474" alt="image"
src="https://github.com/user-attachments/assets/32335552-2f7a-4317-8c01-f37b2eadfdc1"
/>


Release Notes:

- Fix duplicate process entries in WSL debug attach list
2025-10-24 11:49:30 -04:00
Dino
0ee6ca1767 project: Fix inability to open file after save as (#41012)
Update `project::buffer_store::BufferStore.save_buffer_as` in order to
correctly update the `path_to_buffer_id` hash map, ensuring that the
currently open file's path is dissociated from the buffer's id, to
prevent the new buffer from being open when trying to open the original
file.

Closes #29783 

Release Notes:

- Fixed issue where using `workspace: save as` would prevent users from
opening the original file from which the new file was created

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-10-24 15:27:34 +01:00
ToBinio
762082b15a ui_input: Don’t focus previous on decrement in NumberField (#41095)
This PR removes the behavior that the number_field changes focus to the
previous element when using the decrement button.

I mainly noticed this while decreasing the tab-size of a language, since
it there closes the page...

Please note that I am unsure what if any purpose this code has.
I was unable to find a use case and since it is not present in the
`increment_handler` I guess it should never have been here

---

Release Notes:

* Fixed wrongly focus previous element on number_field decrement
2025-10-24 14:03:01 +00:00
Joseph T. Lyons
fcd690d04c Enable source.organizeImports.ruff by default for Python (#41103)
Release Notes:

- Enabled automatic import organization, via
[ruff](https://github.com/astral-sh/ruff), when saving Python files. To
disable this, use:

```json
"Python": {
  "code_actions_on_format": { "source.organizeImports.ruff": false }
}
```
2025-10-24 13:44:23 +00:00
Richard Feldman
8de4b360e8 ACP Extensions (#40663)
Adds the ability to install ACP agents via extensions

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-24 07:52:51 -04:00
Bennet Fenner
7644e797fe agent: Fix edit_agent evals (#40921)
Release Notes:

- N/A
2025-10-24 07:48:14 +00:00
Danilo Leal
4fb91135d1 ui: Use focus_visible method for some components (#41077)
Using the cool, [recently
added](https://github.com/zed-industries/zed/pull/40940) `focus-visible`
support in some components. This will be particularly nice in the
settings UI, as it will not display the focus styles if you're
navigating it with a pointer device as opposed to the keyboard.

Release Notes:

- N/A
2025-10-24 05:47:53 +00:00
Conrad Irwin
f45a9b351d git: Branch diff (#40188)
Release Notes:

- git: Adds the ability to view the diff of the current branch since
main

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-10-23 22:38:40 -06:00
Danilo Leal
f11a3dcc97 settings_ui: Add small UI adjustments (#41065)
Super tiny changes that impact mostly Windows/Linux.

Release Notes:

- N/A
2025-10-24 01:08:26 +00:00
Danilo Leal
5aa82887ec rules library: Improve empty state & fix quirks on Windows (#41064)
Just got a new Windows machine and realized that the rules library empty
state was completly busted. Ended up also adding some little UI tweaks
to make it better for both Windows and Linux.

Release Notes:

- N/A
2025-10-23 21:45:48 -03:00
warrenjokinen
fe730e9129 Fix typo in Font Features description, s/b "OpenType" (#41058)
Fix typo (regression?), 
"Opentype" should be "OpenType"

Closes #ISSUE

Release Notes:

- N/A
2025-10-23 21:19:21 -03:00
John Tur
59a98bae3a Add attribution for code sourced from Windows Terminal (#41061)
Release Notes:

- N/A
2025-10-24 00:12:06 +00:00
Agus Zubiaga
1cf765e126 Revert: Spawn terminal process on background executor (#41060)
Reverts https://github.com/zed-industries/zed/pull/40774 and
https://github.com/zed-industries/zed/pull/40824 since they introduce a
bug where Nushell processes are leaked and Ctrl+C doesn't kill the
current process.

Release Notes:

- Fix a bug where nushell processes wouldn't get killed after closing a
terminal tab
2025-10-23 23:37:23 +00:00
Tom Planche
79eff1fe05 Make cursor move to duplicated line when duplicating line up (#41004)
![Screen Recording 2025-10-23 at 14 50
26](https://github.com/user-attachments/assets/3427aa06-faf4-4f76-a604-bfc5af30f8ce)

Closes #40919
Follow-up of #39610

Release Notes:
- When duplicating line up, fixed cursor to move to the duplicated line
2025-10-24 02:20:16 +03:00
Kunall Banerjee
af0d2ad491 docs: Fix small grammatical typo in JSX section (#41055)
Happened to notice this typo while going through the docs.

Release Notes:

- N/A

---

💖
2025-10-24 00:38:32 +02:00
Anthony Eid
f0ac54e8a5 settings_ui: Fix memory leak (#41036)
Closes #40351

The leak mainly showed up in the appearance page because it had a lot of
dropdown menus. The problem occurred because the drop-down menus were
creating a new entity on each frame instead of using the
`window.use_state...` API.

Release Notes:

- settings ui: Fixed memory leak in UI

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-10-23 21:31:20 +00:00
Nia
68707ffc74 crashes: Avoid crash handler on detached threads (#40883)
Set a TLS bit to skip invoking the crash handler when a detached thread
panics.

cc @P1n3appl3 - is this at odds with what we need the crash handler to
do?

May close #39289, cannot repro without a nightly build

Release Notes:

- Fixed extension panics crashing Zed on Linux

Co-authored-by: dino <dinojoaocosta@gmail.com>
2025-10-23 21:04:22 +00:00
Conrad Irwin
1ce9a85a1a git: Only save dirty files during staging (#41047)
Closes #40581

Release Notes:

- git: No longer save clean files when staging (to avoid triggering
unnecessary rebuilds in external file watchers like vite)
2025-10-23 20:53:19 +00:00
Joseph T. Lyons
1966d4c818 Add an issue template for Git bugs (#41048)
Release Notes:

- N/A
2025-10-23 16:22:32 -04:00
Smit Barmase
b6a18671dc settings_ui: Fix settings window doesn't inherit Zed icon (#41031)
Closes #40946

Release Notes:

- N/A
2025-10-24 01:32:09 +05:30
Jakub Konka
c0ff8ef8e9 remote: Do not compress remote by default when building from source (#40994)
Release Notes:

- N/A
2025-10-23 21:26:34 +02:00
Lukas Wirth
66ec0fc0e1 Revert zero-width non-joiner insertion in text shaping (#41043)
Reverts parts of https://github.com/zed-industries/zed/pull/39928
Closes https://github.com/zed-industries/zed/issues/40987

Release Notes:

- Fixed some fonts rendering with absurd spacing on MacOS
2025-10-23 19:22:28 +00:00
Devdatta Talele
435eab6896 Fix ESLint linebreak-style errors by preserving line endings in LSP communication (#38773)
Closes https://github.com/zed-industries/zed/issues/38453

Current `Buffer` API only allows getting buffer text with `\n` line
breaks — even if the `\r\n` was used in the original file's text.

This it not correct in certain cases like LSP formatting, where language
servers need to have original document context for e.g. formatting
purposes.

Added new `Buffer` API, replaced all buffer LSP registration places with
the new one and added more tests.

Release Notes: 

- Fixed ESLint linebreak-style errors by preserving line endings in LSP
communication

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-10-23 19:11:48 +00:00
Sean Hagstrom
55bc679c19 docs: Ensure macOS and Linux keybindings are escaped in HTML (#39802)
Closes #39654

Release Notes:

- Fixed the formatting of macOS and Linux keybindings in the Zed docs to
escape the backslash character when templating.
2025-10-23 14:13:35 -04:00
Lukas Wirth
6aaf19f276 multi_buffer: Split multi_buffer into more modules (#41033)
There are a of separate APIs in this, partially interleaved making it
difficult to grasp.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-23 17:57:52 +00:00
Marshall Bowers
d83ed4e03e docs: Update docs for theme_overrides setting (#41038)
This PR updates the docs to reference the `theme_overrides` setting
instead of the old `experimental.theme_overrides` setting.

Release Notes:

- N/A
2025-10-23 17:52:35 +00:00
Bennet Fenner
11eba64e68 Rename assistant_context crate to assistant_text_thread (#41024)
Previously we had `Context` and `ContextStore` in both `agent_ui` (used
to store context for the inline assistant) and `assistant_context` (used
for text threads) which is confusing.
This PR makes it so that the `assistant_context` concepts are now called
`TextThread*`, the crate was renamed to `assistant_text_thread`

Release Notes:

- N/A
2025-10-23 17:17:41 +00:00
Cole Miller
63fe1eae59 Fix overly noisy direnv error notification (#41029)
Updates #40531, restoring the previous behavior which didn't surface an
error when no direnv binary was found.

Release Notes:

- N/A
2025-10-23 17:03:55 +00:00
Cole Miller
8b6f3ec647 Fix the project diff sometimes missing updates (#40662)
This PR does two related things:

- First, it gets rid of the undifferentiated `RepositoryEvent::Updated`
in favor of three new events that have clearer definitions:
`BranchChanged`, `StashEntriesChanged`, and `StatusesChanged`. An
implication of this is that we no longer emit a `RepositoryEvent` unless
some git state changed; previously we would emit `RepositoryUpdated`
after doing a git status scan even if no statuses changed.
- Second, it changes the subscription strategy of the project diff to
make it update more robustly. Previously, the project diff only
subscribed to the `GitStore`, so it relied on getting a `GitStoreEvent`
when some buffer's diff hunks changed, even if the git status of the
buffer's file didn't change (e.g. a second hunk in a file that was
already modified). After this PR, it also subscribes to the individual
`BufferDiff` entities for buffers that have a git status, so the
`GitStore` is freed from that responsibility. This also fixes some real
cases where the previous strategy was not effective in keeping the
project diff up to date (captured in a test).

Release Notes:

- Fixed some cases where the project diff would fail to update in
response to git events.
2025-10-23 16:46:27 +00:00
Lukas Wirth
a66098b485 gpui: Revert reuse_prepaint change of #40767 (#41025)
c95ae84d91 (r2455674159)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-23 16:23:35 +00:00
Lukas Wirth
b519ab2758 rope: Improve chunk slicing panic messages (#41023)
We still see a bunch of panics here but the default slicing panic
doesn't tell which side of the range is bad

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-23 16:17:11 +00:00
Jakub Konka
023ac1b649 Revert "Use ShellKind::try_quote whenever we need to quote shell args" (#41022)
Reverts zed-industries/zed#40912

Closes https://github.com/zed-industries/zed/issues/41010
2025-10-23 16:06:47 +00:00
Lukas Wirth
738e248109 gpui: Small perf optimizations (#40767)
Some random findings based on profiling

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-23 13:26:27 +00:00
Kirill Bulatov
4f0a44896a Fix anchor-related panic when gathering applicable inlay chunks (#41002)
Before, inlay chunks were retrieved from the cache based on actualized
anchor ranges, but using an old buffer snapshot. Now, update all chunks
and snapshot to the actual before returning the applicable ones.

Follow-up of https://github.com/zed-industries/zed/pull/40183

Release Notes:

- N/A
2025-10-23 12:51:07 +00:00
Viraj Bhartiya
9a6397fb17 Add keybindings for menu navigation in vim.json (#40877)
- Added "ctrl-p" for selecting the previous menu item
- Added "ctrl-n" for selecting the next menu item

Closes #40619 

Release Notes:
- Ctrl+P now moves to the previous result; Ctrl+N moves to the next.
2025-10-23 13:21:32 +02:00
paneutral
93ef1947b5 vim: Fix cursor movement after entering Helix normal mode (#40528)
Closes #40009 

Release Notes:

- `vim::NormalBefore` now enters `helix_normal` correctly.
2025-10-23 11:15:12 +00:00
Abderrahmane TAHRI JOUTI
8c1b4cb1cd Re-order Helix keymaps and add alt-o/i/p/n (#40527)
Release Notes:

- helix: Re-ordered `helix_normal || helix_select` keybindings to follow the
same order as the keymap on the helix-editor
[documentation](https://docs.helix-editor.com/keymap.html).
- helix: Added `alt-o` & `alt-i` to Select larger and smaller syntax node
respectively
- helix: Added `alt-p` & `alt-n` to Select Next Syntax Node and Previous Syntax
Node respectively



--- 

The new main helix normal & select context looks like follows

```jsonc
{
    "context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
    "bindings": {
      // Movement
      "h": "vim::WrappingLeft",
      "left": "vim::WrappingLeft",
      "l": "vim::WrappingRight",
      "right": "vim::WrappingRight",
      "t": ["vim::PushFindForward", { "before": true, "multiline": true }],
      "f": ["vim::PushFindForward", { "before": false, "multiline": true }],
      "shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
      "shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
      "alt-.": "vim::RepeatFind",
      
      // Changes
      "shift-r": "editor::Paste",
      "`": "vim::ConvertToLowerCase",
      "alt-`": "vim::ConvertToUpperCase",
      "insert": "vim::InsertBefore",
      "shift-u": "editor::Redo",
      "ctrl-r": "vim::Redo",
      "y": "vim::HelixYank",
      "p": "vim::HelixPaste",
      "shift-p": ["vim::HelixPaste", { "before": true }],            
      ">": "vim::Indent",
      "<": "vim::Outdent",
      "=": "vim::AutoIndent",
      "d": "vim::HelixDelete",
      "c": "vim::HelixSubstitute",
      "alt-c": "vim::HelixSubstituteNoYank",
      
      // Selection manipulation
      "s": "vim::HelixSelectRegex",
      "alt-s": ["editor::SplitSelectionIntoLines", { "keep_selections": true }],
      ";": "vim::HelixCollapseSelection",
      "alt-;": "vim::OtherEnd",
      ",": "vim::HelixKeepNewestSelection",
      "shift-c": "vim::HelixDuplicateBelow",
      "alt-shift-c": "vim::HelixDuplicateAbove",
      "%": "editor::SelectAll",
      "x": "vim::HelixSelectLine",
      "shift-x": "editor::SelectLine",
      "ctrl-c": "editor::ToggleComments",
      "alt-o": "editor::SelectLargerSyntaxNode",
      "alt-i": "editor::SelectSmallerSyntaxNode",
      "alt-p": "editor::SelectPreviousSyntaxNode",
      "alt-n": "editor::SelectNextSyntaxNode",

      // Goto mode
      "g e": "vim::EndOfDocument",
      "g h": "vim::StartOfLine",
      "g l": "vim::EndOfLine",
      "g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
      "g t": "vim::WindowTop",
      "g c": "vim::WindowMiddle",
      "g b": "vim::WindowBottom",
      "g r": "editor::FindAllReferences", // zed specific
      "g n": "pane::ActivateNextItem",
      "shift-l": "pane::ActivateNextItem",      
      "g p": "pane::ActivatePreviousItem",
      "shift-h": "pane::ActivatePreviousItem",
      "g .": "vim::HelixGotoLastModification", // go to last modification
      
      // Window mode
      "space w h": "workspace::ActivatePaneLeft",
      "space w l": "workspace::ActivatePaneRight",
      "space w k": "workspace::ActivatePaneUp",
      "space w j": "workspace::ActivatePaneDown",
      "space w q": "pane::CloseActiveItem",
      "space w s": "pane::SplitRight",
      "space w r": "pane::SplitRight",
      "space w v": "pane::SplitDown",
      "space w d": "pane::SplitDown",

      // Space mode
      "space f": "file_finder::Toggle",
      "space k": "editor::Hover",
      "space s": "outline::Toggle",
      "space shift-s": "project_symbols::Toggle",
      "space d": "editor::GoToDiagnostic",
      "space r": "editor::Rename",
      "space a": "editor::ToggleCodeActions",
      "space h": "editor::SelectAllMatches",
      "space c": "editor::ToggleComments",
      "space p": "editor::Paste",
      "space y": "editor::Copy",

      // Other
      ":": "command_palette::Toggle",
      "m": "vim::PushHelixMatch",
      "]": ["vim::PushHelixNext", { "around": true }],
      "[": ["vim::PushHelixPrevious", { "around": true }],
      "g q": "vim::PushRewrap",
      "g w": "vim::PushRewrap",
      // "tab": "pane::ActivateNextItem",
      // "shift-tab": "pane::ActivatePrevItem",
    }
  }
  ```
2025-10-23 12:55:26 +02:00
Kirill Bulatov
3bb4c94ed4 Revert "Round the scroll offset in editor to fix jumping text (#40401)" (#40982)
This reverts commit 3da4cddce2.

The scrolling is ~30% less for the same gesture, and I'm not using
anything lodpi:


https://github.com/user-attachments/assets/b19521fc-9e29-4bfd-9660-dc1e4c8ae846


Release Notes:

- N/A
2025-10-23 09:25:36 +00:00
Lukas Wirth
16f7bd0a2e editor: Translate utf16 to utf8 offsets in copy_highlight_json (#40981)
Fixes ZED-2FM

Release Notes:

- Fixed panic in copy highlight json action
2025-10-23 08:46:50 +00:00
Lukas Wirth
c529a066bf gpui: Arc GlobalElementId (#40979)
This shrinks it from roughly a ~kilobyte to 8 byte, removing a bunch of
memmoves emitted by the compiler. Also `Arc`'s it instead of boxing as
we do clone it a couple times here and there, making that also a fair
bit cheaper

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-23 07:58:33 +00:00
Lukas Wirth
278032c6b8 extension_host: Run extensions on the tokio threadpool (#40936)
Fixes ZED-12D

`wasmtime_wasi` might call into tokio futures (to sleep for example)
which requires access to the tokio runtime. So we are required to run
these extensions in the tokio thread pool

Release Notes:

- Fixed extensions causing zed to occasionally panic
2025-10-23 09:41:05 +02:00
Anthony Eid
5a05986479 debugger: Fix debug scenario picker not showing language subtitles (#40977)
### Before 
<img width="544" height="403" alt="Screenshot 2025-10-23 at 2 58 44 AM"
src="https://github.com/user-attachments/assets/f5a69d27-e54a-4c1e-80f7-5cfff5b0bd47"
/>

### After
<img width="550" height="372" alt="Screenshot 2025-10-23 at 3 08 59 AM"
src="https://github.com/user-attachments/assets/33dd9c5e-054e-4ed1-ba1e-16746a5a697a"
/>

I also changed the debug picker to use a material list to cover the edge
case where there isn't a subtitle for an entry

Release Notes:

- debugger: Fix debug scenario picker not showing language subtitles
2025-10-23 07:37:37 +00:00
Anthony Eid
05c2cc0254 settings_ui: Enable editing project settings for worktrees without setting file (#40971)
I made three significant changes in this PR. 

1. `SettingsWindow::fetch_files` now creates
`SettingsUiFile::Project(..)` for any worktree that contains no project
settings.
2. `update_settings_file` now creates an empty settings file if a
worktree doesn't contain one.
3. `open_current_settings_file` also creates a settings file if the
current one doesn't exist.

Release Notes:

- settings ui: Enable editing project settings for worktrees that don't
have a project setting file.
2025-10-23 02:23:12 -04:00
Anthony Eid
1edb1b3896 settings ui: Update file headers when adding or removing projects (#40968)
This PR gets the `SettingsWindow` struct to subscribe to all
`Entity<Project>` events and any future project entities that are
created. When a project emits an event that signals a worktree has been
added or removed, the settings window refetches all settings files it
can find.

This fixes a bug where the settings ui would notice some project
settings that were created or opened after the `SettingsWindow` has been
initialized.

I also renamed `LOCAL` file mask to `PROJECT` to be inline with the
`SettingsFile` naming convention.

Release Notes:

- settings ui: Fix bug where project setting files wouldn't be detected
if they were created or opened after while an active settings window is
open
2025-10-23 00:49:36 -04:00
Jakub Konka
8f4646d6c3 Use ShellKind::try_quote whenever we need to quote shell args (#40912)
Using `shlex` unconditionally is dangerous as it assumes the underlying
shell is POSIX which is not the case for PowerShell, CMD, or Nushell.
Therefore, whenever we want to quote the args we should utilise our
helper `util::shell::ShellKind::try_quote` which takes into account
which shell is being used to actually exec/spawn the invocation.

Release Notes:

- N/A

---------

Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
2025-10-23 06:44:42 +02:00
Cole Miller
18daa9a839 Simplify environment loading code (#40531)
This is a refactoring PR to simplify our environment loading code by:

- Getting rid of `EnvironmentErrorMessage` in favor of using
`anyhow::Result` everywhere, with a separate `mpsc` channel to
communicate statuses that will be shown in the activity indicator
- Inlining some functions that were only called once to reduce
indirection
- Removing the separate `direnv` module

Release Notes:

- N/A
2025-10-23 03:57:33 +00:00
Cole Miller
bf63ff2b91 Fix path for vscode-html-language-server when found on PATH (#40832)
Don't prepend the worktree root when using an absolute path from
`Worktree::which`, since that does the wrong thing when running in
wasmtime given two Windows absolute paths. Also don't pass this path to
`node`, since when npm installed it's a sh/cmd wrapper not a JS file.

Part of #39153, also needs a fix on the vscode-langservers-extracted
side (missing shebang for the vscode-html-language-server script).

Release Notes:

- Fixed Zed failing to run the HTML language server in some cases.
2025-10-22 22:44:25 -04:00
Danilo Leal
f9e0642a72 settings_ui: Adjust warning banner design (#40952)
Just tidying this up a bit. Really have to fix this Banner component at
some point 😅 Having to add some spacing hacks to make it perfect here
that are not ideal and should be baked into the component.

Release Notes:

- N/A
2025-10-23 00:23:51 +00:00
Danilo Leal
bada88c5b3 Make the rules library window more consistent with the settings UI (#40948)
Now that we have two surface areas that open as separate windows, it's
important they're consistent with one another. This PR make the settings
UI and rules library windows more similar by having them use the same
minimum window size and similar styles for their navbar given they have
fundamentally the same design (nav on the left and content on the
right).

Release Notes:

- N/A
2025-10-22 20:57:01 -03:00
Moo, Kachon
6b8f8592ea agent_ui: Remove ellipses from some menu entries (#40858)
<img width="335" height="236" alt="image"
src="https://github.com/user-attachments/assets/5e09e9ed-f0f3-48df-ac22-032edfc6c114"
/>

Release Notes:
This PR fixes inconsistent use of trailing ellipsis (…) in the MCP
Server menu.
Previously, some menu items (like Add Custom Server…) used hardcoded
ellipses even though they didn’t trigger additional dialogs or steps.
This change removes unnecessary ellipses to align with standard UI/UX
conventions used across Zed’s menus.

---------

Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
2025-10-22 23:54:24 +00:00
Mikayla Maki
4fd4cbbfb7 gpui: Add focus-visible selector support (#40940)
Release Notes:

- N/A

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-22 23:36:18 +00:00
Lukas Wirth
044701e3a5 rope: Implement Rope::is_char_boundary via chars bitmap (#40945)
Slightly more efficient. No new tests as we already have tests
verifiying this.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-22 23:05:43 +00:00
Smit Barmase
a96bf504e0 theme: Fix entry could appear transparent on hover with certain themes (#40944)
Follow-up: https://github.com/zed-industries/zed/pull/34655  

We should use an opaque fallback color for `panel.overlay_hover`. This
helps when a custom theme doesn’t provide it, nor `element.hover`. For
example, VSCode’s default modern dark theme doesn’t include an
`element.hover` color after import.

Release Notes:

- Fixed an issue where the project panel’s sticky entry could appear
transparent on hover with certain themes.
2025-10-23 04:23:25 +05:30
Lukas Wirth
c16f2a1a29 project: Normalize Path env var to PATH in shell env on windows (#40720)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-22 22:47:16 +00:00
Anthony Eid
ca4103246f settings_ui: Fix file header from showing duplicate display names (#40943)
After clicking on the file drop-down and selecting a file, both the
selected file and the first drop-down entry would be the same file name
instead of overwriting a file name.

Release Notes:

- settings ui: Fix bug where duplicate file names showed in the header
files
2025-10-22 22:33:45 +00:00
Danilo Leal
731237222e agent_ui: Focus the message editor after regenerating a user message (#40938)
Release Notes:

- agent: Improved the editing previous messages UX by focusing in the
agent panel's message editor after regenerating a prompt, instead of
moving focus to the nearest regular buffer.
2025-10-22 19:29:35 -03:00
Smit Barmase
2096f256f2 editor: Reduce selection opacity when editor is not focused (#40925)
Focus:
<img width="420" alt="image"
src="https://github.com/user-attachments/assets/97b0c7ed-8ad4-400c-9f36-01d8bd9b362d"
/>

Unfocus:
<img width="420" alt="image"
src="https://github.com/user-attachments/assets/19805293-b419-4669-8a93-e9cf41900403"
/>

Release Notes:

- Reduced selection opacity when the editor is out of focus to make
inactive states clearer.
2025-10-23 03:57:10 +05:30
Ted Robertson
7880e2b961 docs: Clarify providers in edit-prediction.md (#39655)
Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-10-22 21:09:34 +00:00
Joseph T. Lyons
ab22478ed4 Update extension docs to mention BSD 3-Clause is a valid license (#40934)
Release Notes:

- N/A
2025-10-22 20:34:38 +00:00
Ben Kunkle
6ed9c0271d Don't migrate empty formatter array (#40932)
Follow up for #40409
Fix for
https://github.com/zed-industries/zed/issues/40874#issuecomment-3433759849

Release Notes:

- Fixed an issue where having an empty formatter array in your settings
`"formatter": []` would result in an erroneous prompt to migrate
settings
2025-10-22 20:01:45 +00:00
Joseph T. Lyons
93136a9aaa Update extension docs to mention GNU GPLv3 is a valid license (#40933)
Merge after: 

- https://github.com/zed-industries/extensions/pull/3641

Release Notes:

- N/A
2025-10-22 15:59:39 -04:00
Finn Evers
f393138711 Fix keybind hints flickering in certain scenarios (#40927)
Closes #39172

This refactors when we resolve UI keybindings in an effort to reduce
flickering whilst painting these: Previously, we would always resolve
these upon creating the binding. This could lead to cases where the
corresponding context was not yet available and no binding could be
resolved, even if the binding was then available on the next presented
frame. Following that, on the next rerender of whatever requested this
keybinding, the keybind for that context would then be found, we would
render that and then also win a layout shift in that process, as we went
from nothing rendered to something rendered between these frames.

With these changes, this now happens less often, because we only look
for the keybinding once the context can actually be resolved in the
window.

| Before | After | 
| --- | --- |
|
https://github.com/user-attachments/assets/adebf8ac-217d-4c7f-ae5a-bab3aa0b0ee8
|
https://github.com/user-attachments/assets/70a82b4b-488f-4a9f-94d7-b6d0a49aada9
|

Also reduced cloning in the keymap editor in this process, since that
requiered changing due to this anyway.

Release Notes:

- Fixed some cases where keybinds would appear with a slight delay,
causing a flicker in the process
2025-10-22 19:52:38 +00:00
Kirill Bulatov
ed5b9a4705 Rework inlay hints system (#40183)
Closes https://github.com/zed-industries/zed/issues/40047
Closes https://github.com/zed-industries/zed/issues/24798
Closes https://github.com/zed-industries/zed/issues/24788

Before, each editor, even if it's the same buffer split in 2, was
querying for inlay hints separately, and storing the whole inlay hint
twice, in `Editor`'s `display_map` and its `inlay_hint_cache` fields.

Now, instead of `inlay_hint_cache`, each editor maintains a minimal set
of metadata (which area was queried by what task) instead, and all LSP
inlay hint data had been moved into `LspStore`, both local and remote
flavors store the data.
This allows Zed, as long as a buffer is open, to reuse the inlay hint
data similar to how document colors and code lens are now stored and
reused.

Unlike other reused LSP data, inlay hints data is the first one that's
possible to query by document ranges and previous version had issue with
caching and invalidating such ranges already queried for.
The new version re-approaches this by chunking the file into row ranges,
which are queried based on the editors' visible area.

Among the corresponding refactoring, one notable difference in inlays
display are multi buffers: buffers in them are not
[registered](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didOpen)
in the language server until a caret/selection is placed inside their
excerpts inside the multi buffer.

New inlays code does not query language servers for unregistered
buffers, as servers usually respond with empty responses or errors in
such cases.

Release Notes:

- Reworked inlay hints to be less error-prone

---------

Co-authored-by: Lukas Wirth <lukas@zed.dev>
Co-authored-by: dino <dinojoaocosta@gmail.com>
Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
2025-10-22 22:34:15 +03:00
Finn Evers
5738bde3ce gpui: Make Empty request layout with Display::None by default (#40900)
Release Notes:

- N/A
2025-10-22 20:54:29 +02:00
Bennet Fenner
6622902964 agent: Only show compatible tools in profile selector (#40917)
In practice this just hides the web search tool when not using the Zed
provider

Release Notes:

- Fixed an issue where the web search tool would show up in the profile
selector even when not using a model via Zed Pro
2025-10-22 18:17:33 +00:00
Ben Kunkle
cb7881ec0b Fix migration from #40409 for users who haven't been migrated yet (#40916)
Closes #40874

Release Notes:

- Fixed an issue where migrating settings after v0.208.5+ would
spuriously enable prettier. This fix only affects those who have not
updated or migrated yet. For those who have already updated to version
v0.208.5 or later, placing `"formatter": []` in your settings in the
affected languages will fix the issue.

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2025-10-22 18:17:01 +00:00
Bennet Fenner
c60343af71 eval: Port to agent2 (#40704)
Release Notes:

- N/A
2025-10-22 17:55:26 +00:00
Marshall Bowers
4a93719b6b Upgrade async-tar to v0.5.1 (#40911)
This PR switches us back to the upstream version of `async-tar` and
upgrades to v0.5.1.

This version has the patch we need:
0c18195639.

Release Notes:

- N/A
2025-10-22 17:13:28 +00:00
Remco Smits
d558005058 markdown: Add support for colspan and rowspan for HTML tables (#39898)
Closes https://github.com/zed-industries/zed/issues/39837

This PR adds support for `colspan` feature that is only supported for
HTML tables. I also fixed an edge case where the right side border was
not applied because it didn't match the total column count.

**Before**
<img width="725" height="179"
alt="499166907-385cc787-fc89-4e6d-bf06-c72c3c0bd775"
src="https://github.com/user-attachments/assets/69586053-9893-4c92-aa89-7830d2bc7a6d"
/>

**After**
<img width="1165" height="180" alt="Screenshot 2025-10-21 at 22 51 55"
src="https://github.com/user-attachments/assets/f40686e7-d95b-45a6-be42-e226e2f77483"
/>

```html
<table>
    <tr>
        <th rowspan="2">Region</th>
        <th colspan="2">Revenue</th>
        <th rowspan="2">Growth</th>
    </tr>
    <tr>
        <th>Q2 2024</th>
        <th>Q3 2024</th>
    </tr>
    <tr>
        <td>North America</td>
        <td>$2.8M</td>
        <td>$2.4B</td>
        <td>+85,614%</td>
    </tr>
    <tr>
        <td>Europe</td>
        <td>$1.2M</td>
        <td>$1.9B</td>
        <td>+158,233%</td>
    </tr>
    <tr>
        <td>Asia-Pacific</td>
        <td>$0.5M</td>
        <td>$1.4B</td>
        <td>+279,900%</td>
    </tr>
</table>
```

**TODO**:
- [x] Add tests for rending logic
- [x] Test all the tables again

cc @bennetbo

Release Notes:

- Markdown: Added support for `colspan` and `rowspan` for HTML tables

---------

Co-authored-by: Zed AI <ai@zed.nl>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-10-22 13:10:37 -04:00
Donnie Adams
96a0db24d9 Add comment injections for gowork and gomod (#40842)
Release Notes:

- Add comment injections for go.mod and go.work

Signed-off-by: Donnie Adams <donnie@thedadams.com>
2025-10-22 11:07:20 -06:00
Affonso, Guilherme
23e9e32d65 emacs: Improve default keymap to better match the emacs behavior (#40631)
Hello,
I am having a great time setting up the editor, but with a few problems
related to the Emacs keymap.

In this PR I have compiled changes in the default `emacs.json` that I
believe make the onboarding smoother for incoming emacs users.
This includes points that may need further discussion and some breaking
changes, although nothing that cannot be reverted with a quick
`keymap.json` overwrite.

(Please let me know if it is better to split up the PR)

### 1. Avoid fallbacks to the default keymap
all platforms:
- `ctrl-g` activating `go_to_line::Toggle` when there is nothing to
cancel

linux / windows:
- `ctrl-x` activating `editor::Cut` on the 1 second timeout
- `ctrl-p` activating `file_finder::Toggle` when the cursor is on the
first character of the buffer
- `ctrl-n` activating `workspace::NewFile` when the cursor is on the
last character of the buffer

### 2. Make all move commands operate on full words
In the current Zed implementation some commands run on full words and
others on subwords.
Although ultimately a matter of user preference, I think it is sensible
to use full words as the default, since that is what is shipped with
emacs.

### ~~3. Cancel selections after copy/cut commands~~ Moved to #40904
Canceling the selection is the default emacs behavior, but the way to
achieve it might need some brushing.
Currently I am using `workspace::SendKeystrokes` to copy ->
cancel(`ctrl-g`), but this has the following problems:
- can only be used in the main buffer (since `editor::Cancel` would
typically close secondary buffers)
- may cause problems downstream if the user overwrites the `ctrl-g`
binding

### ~~4. Replace killring with normal cut/paste commands~~ Moved to
#40905
Ideally Zed would support emacs-like killrings (#25270 and #22490).
However, I understand that making an emacs emulator is not a project
goal, and the Zed team should have a bunch of tasks with higher
priority.

By using a unified clipboard and standard cut/paste commands, we can
provide an experience that is closer to the out-of-the-box emacs
behavior (#33351) while also avoiding some pitfalls of the current
killring implementation (#28715).

### 5. Promote some bindings to workspace commands
- `alt-x` as `command_palette::Toggle`
- `ctrl-x b` and `ctrl-x ctrl-b` as `tab_switcher::Toggle`

---

Release Notes:

- emacs: Fixed a problem where keys would fallback to their default
keymap binding on certain conditions
- emacs: Changed `alt-f` and `alt-b` to operate on full words, as in the
emacs default
- emacs: `alt-x`, `ctrl-x b`, and `ctrl-x ctrl-b` are now Workspace
bindings
2025-10-22 10:37:00 -06:00
Finn Evers
b207da5a71 gpui: Re-land uniform list scroll fixes (#40899)
Re-lands https://github.com/zed-industries/zed/pull/40719, fixes the
bugs that were discovered with it and improves some more stuff in that
area

Release Notes:

- Fixed a rare issue where the extension page would stutter while
scrolling.

---------

Co-authored-by: Agus Zubiaga <agus@zed.dev>
2025-10-22 16:33:16 +00:00
Danilo Leal
a24601903a agent: Improve discoverability of the quote selection action (#40897)
This PR renames the `agent::QuoteSelection` to
`agent::AddSelectionToThread` _and_ adds it as a menu item in both the
right-click context menu within regular buffers as well as the
"Selection" app menu.

We've received feedback in the past about how hard to discover this
feature is, and after watching [the Syntax podcast
crew](https://www.youtube.com/watch?v=bRK3PeVFfVE) recently struggle
with doing so—and then naturally looking for it in the context menu and
not finding it—it felt like time to push a change. I think the rename +
the availability in these places could help bringing it to surface more.

The same action can be done in Cursor through the `cmd-l` keybinding,
but in Zed, that triggers `editor::SelectLine`, which I don't want to
override by default. However, if you're using Cursor's keymap, then
`cmd-l` does trigger this action, as expected.

<img width="500" height="1812" alt="Screenshot 2025-10-22 at 12  01@2x"
src="https://github.com/user-attachments/assets/dfc2c41c-8d0a-4a1a-8ea1-1bd5d1aa1171"
/>


Release Notes:

- agent: Improves discoverability of the previously called "quote
selection" action—which allows to add a text selection in a buffer as
context within the agent panel—by renaming it to "add selection to
thread" and making it available from the right-click editor context menu
as well as the "Selection" app menu.
2025-10-22 12:56:11 -03:00
David Kleingeld
3a12122d1b Revert "keymaps: Update defaults for inline assist and signature help" (#40903)
Reverts zed-industries/zed#39587
2025-10-22 11:27:37 -04:00
Lukas Wirth
d0398da099 editor: Fix singleton multibuffer titles not being replicated (#40896)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-22 14:59:30 +00:00
Joseph T. Lyons
69b2ee7bf0 Bump Zed to v0.211 (#40895)
Release Notes:

- N/A
2025-10-22 14:50:29 +00:00
Ajani Bilby
88bac4d5fc docs: Change tab character representation in docs (#40667)
The suggested `→` appears tiny, and almost looks like just a dot on my
monitor, and I got quite confused for a while thinking the
`whitespace_map.tab` setting wasn't working properly.
<img width="630" height="105" alt="Image"
src="https://github.com/user-attachments/assets/98feced2-39b3-4734-83e4-b4573b4e52c2"
/>

I think it would be really helpful if `⟶` was suggested instead since
that displays properly.
<img width="625" height="104" alt="Image"
src="https://github.com/user-attachments/assets/176886ab-cf88-4079-90a8-91a8e8182092"
/>

---

I am using `Fira Code` as my font on windows, however when I remove that
config to get the default font, it also still appears the same size. So
I don't believe this is just a font issue on my machine.
Thought I am using Windows, so I would be willing to believe this a
render issue specific to windows

Release Notes:

- N/A
2025-10-22 14:04:29 +00:00
Finn Evers
d53efe4e91 Revert "gpui: Fix uniform list scrolling with vertical padding present" (#40891)
Reverts zed-industries/zed#40719

This unveiled some bigger issues with the UniformList size computations,
which are more crucial than what was fixed here.

Release Notes:

- NOTE: BUGFIX "Fixed a rare issue where the extension page would
stutter while scrolling." was reverted due to some other issues
2025-10-22 13:35:25 +00:00
localcc
14b41b122f Fix JumpHost on Windows (#40713)
Closes #39382 

Release Notes:

- Fixed Windows specific ssh jumphost connection issues
2025-10-22 12:04:30 +00:00
Cameron Mcloughlin
98c7e018ae Add new action and handler for opening a specific setting (#40739)
Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-10-22 11:25:50 +00:00
Lukas Wirth
c81ffaffb6 editor: Use unbounded shifts for chunk bitmaps (#40879)
This simplifies some code and is also more correct in some others (I
believe some of these might've overflowed causing panics in sentry)

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-22 11:05:32 +00:00
Nia
bd69124f2b Add option to disable crash handler (#40799)
Extra info needed for #39289. To be tested out next nightly build...

Release Notes:

- N/A

Co-authored-by: Cole Miller <m@cole-miller.net>
2025-10-22 11:04:58 +00:00
Samuel Oldham
77dbe08d9d project_panel: Fix buffer focus when canceling filename edit (#40747)
Closes #37726

Release Notes:

- Fixed an issue where the buffer would not regain focus when clicked
while a filename edit was in progress in the project panel.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-10-22 15:50:09 +05:30
Owen Law
252b75e493 Disable slang-server for verilog extension (#40442)
Should be merged with
https://github.com/zed-industries/extensions/pull/3584, which adds
`slang-server` as a new language server, but should be disabled by
default due to an issue with it not initializing on Windows and being a
relatively new language server in general.

Release Notes:

- N/A
2025-10-22 11:54:54 +02:00
Kirill Bulatov
bc0bace81f Add basic ico support (#40822)
Closes https://github.com/zed-industries/zed/discussions/40763

<img width="867" height="1088" alt="Screenshot 2025-10-21 at 23 14 47"
src="https://github.com/user-attachments/assets/d691fb2a-afc6-4445-a335-054ef164e0d3"
/>

Also improves error handling on image open failure:

<img width="864" height="1083" alt="Screenshot 2025-10-21 at 23 14 30"
src="https://github.com/user-attachments/assets/d5388b61-995f-441b-b375-ad5136d1533b"
/>


Release Notes:

- Added basic ico support, improved unsupported image handling
2025-10-22 12:07:32 +03:00
Joseph T. Lyons
8ceb2f2c61 Add more tweaks to the troubleshooting docs (#40870)
Release Notes:

- N/A
2025-10-22 09:04:05 +00:00
Joseph T. Lyons
25de2acfdb Correct workspace db directory paths (#40868)
Release Notes:

- N/A
2025-10-22 08:27:59 +00:00
Joseph T. Lyons
8bad2cbd83 Add another informational blog post to docs (#40865)
Release Notes:

- N/A
2025-10-22 07:58:03 +00:00
Joseph T. Lyons
762af0982c Add more troubleshooting information (#40864)
Release Notes:

- N/A
2025-10-22 07:55:49 +00:00
Be
3d3e9130a8 collab: Pin sea-orm-macro crate version together with sea-orm (#40846)
Currently running `cargo update` on Zed will break the collab crate
because the versions of sea-orm and sea-orm-macros will not match. This
results in a bunch of noisy warnings from rust-analyzer.

Release Notes:

- N/A
2025-10-22 07:37:20 +00:00
Joseph T. Lyons
fd9c2e32f3 Organize release docs (#40860)1
Release Notes:

- N/A
2025-10-22 00:53:44 -04:00
Mikayla Maki
08d95ad9d3 chore: Bump gpui to 0.2.2 (#40856)
Release Notes:

- N/A
2025-10-22 03:43:32 +00:00
Alvaro Parker
d096132888 docs: Add git stash to git.md (#40834)
Closes #ISSUE

Release Notes:

- Added git stash documentation
2025-10-21 23:22:20 -04:00
Mikayla Maki
d7c855550c Update async-tar dependency for GPUI (#40850)
Release Notes:

- N/A
2025-10-22 03:07:02 +00:00
Mikayla Maki
9c71a7f43c Revert arm64 runners (#40852)
Release Notes:

- N/A
2025-10-21 20:03:02 -07:00
John Tur
221637ea82 Fix code signing for Windows installer (#40847)
Release Notes:

- N/A

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-10-21 20:21:43 -06:00
Max Brunsfeld
e468edd389 Fix extraction of font runs from text runs (#40840)
Fixes a bug in https://github.com/zed-industries/zed/pull/39928

The bug caused all completions to appear in bold-face

Release Notes:

- Fixed a bug where bold-face font was applied to the wrong characters
in items in the autocomplete menu

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-10-22 01:55:54 +00:00
John Tur
2903a06e5c Fix nightly upload on Windows (#40843)
Release Notes:

- N/A
2025-10-22 01:55:02 +00:00
Be
2338164c10 Revert "title_bar: Add configurable window controls position (#38834)" (#40839)
This reverts commit b479d1ef49.

This PR was accidentally merged prematurely.
https://github.com/zed-industries/zed/pull/38834#issuecomment-3424186051

cc @danilo-leal

Release Notes:

- N/A
2025-10-22 00:54:46 +00:00
Nia
2deafd8706 project_panel: Don't show trash dialog on remote connections (#40838)
In remote connections, we don't detect whether trashing is possible and
generally shouldn't assume it is. This also fixes up some incomplete
logic that was attempting to do that.

Closes #39212

Release Notes:

- No longer show trash option in remote projects
2025-10-22 00:24:38 +00:00
Ben Kunkle
0c13403fa5 settings_ui: Add broken file warning banner (#40823)
Closes #ISSUE

Release Notes:

- settings_ui: Added a warning banner when the settings file you are
actively editing is in a broken or invalid state.
2025-10-21 19:09:49 -04:00
Danilo Leal
8f3da5c5cd settings_ui: Add pickers for theme and icon themes (#40829)
In the process of adding pickers for the theme and icon themes fields in
the settings UI, I felt like there was an improvement opportunity in
regards to where some of these components are stored. The `ui_input`
crate originally was meant only for the text field-like component, which
couldn't be in the regular `ui` crate due to the dependency with
`editor`. Given we had also added the number field there—which is
similar in also having the same dependency—it made sense to think of
this crate more like a home for form-like components rather than for
only one component.

However, we were also storing some settings UI-specific stuff in that
crate, which didn't feel right. So I ended up creating a new directory
within the `settings_ui` for components and moved all the pickers and
the custom input field there. I think this makes it for a cleaner
structure.

Release Notes:

- settings_ui: Added the ability to search for theme and icon themes in
their respective fields.
2025-10-21 19:58:43 -03:00
Martin Pool
b519f53b3e Rope benchmarks: Generate random strings measured in bytes, not chars (#39951)
Follows on from https://github.com/zed-industries/zed/pull/39949.

Again I'm not 100% sure of the intent but I think this is a fix:

`generate_random_string(rng, 4096)` would previously give you a string
of 4096 *chars* which could be anywhere between 4kB and 16kB in bytes.
This seems probably not what was intended, because Ropes generally work
in bytes not chars, including for the offsets used to index into them.

This seems to possibly cause a _regression_ in benchmark performance,
which is surprising because it should generally cause smaller test data.
But, possibly it's doing better at exercising different paths?

cc @mrnugget 

Release Notes:

- N/A
2025-10-22 00:42:05 +02:00
Piotr Osiewicz
50d184b6a6 Revert "search: New old search implementation (#39956)" (#40831)
This reverts commit 7c4fb5a899.

Closes #40792

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-21 22:15:20 +00:00
John Tur
2bba3358b8 Add Windows Arm64 builds to CI (#40821)
Closes https://github.com/zed-industries/zed/issues/40378

Release Notes:

- N/A

---------

Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
2025-10-21 14:51:54 -07:00
Marshall Bowers
fcecf379dc Switch to fork of async-tar (#40828)
This PR switches to our own fork of `async-tar`.

Release Notes:

- N/A
2025-10-21 21:49:29 +00:00
Kirill Bulatov
256fe6e45c Fix task terminal split (#40824)
Before:


https://github.com/user-attachments/assets/efe2aeb6-94bb-46b6-944f-5a6345c072b4


After:


https://github.com/user-attachments/assets/61a7f699-6b4d-465f-add1-07774068420c


Release Notes:

- Fixed task terminal split not working correctly
2025-10-21 21:08:56 +00:00
Jakub Konka
e49edfac74 python: Init venv/virtualenv activation scripts during list/resolve (#40816)
This means that existence of activation scripts for venv/virtualenv will
be checked locally either on the host if editing locally, or the remote
by the remote proxy if editing a remote project.

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

Release Notes:

- N/A
2025-10-21 22:57:31 +02:00
Marshall Bowers
fd10017837 docs: Add model prices for Claude Haiku 4.5 (#40820)
This PR adds the model prices for Claude Haiku 4.5 to the docs.

Release Notes:

- N/A
2025-10-21 19:44:01 +00:00
Delvin
4b429033e7 settings_ui: Correct stepper increment and enforce max value for Centered Layout Padding (#40751)
Closes #40748

This PR improves the Centered Layout Padding in settings ui by limiting the numeric stepper to be within valid values and adding a custom schema generated to improve the JSON LSP completions and warnings when editing the setting field manually.

Release Notes:

- settings ui: limit stepper increment for centered padding between 0 and 0.4 and increment by 0.05 now

---------

Co-authored by: Anthony Eid <anthony@zed.dev>
2025-10-21 19:20:42 +00:00
Joseph T. Lyons
a398f80ba6 Add an action to reveal log file in system file manager (#40815)
We document the location of the log file in many places, we should just
make it easy to open directly within your file browser. The one thing
here is naming. We use dynamic naming for "reveal" actions in the
project panel, to reflect the right file manager name per OS, but for a
command palette action, I dont think we want to have dynamic code for
the action name, just going with finder at the moment.

Release Notes:

- Added a `zed: reveal log in file manager` action to the command
palette.
2025-10-21 14:38:11 -04:00
Danilo Leal
a71cc6a1e7 settings_ui: Add some design tweaks (#40818)
- Improves the UI for subfields of dynamic items
- Makes description writing more consistent (add period at the end of
every sentence, fix capitalization of proper names, ensure description
is always in sentence case)
- Other small details, mostly around spacing/padding

Release Notes:

- N/A
2025-10-21 15:27:44 -03:00
Danilo Leal
2764c51af1 extensions_ui: Increase affordance of download button in cards (#40795)
Hopefully, this will make the install/configure/uninstall buttons in the
right stand out a bit more and make their presence a bit more obvious
for newcomers.

| Before | After |
|--------|--------|
| <img width="1938" height="1276" alt="Screenshot 2025-10-21 at 10 
58@2x"
src="https://github.com/user-attachments/assets/b76115e1-0be2-4d5b-a677-525663d86c7c"
/> | <img width="1938" height="1276" alt="Screenshot 2025-10-21 at 10 
53@2x"
src="https://github.com/user-attachments/assets/9e563b71-b11a-4b69-b687-c0b469ca4eec"
/> |

Release Notes:

- Increased affordance of the download button in each extension card in
the extensions page.
2025-10-21 13:55:42 -03:00
Dong
641ae90cd6 settings_ui: Fix IEEE 754 floating point error when serializing value to JSON (#40677)
Closes #40556

Release Notes:

- settings-ui: Fixed an issue where modifying floating point number fields could result in the values written to the settings file containing IEEE 754 floating point error

> [!Note]
> Seems like there's another pull request fixing the centered layout in
#40661 by normalizing the whole `settings.json` file when updating it.
Not sure which is the better way to handle this issue.

## Description

This pull request solves the IEEE 754 floating point error when
serializing values from settings-ui into `settings.json` by creating a
two `serde_helper` to convert the problematic f32 into a formatted two
decimal placed f32.

Fields currently exists the IEEE 754 error:
- Appearance → Unnecessary Code Fade
- Editor → Drop Size Target
- Window & Layout → Centered Layout Left/Right Padding
- Window & Layout → Inactive Opacity

## How to verify

### Unnecessary Code Fade

As Is | To Be
--- | ---
<video
src="https://github.com/user-attachments/assets/1bf4bad2-63c5-4b03-ac29-8b6b59569e16"
/> | <video
src="https://github.com/user-attachments/assets/dadcd4a1-651b-43dd-913f-edae073ceb68"
/>

### Drop Size Target

As Is | To Be
--- | ---
<video
src="https://github.com/user-attachments/assets/9d5b4173-fcac-44d0-b7fc-772a2e426ef1"
/> | <video
src="https://github.com/user-attachments/assets/4b5adeaf-e678-494d-bd1b-6c1d55824c43"
/>

### Centered Layout Left/Right Padding

As Is | To Be
--- | ---
<video
src="https://github.com/user-attachments/assets/33b4e1ff-7ab2-44f7-9e9b-8abad1565d9a"
/> | <video
src="https://github.com/user-attachments/assets/63d8de9e-28d1-4bd7-a6c9-02452e105486"
/>

### Inactive Opacity

As Is | To Be
--- | ---
<video
src="https://github.com/user-attachments/assets/a7fe2e72-deb6-41dc-82f3-e2649503b8a4"
/> | <video
src="https://github.com/user-attachments/assets/993c314f-b6f6-4dcd-8f74-fa357ab063e9"
/>

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-10-21 16:45:40 +00:00
Ruangyot Nanchiang
fa550de922 Fix Git UI truncation for long branch names (#40598)
Closes #40524

Release Notes:
- Fixed branch names not truncating properly in git branch picker.

Please review if you have time.

PS. I’m not fully sure if this completely fixes the issue, but I’ve
tested it on my local build and it seems to work fine.

Before Fix:
<img width="773" height="799"
alt="502782621-91ac0578-9f55-4fb3-b0da-49a49e862a33"
src="https://github.com/user-attachments/assets/a9597949-c46a-47d0-a9ef-eddd637a9dc7"
/>

After Fix:
<img width="545" height="766" alt="FixedRound2"
src="https://github.com/user-attachments/assets/0d9770dc-a9da-46cd-a69a-4c8de2ca1abd"
/>
2025-10-21 16:42:43 +00:00
Joseph T. Lyons
ce5d597efa Centralize Zed.log documentation (#40808)
Just wanted a single location to point people to for telling them where
to find their log file. I left duplicate text in GitHub Issue templates,
as it seems annoying to have to follow a link when making an issue.

Release Notes:

- N/A
2025-10-21 16:39:10 +00:00
Ben Kunkle
cf8422f7fd settings_ui: Fix focus bugs (#40806)
Closes #40608


Release Notes:

- settings_ui: Fixed an issue where tabbing to the nav bar from the
search bar while the nav bar was scrolled would result in the first
_visible_ nav entry being selected, instead of the literal first nav
entry
- settings_ui: Fixed an issue where scrolling the selected nav entry off
screen would cause the keyboard shortcut hint for the focus nav / focus
content binding to dissapear
- settings_ui: Fixed an issue where text input controls could not be
focused via the keyboard
2025-10-21 12:25:07 -04:00
Jason Lee
d7ffc37b14 editor: Improve text color in document color highlight (#39372)
Release Notes:

- Improved text color in LSP document color highlight.

----

Because highlight ranges are implemented using a paint background,
there's no way to control the text color.

I've been thinking about this problem for a long time, want to solve it.

~~Today, I come up with a new idea. Re-rendering the document color text
at the top should solve this problem.~~

#### Update 10/6: 

> The previous version is not good, when we have soft wrap text, that
version will not work correct.

Now use exists `bg_segments_per_row` feature to fix text color.

## Before

<img width="563" height="540" alt="image"
src="https://github.com/user-attachments/assets/99722253-0cab-4d2a-a5d1-7f28393bcaed"
/>


## After

<img width="544" height="527" alt="image"
src="https://github.com/user-attachments/assets/a1bf6cdb-0e9c-435d-b14a-6ee9159a63d9"
/>
2025-10-21 21:35:40 +05:30
Lukas Wirth
b79837695e fs: Implement FileHandle::current_path for windows (#40804)
Release Notes:

- Fixed worktree renames not working on windows
2025-10-21 15:48:40 +00:00
Lukas Wirth
69025f3bf4 Add linux_repo_snapshot got .gitignore (#40802)
It keeps popping up in my project searches ... This prevents that. Not
like we are planning on editing that file again.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-21 15:31:40 +00:00
Tim Vermeulen
981fa288eb editor: Hide the git blame popover on escape (#40549)
Release Notes:

- Added way to hide git blame popover by pressing the escape key.
2025-10-21 20:34:13 +05:30
Lukas Wirth
854d1ec4dc acp_thread: Fix panic when following acp agents across buffers (#40798)
Fixes ZED-2D7

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-21 14:37:16 +00:00
Smit Barmase
10b9ae5e44 multi_buffer: Assert char boundary for panic due to point_to_buffer_offset (#40777)
In an attempt to figure out what's wrong with `point_to_buffer_offset`
for crash https://github.com/zed-industries/zed/issues/40453. We want to
know which branch among these two is the bad one.

Release Notes:

- N/A

Co-authored-by: Lukas Wirth <lukas@zed.dev>
2025-10-21 19:01:35 +05:30
Smit Barmase
cad06011c5 language: Fix hang when editing certain tailwind class names (#40791)
Closes #36223

Upsteam issue to track:
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1479

Release Notes:

- Fixed an issue where Zed hanged when editing certain Tailwind class
names.
2025-10-21 19:00:02 +05:30
Lukas Wirth
0eccdfe61f project: Spawn terminal process on background executor (#40774)
We were spawning the process on the foreground thread before which can
block an arbitrary amount of time. Likewise we no longer block
deserialization on the terminal loading.

Release Notes:

- Improved startup time on systems with slow process spawning
capabilities
2025-10-21 13:10:21 +00:00
Lukas Wirth
0be70e24d6 persistence: More error contexts (#40787)
Release Notes:

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

Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-10-21 11:29:43 +00:00
Dino
3d4abde55a vim: Fix hang in visual block motion (#40723)
The `vim::visual::Vim.visual_block_motion` method was recently updated
(https://github.com/zed-industries/zed/pull/39355) in order to jump
between buffer rows instead of display rows. However, with this now
being the case, the `break` condition was never met when the motion was
horizontal rather than vertical and soft wrapped lines were used. As
such, this commit udpates the condition to ensure it's always reached,
preventing the hanging from happening.

Release Notes:

- Fixed hang in Vim's visual block motions when updating selections

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-10-21 12:23:00 +01:00
Piotr Osiewicz
2bfbe031c6 python: Bump version & get rid of explicit deps specifications for PET (#40785)
Release Notes:

- N/A
2025-10-21 12:50:56 +02:00
Agus Zubiaga
12d912114f ci: Update typos versions and fix new occurrences (#40784)
I noticed we had some typos that were getting through CI, but it looks
like the new version of `typos` catches them. So I updated it and fixed
them.

Release Notes:

- N/A
2025-10-21 10:43:22 +00:00
Agus Zubiaga
b487d2cfe0 zeta2 inspector: Feedback box (#40732)
Adds a way to submit feedback about a zeta2 prediction from the
inspector. The telemetry event includes:
- project snapshot (git + unsaved buffer state)
- the full request and response
- user feedback kind and text 

Release Notes:

- N/A
2025-10-21 10:30:21 +00:00
Piotr Osiewicz
977887b65f ci: Bump max target directory size on Mac to 300GB (#40778)
I did not bump it for Linux as some machines have smaller disks (~300GB
or so); with Mac, we have at least 1TB on all of our boxes

Release Notes:

- N/A
2025-10-21 11:24:22 +02:00
Finn Evers
0721c7873a Make kotlin-lsp the default language server (#40776)
Following a conversation with the maintainer/owner of
kotlin-language-server, he recommended switching to the official
language server, which is better in many aspects and also more actively
maintained.

Release Notes:

- Made the official Kotlin Language Server the default language server
for Kotlin.
2025-10-21 09:03:12 +00:00
Finn Evers
0aa7b7c773 editor: Toggle diff hunk based on current mouse position (#40773)
This fixes an issue where we would search for the hovered diff hunk
based on the mouse hit test computed during (or prior) editor paint
instead of the mouse hit test computed prior to the mouse event
invocation.

That in turn could lead to cases where moving the mouse from the editor
to the project panel and then clicking a file shortly after would expand
a diff hunk when actually nothing should happen in that case.

Release Notes:

- Fixed an issue where diff hunks would sometimes erroneously toggle
upon mouse clicks.
2025-10-21 08:38:40 +00:00
Piotr Osiewicz
1b544b9e19 ci: Run slow tests first (#40769)
Tests are hand-picked based on yours truly's preference

Release Notes:

- N/A
2025-10-21 09:58:26 +02:00
Julia Ryan
ea6e6dbda1 Add log message on first render (#40749)
Having this in our logs with a timestamp should help when users submit
issues with logs about slow startup time.

Release Notes:

- N/A
2025-10-21 00:17:26 -07:00
Piotr Osiewicz
a56122e144 ci: Do not use full debug info in CI builds (#40764)
For good backtraces in tests 'limited' is all we need.

Closes #ISSUE

Release Notes:

- N/A
2025-10-21 06:53:45 +00:00
Dario Griffo
04a45e3501 Add debian community repository (#40698)
I maintain this repository that contains several developer tools like
- ghostty
- zig
- yazi

all of them are updated usually the same day as upstream.

Release Notes: 

- N/A
2025-10-21 09:50:57 +03:00
Mateo Noel Rabines
4b489f4ce9 cli: Add --reuse flag for replacing workspace in existing window (#38131)
Closes #ISSUE 

it is was still in
[discussion](https://github.com/zed-industries/zed/discussions/37983)

Release Notes:

- Added: `--reuse` (`-r`) CLI flag to replace the workspace in an
existing window instead of opening a new one

This PR adds a new `--reuse` (`-r`) CLI flag that allows users to
replace the workspace in an existing Zed window instead of opening a new
one or adding files to the current workspace.

### What it does

The `--reuse` flag finds an available local workspace window and
replaces its workspace with the newly specified paths. This provides a
third workspace opening mode alongside the existing `--add` and `--new`
flags.

### Implementation Details

- **CLI Flag**: Added `--reuse` (`-r`) flag with proper mutual exclusion
with `--add` and `--new`
- **Window Replacement**: Uses the existing `replace_window` option in
`workspace::OpenOptions`
- **Window Selection**: Reuses the first available local workspace
window
- **Fallback Behavior**: When no existing windows are found, creates a
new window
- **Test Coverage**: Added comprehensive test for the reuse
functionality

### Behavior

- `zed -r file.txt` - Replaces the workspace in an available window with
`file.txt`
- If no windows are open, creates a new window (same as default
behavior)
- Mutually exclusive with `-a/--add` and `-n/--new` flags
- Works with multiple files and directories

### Files Changed

- `crates/cli/src/cli.rs` - Added `reuse` field to `CliRequest::Open`
- `crates/cli/src/main.rs` - Added CLI argument definition and parsing
- `crates/zed/src/zed/open_listener.rs` - Implemented reuse logic and
added tests
- `crates/zed/src/zed/windows_only_instance.rs` - Updated for Windows
compatibility

### Testing

-  Unit tests pass
-  Manual testing confirms expected behavior:
  - Works when no windows are open
  - Replaces workspace in existing window
  - Maintains compatibility with existing `-a` and `-n` flags
  - Proper help text display


## Manual testing

#### In this first video we do a couple of tests: 

* **1**: What happens if we use the -r flag when there are no windows
open?
        - works as expected. It opens the files in a new window.
        
* **2**: Does it work as expected if there is already a window open.
Does it overrides the workspace?
- yes it does. When opening a different file it overrides the current
window instead of creating a new one.
        
* **3**: Does the -n flag still works as expected?
        - yes, it creates the project in a new window

* **4**: What about the -a flag?
       - yes, on the last accessed page 
       
* **5**: we do the replace command. It overrides the first opened
window, do we want this behavior?
- It is good enough that it overrides one of the opened windows with the
new project. It still makes the user automatically go to the window with
the specified files

* **6**: we use the -r command again replacing the workspace with a new
one.
       - this indeed worked as expected


https://github.com/user-attachments/assets/f1cd7f4b-f4af-4da2-a755-c0be7ce96c0d


#### In here the we check how the --help flag now displays the new
command. (Description was later updated)


https://github.com/user-attachments/assets/a8a7a288-d926-431b-a9f9-a8c3d909a2ec
2025-10-20 22:41:13 -06:00
Willy Hetland
71ea133d72 Theme-able Vim Mode wrapper (#39813)
Closes [#14093](https://github.com/zed-industries/zed/issues/14093)
Builds on [#32279](https://github.com/zed-industries/zed/pull/32279) by
making it theme dependent.
Discussion
[#37816](https://github.com/zed-industries/zed/discussions/37816)

Wraps the mode label indicator in a div and makes the wrapper and label
theme-able. Label weight to medium
Mode indicator will render like previously if not theme colors have been
set. (i.e., they match zed default- and fallbacks)
Really helps with visual confirmation of current mode.

_Did not investigate further if there is a way to keep the leading and
trailing -- if no theme var given._

Can be applied either by a theme itself or using `theme_overrides` in
settings.json

Theme colors applied via `theme_overrides`
<img width="233" height="34" alt="Screenshot 2025-10-08 at 23 01 08"
src="https://github.com/user-attachments/assets/a00d9ae4-b6db-46a0-84e2-98d2691a11ad"
/>
<img width="233" height="34" alt="Screenshot 2025-10-08 at 23 01 16"
src="https://github.com/user-attachments/assets/f27fddab-524d-43c4-9307-46b6a656cd35"
/>
<img width="233" height="34" alt="Screenshot 2025-10-08 at 23 01 23"
src="https://github.com/user-attachments/assets/7e477fff-7a40-4c01-95a7-fbd40fff6caa"
/>

No theme applied
<img width="233" height="34" alt="Screenshot 2025-10-08 at 23 01 31"
src="https://github.com/user-attachments/assets/8b7b2c75-007b-4074-a552-181c53f31213"
/>
<img width="233" height="34" alt="Screenshot 2025-10-08 at 23 01 36"
src="https://github.com/user-attachments/assets/7a708d81-2033-4d72-a844-57607a0434ea"
/>
<img width="233" height="34" alt="Screenshot 2025-10-08 at 23 01 40"
src="https://github.com/user-attachments/assets/526f9d10-4d0f-4bc5-af89-31fcca538ce4"
/>



https://github.com/user-attachments/assets/d0d71d4d-504f-4d18-bbd9-83d3a4b2adb7


Release Notes:

- Vim make mode indicator themeable

---------

Co-authored-by: willyHetland <willy.hetland@zeekit.no>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-10-21 04:30:30 +00:00
Cole Miller
917f22f884 Don't auto-release preview (#40728)
This feels a bit dangerous as long as we have the split releases problem

Release Notes:

- N/A
2025-10-20 21:29:19 -04:00
vipex
36c006828e pane: Ignore max tabs on terminal pane (#40740)
Closes #39901

I'm unsure as to which direction the team wants to go with this, but
this is the behavior of VSCode which is what this feature is based off
so i'm going with this.

Changes: 

1. Introduced a new argument to the `new` method on the Pane called
`ignore_max_tabs` that forces the `max_tabs` to None if it's true.
2. Added a new test `test_bypass_max_tabs_limit`.

Release Notes:

- Fixed: `max_tabs` Setting affecting the terminal pane.

---------

Co-authored-by: Joseph T. Lyons <JosephTLyons@gmail.com>
2025-10-20 21:19:40 -04:00
Bartosz Kaszubowski
a2c42813c4 markdown_preview: Apply few appearance tweaks for tables (#39190)
# Why

Refs:
*
https://github.com/zed-industries/zed/pull/39101#issuecomment-3350557981

# How

Apply suggested appearance changes in the comment mentioned above. I
have also retained the different background for header rows, since it
feels to me that it is something that GitHub styling lacks.

I have also attempted to shrink the table table element, to fit the
content width (so it does not span for the full width of preview), but I
have failed on those attempts. Tried to use many various GPUI
attributes, but only thing that worked was setting the exact width on
table container, also tried to reuse `max_lengths` values, but those are
counting characters, not the rendered width. I would like to explore
this a bit more, and try to follow up on those changes in a separate PR.

Release Notes:

- Improved table elements styling in Markdown Preview

# Preview

<img width="1616" height="582" alt="Screenshot 2025-09-30 at 12 04 30"
src="https://github.com/user-attachments/assets/4f1517cb-9046-4e09-a1e1-5223421efb71"
/>
<img width="1616" height="582" alt="Screenshot 2025-09-30 at 12 04 23"
src="https://github.com/user-attachments/assets/61303160-2b62-4213-80fc-ee8432cdf1fa"
/>
<img width="1616" height="582" alt="Screenshot 2025-09-30 at 12 04 15"
src="https://github.com/user-attachments/assets/059a447e-574d-4545-870a-93f1c00b3bb8"
/>
<img width="1616" height="582" alt="Screenshot 2025-09-30 at 12 04 42"
src="https://github.com/user-attachments/assets/8e7c6f9b-672f-4943-aded-1b644d2ff750"
/>
<img width="1616" height="582" alt="Screenshot 2025-09-30 at 12 04 34"
src="https://github.com/user-attachments/assets/6d31f7f3-d0ea-4987-bf8c-78f6b307a2b3"
/>

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-10-21 00:38:47 +00:00
Julia Ryan
267052f891 Editor end of input context (#40735)
This is needed for #38914 and seems generally useful to have for
contextual keybindings.

Release Notes:

- N/A

---------

Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-10-20 17:08:51 -07:00
Dmitry Nefedov
62516e8f1f themes: Improve Gruvbox scrollbar colors (#38145)
Changes that I made:
- add "scrollbar.thumb.active_background" to all themes
- for dark themes: scrollbar.thumb.background is darker than hover (fg4
from palette for background and fg0 for hover)
- for light themes: scrollbar.thumb.background is lighter than hover
(fg4 for background and fg0 for hover like in dark theme case)

Those changes is consistent with VSCode gruvbox theme and other
applications.
For active_background I chose orange color, but we can use cyan color to
match vscode theme.

UPDATE: decided to use blue for active scrollbar as this color is used
as accent in other parts of gruvbox themes

Release Notes:

- Improved scrollbar colors for Gruvbox theme

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-10-20 21:05:54 -03:00
Akira Sousa
b479d1ef49 title_bar: Add configurable window controls position (#38834)
## 🎯 Description
Adds configurable window control buttons (minimize, maximize, close)
positioning for Linux, allowing users to choose between macOS-style
(left side) or Windows-style (right side) placement.

##  Features
- New `title_bar.window_controls_position` setting with `"left"` and
`"right"` options
- Left positioning: macOS style (Close → Minimize → Maximize)
- Right positioning: Windows style (Minimize → Maximize → Close)
- Fixed transparent background issues for window controls
- Maintains consistent styling with Zed's theme

## 🔧 Technical Changes

### Settings System
- Added `WindowControlsPosition` enum in `settings_content.rs`
- Extended `TitleBarSettingsContent` with `window_controls_position`
field
- Updated `TitleBarSettings` to include the new configuration

### Title Bar Layout
- Modified `platform_title_bar.rs` to use setting for layout positioning
- Added conditional logic for `justify_start()` vs `justify_between()`
based on position
- Fixed transparent container background by adding `bg(titlebar_color)`

### Window Controls
- Updated `platform_linux.rs` to reorder buttons based on position
setting
- Changed button background from `ghost_element_background` to
`title_bar_background`
- Implemented proper button sequencing for both positions

## 🧪 How to Test
1. Add to your Zed settings:
   ```json
   {
     "title_bar": {
       "window_controls_position": "left"
     }
   }
   ```
   or
   ```json
   {
     "title_bar": {
       "window_controls_position": "right"
     }
   }
   ```
2. Restart Zed
3. Verify buttons are positioned correctly
4. Check that background is not transparent
5. Test button functionality (minimize, maximize, close)

## �� Expected Behavior
- **Left position**: Buttons appear on the left side of the title bar in
Close → Minimize → Maximize order
- **Right position**: Buttons appear on the right side of the title bar
in Minimize → Maximize → Close order
- **Background**: Solid background matching Zed's theme (no
transparency)

## 🔍 Files Changed
- `crates/settings/src/settings_content.rs` - Added enum and setting
- `crates/title_bar/src/title_bar_settings.rs` - Updated settings struct
- `crates/title_bar/src/platform_title_bar.rs` - Modified layout logic
- `crates/title_bar/src/platforms/platform_linux.rs` - Updated button
ordering and styling

## 🎨 Design Rationale
This feature provides Linux users with the flexibility to choose their
preferred window control button layout, improving the user experience by
allowing them to match their desktop environment's conventions or
personal preferences.

##  Checklist
- [x] Code compiles without errors
- [x] Settings are properly serialized/deserialized
- [x] Background transparency issues resolved
- [x] Button ordering works correctly for both positions
- [x] Layout adapts properly based on configuration
- [x] No breaking changes to existing functionality

## 🔗 Related
This addresses the need for customizable window control positioning on
Linux, providing consistency with user expectations from different
desktop environments.


![demo2](https://github.com/user-attachments/assets/7333db34-d54e-427c-ac52-140925363f91)
2025-10-21 00:00:56 +00:00
ofetch
684f4dced9 settings_ui: Fix typo (#40743)
Fixes #40742

Release Notes:

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

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-10-20 23:04:00 +00:00
Jose Garcia
de6750d3f4 Add line_endings_button in VSCode settings to fix semantic merge conflict (#40745)
I have partially solved a problem caused by a structure in commit:
f7a0971d2b

Release Notes:

- N/A

Before:
<img width="723" height="480" alt="image"
src="https://github.com/user-attachments/assets/6600e0af-36cf-4aec-ace1-7ee4921e001e"
/>

After: 
<img width="764" height="217" alt="image"
src="https://github.com/user-attachments/assets/52111c27-c67a-4224-82bd-782cc6e07f97"
/>
2025-10-20 22:31:04 +00:00
Anthony Eid
4f5f299265 settings ui: Autoscroll content during keyboard navigation (#40734)
Closes #40608

This fixes tabbing in both the settings ui nav bar and page content
going off screen instead of scrolling the focused element into a visible
view.

The bug occurred because `gpui::list` and `gpui::uniform_list` only
render visible elements, preventing non visible elements in a view from
having their focus handle added to the element tree. Thus making the tab
stop map skip over those elements because they weren't present.

The fix for this is scrolling to reveal non visible elements and then
focus the selected element on the next frame.

Release Notes:

- settings ui: Auto scroll to reveal items in navigation bar and window
when tabbing

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-10-20 17:53:47 -04:00
Kirill Bulatov
32a442d522 Fix inlay hint cleanup on excerpts removal (#40738)
A cherry-pick of
f5188d55fb

This fixes a hard-to-reproduce crash caused excerpts removal not
updating previous snapshot data after corresponding inlay data was
removed.
Same branch has a test:
8783a9eb4f
that does not fail on `main` due to different way inlays are queried, it
will be merged later.

Release Notes:

- N/A
2025-10-20 21:43:33 +00:00
kitt
f7a0971d2b Add line endings indicator in status bar (#39609)
Closes #5294

This PR adds a line ending indicator to the status bar, hidden by
default as discussed in
https://github.com/zed-industries/zed/issues/5294.

### Changes

- 8b063a22d8700bed9c93989b9e0f6a064b2e86cf add the indicator and
`status_bar.line_endings_button` setting.

- ~~9926237b709dd4e25ce58d558fd385d63b405f3b changes
`status_bar.line_endings_button` from a boolean to an enum:~~
  <details> <summary> show details </summary>

   - `always`     Always show line endings indicator.
- `non_native` Indicate when line endings do not match the current
platform.
   - `lf_only`    Indicate when using unix-style (LF) line endings only.
- `crlf_only` Indicate when using windows-style (CRLF) line endings
only.
   - `never`      Do not show line endings indicator.
   
I know this many options might be overdoing it, but I was torn between
the pleasant default of `non_native` and the simplicity of `lf_only` /
`crlf_only`.

My thinking was if one is developing on a project which exclusively uses
one line-ending style or the other, it would be nice to be able to
configure no-indicator-in-the-happy-case behavior regardless of the
platform zed is running on. But I'm not really familiar with any
projects that use exclusively CRLF line endings in practice. Is this a
scenario worth supporting or just something I dreamed up?

   </details>

- 01174191e4cf337069e7a31b0f0432ae94c52515 rename the action context for
`line ending: Toggle` -> `line ending selector: Toggle`.
When running the action in the command palette with the old name I felt
surprised to be greeted with an additional menu, with the new name it
feels more predictable (plus now it matches
`language_selector::Toggle`!)

### Future work

Hidden status bar items still get padding, creating inconsistent spacing
(and it kind of stands out where I placed the line-endings button):

<img alt="the gap after the indicator is larger than for other buttons"
src="https://github.com/user-attachments/assets/24a346d4-3ff6-4f7f-bd87-64d453c2441a"
/>

I started a new follow-up PR to address that:
https://github.com/zed-industries/zed/pull/39992

Release Notes:

- Added line ending indicator to the status bar (disabled by default;
enabled by setting `status_bar.line_endings_button` to `true`)
2025-10-20 15:24:41 -06:00
Lukas Wirth
ed82233030 gpui: Box Window instances (#40733)
We very frequently move this in and out of the windows slot map on
`update_window_id` calls (and we call this a lot!). This alone showed up
as `memmove`s at roughly 1% perf in Instruments when scrolling a buffer
which makes sense, `Window` itself is 4kb in size. The fix is simple,
just box the `Window` instances, moving a pointer is cheap in
comparison.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-20 21:03:28 +00:00
Alex Miller
ec0efc9360 git_ui: Close branch selector as soon as branch is selected (#39725)
I noticed that branch picker doesn't close until the checkout operation
is completed.
While normally it's not an issue, it becomes obvious if there are longer
running post checkout hooks. In that case selecting a branch makes it
feel like nothing has happened (there's a small indicator in the footer)
so it's possible to click it multiple times. Closing the modal before
the operation completes leads to the error modal saying `Failed to
change branch. entity released. Please try again.` even though the
checkout was successful.

The new behavior is to close the branch picker as soon as the branch is
selected. This also aligns with the existing behavior in `create_branch`
where `cx.emit(DismissEvent);` is called without waiting for
`repo.update`.
And as I mentioned before there an indicator in the footer saying `git
switch <branch_name>` with a spinner thingy.

I also added a check in the picker's `open` function where it first
checks if there's currently an active job and does not show the picker
in that case.

If this generally makes sense I can add the tests as well if needed.

P.S I checked how it works in VSCode and yes it also closes the branch
picker as soon as the branch is selected. The only difference is that
they show the loading indicator right next to the branch name (with a
new branch) but in our case the current branch and activity indicator
are located in different places.

<details><summary>Before</summary>


https://github.com/user-attachments/assets/adf08967-d908-45fa-b3f6-96f73d321262

</details>

<details><summary>After</summary>


https://github.com/user-attachments/assets/88c7ca41-7b39-42d6-a98b-3ad19da9317c

</details>

Release Notes:

- The branch picker now closes immediately after a branch is selected,
instead of waiting for the branch switch to complete.
2025-10-20 16:55:44 -04:00
Coenen Benjamin
1c639da8a8 file_finder: Include worktree root name in multi-worktrees workspace (#40415)
Closes #39865

Release Notes:

- Fixed file finder display when searching for files in history if you
had several worktrees opened in a workspace. It now displays the
worktree root name to avoid confusion if you have several files with
same name in different worktrees.

---------

Signed-off-by: Benjamin <5719034+bnjjj@users.noreply.github.com>
2025-10-20 23:54:56 +03:00
Ben Kunkle
ebaefa8cbc settings_ui: Add maybe settings (#40724)
Closes #ISSUE

Adds a `Maybe<T>` type to `settings_content`, that makes the distinction
between `null` and omitted settings values explicit. This unlocks a few
more settings in the settings UI

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-20 16:25:20 -04:00
Jose Garcia
33bc586ed1 theme: Change the icon used for JSONC files (#40726)
Closes #40683 

Release Notes:

- Changed jsonc files' icon
2025-10-20 23:23:24 +03:00
Finn Evers
853d7c3f92 gpui: Fix uniform list scrolling with vertical padding present (#40719)
Closes #40267

Release Notes:

- Fixed a rare issue where the extension page would stutter while
scrolling.
2025-10-20 18:57:00 +00:00
Ben Brandt
79ada634ac acp: Fix following for agents that only provide locations (#40710)
We were dropping the entities once we created the buffers, so the weak
entities could never be upgraded. This treats new locations we see the
same as we would for a read/write call and stores the entity so that we
can follow like we normally would.

Release Notes:

- acp: Fix following not working with certain tool calls.
2025-10-20 18:49:05 +00:00
Josh Piasecki
057c3c1206 Add dock state to workspace context (#40454)
I love keybindings.

I spend way to much time thinking about them.

I also REALLY like working in Zed. 

so far, however, I have found the key context system in Zed to be less
flexible than in VSCode.
the HUGE context that is available in VSCode helps you create
keybindings for very specific targeted scenarios.

the tree like structure of the Zed key context means you loose some
information as focus moves throughout the application.
For example, it is not currently possible to create a keybinding in the
editor that will only work when one of the Docks is open, or if a
specific dock is open.

this would be useful in implementing solutions to ideas like #24222 
we already have an action for moving focus to the dock, and we have an
action for opening/closing the dock, but to my knowledge (very limited
lol) we cannot determine if that dock *is open* unless we are focused on
it.

I think it is possible to create a more flexible key binding system by
adding more context information to the higher up context ancestors.
while:
``` 
Workspace right_dock=GitPanel
    Dock
        GitPanel
            Editor
```
may seem redundant, it actually communicates fundamentally different
information than:
```
Workspace right_dock=GitPanel
    Pane
        Editor
```  

the first says "the GitPanel is in the right hand dock AND IT IS
FOCUSED",
while the second means "Focus is on the Editor, and the GitPanel just
happens to be open in the right hand dock"

This change adds a new set of identifiers to the `Workspace` key_context
that will indicate which docks are open and what is the specific panel
that is currently visible in that dock.

examples:
- `left_dock=ProjectPanel`
- `bottom_dock=TerminalPanel`
- `right_dock=GitPanel`

in my testing the following types of keybindings seem to be supported
with this change:
```jsonc
// match for any value of the identifier
"context": "Workspace && bottom_dock"
"context": "Workspace && !bottom_dock"
// match only a specific value to an identifier
"context": "Workspace && bottom_dock=TerminalPanel"
// match only in a child context if the ancestor workspace has the correct identifier
"context": "Workspace && !bottom_dock=DebugPanel > Editor"
```

some screen shots of the context matching in different circumstances:
<img width="2032" height="1167" alt="Screenshot 2025-10-16 at 23 20 34"
src="https://github.com/user-attachments/assets/116d0575-a1ae-4577-95b9-8415cda57e52"
/>
<img width="2032" height="1167" alt="Screenshot 2025-10-16 at 23 20 57"
src="https://github.com/user-attachments/assets/000fdbb6-80bd-46e9-b668-f4b54ab708d2"
/>
<img width="2032" height="1167" alt="Screenshot 2025-10-16 at 23 21 37"
src="https://github.com/user-attachments/assets/7b1c82da-b82f-4e14-a97c-3cd0e71bbca0"
/>
<img width="2032" height="1167" alt="Screenshot 2025-10-16 at 23 21 52"
src="https://github.com/user-attachments/assets/1fd4b65a-09f7-47a9-a9b7-fdce4252aec3"
/>
<img width="2032" height="1167" alt="Screenshot 2025-10-16 at 23 22 38"
src="https://github.com/user-attachments/assets/f4c2ac5c-e6f9-4e0e-b683-522b237e3328"
/>


the persistent_name values for `ProjectPanel` and `OutlinePanel` needed
to be updated to not have a space in them in order to pass the
`Identifier` check. all the other Panels already had names that did not
include spaces, so it just makes these conform with the other ones.

I think this is a great place to start with adding more context
identifiers and i think this type of additional information will make it
possible to create really dynamic keybindings!

Release Notes:

- Workspace key context now includes the state of the 3 docks
2025-10-20 18:23:46 +00:00
Josh Piasecki
13fe9938c2 CollapseAllDiffHunks action for editor (#40668)
This PR adds a new action `editor::CollapseAllDiffHunks`
which will allow the user to choose any keybinding for hiding the
Expanded Diff Hunks.
2025-10-20 18:18:01 +00:00
Lukas Wirth
78bfda5045 gpui: Improve some log_err calls in windows backend (#40717)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-20 18:01:46 +00:00
Lukas Wirth
d8f4293ac3 sum_tree: Implement recursive Sumtree::find, use it over Cursor::seek if possible (#40700)
Reduces peak stack usage in these functions and should generally be a
bit performant.

Display map benchmark results
```
To tab point/to_tab_point/1024
                        time:   [531.40 ns 532.10 ns 532.97 ns]
                        change: [-2.1824% -2.0054% -1.8125%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high severe

To fold point/to_fold_point/1024
                        time:   [530.81 ns 531.30 ns 531.80 ns]
                        change: [-2.0295% -1.9054% -1.7716%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 3 outliers among 100 measurements (3.00%)
  2 (2.00%) high mild
  1 (1.00%) high severe
```

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-20 17:20:09 +00:00
Ben Kunkle
b6fb1d0a19 settings_ui: Add more settings (#40708)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-20 12:37:55 -04:00
Mikayla Maki
0a17f91923 chore: Fix displayed git command (#40548)
Too small for release notes IMO

Release Notes:

- N/A
2025-10-20 09:25:34 -07:00
Lukas Wirth
7aa0626098 editor: Refresh document highlights when expanding excerpts (#40715)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-20 16:19:57 +00:00
ming_jiang
ea5f3e6086 Fix PATH lookup to handle Windows case sensitivity (#40711)
Closes #40448

On Windows, PATH might be "Path" instead of "PATH"

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-20 16:12:05 +00:00
Alvaro Parker
db404fc2e3 git: Add diff view for stash entries (#38280)
Continues the work from #35927 to add a git diff view for stash entries.

[Screencast From 2025-09-17
19-46-01.webm](https://github.com/user-attachments/assets/ded33782-adef-4696-8e34-3665911c09c7)

Stash entries are [represented as
commits](https://git-scm.com/docs/git-stash#_discussion) except they
have up to 3 parents:

```
       .----W (this is the stash entry)
      /    /|
-----H----I |
           \|
            U
```

Where `H` is the `HEAD` commit, `I` is a commit that records the state
of the index, and `U` is another commit that records untracked files
(when using `git stash -u`).

Given this, I modified the existing commit view struct to allow loading
stash and commits entries with git sha identifier so that we can get a
similar git diff view for both of them.

The stash diff is generated by comparing the stash commit with its
parent (`<commit>^` or `H` in the diagram) which generates the same diff
as doing `git stash show -p <stash entry>`. This *can* be
counter-intuitive since a user may expect the comparison to be made
between the stash commit and the current commit (`HEAD`), but given that
the default behavior in git cli is to compare with the stash parent, I
went for that approach.

Hoping to get some feedback from a Zed team member to see if they agree
with this approach.

Release Notes:

- Add git diff view for stash entries
- Add toolbar on git diff view for stash entries
- Prompt before executing a destructive stash action on diff view
- Fix commit view for merge commits  (see #38289)
2025-10-20 11:47:15 -04:00
Agus Zubiaga
eda7a49f01 zeta2: Max retrieved definitions option (#40515)
Release Notes:

- N/A
2025-10-20 15:26:41 +00:00
Rian Drake
8bef4800f0 workspace: Add NewFileSplit action with direction (#39726)
Add new `workspace::NewFileSplit` action which expects a
`SplitDirection` argument, allowing users to programmatically control
the direction of the split in keymaps, for example:

```json
{
    "context": "Editor",
    "bindings": {
        "ctrl-s ctrl-h": ["workspace::NewFileSplit", "left"],
        "ctrl-s ctrl-j": ["workspace::NewFileSplit", "down"],
        "ctrl-s ctrl-k": ["workspace::NewFileSplit", "up"],
        "ctrl-s ctrl-l": ["workspace::NewFileSplit", "right"]
    }
}
```

Release Notes:

- Added `workspace::NewFileSplit` action, which can be used to
programmatically split the editor in the provided direction.

Co-authored-by: Rian Drake <rian.drake@rocketwerkz.com>
Co-authored-by: dino <dinojoaocosta@gmail.com>
2025-10-20 16:01:26 +01:00
Lukas Wirth
67b9d480b4 project: Make textDocument/signatureHelp implementation lsp compliant (#40707)
The parameter label offsets are utf16 offsets, not utf8. Additionally we
now validate the language server output.

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

Companion bug on rust-analyzer side
https://github.com/rust-lang/rust-analyzer/pull/20876

Release Notes:

- Fixed `textDocument/signatureHelp` implementation not being LSP
compliant
2025-10-20 14:57:23 +00:00
Smit Barmase
30f3152e65 settings_ui: Use window controls on Linux (#40706)
Closes #40657

Release Notes:

- Added window controls to the settings window on Linux.
2025-10-20 20:23:49 +05:30
Piotr Osiewicz
7c4fb5a899 search: New old search implementation (#39956)
This is an in-progress work on changing how task scheduler affects
performance of project search. Instead of relying on tasks being
executed at a discretion of the task scheduler, we want to experiment
with having a set of "agents" that prioritize driving in-progress
project search matches to completion over pushing the whole thing to
completion. This should hopefully significantly improve throughput &
latency of project search.

Release Notes:

- Improved project search performance

---------

Co-authored-by: Smit Barmase <smit@zed.dev>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-10-20 16:40:02 +02:00
Ben Brandt
85c2aa7325 Update to acp 0.5 (#40701)
Release Notes:

- N/A
2025-10-20 13:52:05 +00:00
Donnie Adams
08ecaa3931 Add comment language injection for supported languages (#39884)
Release Notes:

- Added comment language injections for builtin languages. This enables
highlighting of `TODO`s and similar notes with the comment extension
installed.

Signed-off-by: Donnie Adams <donnie@thedadams.com>
2025-10-20 14:45:51 +02:00
Alex Zeier
96415e2d19 languages: Separate control flow keywords for JS/TS/TSX (#39801)
Related to #9461, inspired by #39683

`await` and `yield` both seem somewhat debatable on whether they should
be considered the be control flow keywords.

For now I went with:
- `await`: no – The control flow effect of `await` is at a level does
not seem relevant for syntax highlighting.
- `yield`: yes – `yield` directly affects the output of a generator, and
is also included for consistency with Rust (#39683).
 
 Happy to change these either direction.

<img width="1151" height="730" alt="SCR-20251008-izus"
src="https://github.com/user-attachments/assets/533ea670-863a-4c5c-aaa5-4a9bfa0bf0dd"
/>

--- 

Release Notes:

- Improved granularity of keyword highlighting for JS/TS/TSX: Themes can
now specify `keyword.control` for control flow keywords like `if`,
`else`, `return`, etc.
2025-10-20 14:40:05 +02:00
Danilo Leal
9a3c7945a9 docs: Add section about MCP servers with external agents (#40658)
Adding this content after seeing people ask about how to make MCP
servers installed from Zed be picked up by external agents. At the
moment, this varies depending on the agent, and felt relevant to be
documented.

Release Notes:

- N/A
2025-10-20 09:11:11 -03:00
localcc
cc21089736 Fix default window size on small displays (#40398)
Fixed: #40039
Fixed: #40404
Fixed: #40272
Fixed: #40666

Release Notes:

- N/A
2025-10-20 14:09:36 +02:00
Lukas Wirth
bdb7c642a1 clock: Bump the min collaborator ID (#40694)
This allows us to play with IDs < 8 without having to do another
redeploy

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-20 11:59:35 +00:00
Danilo Leal
b827d8cfc0 ai onboarding: Add dismiss button to the sign in banner (#40660)
Release Notes:

- N/A
2025-10-20 08:35:28 -03:00
Hayashi Mikihiro
9a72453a2b Highlight control flow in Rust/C/C++ (#39683)
part of https://github.com/zed-industries/zed/issues/9461


Release Notes:
- Added the ability to seperately highlight control flow keywords for
Rust, C and C++ for users and theme authors via the `keyword.control`
syntax property
<img width="805" height="475" alt="スクリーンショット 2025-10-07 22 21 59"
src="https://github.com/user-attachments/assets/40ed03ea-a129-44ce-b6d8-284656b9f3ba"
/>
2025-10-20 13:28:54 +02:00
Lukas Wirth
43a9368dff clock: Cleanup ReplicaId, Lamport and Global (#40600)
- Notable change is the use of a newtype for `ReplicaId`
- Fixes `WorktreeStore::create_remote_worktree` creating a remote
worktree with the local replica id, though this is not currently used
- Fixes observing the `Agent` (that is following the agent) causing
global clocks to allocate 65535 elements
- Shrinks the size of `Global` a bit. In a local or non-collab remote
session it won't ever allocate still.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-20 13:26:20 +02:00
Piotr Osiewicz
37e264ab99 fs: Reintroduce benchmarks crate (#40689)
It was erroenously removed in #40216

Release Notes:

- N/A
2025-10-20 10:30:06 +00:00
Daniel Wargh
94c28ba14a Improve TS and JS symbol outline (#39797)
Added more granular symbols for ts and js in outline panel. This is a
bit closer to what vscode offers.

<details><summary>Screenshots of current vs new</summary>
<p>
New:
<img width="1723" height="1221" alt="image"
src="https://github.com/user-attachments/assets/796d3b59-fffa-4a66-9986-f7c75e618103"
/>

Current:
<img width="1714" height="1347" alt="image"
src="https://github.com/user-attachments/assets/f7cff463-de2a-4d86-b1a6-e19f4fd8dc6e"
/>

Current vscode (cursor):
<img width="1710" height="1177" alt="image"
src="https://github.com/user-attachments/assets/31902d52-becf-4d3f-960d-7e054e00e32d"
/>

</p>
</details> 

I have never touched scheme before, and pair-programmed this with ai, so
please let me know if there's any glaring issues with the
implementation. I just miss the outline panel in vscode very much, and
would love to see this land.
Happy to help with tsx/jsx as well if this is the direction you guys
were thinking of taking the outline impl.

Doesn't fully close https://github.com/zed-industries/zed/issues/20964
as there is no support for chained class method callbacks or
`Class.prototype.method = ...` as mentioned in
https://github.com/zed-industries/zed/issues/21243, but this is a step
forward.

Release Notes:

- Improved typescript and javascript symbol outline panel
2025-10-20 12:22:07 +02:00
Maël Nison
36210e72af Make the Yarn SDK path relative to the worktree (#40062)
Let's say you run this:

```
cd ~/proj-a
zed ~/proj-b
```

The `zed` process will execute with `current_dir() = ~/proj-a`, but a
`worktree_root_path() = ~/proj-b`. The old detection was then checking
if the Yarn SDK was installed in `proj-a` to decide whether to set the
tsdk value or not. This was incorrect, as we should instead check for
the SDK presence inside `proj-b`.

Release Notes:

- Fixed the Yarn SDK detection when the Zed pwd is different from the
opened folder.
2025-10-20 11:46:34 +02:00
Sylvain Brunerie
e3297cdcae Add "Setting up Xdebug" section in PHP docs (#40470)
The page about PHP in the docs doesn’t explain how to use Xdebug. I had
a lof of trouble setting it up the first time, then recently had another
headache trying to get it to work again, because the value for `adapter`
had changed from `PHP` to `Xdebug`.

It’s likely that my example config isn’t perfect or has redundant stuff
or whatever, feel free to amend it.

I also took the liberty to set the Phpactor and Intelephense headings to
level 3 because I felt like they were part of "Choosing a language
server."

Release Notes:

- N/A
2025-10-20 11:44:58 +02:00
Smit Barmase
92ff29fa7d Add Vue language server v3 support (#40651)
Closes https://github.com/zed-extensions/vue/issues/48

Migration guide:
https://github.com/vuejs/language-tools/discussions/5456

PR to remove tdsk: https://github.com/zed-extensions/vue/pull/61

Release Notes:

- Added support for Vue language server version 3. Know more
[here](https://github.com/vuejs/language-tools/releases/tag/v3.0.0).

---------

Co-authored-by: MrSubidubi <dev@bahn.sh>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-10-20 13:32:31 +05:30
Julia Ryan
1d3bf9789e Add DelayMs type for settings (#40659)
Closes #40610

Release Notes:

- N/A
2025-10-20 04:34:55 +00:00
Bartosz Kaszubowski
59b87d5c71 git_ui: When no changes, disable stage/unstage toolbar buttons (#39909)
# Why

While working on recent PR I have spotted that "Stage" and "Unstage"
buttons in "Uncommited Changes" toolbar are always active, even when
there is no changes made locally.

<img width="1628" height="656" alt="Screenshot 2025-10-10 at 00 49 06"
src="https://github.com/user-attachments/assets/6bdb9ded-17c8-4f84-8649-b297162c1992"
/>

# How

Re-use already existing button states for managing the disabled state of
"Uncommited Changes" toolbar buttons when changeset is empty.

Release Notes:

- Added disabled state for "Uncommited Changes" toolbar buttons when
there are no changes present

# Preview

<img width="1728" height="772" alt="Screenshot 2025-10-10 at 08 40 14"
src="https://github.com/user-attachments/assets/ff41d852-974e-4ce1-9163-ecd30e17d5d8"
/>
2025-10-19 22:21:21 -04:00
Joseph T. Lyons
fd4682c8d1 Remove Windows beta issue template (#40650)
Release Notes:

- N/A
2025-10-19 19:29:09 +00:00
Smit Barmase
f2b966b139 remote: Use SFTP over SCP for uploading files and directories (#40510)
Closes #37322

Uses SFTP if available, otherwise falls back to SCP for uploading files
and directories to remote. This fixes an issue on older macOS versions
where outdated SCP can throw an ambiguous target error.

Release Notes:

- Fixed an issue where extensions wouldn’t work when SSHing into a
remote from older macOS versions.
2025-10-20 00:01:43 +05:30
Bennet Fenner
4507110981 settings: Remove unused stream_edits setting in agent (#40640)
This setting is unused (we always stream edits)

Release Notes:

- N/A
2025-10-19 15:52:28 +00:00
Lukas Wirth
1b43a632dc fs: Fix RealFs::open_handle implementation for directories on windows (#40639)
Release Notes:

- Fixed worktree names not updating when renaming the root folder on
windows
2025-10-19 15:25:13 +00:00
Xiaobo Liu
8f3f7232fb gpui: Add exit in tab title update loop (#40628)
Release Notes:

- N/A

---------

Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>
2025-10-19 16:52:16 +02:00
Finn Evers
5f13ce6d7d docs: Update section about installing extensions locally (#40636)
Release Notes:

- N/A
2025-10-19 14:30:01 +00:00
Cameron Mcloughlin
57387812dc terminal_ui: Terminal failed to spawn UI (#40246)
Co-authored-by: Piotr piotr@zed.dev
Co-authored-by: Lukas lukas@zed.dev
Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
Co-authored-by: Gaauwe Rombouts <mail@grombouts.nl>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-10-19 15:26:41 +01:00
Bartosz Kaszubowski
197d244378 image_viewer: Use buffer font in breadcrumbs (#40601)
# Why

Spotted that image path in editor breadcrumb uses regular (UI) font in
comparison to paths of any other code-related files.

<img width="842" height="214" alt="Screenshot 2025-10-18 at 19 32 55"
src="https://github.com/user-attachments/assets/07823fd2-778a-4341-a647-3ab50192c8fa"
/>

# How

Use buffer font for image path in Image Viewer breadcrumbs.

Release Notes:

- Aligned appearance of path displayed by Image Viewer breadcrumbs with
other panes.

# Preview

### Before

<img width="842" height="214" alt="Screenshot 2025-10-18 at 19 26 17"
src="https://github.com/user-attachments/assets/921df27f-c104-457e-908c-e4beaea3a27e"
/>

### After

<img width="842" height="214" alt="Screenshot 2025-10-18 at 19 24 17"
src="https://github.com/user-attachments/assets/112ce5f3-1a2b-40e4-bf4f-e258f3518812"
/>
2025-10-18 22:00:28 -07:00
Joseph T. Lyons
6d975984ee Register rules files as Markdown (#40614)
Release Notes:

- `.rules`, `.cursorrules`, `.windsurfrules`, and `.clinerules` are now
syntax highlighted as Markdown files.
2025-10-19 01:47:17 +00:00
Joseph T. Lyons
3aee14378d python: Only enable basedpyright and ruff by default (#40604)
Though we ship with `basedpyright`, `ruff` and a few other laps for
python, we run them all at once.

Release Notes:

- Only enable `basedpyright` and `ruff` by default when opening Python
files. If you prefer one of the other.
2025-10-18 22:52:11 +00:00
Kirill Bulatov
02b15f0062 Add Windows path into custom theme docs (#40599)
Closes https://github.com/zed-industries/zed/issues/40584
Closes https://github.com/zed-industries/zed/issues/40057

Release Notes:

- N/A
2025-10-18 16:53:19 +00:00
Xiaobo Liu
8d48f9cdae gpui: Simplify tab group lookup logic in SystemWindowTabController (#40466)
Refactor the find_tab_group method to use the question mark operator for
cleaner error handling, replacing the explicit if-else pattern with a
more concise chained approach.

Release Notes:

- N/A

Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>
2025-10-18 15:04:40 +00:00
Andrew Farkas
41994452f2 Fix extension keymap context for single file worktree (#40425)
Closes #40353

Release Notes:

- Fixed `extension` in keymap context being empty for single file
worktree

Co-authored-by: Cole Miller <cole@zed.dev>
2025-10-18 14:57:59 +00:00
Smit Barmase
89be2635d4 project_panel: Fix double-click on blank area to create a new file (#40503)
Regressed in https://github.com/zed-industries/zed/pull/38008

Release Notes:

- Fixed an issue where double-clicking empty space in the project panel
wouldn’t create a new file.
2025-10-18 18:14:41 +05:30
Bedis Nbiba
35664461c6 docs: Add deno.jsonc to JSON LSP settings (#40563)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-18 12:08:00 +02:00
Joseph T. Lyons
219ae05d1f Add a doc on crafting release notes (#40557)
Release Notes:

- N/A
2025-10-18 05:09:27 +00:00
Julia Ryan
e702df21a4 Fix Rust macro_invocation injections (#40534)
Closes #40317

Release Notes:

- N/A
2025-10-18 01:33:00 +00:00
joel
3d6722be9a Fix Right Alt key not working in keybindings on Windows (#40536)
### Problem
On Windows, the right Alt key was not working in keybindings (e.g.,
`Ctrl+Right Alt+B`), while the left Alt key worked correctly. This was
due to overly aggressive AltGr detection that treated any `right Alt +
left Ctrl` combination as AltGr, even on US keyboards where AltGr
doesn't exist.

### Root Cause
Windows internally represents AltGr (Alt Graph) as `right Alt + left
Ctrl` pressed simultaneously. The previous implementation always
excluded this combination from being treated as regular modifier keys to
support international keyboards. However, this broke keybindings using
right Alt on US/UK keyboards where users expect right Alt to behave
identically to left Alt.

### Solution
Implemented keyboard layout-aware AltGr detection:

1. Added `uses_altgr()` method to `WindowsKeyboardLayout` that checks if
the current keyboard layout is known to use AltGr (German, French,
Spanish, Polish, etc.)
2. Modified `current_modifiers()` to only apply AltGr special handling
when the keyboard layout actually uses it
3. Added explicit checking for both `VK_LMENU` and `VK_RMENU` instead of
relying solely on the generic `VK_MENU`

### Behavior
- **US/UK keyboards**: Right Alt now works identically to left Alt in
keybindings. `Ctrl+Right Alt+B` triggers the same action as `Ctrl+Left
Alt+B`
- **International keyboards** (German, French, Spanish, etc.): AltGr
continues to work correctly for typing special characters and doesn't
trigger keybindings
- **All keyboards**: Both Alt keys are detected symmetrically, matching
the behavior of left/right Windows keys

### Testing
Manually tested on Windows with US keyboard layout:
-  `Ctrl+Left Alt+B` triggers keybinding
-  `Ctrl+Right Alt+B` triggers keybinding  
-  Both Alt keys work independently in keybindings


Release Notes:
- Fixed Right Alt key not working in keybindings on Windows
2025-10-18 02:27:45 +02:00
Delvin
47c6ae7b1f settings_ui: Fix stepper buttons to Inactive Opacity to 0.1 increment adjustments (#40477)
Closes #40279

Release Notes:

- Fix stepper buttons (+/-) to the Inactive Opacity setting for 0.1
increment adjustments on settings UI

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-10-17 23:12:05 +00:00
David
9984614f3d settings_ui: Fix misplaced comma in autoclose setting description (#40519)
Release Notes:

- Fixed misplaced comma in the autoclose description from:
"when you type (, Zed will ...)"
to 
"when you type, (Zed will ...)"

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-10-17 19:45:44 -03:00
Bartosz Kaszubowski
287314f415 markdown_preview: Improve the link decoration logic (#39905)
Closes #39838

Refs:
*
https://github.com/zed-industries/zed/pull/39149#issuecomment-3383015060

# How

After digging a bit more to find out why raw links are not colored in
Markdown renderer I have found a simpler approach to applying color
decoration, which also fixed the lack of colors on raw links mentioned
in issue and comment above.

Release Notes:

- Improved decoration logic for links in Markdown

# Preview

<img width="1712" height="820" alt="Screenshot 2025-10-09 at 23 39 09"
src="https://github.com/user-attachments/assets/3864cb6c-3fc6-4110-8067-6158cd4b58f5"
/>
2025-10-17 19:43:44 -03:00
Conrad Irwin
63e719fadf Disallow rename/copy/delete on unshared files (#40540)
Release Notes:

- Disallow rename/delete/copy on unshared files

Co-Authored-By: Cole <cole@zed.dev>
2025-10-17 22:33:08 +00:00
Cole Miller
1e69e5d844 Set the minimum log level to info for the remote server (#40543)
`env_logger` defaults to only showing error-level logs, but we show
info-level logs and above for the main Zed process, so I think it makes
sense for the remote server to behave the same way.

Release Notes:

- N/A
2025-10-17 18:32:52 -04:00
Max Brunsfeld
30c4434b70 Ignore flaky ignored_dirs_events test (#40546)
Release Notes:

- N/A
2025-10-17 22:28:43 +00:00
Mikayla Maki
d7e193caf3 Show telemetry for adding all items (#40541)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-17 22:11:36 +00:00
Nia
438c890816 perf: Add on search + fixups (#40537)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-17 22:11:09 +00:00
Max Brunsfeld
4dd463f8ac Fix repo path to project path conversion in git panel (#40535)
Closes https://github.com/zed-industries/zed/issues/40422
Closes https://github.com/zed-industries/zed/issues/40379
Closes https://github.com/zed-industries/zed/issues/40307

Release Notes:

- Fixed an issue where the project diff view did not work for multi-repo
projects on Windows when using WSL or SSH remoting
2025-10-17 14:57:03 -07:00
Kirill Bulatov
22fd91d490 Re-register buffers on server stop (#40504)
Follow-up of https://github.com/zed-industries/zed/pull/40388

Release Notes:

- N/A
2025-10-17 21:04:08 +00:00
Seeni
a660a39ae8 docs: Update cpp.md to indicate GDB version requirements (#40027)
Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-10-17 21:02:53 +00:00
620 changed files with 36242 additions and 16209 deletions

View File

@@ -5,12 +5,16 @@
# Arrays are merged together though. See: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
# The intent for this file is to configure CI build process with a divergance from Zed developers experience; for example, in this config file
# we use `-D warnings` for rustflags (which makes compilation fail in presence of warnings during build process). Placing that in developers `config.toml`
# would be incovenient.
# would be inconvenient.
# The reason for not using the RUSTFLAGS environment variable is that doing so would override all the settings in the config.toml file, even if the contents of the latter are completely nonsensical. See: https://github.com/rust-lang/cargo/issues/5376
# Here, we opted to use `[target.'cfg(all())']` instead of `[build]` because `[target.'**']` is guaranteed to be cumulative.
[target.'cfg(all())']
rustflags = ["-D", "warnings"]
# We don't need fullest debug information for dev stuff (tests etc.) in CI.
[profile.dev]
debug = "limited"
# Use Mold on Linux, because it's faster than GNU ld and LLD.
#
# We no longer set this in the default `config.toml` so that developers can opt in to Wild, which

View File

@@ -4,3 +4,17 @@ sequential-db-tests = { max-threads = 1 }
[[profile.default.overrides]]
filter = 'package(db)'
test-group = 'sequential-db-tests'
# Run slowest tests first.
#
[[profile.default.overrides]]
filter = 'package(worktree) and test(test_random_worktree_changes)'
priority = 100
[[profile.default.overrides]]
filter = 'package(collab) and (test(random_project_collaboration_tests) or test(random_channel_buffer_tests) or test(test_contact_requests) or test(test_basic_following))'
priority = 99
[[profile.default.overrides]]
filter = 'package(extension_host) and test(test_extension_store_with_test_extension)'
priority = 99

View File

@@ -1,8 +1,8 @@
name: Bug Report (Windows Beta)
description: Zed Windows Beta Related Bugs
name: Bug Report (Git)
description: Zed Git Related Bugs
type: "Bug"
labels: ["windows"]
title: "Windows Beta: <a short description of the Windows bug>"
labels: ["git"]
title: "Git: <a short description of the Git bug>"
body:
- type: textarea
attributes:

View File

@@ -33,9 +33,10 @@ body:
required: true
- type: textarea
attributes:
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
label: If applicable, attach your `Zed.log` file to this issue.
description: |
macOS: `~/Library/Logs/Zed/Zed.log`
Windows: `C:\Users\YOU\AppData\Local\Zed\logs\Zed.log`
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
value: |

View File

@@ -15,8 +15,11 @@ runs:
node-version: "18"
- name: Limit target directory size
env:
MAX_SIZE: ${{ runner.os == 'macOS' && 300 || 100 }}
shell: bash -euxo pipefail {0}
run: script/clear-target-dir-if-larger-than 100
# Use the variable in the run command
run: script/clear-target-dir-if-larger-than ${{ env.MAX_SIZE }}
- name: Run tests
shell: bash -euxo pipefail {0}

69
.github/workflows/after_release.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
# Generated from xtask::workflows::after_release
# Rebuild with `cargo xtask workflows`.
name: after_release
on:
release:
types:
- published
jobs:
rebuild_releases_page:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: after_release::rebuild_releases_page
run: 'curl https://zed.dev/api/revalidate-releases -H "Authorization: Bearer ${RELEASE_NOTES_API_TOKEN}"'
shell: bash -euxo pipefail {0}
env:
RELEASE_NOTES_API_TOKEN: ${{ secrets.RELEASE_NOTES_API_TOKEN }}
post_to_discord:
needs:
- rebuild_releases_page
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- id: get-release-url
name: after_release::post_to_discord::get_release_url
run: |
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
URL="https://zed.dev/releases/preview"
else
URL="https://zed.dev/releases/stable"
fi
echo "URL=$URL" >> "$GITHUB_OUTPUT"
shell: bash -euxo pipefail {0}
- id: get-content
name: after_release::post_to_discord::get_content
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757
with:
stringToTruncate: |
📣 Zed [${{ github.event.release.tag_name }}](<${{ steps.get-release-url.outputs.URL }}>) was just released!
${{ github.event.release.body }}
maxLength: 2000
truncationSymbol: '...'
- name: after_release::post_to_discord::discord_webhook_action
uses: tsickert/discord-webhook@c840d45a03a323fbc3f7507ac7769dbd91bfb164
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_RELEASE_NOTES }}
content: ${{ steps.get-content.outputs.string }}
publish_winget:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- id: set-package-name
name: after_release::publish_winget::set_package_name
run: |
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
PACKAGE_NAME=ZedIndustries.Zed.Preview
else
PACKAGE_NAME=ZedIndustries.Zed
fi
echo "PACKAGE_NAME=$PACKAGE_NAME" >> "$GITHUB_OUTPUT"
shell: bash -euxo pipefail {0}
- name: after_release::publish_winget::winget_releaser
uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f
with:
identifier: ${{ steps.set-package-name.outputs.PACKAGE_NAME }}
max-versions-to-keep: 5
token: ${{ secrets.WINGET_TOKEN }}

39
.github/workflows/cherry_pick.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
# Generated from xtask::workflows::cherry_pick
# Rebuild with `cargo xtask workflows`.
name: cherry_pick
on:
workflow_dispatch:
inputs:
commit:
description: commit
required: true
type: string
branch:
description: branch
required: true
type: string
channel:
description: channel
required: true
type: string
jobs:
run_cherry_pick:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- id: get-app-token
name: cherry_pick::run_cherry_pick::authenticate_as_zippy
uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1
with:
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
- name: cherry_pick::run_cherry_pick::cherry_pick
run: ./script/cherry-pick ${{ inputs.branch }} ${{ inputs.commit }} ${{ inputs.channel }}
shell: bash -euxo pipefail {0}
env:
GIT_COMMITTER_NAME: Zed Zippy
GIT_COMMITTER_EMAIL: hi@zed.dev
GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}

View File

@@ -1,868 +0,0 @@
name: CI
on:
push:
branches:
- main
- "v[0-9]+.[0-9]+.x"
tags:
- "v*"
pull_request:
branches:
- "**"
concurrency:
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
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_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
jobs:
job_spec:
name: Decide which jobs to run
if: github.repository_owner == 'zed-industries'
outputs:
run_tests: ${{ steps.filter.outputs.run_tests }}
run_license: ${{ steps.filter.outputs.run_license }}
run_docs: ${{ steps.filter.outputs.run_docs }}
run_nix: ${{ steps.filter.outputs.run_nix }}
run_actionlint: ${{ steps.filter.outputs.run_actionlint }}
runs-on:
- namespace-profile-2x4-ubuntu-2404
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
# 350 is arbitrary; ~10days of history on main (5secs); full history is ~25secs
fetch-depth: ${{ github.ref == 'refs/heads/main' && 2 || 350 }}
- name: Fetch git history and generate output filters
id: filter
run: |
if [ -z "$GITHUB_BASE_REF" ]; then
echo "Not in a PR context (i.e., push to main/stable/preview)"
COMPARE_REV="$(git rev-parse HEAD~1)"
else
echo "In a PR context comparing to pull_request.base.ref"
git fetch origin "$GITHUB_BASE_REF" --depth=350
COMPARE_REV="$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)"
fi
CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" ${{ github.sha }})"
# Specify anything which should potentially skip full test suite in this regex:
# - docs/
# - script/update_top_ranking_issues/
# - .github/ISSUE_TEMPLATE/
# - .github/workflows/ (except .github/workflows/ci.yml)
SKIP_REGEX='^(docs/|script/update_top_ranking_issues/|\.github/(ISSUE_TEMPLATE|workflows/(?!ci)))'
echo "$CHANGED_FILES" | grep -qvP "$SKIP_REGEX" && \
echo "run_tests=true" >> "$GITHUB_OUTPUT" || \
echo "run_tests=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^docs/' && \
echo "run_docs=true" >> "$GITHUB_OUTPUT" || \
echo "run_docs=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^\.github/(workflows/|actions/|actionlint.yml)' && \
echo "run_actionlint=true" >> "$GITHUB_OUTPUT" || \
echo "run_actionlint=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^(Cargo.lock|script/.*licenses)' && \
echo "run_license=true" >> "$GITHUB_OUTPUT" || \
echo "run_license=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^(nix/|flake\.|Cargo\.|rust-toolchain.toml|\.cargo/config.toml)' && \
echo "$GITHUB_REF_NAME" | grep -qvP '^v[0-9]+\.[0-9]+\.[0-9x](-pre)?$' && \
echo "run_nix=true" >> "$GITHUB_OUTPUT" || \
echo "run_nix=false" >> "$GITHUB_OUTPUT"
migration_checks:
name: Check Postgres and Protobuf migrations, mergability
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
timeout-minutes: 60
runs-on:
- self-mini-macos
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
fetch-depth: 0 # fetch full history
- name: Remove untracked files
run: git clean -df
- name: Find modified migrations
shell: bash -euxo pipefail {0}
run: |
export SQUAWK_GITHUB_TOKEN=${{ github.token }}
. ./script/squawk
- name: Ensure fresh merge
shell: bash -euxo pipefail {0}
run: |
if [ -z "$GITHUB_BASE_REF" ];
then
echo "BUF_BASE_BRANCH=$(git merge-base origin/main HEAD)" >> "$GITHUB_ENV"
else
git checkout -B temp
git merge -q "origin/$GITHUB_BASE_REF" -m "merge main into temp"
echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> "$GITHUB_ENV"
fi
- uses: bufbuild/buf-setup-action@v1
with:
version: v1.29.0
- uses: bufbuild/buf-breaking-action@v1
with:
input: "crates/proto/proto/"
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/proto/proto/"
style:
timeout-minutes: 60
name: Check formatting and spelling
needs: [job_spec]
if: github.repository_owner == 'zed-industries'
runs-on:
- namespace-profile-4x8-ubuntu-2204
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
with:
version: 9
- name: Prettier Check on /docs
working-directory: ./docs
run: |
pnpm dlx "prettier@${PRETTIER_VERSION}" . --check || {
echo "To fix, run from the root of the Zed repo:"
echo " cd docs && pnpm dlx prettier@${PRETTIER_VERSION} . --write && cd .."
false
}
env:
PRETTIER_VERSION: 3.5.0
- name: Prettier Check on default.json
run: |
pnpm dlx "prettier@${PRETTIER_VERSION}" assets/settings/default.json --check || {
echo "To fix, run from the root of the Zed repo:"
echo " pnpm dlx prettier@${PRETTIER_VERSION} assets/settings/default.json --write"
false
}
env:
PRETTIER_VERSION: 3.5.0
# To support writing comments that they will certainly be revisited.
- name: Check for todo! and FIXME comments
run: script/check-todos
- name: Check modifier use in keymaps
run: script/check-keymaps
- name: Run style checks
uses: ./.github/actions/check_style
- name: Check for typos
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6
with:
config: ./typos.toml
check_docs:
timeout-minutes: 60
name: Check docs
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
(needs.job_spec.outputs.run_tests == 'true' || needs.job_spec.outputs.run_docs == 'true')
runs-on:
- namespace-profile-8x16-ubuntu-2204
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Build docs
uses: ./.github/actions/build_docs
actionlint:
runs-on: namespace-profile-2x4-ubuntu-2404
if: github.repository_owner == 'zed-industries' && needs.job_spec.outputs.run_actionlint == 'true'
needs: [job_spec]
steps:
- uses: actions/checkout@v4
- name: Download actionlint
id: get_actionlint
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
shell: bash
- name: Check workflow files
run: ${{ steps.get_actionlint.outputs.executable }} -color
shell: bash
macos_tests:
timeout-minutes: 60
name: (macOS) Run Clippy and tests
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- self-mini-macos
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Check that Cargo.lock is up to date
run: |
cargo update --locked --workspace
- name: cargo clippy
run: ./script/clippy
- name: Install cargo-machete
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2
with:
command: install
args: cargo-machete@0.7.0
- name: Check unused dependencies
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2
with:
command: machete
- name: Check licenses
run: |
script/check-licenses
if [[ "${{ needs.job_spec.outputs.run_license }}" == "true" ]]; then
script/generate-licenses /tmp/zed_licenses_output
fi
- name: Check for new vulnerable dependencies
if: github.event_name == 'pull_request'
uses: actions/dependency-review-action@67d4f4bd7a9b17a0db54d2a7519187c65e339de8 # v4
with:
license-check: false
- name: Run tests
uses: ./.github/actions/run_tests
- name: Build collab
run: cargo build -p collab
- name: Build other binaries and features
run: |
cargo build --workspace --bins --all-features
cargo check -p gpui --features "macos-blade"
cargo check -p workspace
cargo build -p remote_server
cargo check -p gpui --examples
# Since the macOS runners are stateful, so we need to remove the config file to prevent potential bug.
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo
linux_tests:
timeout-minutes: 60
name: (Linux) Run Clippy and tests
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
# cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: cargo clippy
run: ./script/clippy
- name: Run tests
uses: ./.github/actions/run_tests
- name: Build other binaries and features
run: |
cargo build -p zed
cargo check -p workspace
cargo check -p gpui --examples
# Even the Linux runner is not stateful, in theory there is no need to do this cleanup.
# But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code
# to clean up the config file, Ive included the cleanup code here as a precaution.
# While its not strictly necessary at this moment, I believe its better to err on the side of caution.
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo
doctests:
# Nextest currently doesn't support doctests, so run them separately and in parallel.
timeout-minutes: 60
name: (Linux) Run doctests
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
# cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Run doctests
run: cargo test --workspace --doc --no-fail-fast
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo
build_remote_server:
timeout-minutes: 60
name: (Linux) Build Remote Server
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
# cache-provider: "buildjet"
- name: Install Clang & Mold
run: ./script/remote-server && ./script/install-mold 2.34.0
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Build Remote Server
run: cargo build -p remote_server
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo
windows_tests:
timeout-minutes: 60
name: (Windows) Run Clippy and tests
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on: [self-32vcpu-windows-2022]
steps:
- name: Environment Setup
run: |
$RunnerDir = Split-Path -Parent $env:RUNNER_WORKSPACE
Write-Output `
"RUSTUP_HOME=$RunnerDir\.rustup" `
"CARGO_HOME=$RunnerDir\.cargo" `
"PATH=$RunnerDir\.cargo\bin;$env:PATH" `
>> $env:GITHUB_ENV
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Configure CI
run: |
New-Item -ItemType Directory -Path "./../.cargo" -Force
Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
- name: cargo clippy
run: |
.\script\clippy.ps1
- name: Run tests
uses: ./.github/actions/run_tests_windows
- name: Build Zed
run: cargo build
- name: Limit target directory size
run: ./script/clear-target-dir-if-larger-than.ps1 250
- name: Clean CI config file
if: always()
run: Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
tests_pass:
name: Tests Pass
runs-on: namespace-profile-2x4-ubuntu-2404
needs:
- job_spec
- style
- check_docs
- actionlint
- migration_checks
# run_tests: If adding required tests, add them here and to script below.
- linux_tests
- build_remote_server
- macos_tests
- windows_tests
if: |
github.repository_owner == 'zed-industries' &&
always()
steps:
- name: Check all tests passed
run: |
# Check dependent jobs...
RET_CODE=0
# Always check style
[[ "${{ needs.style.result }}" != 'success' ]] && { RET_CODE=1; echo "style tests failed"; }
if [[ "${{ needs.job_spec.outputs.run_docs }}" == "true" ]]; then
[[ "${{ needs.check_docs.result }}" != 'success' ]] && { RET_CODE=1; echo "docs checks failed"; }
fi
if [[ "${{ needs.job_spec.outputs.run_actionlint }}" == "true" ]]; then
[[ "${{ needs.actionlint.result }}" != 'success' ]] && { RET_CODE=1; echo "actionlint checks failed"; }
fi
# Only check test jobs if they were supposed to run
if [[ "${{ needs.job_spec.outputs.run_tests }}" == "true" ]]; then
[[ "${{ needs.macos_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "macOS tests failed"; }
[[ "${{ needs.linux_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "Linux tests failed"; }
[[ "${{ needs.windows_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "Windows tests failed"; }
[[ "${{ needs.build_remote_server.result }}" != 'success' ]] && { RET_CODE=1; echo "Remote server build failed"; }
# This check is intentionally disabled. See: https://github.com/zed-industries/zed/pull/28431
# [[ "${{ needs.migration_checks.result }}" != 'success' ]] && { RET_CODE=1; echo "Migration Checks failed"; }
fi
if [[ "$RET_CODE" -eq 0 ]]; then
echo "All tests passed successfully!"
fi
exit $RET_CODE
bundle-mac:
timeout-minutes: 120
name: Create a macOS bundle
runs-on:
- self-mini-macos
if: |
( startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
needs: [macos_tests]
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "18"
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
# We need to fetch more than one commit so that `script/draft-release-notes`
# is able to diff between the current and previous tag.
#
# 25 was chosen arbitrarily.
fetch-depth: 25
clean: false
ref: ${{ github.ref }}
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: |
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
script/determine-release-channel
- name: Draft release notes
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: |
mkdir -p target/
# Ignore any errors that occur while drafting release notes to not fail the build.
script/draft-release-notes "$RELEASE_VERSION" "$RELEASE_CHANNEL" > target/release-notes.md || true
script/create-draft-release target/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create macOS app bundle
run: script/bundle-mac
- name: Rename binaries
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
run: |
mv target/aarch64-apple-darwin/release/Zed.dmg target/aarch64-apple-darwin/release/Zed-aarch64.dmg
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
- uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
name: Upload app bundle to release
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: |
target/zed-remote-server-macos-x86_64.gz
target/zed-remote-server-macos-aarch64.gz
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
bundle-linux-x86_x64:
timeout-minutes: 60
name: Linux x86_x64 release bundle
runs-on:
- namespace-profile-16x32-ubuntu-2004 # ubuntu 20.04 for minimal glibc
if: |
( startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
needs: [linux_tests]
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Install Linux dependencies
run: ./script/linux && ./script/install-mold 2.34.0
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Determine version and release channel
if: startsWith(github.ref, 'refs/tags/v')
run: |
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
script/determine-release-channel
- name: Create Linux .tar.gz bundle
run: script/bundle-linux
- name: Upload Artifact to Workflow - zed (run-bundling)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
path: target/release/zed-*.tar.gz
- name: Upload Artifact to Workflow - zed-remote-server (run-bundling)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
with:
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.gz
path: target/zed-remote-server-linux-x86_64.gz
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: |
target/zed-remote-server-linux-x86_64.gz
target/release/zed-linux-x86_64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
bundle-linux-aarch64: # this runs on ubuntu22.04
timeout-minutes: 60
name: Linux arm64 release bundle
runs-on:
- namespace-profile-8x32-ubuntu-2004-arm-m4 # ubuntu 20.04 for minimal glibc
if: |
startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
needs: [linux_tests]
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Install Linux dependencies
run: ./script/linux
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Determine version and release channel
if: startsWith(github.ref, 'refs/tags/v')
run: |
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
script/determine-release-channel
- name: Create and upload Linux .tar.gz bundles
run: script/bundle-linux
- name: Upload Artifact to Workflow - zed (run-bundling)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
path: target/release/zed-*.tar.gz
- name: Upload Artifact to Workflow - zed-remote-server (run-bundling)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
with:
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.gz
path: target/zed-remote-server-linux-aarch64.gz
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: |
target/zed-remote-server-linux-aarch64.gz
target/release/zed-linux-aarch64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
freebsd:
timeout-minutes: 60
runs-on: github-8vcpu-ubuntu-2404
if: |
false && ( startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
needs: [linux_tests]
name: Build Zed on FreeBSD
steps:
- uses: actions/checkout@v4
- name: Build FreeBSD remote-server
id: freebsd-build
uses: vmactions/freebsd-vm@c3ae29a132c8ef1924775414107a97cac042aad5 # v1.2.0
with:
usesh: true
release: 13.5
copyback: true
prepare: |
pkg install -y \
bash curl jq git \
rustup-init cmake-core llvm-devel-lite pkgconf protobuf # ibx11 alsa-lib rust-bindgen-cli
run: |
freebsd-version
sysctl hw.model
sysctl hw.ncpu
sysctl hw.physmem
sysctl hw.usermem
git config --global --add safe.directory /home/runner/work/zed/zed
rustup-init --profile minimal --default-toolchain none -y
. "$HOME/.cargo/env"
./script/bundle-freebsd
mkdir -p out/
mv "target/zed-remote-server-freebsd-x86_64.gz" out/
rm -rf target/
cargo clean
- name: Upload Artifact to Workflow - zed-remote-server (run-bundling)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
with:
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-freebsd.gz
path: out/zed-remote-server-freebsd-x86_64.gz
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: |
out/zed-remote-server-freebsd-x86_64.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
nix-build:
name: Build with Nix
uses: ./.github/workflows/nix.yml
needs: [job_spec]
if: github.repository_owner == 'zed-industries' &&
(contains(github.event.pull_request.labels.*.name, 'run-nix') ||
needs.job_spec.outputs.run_nix == 'true')
secrets: inherit
with:
flake-output: debug
# excludes the final package to only cache dependencies
cachix-filter: "-zed-editor-[0-9.]*-nightly"
bundle-windows-x64:
timeout-minutes: 120
name: Create a Windows installer
runs-on: [self-32vcpu-windows-2022]
if: |
( startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
needs: [windows_tests]
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com"
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Determine version and release channel
working-directory: ${{ env.ZED_WORKSPACE }}
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: |
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
script/determine-release-channel.ps1
- name: Build Zed installer
working-directory: ${{ env.ZED_WORKSPACE }}
run: script/bundle-windows.ps1
- name: Upload installer (x86_64) to Workflow - zed (run-bundling)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.exe
path: ${{ env.SETUP_PATH }}
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: ${{ env.SETUP_PATH }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
auto-release-preview:
name: Auto release preview
if: |
startsWith(github.ref, 'refs/tags/v')
&& endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, bundle-windows-x64]
runs-on:
- self-mini-macos
steps:
- name: gh release
run: gh release edit "$GITHUB_REF_NAME" --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Sentry release
uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c # v3
env:
SENTRY_ORG: zed-dev
SENTRY_PROJECT: zed
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
with:
environment: production

View File

@@ -1,93 +0,0 @@
# IF YOU UPDATE THE NAME OF ANY GITHUB SECRET, YOU MUST CHERRY PICK THE COMMIT
# TO BOTH STABLE AND PREVIEW CHANNELS
name: Release Actions
on:
release:
types: [published]
jobs:
discord_release:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
- name: Get release URL
id: get-release-url
run: |
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
URL="https://zed.dev/releases/preview"
else
URL="https://zed.dev/releases/stable"
fi
echo "URL=$URL" >> "$GITHUB_OUTPUT"
- name: Get content
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757 # v1.4.1
id: get-content
with:
stringToTruncate: |
📣 Zed [${{ github.event.release.tag_name }}](<${{ steps.get-release-url.outputs.URL }}>) was just released!
${{ github.event.release.body }}
maxLength: 2000
truncationSymbol: "..."
- name: Discord Webhook Action
uses: tsickert/discord-webhook@c840d45a03a323fbc3f7507ac7769dbd91bfb164 # v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_RELEASE_NOTES }}
content: ${{ steps.get-content.outputs.string }}
publish-winget:
runs-on:
- ubuntu-latest
steps:
- name: Set Package Name
id: set-package-name
run: |
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
PACKAGE_NAME=ZedIndustries.Zed.Preview
else
PACKAGE_NAME=ZedIndustries.Zed
fi
echo "PACKAGE_NAME=$PACKAGE_NAME" >> "$GITHUB_OUTPUT"
- uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f # v2
with:
identifier: ${{ steps.set-package-name.outputs.PACKAGE_NAME }}
max-versions-to-keep: 5
token: ${{ secrets.WINGET_TOKEN }}
send_release_notes_email:
if: false && github.repository_owner == 'zed-industries' && !github.event.release.prerelease
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Check if release was promoted from preview
id: check-promotion-from-preview
run: |
VERSION="${{ github.event.release.tag_name }}"
PREVIEW_TAG="${VERSION}-pre"
if git rev-parse "$PREVIEW_TAG" > /dev/null 2>&1; then
echo "was_promoted_from_preview=true" >> "$GITHUB_OUTPUT"
else
echo "was_promoted_from_preview=false" >> "$GITHUB_OUTPUT"
fi
- name: Send release notes email
if: steps.check-promotion-from-preview.outputs.was_promoted_from_preview == 'true'
run: |
TAG="${{ github.event.release.tag_name }}"
cat << 'EOF' > release_body.txt
${{ github.event.release.body }}
EOF
jq -n --arg tag "$TAG" --rawfile body release_body.txt '{version: $tag, markdown_body: $body}' \
> release_data.json
curl -X POST "https://zed.dev/api/send_release_notes_email" \
-H "Authorization: Bearer ${{ secrets.RELEASE_NOTES_API_TOKEN }}" \
-H "Content-Type: application/json" \
-d @release_data.json

78
.github/workflows/compare_perf.yml vendored Normal file
View File

@@ -0,0 +1,78 @@
# Generated from xtask::workflows::compare_perf
# Rebuild with `cargo xtask workflows`.
name: compare_perf
on:
workflow_dispatch:
inputs:
head:
description: head
required: true
type: string
base:
description: base
required: true
type: string
crate_name:
description: crate_name
type: string
default: ''
jobs:
run_perf:
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: compare_perf::run_perf::install_hyperfine
run: cargo install hyperfine
shell: bash -euxo pipefail {0}
- name: steps::git_checkout
run: git fetch origin ${{ inputs.base }} && git checkout ${{ inputs.base }}
shell: bash -euxo pipefail {0}
- name: compare_perf::run_perf::cargo_perf_test
run: |2-
if [ -n "${{ inputs.crate_name }}" ]; then
cargo perf-test -p ${{ inputs.crate_name }} -- --json=${{ inputs.base }};
else
cargo perf-test -p vim -- --json=${{ inputs.base }};
fi
shell: bash -euxo pipefail {0}
- name: steps::git_checkout
run: git fetch origin ${{ inputs.head }} && git checkout ${{ inputs.head }}
shell: bash -euxo pipefail {0}
- name: compare_perf::run_perf::cargo_perf_test
run: |2-
if [ -n "${{ inputs.crate_name }}" ]; then
cargo perf-test -p ${{ inputs.crate_name }} -- --json=${{ inputs.head }};
else
cargo perf-test -p vim -- --json=${{ inputs.head }};
fi
shell: bash -euxo pipefail {0}
- name: compare_perf::run_perf::compare_runs
run: cargo perf-compare --save=results.md ${{ inputs.base }} ${{ inputs.head }}
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact results.md'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: results.md
path: results.md
if-no-files-found: error
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}

View File

@@ -1,42 +1,40 @@
name: Danger
# Generated from xtask::workflows::danger
# Rebuild with `cargo xtask workflows`.
name: danger
on:
pull_request:
branches: [main]
types:
- opened
- synchronize
- reopened
- edited
- opened
- synchronize
- reopened
- edited
branches:
- main
jobs:
danger:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
with:
version: 9
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "20"
cache: "pnpm"
cache-dependency-path: "script/danger/pnpm-lock.yaml"
- run: pnpm install --dir script/danger
- name: Run Danger
run: pnpm run --dir script/danger danger ci
env:
# This GitHub token is not used, but the value needs to be here to prevent
# Danger from throwing an error.
GITHUB_TOKEN: "not_a_real_token"
# All requests are instead proxied through an instance of
# https://github.com/maxdeviant/danger-proxy that allows Danger to securely
# authenticate with GitHub while still being able to run on PRs from forks.
DANGER_GITHUB_API_BASE_URL: "https://danger-proxy.fly.dev/github"
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_pnpm
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
with:
version: '9'
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
cache: pnpm
cache-dependency-path: script/danger/pnpm-lock.yaml
- name: danger::danger_job::install_deps
run: pnpm install --dir script/danger
shell: bash -euxo pipefail {0}
- name: danger::danger_job::run
run: pnpm run --dir script/danger danger ci
shell: bash -euxo pipefail {0}
env:
GITHUB_TOKEN: not_a_real_token
DANGER_GITHUB_API_BASE_URL: https://danger-proxy.fly.dev/github

View File

@@ -49,7 +49,7 @@ jobs:
- name: Limit target directory size
shell: bash -euxo pipefail {0}
run: script/clear-target-dir-if-larger-than 100
run: script/clear-target-dir-if-larger-than 300
- name: Run tests
shell: bash -euxo pipefail {0}

View File

@@ -1,71 +0,0 @@
name: Run Agent Eval
on:
schedule:
- cron: "0 0 * * *"
pull_request:
branches:
- "**"
types: [synchronize, reopened, labeled]
workflow_dispatch:
concurrency:
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_EVAL_TELEMETRY: 1
jobs:
run_eval:
timeout-minutes: 60
name: Run Agent Eval
if: >
github.repository_owner == 'zed-industries' &&
(github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-eval'))
runs-on:
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
# cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Compile eval
run: cargo build --package=eval
- name: Run eval
run: cargo run --package=eval -- --repetitions=8 --concurrency=1
# Even the Linux runner is not stateful, in theory there is no need to do this cleanup.
# But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code
# to clean up the config file, Ive included the cleanup code here as a precaution.
# While its not strictly necessary at this moment, I believe its better to err on the side of caution.
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo

View File

@@ -1,69 +0,0 @@
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: namespace-profile-16x32-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_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
GIT_LFS_SKIP_SMUDGE: 1 # breaks the livekit rust sdk examples which we don't actually depend on
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
# on our macs we manually install nix. for some reason the cachix action is running
# under a non-login /bin/bash shell which doesn't source the proper script to add the
# nix profile to PATH, so we manually add them here
- name: Set path
if: ${{ ! matrix.system.install_nix }}
run: |
echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH"
echo "/Users/administrator/.nix-profile/bin" >> "$GITHUB_PATH"
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
if: ${{ matrix.system.install_nix }}
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
name: zed
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
pushFilter: "${{ inputs.cachix-filter }}"
cachixArgs: "-v"
- run: nix build .#${{ inputs.flake-output }} -L --accept-flake-config
- name: Limit /nix/store to 50GB on macs
if: ${{ ! matrix.system.install_nix }}
run: |
if [ "$(du -sm /nix/store | cut -f1)" -gt 50000 ]; then
nix-collect-garbage -d || true
fi

491
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,491 @@
# Generated from xtask::workflows::release
# Rebuild with `cargo xtask workflows`.
name: release
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: '1'
on:
push:
tags:
- v*
jobs:
run_tests_mac:
if: github.repository_owner == 'zed-industries'
runs-on: self-mini-macos
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::clippy
run: ./script/clippy
shell: bash -euxo pipefail {0}
- name: steps::cargo_install_nextest
run: cargo install cargo-nextest --locked
shell: bash -euxo pipefail {0}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: steps::cargo_nextest
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
shell: bash -euxo pipefail {0}
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
timeout-minutes: 60
run_tests_linux:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::clippy
run: ./script/clippy
shell: bash -euxo pipefail {0}
- name: steps::cargo_install_nextest
run: cargo install cargo-nextest --locked
shell: bash -euxo pipefail {0}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 250
shell: bash -euxo pipefail {0}
- name: steps::cargo_nextest
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
shell: bash -euxo pipefail {0}
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
timeout-minutes: 60
run_tests_windows:
if: github.repository_owner == 'zed-industries'
runs-on: self-32vcpu-windows-2022
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
New-Item -ItemType Directory -Path "./../.cargo" -Force
Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
shell: pwsh
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::clippy
run: ./script/clippy.ps1
shell: pwsh
- name: steps::cargo_install_nextest
run: cargo install cargo-nextest --locked
shell: pwsh
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than.ps1 250
shell: pwsh
- name: steps::cargo_nextest
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
shell: pwsh
- name: steps::cleanup_cargo_config
if: always()
run: |
Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
shell: pwsh
timeout-minutes: 60
check_scripts:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_tests::check_scripts::run_shellcheck
run: ./script/shellcheck-scripts error
shell: bash -euxo pipefail {0}
- id: get_actionlint
name: run_tests::check_scripts::download_actionlint
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
shell: bash -euxo pipefail {0}
- name: run_tests::check_scripts::run_actionlint
run: |
${{ steps.get_actionlint.outputs.executable }} -color
shell: bash -euxo pipefail {0}
- name: run_tests::check_scripts::check_xtask_workflows
run: |
cargo xtask workflows
if ! git diff --exit-code .github; then
echo "Error: .github directory has uncommitted changes after running 'cargo xtask workflows'"
echo "Please run 'cargo xtask workflows' locally and commit the changes"
exit 1
fi
shell: bash -euxo pipefail {0}
timeout-minutes: 60
create_draft_release:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
fetch-depth: 25
ref: ${{ github.ref }}
- name: script/determine-release-channel
run: script/determine-release-channel
shell: bash -euxo pipefail {0}
- name: mkdir -p target/
run: mkdir -p target/
shell: bash -euxo pipefail {0}
- name: release::create_draft_release::generate_release_notes
run: node --redirect-warnings=/dev/null ./script/draft-release-notes "$RELEASE_VERSION" "$RELEASE_CHANNEL" > target/release-notes.md
shell: bash -euxo pipefail {0}
- name: release::create_draft_release::create_release
run: script/create-draft-release target/release-notes.md
shell: bash -euxo pipefail {0}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
timeout-minutes: 60
bundle_linux_aarch64:
needs:
- run_tests_linux
- check_scripts
runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-linux-aarch64.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-linux-aarch64.tar.gz
path: target/release/zed-linux-aarch64.tar.gz
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-linux-aarch64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-linux-aarch64.gz
path: target/zed-remote-server-linux-aarch64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_linux_x86_64:
needs:
- run_tests_linux
- check_scripts
runs-on: namespace-profile-32x64-ubuntu-2004
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-linux-x86_64.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-linux-x86_64.tar.gz
path: target/release/zed-linux-x86_64.tar.gz
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-linux-x86_64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-linux-x86_64.gz
path: target/zed-remote-server-linux-x86_64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_mac_aarch64:
needs:
- run_tests_mac
- check_scripts
runs-on: self-mini-macos
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac::bundle_mac
run: ./script/bundle-mac aarch64-apple-darwin
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact Zed-aarch64.dmg'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-macos-aarch64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-macos-aarch64.gz
path: target/zed-remote-server-macos-aarch64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_mac_x86_64:
needs:
- run_tests_mac
- check_scripts
runs-on: self-mini-macos
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac::bundle_mac
run: ./script/bundle-mac x86_64-apple-darwin
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact Zed-x86_64.dmg'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-x86_64.dmg
path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-macos-x86_64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-macos-x86_64.gz
path: target/zed-remote-server-macos-x86_64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_windows_aarch64:
needs:
- run_tests_windows
- check_scripts
runs-on: self-32vcpu-windows-2022
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: run_bundling::bundle_windows::bundle_windows
run: script/bundle-windows.ps1 -Architecture aarch64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: '@actions/upload-artifact Zed-aarch64.exe'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-aarch64.exe
path: target/Zed-aarch64.exe
if-no-files-found: error
timeout-minutes: 60
bundle_windows_x86_64:
needs:
- run_tests_windows
- check_scripts
runs-on: self-32vcpu-windows-2022
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: run_bundling::bundle_windows::bundle_windows
run: script/bundle-windows.ps1 -Architecture x86_64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: '@actions/upload-artifact Zed-x86_64.exe'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-x86_64.exe
path: target/Zed-x86_64.exe
if-no-files-found: error
timeout-minutes: 60
upload_release_assets:
needs:
- create_draft_release
- bundle_linux_aarch64
- bundle_linux_x86_64
- bundle_mac_aarch64
- bundle_mac_x86_64
- bundle_windows_aarch64
- bundle_windows_x86_64
runs-on: namespace-profile-4x8-ubuntu-2204
steps:
- name: release::download_workflow_artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
path: ./artifacts/
- name: ls -lR ./artifacts
run: ls -lR ./artifacts
shell: bash -euxo pipefail {0}
- name: release::prep_release_artifacts
run: |-
mkdir -p release-artifacts/
mv ./artifacts/Zed-aarch64.dmg/Zed-aarch64.dmg release-artifacts/Zed-aarch64.dmg
mv ./artifacts/Zed-x86_64.dmg/Zed-x86_64.dmg release-artifacts/Zed-x86_64.dmg
mv ./artifacts/zed-linux-aarch64.tar.gz/zed-linux-aarch64.tar.gz release-artifacts/zed-linux-aarch64.tar.gz
mv ./artifacts/zed-linux-x86_64.tar.gz/zed-linux-x86_64.tar.gz release-artifacts/zed-linux-x86_64.tar.gz
mv ./artifacts/Zed-x86_64.exe/Zed-x86_64.exe release-artifacts/Zed-x86_64.exe
mv ./artifacts/Zed-aarch64.exe/Zed-aarch64.exe release-artifacts/Zed-aarch64.exe
mv ./artifacts/zed-remote-server-macos-aarch64.gz/zed-remote-server-macos-aarch64.gz release-artifacts/zed-remote-server-macos-aarch64.gz
mv ./artifacts/zed-remote-server-macos-x86_64.gz/zed-remote-server-macos-x86_64.gz release-artifacts/zed-remote-server-macos-x86_64.gz
mv ./artifacts/zed-remote-server-linux-aarch64.gz/zed-remote-server-linux-aarch64.gz release-artifacts/zed-remote-server-linux-aarch64.gz
mv ./artifacts/zed-remote-server-linux-x86_64.gz/zed-remote-server-linux-x86_64.gz release-artifacts/zed-remote-server-linux-x86_64.gz
shell: bash -euxo pipefail {0}
- name: gh release upload "$GITHUB_REF_NAME" --repo=zed-industries/zed release-artifacts/*
run: gh release upload "$GITHUB_REF_NAME" --repo=zed-industries/zed release-artifacts/*
shell: bash -euxo pipefail {0}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
auto_release_preview:
needs:
- upload_release_assets
if: |
false
&& startsWith(github.ref, 'refs/tags/v')
&& endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: gh release edit "$GITHUB_REF_NAME" --repo=zed-industries/zed --draft=false
run: gh release edit "$GITHUB_REF_NAME" --repo=zed-industries/zed --draft=false
shell: bash -euxo pipefail {0}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: release::create_sentry_release
uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c
with:
environment: production
env:
SENTRY_ORG: zed-dev
SENTRY_PROJECT: zed
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true

View File

@@ -1,256 +1,276 @@
name: Release Nightly
on:
schedule:
# Fire every day at 7:00am UTC (Roughly before EU workday and after US workday)
- cron: "0 7 * * *"
push:
tags:
- "nightly"
# Generated from xtask::workflows::release_nightly
# Rebuild with `cargo xtask workflows`.
name: release_nightly
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
RUST_BACKTRACE: '1'
on:
push:
tags:
- nightly
schedule:
- cron: 0 7 * * *
jobs:
style:
timeout-minutes: 60
name: Check formatting and Clippy lints
check_style:
if: github.repository_owner == 'zed-industries'
runs-on:
- self-hosted
- macOS
runs-on: self-mini-macos
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
fetch-depth: 0
- name: Run style checks
uses: ./.github/actions/check_style
- name: Run clippy
run: ./script/clippy
tests:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
fetch-depth: 0
- name: steps::cargo_fmt
run: cargo fmt --all -- --check
shell: bash -euxo pipefail {0}
- name: ./script/clippy
run: ./script/clippy
shell: bash -euxo pipefail {0}
timeout-minutes: 60
name: Run tests
run_tests_windows:
if: github.repository_owner == 'zed-industries'
runs-on:
- self-hosted
- macOS
needs: style
runs-on: self-32vcpu-windows-2022
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Run tests
uses: ./.github/actions/run_tests
windows-tests:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
New-Item -ItemType Directory -Path "./../.cargo" -Force
Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
shell: pwsh
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::clippy
run: ./script/clippy.ps1
shell: pwsh
- name: steps::cargo_install_nextest
run: cargo install cargo-nextest --locked
shell: pwsh
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than.ps1 250
shell: pwsh
- name: steps::cargo_nextest
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
shell: pwsh
- name: steps::cleanup_cargo_config
if: always()
run: |
Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
shell: pwsh
timeout-minutes: 60
name: Run tests on Windows
if: github.repository_owner == 'zed-industries'
runs-on: [self-32vcpu-windows-2022]
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Configure CI
run: |
New-Item -ItemType Directory -Path "./../.cargo" -Force
Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
- name: Run tests
uses: ./.github/actions/run_tests_windows
- name: Limit target directory size
run: ./script/clear-target-dir-if-larger-than.ps1 1024
- name: Clean CI config file
if: always()
run: Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
bundle-mac:
timeout-minutes: 60
name: Create a macOS bundle
if: github.repository_owner == 'zed-industries'
runs-on:
- self-mini-macos
needs: tests
bundle_linux_aarch64:
needs:
- check_style
- run_tests_windows
runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
run: |
set -eu
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
shell: bash -euxo pipefail {0}
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-linux-aarch64.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-linux-aarch64.tar.gz
path: target/release/zed-linux-aarch64.tar.gz
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-linux-aarch64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-linux-aarch64.gz
path: target/zed-remote-server-linux-aarch64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_linux_x86_64:
needs:
- check_style
- run_tests_windows
runs-on: namespace-profile-32x64-ubuntu-2004
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
run: |
set -eu
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
shell: bash -euxo pipefail {0}
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-linux-x86_64.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-linux-x86_64.tar.gz
path: target/release/zed-linux-x86_64.tar.gz
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-linux-x86_64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-linux-x86_64.gz
path: target/zed-remote-server-linux-x86_64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_mac_aarch64:
needs:
- check_style
- run_tests_windows
runs-on: self-mini-macos
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "18"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Set release channel to nightly
run: |
set -eu
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Create macOS app bundle
run: script/bundle-mac
- name: Upload Zed Nightly
run: script/upload-nightly macos
bundle-linux-x86:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
run: |
set -eu
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
shell: bash -euxo pipefail {0}
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac::bundle_mac
run: ./script/bundle-mac aarch64-apple-darwin
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact Zed-aarch64.dmg'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-macos-aarch64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-macos-aarch64.gz
path: target/zed-remote-server-macos-aarch64.gz
if-no-files-found: error
timeout-minutes: 60
name: Create a Linux *.tar.gz bundle for x86
if: github.repository_owner == 'zed-industries'
runs-on:
- namespace-profile-16x32-ubuntu-2004 # ubuntu 20.04 for minimal glibc
needs: tests
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Install Linux dependencies
run: ./script/linux && ./script/install-mold 2.34.0
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
- name: Set release channel to nightly
run: |
set -euo pipefail
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
- name: Create Linux .tar.gz bundle
run: script/bundle-linux
- name: Upload Zed Nightly
run: script/upload-nightly linux-targz
bundle-linux-arm:
timeout-minutes: 60
name: Create a Linux *.tar.gz bundle for ARM
if: github.repository_owner == 'zed-industries'
runs-on:
- namespace-profile-8x32-ubuntu-2004-arm-m4 # ubuntu 20.04 for minimal glibc
needs: tests
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Install Linux dependencies
run: ./script/linux
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
- name: Set release channel to nightly
run: |
set -euo pipefail
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
- name: Create Linux .tar.gz bundle
run: script/bundle-linux
- name: Upload Zed Nightly
run: script/upload-nightly linux-targz
freebsd:
timeout-minutes: 60
if: false && github.repository_owner == 'zed-industries'
runs-on: github-8vcpu-ubuntu-2404
needs: tests
name: Build Zed on FreeBSD
steps:
- uses: actions/checkout@v4
- name: Build FreeBSD remote-server
id: freebsd-build
uses: vmactions/freebsd-vm@c3ae29a132c8ef1924775414107a97cac042aad5 # v1.2.0
with:
# envs: "MYTOKEN MYTOKEN2"
usesh: true
release: 13.5
copyback: true
prepare: |
pkg install -y \
bash curl jq git \
rustup-init cmake-core llvm-devel-lite pkgconf protobuf # ibx11 alsa-lib rust-bindgen-cli
run: |
freebsd-version
sysctl hw.model
sysctl hw.ncpu
sysctl hw.physmem
sysctl hw.usermem
git config --global --add safe.directory /home/runner/work/zed/zed
rustup-init --profile minimal --default-toolchain none -y
. "$HOME/.cargo/env"
./script/bundle-freebsd
mkdir -p out/
mv "target/zed-remote-server-freebsd-x86_64.gz" out/
rm -rf target/
cargo clean
- name: Upload Zed Nightly
run: script/upload-nightly freebsd
bundle-nix:
name: Build and cache Nix package
needs: tests
secrets: inherit
uses: ./.github/workflows/nix.yml
bundle-windows-x64:
timeout-minutes: 60
name: Create a Windows installer
if: github.repository_owner == 'zed-industries'
runs-on: [self-32vcpu-windows-2022]
needs: windows-tests
bundle_mac_x86_64:
needs:
- check_style
- run_tests_windows
runs-on: self-mini-macos
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
run: |
set -eu
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
shell: bash -euxo pipefail {0}
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac::bundle_mac
run: ./script/bundle-mac x86_64-apple-darwin
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact Zed-x86_64.dmg'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-x86_64.dmg
path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-macos-x86_64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-macos-x86_64.gz
path: target/zed-remote-server-macos-x86_64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_windows_aarch64:
needs:
- check_style
- run_tests_windows
runs-on: self-32vcpu-windows-2022
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
@@ -259,65 +279,211 @@ jobs:
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com"
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Set release channel to nightly
working-directory: ${{ env.ZED_WORKSPACE }}
run: |
$ErrorActionPreference = "Stop"
$version = git rev-parse --short HEAD
Write-Host "Publishing version: $version on release channel nightly"
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Build Zed installer
working-directory: ${{ env.ZED_WORKSPACE }}
run: script/bundle-windows.ps1
- name: Upload Zed Nightly
working-directory: ${{ env.ZED_WORKSPACE }}
run: script/upload-nightly.ps1 windows
update-nightly-tag:
name: Update nightly tag
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
run: |
$ErrorActionPreference = "Stop"
$version = git rev-parse --short HEAD
Write-Host "Publishing version: $version on release channel nightly"
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: run_bundling::bundle_windows::bundle_windows
run: script/bundle-windows.ps1 -Architecture aarch64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: '@actions/upload-artifact Zed-aarch64.exe'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-aarch64.exe
path: target/Zed-aarch64.exe
if-no-files-found: error
timeout-minutes: 60
bundle_windows_x86_64:
needs:
- bundle-mac
- bundle-linux-x86
- bundle-linux-arm
- bundle-windows-x64
- check_style
- run_tests_windows
runs-on: self-32vcpu-windows-2022
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
run: |
$ErrorActionPreference = "Stop"
$version = git rev-parse --short HEAD
Write-Host "Publishing version: $version on release channel nightly"
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: run_bundling::bundle_windows::bundle_windows
run: script/bundle-windows.ps1 -Architecture x86_64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: '@actions/upload-artifact Zed-x86_64.exe'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-x86_64.exe
path: target/Zed-x86_64.exe
if-no-files-found: error
timeout-minutes: 60
build_nix_linux_x86_64:
needs:
- check_style
- run_tests_windows
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-32x64-ubuntu-2004
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
GIT_LFS_SKIP_SMUDGE: '1'
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: nix_build::build_nix::install_nix
uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- name: nix_build::build_nix::cachix_action
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
with:
name: zed
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
cachixArgs: -v
- name: nix_build::build_nix::build
run: nix build .#default -L --accept-flake-config
shell: bash -euxo pipefail {0}
timeout-minutes: 60
continue-on-error: true
build_nix_mac_aarch64:
needs:
- check_style
- run_tests_windows
if: github.repository_owner == 'zed-industries'
runs-on: self-mini-macos
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
GIT_LFS_SKIP_SMUDGE: '1'
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: nix_build::build_nix::set_path
run: |
echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH"
echo "/Users/administrator/.nix-profile/bin" >> "$GITHUB_PATH"
shell: bash -euxo pipefail {0}
- name: nix_build::build_nix::cachix_action
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
with:
name: zed
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
cachixArgs: -v
- name: nix_build::build_nix::build
run: nix build .#default -L --accept-flake-config
shell: bash -euxo pipefail {0}
- name: nix_build::build_nix::limit_store
run: |-
if [ "$(du -sm /nix/store | cut -f1)" -gt 50000 ]; then
nix-collect-garbage -d || true
fi
shell: bash -euxo pipefail {0}
timeout-minutes: 60
continue-on-error: true
update_nightly_tag:
needs:
- bundle_linux_aarch64
- bundle_linux_x86_64
- bundle_mac_aarch64
- bundle_mac_x86_64
- bundle_windows_aarch64
- bundle_windows_x86_64
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-4x8-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
fetch-depth: 0
- name: release::download_workflow_artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
path: ./artifacts/
- name: ls -lR ./artifacts
run: ls -lR ./artifacts
shell: bash -euxo pipefail {0}
- name: release::prep_release_artifacts
run: |-
mkdir -p release-artifacts/
- name: Update nightly tag
run: |
if [ "$(git rev-parse nightly)" = "$(git rev-parse HEAD)" ]; then
echo "Nightly tag already points to current commit. Skipping tagging."
exit 0
fi
git config user.name github-actions
git config user.email github-actions@github.com
git tag -f nightly
git push origin nightly --force
- name: Create Sentry release
uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c # v3
env:
SENTRY_ORG: zed-dev
SENTRY_PROJECT: zed
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
with:
environment: production
mv ./artifacts/Zed-aarch64.dmg/Zed-aarch64.dmg release-artifacts/Zed-aarch64.dmg
mv ./artifacts/Zed-x86_64.dmg/Zed-x86_64.dmg release-artifacts/Zed-x86_64.dmg
mv ./artifacts/zed-linux-aarch64.tar.gz/zed-linux-aarch64.tar.gz release-artifacts/zed-linux-aarch64.tar.gz
mv ./artifacts/zed-linux-x86_64.tar.gz/zed-linux-x86_64.tar.gz release-artifacts/zed-linux-x86_64.tar.gz
mv ./artifacts/Zed-x86_64.exe/Zed-x86_64.exe release-artifacts/Zed-x86_64.exe
mv ./artifacts/Zed-aarch64.exe/Zed-aarch64.exe release-artifacts/Zed-aarch64.exe
mv ./artifacts/zed-remote-server-macos-aarch64.gz/zed-remote-server-macos-aarch64.gz release-artifacts/zed-remote-server-macos-aarch64.gz
mv ./artifacts/zed-remote-server-macos-x86_64.gz/zed-remote-server-macos-x86_64.gz release-artifacts/zed-remote-server-macos-x86_64.gz
mv ./artifacts/zed-remote-server-linux-aarch64.gz/zed-remote-server-linux-aarch64.gz release-artifacts/zed-remote-server-linux-aarch64.gz
mv ./artifacts/zed-remote-server-linux-x86_64.gz/zed-remote-server-linux-x86_64.gz release-artifacts/zed-remote-server-linux-x86_64.gz
shell: bash -euxo pipefail {0}
- name: ./script/upload-nightly
run: ./script/upload-nightly
shell: bash -euxo pipefail {0}
env:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
- name: release_nightly::update_nightly_tag_job::update_nightly_tag
run: |
if [ "$(git rev-parse nightly)" = "$(git rev-parse HEAD)" ]; then
echo "Nightly tag already points to current commit. Skipping tagging."
exit 0
fi
git config user.name github-actions
git config user.email github-actions@github.com
git tag -f nightly
git push origin nightly --force
shell: bash -euxo pipefail {0}
- name: release::create_sentry_release
uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c
with:
environment: production
env:
SENTRY_ORG: zed-dev
SENTRY_PROJECT: zed
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
timeout-minutes: 60

62
.github/workflows/run_agent_evals.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
# Generated from xtask::workflows::run_agent_evals
# Rebuild with `cargo xtask workflows`.
name: run_agent_evals
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: '0'
RUST_BACKTRACE: '1'
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_EVAL_TELEMETRY: '1'
on:
pull_request:
types:
- synchronize
- reopened
- labeled
branches:
- '**'
schedule:
- cron: 0 0 * * *
workflow_dispatch: {}
jobs:
agent_evals:
if: |
github.repository_owner == 'zed-industries' &&
(github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-eval'))
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: cargo build --package=eval
run: cargo build --package=eval
shell: bash -euxo pipefail {0}
- name: run_agent_evals::agent_evals::run_eval
run: cargo run --package=eval -- --repetitions=8 --concurrency=1
shell: bash -euxo pipefail {0}
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
timeout-minutes: 60
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true

263
.github/workflows/run_bundling.yml vendored Normal file
View File

@@ -0,0 +1,263 @@
# Generated from xtask::workflows::run_bundling
# Rebuild with `cargo xtask workflows`.
name: run_bundling
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: '1'
on:
pull_request:
types:
- labeled
- synchronize
jobs:
bundle_linux_aarch64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-linux-aarch64.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-linux-aarch64.tar.gz
path: target/release/zed-linux-aarch64.tar.gz
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-linux-aarch64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-linux-aarch64.gz
path: target/zed-remote-server-linux-aarch64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_linux_x86_64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: namespace-profile-32x64-ubuntu-2004
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-linux-x86_64.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-linux-x86_64.tar.gz
path: target/release/zed-linux-x86_64.tar.gz
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-linux-x86_64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-linux-x86_64.gz
path: target/zed-remote-server-linux-x86_64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_mac_aarch64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: self-mini-macos
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac::bundle_mac
run: ./script/bundle-mac aarch64-apple-darwin
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact Zed-aarch64.dmg'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-macos-aarch64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-macos-aarch64.gz
path: target/zed-remote-server-macos-aarch64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_mac_x86_64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: self-mini-macos
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac::bundle_mac
run: ./script/bundle-mac x86_64-apple-darwin
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact Zed-x86_64.dmg'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-x86_64.dmg
path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-macos-x86_64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-macos-x86_64.gz
path: target/zed-remote-server-macos-x86_64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_windows_aarch64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: self-32vcpu-windows-2022
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: run_bundling::bundle_windows::bundle_windows
run: script/bundle-windows.ps1 -Architecture aarch64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: '@actions/upload-artifact Zed-aarch64.exe'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-aarch64.exe
path: target/Zed-aarch64.exe
if-no-files-found: error
timeout-minutes: 60
bundle_windows_x86_64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: self-32vcpu-windows-2022
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: run_bundling::bundle_windows::bundle_windows
run: script/bundle-windows.ps1 -Architecture x86_64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: '@actions/upload-artifact Zed-x86_64.exe'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-x86_64.exe
path: target/Zed-x86_64.exe
if-no-files-found: error
timeout-minutes: 60
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true

569
.github/workflows/run_tests.yml vendored Normal file
View File

@@ -0,0 +1,569 @@
# Generated from xtask::workflows::run_tests
# Rebuild with `cargo xtask workflows`.
name: run_tests
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: '1'
CARGO_INCREMENTAL: '0'
on:
pull_request:
branches:
- '**'
push:
branches:
- main
- v[0-9]+.[0-9]+.x
jobs:
orchestrate:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
fetch-depth: ${{ github.ref == 'refs/heads/main' && 2 || 350 }}
- id: filter
name: filter
run: |
if [ -z "$GITHUB_BASE_REF" ]; then
echo "Not in a PR context (i.e., push to main/stable/preview)"
COMPARE_REV="$(git rev-parse HEAD~1)"
else
echo "In a PR context comparing to pull_request.base.ref"
git fetch origin "$GITHUB_BASE_REF" --depth=350
COMPARE_REV="$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)"
fi
CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" ${{ github.sha }})"
check_pattern() {
local output_name="$1"
local pattern="$2"
local grep_arg="$3"
echo "$CHANGED_FILES" | grep "$grep_arg" "$pattern" && \
echo "${output_name}=true" >> "$GITHUB_OUTPUT" || \
echo "${output_name}=false" >> "$GITHUB_OUTPUT"
}
check_pattern "run_action_checks" '^\.github/(workflows/|actions/|actionlint.yml)|tooling/xtask|script/' -qP
check_pattern "run_docs" '^docs/' -qP
check_pattern "run_licenses" '^(Cargo.lock|script/.*licenses)' -qP
check_pattern "run_nix" '^(nix/|flake\.|Cargo\.|rust-toolchain.toml|\.cargo/config.toml)' -qP
check_pattern "run_tests" '^(docs/|script/update_top_ranking_issues/|\.github/(ISSUE_TEMPLATE|workflows/(?!run_tests)))' -qvP
shell: bash -euxo pipefail {0}
outputs:
run_action_checks: ${{ steps.filter.outputs.run_action_checks }}
run_docs: ${{ steps.filter.outputs.run_docs }}
run_licenses: ${{ steps.filter.outputs.run_licenses }}
run_nix: ${{ steps.filter.outputs.run_nix }}
run_tests: ${{ steps.filter.outputs.run_tests }}
check_style:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-4x8-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: steps::setup_pnpm
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
with:
version: '9'
- name: ./script/prettier
run: ./script/prettier
shell: bash -euxo pipefail {0}
- name: ./script/check-todos
run: ./script/check-todos
shell: bash -euxo pipefail {0}
- name: ./script/check-keymaps
run: ./script/check-keymaps
shell: bash -euxo pipefail {0}
- name: run_tests::check_style::check_for_typos
uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1
with:
config: ./typos.toml
- name: steps::cargo_fmt
run: cargo fmt --all -- --check
shell: bash -euxo pipefail {0}
timeout-minutes: 60
run_tests_windows:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_tests == 'true'
runs-on: self-32vcpu-windows-2022
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
New-Item -ItemType Directory -Path "./../.cargo" -Force
Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
shell: pwsh
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::clippy
run: ./script/clippy.ps1
shell: pwsh
- name: steps::cargo_install_nextest
run: cargo install cargo-nextest --locked
shell: pwsh
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than.ps1 250
shell: pwsh
- name: steps::cargo_nextest
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
shell: pwsh
- name: steps::cleanup_cargo_config
if: always()
run: |
Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
shell: pwsh
timeout-minutes: 60
run_tests_linux:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_tests == 'true'
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::clippy
run: ./script/clippy
shell: bash -euxo pipefail {0}
- name: steps::cargo_install_nextest
run: cargo install cargo-nextest --locked
shell: bash -euxo pipefail {0}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 250
shell: bash -euxo pipefail {0}
- name: steps::cargo_nextest
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
shell: bash -euxo pipefail {0}
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
timeout-minutes: 60
run_tests_mac:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_tests == 'true'
runs-on: self-mini-macos
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::clippy
run: ./script/clippy
shell: bash -euxo pipefail {0}
- name: steps::cargo_install_nextest
run: cargo install cargo-nextest --locked
shell: bash -euxo pipefail {0}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: steps::cargo_nextest
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
shell: bash -euxo pipefail {0}
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
timeout-minutes: 60
doctests:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_tests == 'true'
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- id: run_doctests
name: run_tests::doctests::run_doctests
run: |
cargo test --workspace --doc --no-fail-fast
shell: bash -euxo pipefail {0}
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
timeout-minutes: 60
check_workspace_binaries:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_tests == 'true'
runs-on: namespace-profile-8x16-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: cargo build -p collab
run: cargo build -p collab
shell: bash -euxo pipefail {0}
- name: cargo build --workspace --bins --examples
run: cargo build --workspace --bins --examples
shell: bash -euxo pipefail {0}
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
timeout-minutes: 60
check_postgres_and_protobuf_migrations:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_tests == 'true'
runs-on: self-mini-macos
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0
- name: run_tests::check_postgres_and_protobuf_migrations::remove_untracked_files
run: git clean -df
shell: bash -euxo pipefail {0}
- name: run_tests::check_postgres_and_protobuf_migrations::ensure_fresh_merge
run: |
if [ -z "$GITHUB_BASE_REF" ];
then
echo "BUF_BASE_BRANCH=$(git merge-base origin/main HEAD)" >> "$GITHUB_ENV"
else
git checkout -B temp
git merge -q "origin/$GITHUB_BASE_REF" -m "merge main into temp"
echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> "$GITHUB_ENV"
fi
shell: bash -euxo pipefail {0}
- name: run_tests::check_postgres_and_protobuf_migrations::bufbuild_setup_action
uses: bufbuild/buf-setup-action@v1
with:
version: v1.29.0
- name: run_tests::check_postgres_and_protobuf_migrations::bufbuild_breaking_action
uses: bufbuild/buf-breaking-action@v1
with:
input: crates/proto/proto/
against: https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/proto/proto/
timeout-minutes: 60
check_dependencies:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_tests == 'true'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: run_tests::check_dependencies::install_cargo_machete
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386
with:
command: install
args: cargo-machete@0.7.0
- name: run_tests::check_dependencies::run_cargo_machete
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386
with:
command: machete
- name: run_tests::check_dependencies::check_cargo_lock
run: cargo update --locked --workspace
shell: bash -euxo pipefail {0}
- name: run_tests::check_dependencies::check_vulnerable_dependencies
if: github.event_name == 'pull_request'
uses: actions/dependency-review-action@67d4f4bd7a9b17a0db54d2a7519187c65e339de8
with:
license-check: false
timeout-minutes: 60
check_docs:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_docs == 'true'
runs-on: namespace-profile-8x16-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: run_tests::check_docs::lychee_link_check
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332
with:
args: --no-progress --exclude '^http' './docs/src/**/*'
fail: true
jobSummary: false
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: run_tests::check_docs::install_mdbook
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08
with:
mdbook-version: 0.4.37
- name: run_tests::check_docs::build_docs
run: |
mkdir -p target/deploy
mdbook build ./docs --dest-dir=../target/deploy/docs/
shell: bash -euxo pipefail {0}
- name: run_tests::check_docs::lychee_link_check
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332
with:
args: --no-progress --exclude '^http' 'target/deploy/docs'
fail: true
jobSummary: false
timeout-minutes: 60
check_licenses:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_licenses == 'true'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: ./script/check-licenses
run: ./script/check-licenses
shell: bash -euxo pipefail {0}
- name: ./script/generate-licenses
run: ./script/generate-licenses
shell: bash -euxo pipefail {0}
check_scripts:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_action_checks == 'true'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_tests::check_scripts::run_shellcheck
run: ./script/shellcheck-scripts error
shell: bash -euxo pipefail {0}
- id: get_actionlint
name: run_tests::check_scripts::download_actionlint
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
shell: bash -euxo pipefail {0}
- name: run_tests::check_scripts::run_actionlint
run: |
${{ steps.get_actionlint.outputs.executable }} -color
shell: bash -euxo pipefail {0}
- name: run_tests::check_scripts::check_xtask_workflows
run: |
cargo xtask workflows
if ! git diff --exit-code .github; then
echo "Error: .github directory has uncommitted changes after running 'cargo xtask workflows'"
echo "Please run 'cargo xtask workflows' locally and commit the changes"
exit 1
fi
shell: bash -euxo pipefail {0}
timeout-minutes: 60
build_nix_linux_x86_64:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_nix == 'true'
runs-on: namespace-profile-32x64-ubuntu-2004
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
GIT_LFS_SKIP_SMUDGE: '1'
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: nix_build::build_nix::install_nix
uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- name: nix_build::build_nix::cachix_action
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
with:
name: zed
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
cachixArgs: -v
pushFilter: -zed-editor-[0-9.]*-nightly
- name: nix_build::build_nix::build
run: nix build .#debug -L --accept-flake-config
shell: bash -euxo pipefail {0}
timeout-minutes: 60
continue-on-error: true
build_nix_mac_aarch64:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_nix == 'true'
runs-on: self-mini-macos
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
GIT_LFS_SKIP_SMUDGE: '1'
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: nix_build::build_nix::set_path
run: |
echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH"
echo "/Users/administrator/.nix-profile/bin" >> "$GITHUB_PATH"
shell: bash -euxo pipefail {0}
- name: nix_build::build_nix::cachix_action
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
with:
name: zed
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
cachixArgs: -v
pushFilter: -zed-editor-[0-9.]*-nightly
- name: nix_build::build_nix::build
run: nix build .#debug -L --accept-flake-config
shell: bash -euxo pipefail {0}
- name: nix_build::build_nix::limit_store
run: |-
if [ "$(du -sm /nix/store | cut -f1)" -gt 50000 ]; then
nix-collect-garbage -d || true
fi
shell: bash -euxo pipefail {0}
timeout-minutes: 60
continue-on-error: true
tests_pass:
needs:
- orchestrate
- check_style
- run_tests_windows
- run_tests_linux
- run_tests_mac
- doctests
- check_workspace_binaries
- check_postgres_and_protobuf_migrations
- check_dependencies
- check_docs
- check_licenses
- check_scripts
- build_nix_linux_x86_64
- build_nix_mac_aarch64
if: github.repository_owner == 'zed-industries' && always()
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: run_tests::tests_pass
run: |
set +x
EXIT_CODE=0
check_result() {
echo "* $1: $2"
if [[ "$2" != "skipped" && "$2" != "success" ]]; then EXIT_CODE=1; fi
}
check_result "orchestrate" "${{ needs.orchestrate.result }}"
check_result "check_style" "${{ needs.check_style.result }}"
check_result "run_tests_windows" "${{ needs.run_tests_windows.result }}"
check_result "run_tests_linux" "${{ needs.run_tests_linux.result }}"
check_result "run_tests_mac" "${{ needs.run_tests_mac.result }}"
check_result "doctests" "${{ needs.doctests.result }}"
check_result "check_workspace_binaries" "${{ needs.check_workspace_binaries.result }}"
check_result "check_postgres_and_protobuf_migrations" "${{ needs.check_postgres_and_protobuf_migrations.result }}"
check_result "check_dependencies" "${{ needs.check_dependencies.result }}"
check_result "check_docs" "${{ needs.check_docs.result }}"
check_result "check_licenses" "${{ needs.check_licenses.result }}"
check_result "check_scripts" "${{ needs.check_scripts.result }}"
check_result "build_nix_linux_x86_64" "${{ needs.build_nix_linux_x86_64.result }}"
check_result "build_nix_mac_aarch64" "${{ needs.build_nix_mac_aarch64.result }}"
exit $EXIT_CODE
shell: bash -euxo pipefail {0}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true

63
.github/workflows/run_unit_evals.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
# Generated from xtask::workflows::run_agent_evals
# Rebuild with `cargo xtask workflows`.
name: run_agent_evals
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: '0'
RUST_BACKTRACE: '1'
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
on:
schedule:
- cron: 47 1 * * 2
workflow_dispatch: {}
jobs:
unit_evals:
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::cargo_install_nextest
run: cargo install cargo-nextest --locked
shell: bash -euxo pipefail {0}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 250
shell: bash -euxo pipefail {0}
- name: ./script/run-unit-evals
run: ./script/run-unit-evals
shell: bash -euxo pipefail {0}
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- name: run_agent_evals::unit_evals::send_failure_to_slack
if: ${{ failure() }}
uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52
with:
method: chat.postMessage
token: ${{ secrets.SLACK_APP_ZED_UNIT_EVALS_BOT_TOKEN }}
payload: |
channel: C04UDRNNJFQ
text: "Unit Evals Failed: https://github.com/zed-industries/zed/actions/runs/${{ github.run_id }}"
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true

View File

@@ -1,21 +0,0 @@
name: Script
on:
pull_request:
paths:
- "script/**"
push:
branches:
- main
jobs:
shellcheck:
name: "ShellCheck Scripts"
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Shellcheck ./scripts
run: |
./script/shellcheck-scripts error

View File

@@ -1,86 +0,0 @@
name: Run Unit Evals
on:
schedule:
# GitHub might drop jobs at busy times, so we choose a random time in the middle of the night.
- cron: "47 1 * * 2"
workflow_dispatch:
concurrency:
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
jobs:
unit_evals:
if: github.repository_owner == 'zed-industries'
timeout-minutes: 60
name: Run unit evals
runs-on:
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
# cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Install Rust
shell: bash -euxo pipefail {0}
run: |
cargo install cargo-nextest --locked
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "18"
- name: Limit target directory size
shell: bash -euxo pipefail {0}
run: script/clear-target-dir-if-larger-than 100
- name: Run unit evals
shell: bash -euxo pipefail {0}
run: cargo nextest run --workspace --no-fail-fast --features eval --no-capture -E 'test(::eval_)'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Send failure message to Slack channel if needed
if: ${{ failure() }}
uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52
with:
method: chat.postMessage
token: ${{ secrets.SLACK_APP_ZED_UNIT_EVALS_BOT_TOKEN }}
payload: |
channel: C04UDRNNJFQ
text: "Unit Evals Failed: https://github.com/zed-industries/zed/actions/runs/${{ github.run_id }}"
# Even the Linux runner is not stateful, in theory there is no need to do this cleanup.
# But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code
# to clean up the config file, Ive included the cleanup code here as a precaution.
# While its not strictly necessary at this moment, I believe its better to err on the side of caution.
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo

1
.gitignore vendored
View File

@@ -25,6 +25,7 @@
/crates/collab/seed.json
/crates/theme/schemas/theme.json
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
/crates/project_panel/benches/linux_repo_snapshot.txt
/dev.zed.Zed*.json
/node_modules/
/plugins/bin

458
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ members = [
"crates/anthropic",
"crates/askpass",
"crates/assets",
"crates/assistant_context",
"crates/assistant_text_thread",
"crates/assistant_slash_command",
"crates/assistant_slash_commands",
"crates/audio",
@@ -58,7 +58,7 @@ members = [
"crates/edit_prediction_context",
"crates/zeta2_tools",
"crates/editor",
# "crates/eval",
"crates/eval",
"crates/explorer_command_injector",
"crates/extension",
"crates/extension_api",
@@ -70,6 +70,7 @@ members = [
"crates/file_finder",
"crates/file_icons",
"crates/fs",
"crates/fs_benchmarks",
"crates/fsevent",
"crates/fuzzy",
"crates/git",
@@ -147,6 +148,7 @@ members = [
"crates/semantic_version",
"crates/session",
"crates/settings",
"crates/settings_json",
"crates/settings_macros",
"crates/settings_profile_selector",
"crates/settings_ui",
@@ -245,7 +247,7 @@ ai_onboarding = { path = "crates/ai_onboarding" }
anthropic = { path = "crates/anthropic" }
askpass = { path = "crates/askpass" }
assets = { path = "crates/assets" }
assistant_context = { path = "crates/assistant_context" }
assistant_text_thread = { path = "crates/assistant_text_thread" }
assistant_slash_command = { path = "crates/assistant_slash_command" }
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
audio = { path = "crates/audio" }
@@ -379,6 +381,7 @@ search = { path = "crates/search" }
semantic_version = { path = "crates/semantic_version" }
session = { path = "crates/session" }
settings = { path = "crates/settings" }
settings_json = { path = "crates/settings_json" }
settings_macros = { path = "crates/settings_macros" }
settings_ui = { path = "crates/settings_ui" }
snippet = { path = "crates/snippet" }
@@ -437,7 +440,7 @@ zlog_settings = { path = "crates/zlog_settings" }
# External crates
#
agent-client-protocol = { version = "=0.4.3", features = ["unstable"] }
agent-client-protocol = { version = "0.7.0", features = ["unstable"] }
aho-corasick = "1.1"
alacritty_terminal = "0.25.1-rc1"
any_vec = "0.14"
@@ -451,7 +454,7 @@ async-fs = "2.1"
async-lock = "2.1"
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
async-recursion = "1.0.0"
async-tar = "0.5.0"
async-tar = "0.5.1"
async-task = "4.7"
async-trait = "0.1"
async-tungstenite = "0.31.0"
@@ -505,6 +508,7 @@ fork = "0.2.0"
futures = "0.3"
futures-batch = "0.6.1"
futures-lite = "1.13"
gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "3eaa84abca0778eb54272f45a312cb24f9a0b435" }
git2 = { version = "0.20.1", default-features = false }
globset = "0.4"
handlebars = "4.3"
@@ -533,7 +537,7 @@ libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "0874f8742fe55b4dc94308c1e3c0069710d8eeaf" }
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "b71ab4eeb27d9758be8092020a46fe33fbca4e33" }
mach2 = "0.5"
markup5ever_rcdom = "0.3.0"
metal = "0.29"
@@ -579,14 +583,14 @@ partial-json-fixer = "0.5.3"
parse_int = "0.9"
pciid-parser = "0.8.0"
pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
portable-pty = "0.9.0"
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
@@ -774,6 +778,8 @@ windows-capture = { git = "https://github.com/zed-industries/windows-capture.git
[profile.dev]
split-debuginfo = "unpacked"
# https://github.com/rust-lang/cargo/issues/16104
incremental = false
codegen-units = 16
# mirror configuration for crates compiled for the build platform
@@ -899,4 +905,5 @@ ignored = [
"serde",
"component",
"documented",
"sea-orm-macros",
]

View File

@@ -1,2 +0,0 @@
app: postgrest crates/collab/postgrest_app.conf
llm: postgrest crates/collab/postgrest_llm.conf

View File

@@ -1,2 +1 @@
postgrest_llm: postgrest crates/collab/postgrest_llm.conf
website: cd ../zed.dev; npm run dev -- --port=3000

107
REVIEWERS.conl Normal file
View File

@@ -0,0 +1,107 @@
; This file contains a list of people who're interested in reviewing pull requests
; to certain parts of the code-base.
;
; This is mostly used internally for PR assignment, and may change over time.
;
; If you have permission to merge PRs (mostly equivalent to "do you work at Zed Industries"),
; we strongly encourage you to put your name in the "all" bucket, but you can also add yourself
; to other areas too.
<all>
= @ConradIrwin
= @maxdeviant
= @SomeoneToIgnore
= @probably-neb
= @danilo-leal
= @Veykril
= @kubkon
= @p1n3appl3
= @dinocosta
= @smitbarmase
= @cole-miller
vim
= @ConradIrwin
= @probably-neb
= @p1n3appl3
= @dinocosta
gpui
= @mikayla-maki
git
= @cole-miller
= @danilo-leal
linux
= @dvdsk
= @smitbarmase
= @p1n3appl3
= @cole-miller
windows
= @reflectronic
= @localcc
pickers
= @p1n3appl3
= @dvdsk
= @SomeoneToIgnore
audio
= @dvdsk
helix
= @kubkon
terminal
= @kubkon
= @Veykril
debugger
= @kubkon
= @osiewicz
= @Anthony-Eid
extension
= @kubkon
settings_ui
= @probably-neb
= @danilo-leal
= @Anthony-Eid
crashes
= @p1n3appl3
= @Veykril
ai
= @rtfeldman
= @danilo-leal
= @benbrandt
design
= @danilo-leal
multi_buffer
= @Veykril
= @SomeoneToIgnore
lsp
= @osiewicz
= @Veykril
= @smitbarmase
= @SomeoneToIgnore
languages
= @osiewicz
= @Veykril
= @smitbarmase
= @SomeoneToIgnore
project_panel
= @smitbarmase
tasks
= @SomeoneToIgnore
= @Veykril

View File

@@ -139,7 +139,7 @@
"find": "buffer_search::Deploy",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
"ctrl->": "agent::QuoteSelection",
"ctrl->": "agent::AddSelectionToThread",
"ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
@@ -243,7 +243,7 @@
"ctrl-shift-i": "agent::ToggleOptionsMenu",
"ctrl-alt-shift-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl->": "agent::QuoteSelection",
"ctrl->": "agent::AddSelectionToThread",
"ctrl-alt-e": "agent::RemoveAllContext",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
@@ -366,7 +366,7 @@
}
},
{
"context": "PromptLibrary",
"context": "RulesLibrary",
"bindings": {
"new": "rules_library::NewRule",
"ctrl-n": "rules_library::NewRule",
@@ -539,7 +539,7 @@
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
"ctrl-shift-space": "editor::ShowSignatureHelp",
"ctrl-shift-space": "editor::ShowWordCompletions",
"ctrl-.": "editor::ToggleCodeActions",
"ctrl-k r": "editor::RevealInFileManager",
"ctrl-k p": "editor::CopyPath",
@@ -609,7 +609,7 @@
"ctrl-alt-b": "workspace::ToggleRightDock",
"ctrl-b": "workspace::ToggleLeftDock",
"ctrl-j": "workspace::ToggleBottomDock",
"ctrl-alt-y": "workspace::CloseAllDocks",
"ctrl-alt-y": "workspace::ToggleAllDocks",
"ctrl-alt-0": "workspace::ResetActiveDockSize",
// For 0px parameter, uses UI font size value.
"ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }],
@@ -799,7 +799,7 @@
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPreviousHunk",
"ctrl-i": "assistant::InlineAssist",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints"
}
},
@@ -1080,7 +1080,8 @@
{
"context": "StashList || (StashList > Picker > Editor)",
"bindings": {
"ctrl-shift-backspace": "stash_picker::DropStashItem"
"ctrl-shift-backspace": "stash_picker::DropStashItem",
"ctrl-shift-v": "stash_picker::ShowStashItem"
}
},
{
@@ -1093,7 +1094,7 @@
"paste": "terminal::Paste",
"shift-insert": "terminal::Paste",
"ctrl-shift-v": "terminal::Paste",
"ctrl-i": "assistant::InlineAssist",
"ctrl-enter": "assistant::InlineAssist",
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
"alt-.": ["terminal::SendText", "\u001b."],
@@ -1266,12 +1267,22 @@
"ctrl-pagedown": "settings_editor::FocusNextFile"
}
},
{
"context": "StashDiff > Editor",
"bindings": {
"ctrl-space": "git::ApplyCurrentStash",
"ctrl-shift-space": "git::PopCurrentStash",
"ctrl-shift-backspace": "git::DropCurrentStash"
}
},
{
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"up": "settings_editor::FocusPreviousNavEntry",
"shift-tab": "settings_editor::FocusPreviousNavEntry",
"down": "settings_editor::FocusNextNavEntry",
"tab": "settings_editor::FocusNextNavEntry",
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",
@@ -1279,5 +1290,20 @@
"home": "settings_editor::FocusFirstNavEntry",
"end": "settings_editor::FocusLastNavEntry"
}
},
{
"context": "Zeta2Feedback > Editor",
"bindings": {
"enter": "editor::Newline",
"ctrl-enter up": "dev::Zeta2RatePredictionPositive",
"ctrl-enter down": "dev::Zeta2RatePredictionNegative"
}
},
{
"context": "Zeta2Context > Editor",
"bindings": {
"alt-left": "dev::Zeta2ContextGoBack",
"alt-right": "dev::Zeta2ContextGoForward"
}
}
]

View File

@@ -142,7 +142,7 @@
"cmd-\"": "editor::ExpandAllDiffHunks",
"cmd-alt-g b": "git::Blame",
"cmd-alt-g m": "git::OpenModifiedFiles",
"cmd-shift-space": "editor::ShowSignatureHelp",
"cmd-i": "editor::ShowSignatureHelp",
"f9": "editor::ToggleBreakpoint",
"shift-f9": "editor::EditLogBreakpoint",
"ctrl-f12": "editor::GoToDeclaration",
@@ -163,7 +163,7 @@
"cmd-alt-f": "buffer_search::DeployReplace",
"cmd-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
"cmd-e": ["buffer_search::Deploy", { "focus": false }],
"cmd->": "agent::QuoteSelection",
"cmd->": "agent::AddSelectionToThread",
"cmd-<": "assistant::InsertIntoEditor",
"cmd-alt-e": "editor::SelectEnclosingSymbol",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
@@ -282,7 +282,7 @@
"cmd-shift-i": "agent::ToggleOptionsMenu",
"cmd-alt-shift-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"cmd->": "agent::QuoteSelection",
"cmd->": "agent::AddSelectionToThread",
"cmd-alt-e": "agent::RemoveAllContext",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-ctrl-b": "agent::ToggleBurnMode",
@@ -423,7 +423,7 @@
}
},
{
"context": "PromptLibrary",
"context": "RulesLibrary",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "rules_library::NewRule",
@@ -679,7 +679,7 @@
"cmd-alt-b": "workspace::ToggleRightDock",
"cmd-r": "workspace::ToggleRightDock",
"cmd-j": "workspace::ToggleBottomDock",
"alt-cmd-y": "workspace::CloseAllDocks",
"alt-cmd-y": "workspace::ToggleAllDocks",
// For 0px parameter, uses UI font size value.
"ctrl-alt-0": "workspace::ResetActiveDockSize",
"ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }],
@@ -864,7 +864,7 @@
"cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPreviousHunk",
"cmd-i": "assistant::InlineAssist",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints"
}
},
@@ -1153,7 +1153,8 @@
"context": "StashList || (StashList > Picker > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-backspace": "stash_picker::DropStashItem"
"ctrl-shift-backspace": "stash_picker::DropStashItem",
"ctrl-shift-v": "stash_picker::ShowStashItem"
}
},
{
@@ -1167,7 +1168,7 @@
"cmd-a": "editor::SelectAll",
"cmd-k": "terminal::Clear",
"cmd-n": "workspace::NewTerminal",
"cmd-i": "assistant::InlineAssist",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-_": null, // emacs undo
// Some nice conveniences
"cmd-backspace": ["terminal::SendText", "\u0015"], // ctrl-u: clear line
@@ -1371,12 +1372,23 @@
"cmd-}": "settings_editor::FocusNextFile"
}
},
{
"context": "StashDiff > Editor",
"use_key_equivalents": true,
"bindings": {
"ctrl-space": "git::ApplyCurrentStash",
"ctrl-shift-space": "git::PopCurrentStash",
"ctrl-shift-backspace": "git::DropCurrentStash"
}
},
{
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"up": "settings_editor::FocusPreviousNavEntry",
"shift-tab": "settings_editor::FocusPreviousNavEntry",
"down": "settings_editor::FocusNextNavEntry",
"tab": "settings_editor::FocusNextNavEntry",
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",
@@ -1384,5 +1396,20 @@
"home": "settings_editor::FocusFirstNavEntry",
"end": "settings_editor::FocusLastNavEntry"
}
},
{
"context": "Zeta2Feedback > Editor",
"bindings": {
"enter": "editor::Newline",
"cmd-enter up": "dev::Zeta2RatePredictionPositive",
"cmd-enter down": "dev::Zeta2RatePredictionNegative"
}
},
{
"context": "Zeta2Context > Editor",
"bindings": {
"alt-left": "dev::Zeta2ContextGoBack",
"alt-right": "dev::Zeta2ContextGoForward"
}
}
]

View File

@@ -134,7 +134,7 @@
"ctrl-k z": "editor::ToggleSoftWrap",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
"ctrl-shift-.": "agent::QuoteSelection",
"ctrl-shift-.": "agent::AddSelectionToThread",
"ctrl-shift-,": "assistant::InsertIntoEditor",
"shift-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
@@ -244,7 +244,7 @@
"ctrl-shift-i": "agent::ToggleOptionsMenu",
// "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl-shift-.": "agent::QuoteSelection",
"ctrl-shift-.": "agent::AddSelectionToThread",
"shift-alt-e": "agent::RemoveAllContext",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
@@ -375,7 +375,7 @@
}
},
{
"context": "PromptLibrary",
"context": "RulesLibrary",
"use_key_equivalents": true,
"bindings": {
"ctrl-n": "rules_library::NewRule",
@@ -548,7 +548,7 @@
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
"ctrl-shift-space": "editor::ShowSignatureHelp",
"ctrl-shift-space": "editor::ShowWordCompletions",
"ctrl-.": "editor::ToggleCodeActions",
"ctrl-k r": "editor::RevealInFileManager",
"ctrl-k p": "editor::CopyPath",
@@ -614,7 +614,7 @@
"ctrl-alt-b": "workspace::ToggleRightDock",
"ctrl-b": "workspace::ToggleLeftDock",
"ctrl-j": "workspace::ToggleBottomDock",
"ctrl-shift-y": "workspace::CloseAllDocks",
"ctrl-shift-y": "workspace::ToggleAllDocks",
"alt-r": "workspace::ResetActiveDockSize",
// For 0px parameter, uses UI font size value.
"shift-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }],
@@ -812,7 +812,7 @@
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPreviousHunk",
"ctrl-i": "assistant::InlineAssist",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-shift-;": "editor::ToggleInlayHints"
}
},
@@ -1106,7 +1106,8 @@
"context": "StashList || (StashList > Picker > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-backspace": "stash_picker::DropStashItem"
"ctrl-shift-backspace": "stash_picker::DropStashItem",
"ctrl-shift-v": "stash_picker::ShowStashItem"
}
},
{
@@ -1119,7 +1120,7 @@
"shift-insert": "terminal::Paste",
"ctrl-v": "terminal::Paste",
"ctrl-shift-v": "terminal::Paste",
"ctrl-i": "assistant::InlineAssist",
"ctrl-enter": "assistant::InlineAssist",
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
"alt-.": ["terminal::SendText", "\u001b."],
@@ -1294,12 +1295,23 @@
"ctrl-pagedown": "settings_editor::FocusNextFile"
}
},
{
"context": "StashDiff > Editor",
"use_key_equivalents": true,
"bindings": {
"ctrl-space": "git::ApplyCurrentStash",
"ctrl-shift-space": "git::PopCurrentStash",
"ctrl-shift-backspace": "git::DropCurrentStash"
}
},
{
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"up": "settings_editor::FocusPreviousNavEntry",
"shift-tab": "settings_editor::FocusPreviousNavEntry",
"down": "settings_editor::FocusNextNavEntry",
"tab": "settings_editor::FocusNextNavEntry",
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",
@@ -1307,5 +1319,20 @@
"home": "settings_editor::FocusFirstNavEntry",
"end": "settings_editor::FocusLastNavEntry"
}
},
{
"context": "Zeta2Feedback > Editor",
"bindings": {
"enter": "editor::Newline",
"ctrl-enter up": "dev::Zeta2RatePredictionPositive",
"ctrl-enter down": "dev::Zeta2RatePredictionNegative"
}
},
{
"context": "Zeta2Context > Editor",
"bindings": {
"alt-left": "dev::Zeta2ContextGoBack",
"alt-right": "dev::Zeta2ContextGoForward"
}
}
]

View File

@@ -17,8 +17,8 @@
"bindings": {
"ctrl-i": "agent::ToggleFocus",
"ctrl-shift-i": "agent::ToggleFocus",
"ctrl-shift-l": "agent::QuoteSelection", // In cursor uses "Ask" mode
"ctrl-l": "agent::QuoteSelection", // In cursor uses "Agent" mode
"ctrl-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
"ctrl-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
"ctrl-k": "assistant::InlineAssist",
"ctrl-shift-k": "assistant::InsertIntoEditor"
}

View File

@@ -8,13 +8,23 @@
"ctrl-g": "menu::Cancel"
}
},
{
// Workaround to avoid falling back to default bindings.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE: must be declared before the `Editor` override.
// NOTE: in macos the 'ctrl-x' 'ctrl-p' and 'ctrl-n' rebindings are not needed, since they default to 'cmd'.
"context": "Editor",
"bindings": {
"ctrl-g": null, // currently activates `go_to_line::Toggle` when there is nothing to cancel
"ctrl-x": null, // currently activates `editor::Cut` if no following key is pressed for 1 second
"ctrl-p": null, // currently activates `file_finder::Toggle` when the cursor is on the first character of the buffer
"ctrl-n": null // currently activates `workspace::NewFile` when the cursor is on the last character of the buffer
}
},
{
"context": "Editor",
"bindings": {
"alt-x": "command_palette::Toggle",
"ctrl-g": "editor::Cancel",
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
"alt-g g": "go_to_line::Toggle", // goto-line
"alt-g alt-g": "go_to_line::Toggle", // goto-line
"ctrl-space": "editor::SetMark", // set-mark
@@ -33,8 +43,8 @@
"alt-m": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }], // back-to-indentation
"alt-left": "editor::MoveToPreviousWordStart", // left-word
"alt-right": "editor::MoveToNextWordEnd", // right-word
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-f": "editor::MoveToNextWordEnd", // forward-word
"alt-b": "editor::MoveToPreviousWordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
"alt-l": "editor::ConvertToLowerCase", // downcase-word
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
@@ -98,7 +108,7 @@
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-m": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }],
"alt-f": "editor::SelectToNextWordEnd",
"alt-b": "editor::SelectToPreviousSubwordStart",
"alt-b": "editor::SelectToPreviousWordStart",
"alt-{": "editor::SelectToStartOfParagraph",
"alt-}": "editor::SelectToEndOfParagraph",
"ctrl-up": "editor::SelectToStartOfParagraph",
@@ -126,15 +136,28 @@
"ctrl-n": "editor::SignatureHelpNext"
}
},
// Example setting for using emacs-style tab
// (i.e. indent the current line / selection or perform symbol completion depending on context)
// {
// "context": "Editor && !showing_code_actions && !showing_completions",
// "bindings": {
// "tab": "editor::AutoIndent" // indent-for-tab-command
// }
// },
{
"context": "Workspace",
"bindings": {
"alt-x": "command_palette::Toggle", // execute-extended-command
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
// "ctrl-x ctrl-c": "workspace::CloseWindow" // in case you only want to exit the current Zed instance
"ctrl-x ctrl-c": "zed::Quit", // save-buffers-kill-terminal
"ctrl-x 5 0": "workspace::CloseWindow", // delete-frame
"ctrl-x 5 2": "workspace::NewWindow", // make-frame-command
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
// "ctrl-x 1": "pane::JoinAll", // in case you prefer to delete the splits but keep the buffers open
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
@@ -145,10 +168,19 @@
}
},
{
// Workaround to enable using emacs in the Zed terminal.
// Workaround to enable using native emacs from the Zed terminal.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE:
// "terminal::SendKeystroke" only works for a single key stroke (e.g. ctrl-x),
// so override with null for compound sequences (e.g. ctrl-x ctrl-c).
"context": "Terminal",
"bindings": {
// If you want to perfect your emacs-in-zed setup, also consider the following.
// You may need to enable "option_as_meta" from the Zed settings for "alt-x" to work.
// "alt-x": ["terminal::SendKeystroke", "alt-x"],
// "ctrl-x": ["terminal::SendKeystroke", "ctrl-x"],
// "ctrl-n": ["terminal::SendKeystroke", "ctrl-n"],
// ...
"ctrl-x ctrl-c": null, // save-buffers-kill-terminal
"ctrl-x ctrl-f": null, // find-file
"ctrl-x ctrl-s": null, // save-buffer

View File

@@ -91,7 +91,7 @@
{
"context": "Workspace",
"bindings": {
"ctrl-shift-f12": "workspace::CloseAllDocks",
"ctrl-shift-f12": "workspace::ToggleAllDocks",
"ctrl-shift-r": ["pane::DeploySearch", { "replace_enabled": true }],
"alt-shift-f10": "task::Spawn",
"ctrl-e": "file_finder::Toggle",

View File

@@ -17,8 +17,8 @@
"bindings": {
"cmd-i": "agent::ToggleFocus",
"cmd-shift-i": "agent::ToggleFocus",
"cmd-shift-l": "agent::QuoteSelection", // In cursor uses "Ask" mode
"cmd-l": "agent::QuoteSelection", // In cursor uses "Agent" mode
"cmd-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
"cmd-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
"cmd-k": "assistant::InlineAssist",
"cmd-shift-k": "assistant::InsertIntoEditor"
}

View File

@@ -9,13 +9,19 @@
"ctrl-g": "menu::Cancel"
}
},
{
// Workaround to avoid falling back to default bindings.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE: must be declared before the `Editor` override.
"context": "Editor",
"bindings": {
"ctrl-g": null // currently activates `go_to_line::Toggle` when there is nothing to cancel
}
},
{
"context": "Editor",
"bindings": {
"alt-x": "command_palette::Toggle",
"ctrl-g": "editor::Cancel",
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
"alt-g g": "go_to_line::Toggle", // goto-line
"alt-g alt-g": "go_to_line::Toggle", // goto-line
"ctrl-space": "editor::SetMark", // set-mark
@@ -34,8 +40,8 @@
"alt-m": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }], // back-to-indentation
"alt-left": "editor::MoveToPreviousWordStart", // left-word
"alt-right": "editor::MoveToNextWordEnd", // right-word
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-f": "editor::MoveToNextWordEnd", // forward-word
"alt-b": "editor::MoveToPreviousWordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
"alt-l": "editor::ConvertToLowerCase", // downcase-word
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
@@ -99,7 +105,7 @@
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-m": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }],
"alt-f": "editor::SelectToNextWordEnd",
"alt-b": "editor::SelectToPreviousSubwordStart",
"alt-b": "editor::SelectToPreviousWordStart",
"alt-{": "editor::SelectToStartOfParagraph",
"alt-}": "editor::SelectToEndOfParagraph",
"ctrl-up": "editor::SelectToStartOfParagraph",
@@ -127,15 +133,28 @@
"ctrl-n": "editor::SignatureHelpNext"
}
},
// Example setting for using emacs-style tab
// (i.e. indent the current line / selection or perform symbol completion depending on context)
// {
// "context": "Editor && !showing_code_actions && !showing_completions",
// "bindings": {
// "tab": "editor::AutoIndent" // indent-for-tab-command
// }
// },
{
"context": "Workspace",
"bindings": {
"alt-x": "command_palette::Toggle", // execute-extended-command
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
// "ctrl-x ctrl-c": "workspace::CloseWindow" // in case you only want to exit the current Zed instance
"ctrl-x ctrl-c": "zed::Quit", // save-buffers-kill-terminal
"ctrl-x 5 0": "workspace::CloseWindow", // delete-frame
"ctrl-x 5 2": "workspace::NewWindow", // make-frame-command
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
// "ctrl-x 1": "pane::JoinAll", // in case you prefer to delete the splits but keep the buffers open
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
@@ -146,10 +165,19 @@
}
},
{
// Workaround to enable using emacs in the Zed terminal.
// Workaround to enable using native emacs from the Zed terminal.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE:
// "terminal::SendKeystroke" only works for a single key stroke (e.g. ctrl-x),
// so override with null for compound sequences (e.g. ctrl-x ctrl-c).
"context": "Terminal",
"bindings": {
// If you want to perfect your emacs-in-zed setup, also consider the following.
// You may need to enable "option_as_meta" from the Zed settings for "alt-x" to work.
// "alt-x": ["terminal::SendKeystroke", "alt-x"],
// "ctrl-x": ["terminal::SendKeystroke", "ctrl-x"],
// "ctrl-n": ["terminal::SendKeystroke", "ctrl-n"],
// ...
"ctrl-x ctrl-c": null, // save-buffers-kill-terminal
"ctrl-x ctrl-f": null, // find-file
"ctrl-x ctrl-s": null, // save-buffer

View File

@@ -93,7 +93,7 @@
{
"context": "Workspace",
"bindings": {
"cmd-shift-f12": "workspace::CloseAllDocks",
"cmd-shift-f12": "workspace::ToggleAllDocks",
"cmd-shift-r": ["pane::DeploySearch", { "replace_enabled": true }],
"ctrl-alt-r": "task::Spawn",
"cmd-e": "file_finder::Toggle",

View File

@@ -220,6 +220,8 @@
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
"] )": ["vim::UnmatchedForward", { "char": ")" }],
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
"[ r": "vim::GoToPreviousReference",
"] r": "vim::GoToNextReference",
// tree-sitter related commands
"[ x": "vim::SelectLargerSyntaxNode",
"] x": "vim::SelectSmallerSyntaxNode"
@@ -422,56 +424,66 @@
{
"context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
"bindings": {
";": "vim::HelixCollapseSelection",
":": "command_palette::Toggle",
"m": "vim::PushHelixMatch",
"s": "vim::HelixSelectRegex",
"]": ["vim::PushHelixNext", { "around": true }],
"[": ["vim::PushHelixPrevious", { "around": true }],
"left": "vim::WrappingLeft",
"right": "vim::WrappingRight",
// Movement
"h": "vim::WrappingLeft",
"left": "vim::WrappingLeft",
"l": "vim::WrappingRight",
"right": "vim::WrappingRight",
"t": ["vim::PushFindForward", { "before": true, "multiline": true }],
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
"alt-.": "vim::RepeatFind",
// Changes
"shift-r": "editor::Paste",
"`": "vim::ConvertToLowerCase",
"alt-`": "vim::ConvertToUpperCase",
"insert": "vim::InsertBefore",
"shift-u": "editor::Redo",
"ctrl-r": "vim::Redo",
"y": "vim::HelixYank",
"p": "vim::HelixPaste",
"shift-p": ["vim::HelixPaste", { "before": true }],
"alt-;": "vim::OtherEnd",
"ctrl-r": "vim::Redo",
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
"t": ["vim::PushFindForward", { "before": true, "multiline": true }],
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
">": "vim::Indent",
"<": "vim::Outdent",
"=": "vim::AutoIndent",
"`": "vim::ConvertToLowerCase",
"alt-`": "vim::ConvertToUpperCase",
"g q": "vim::PushRewrap",
"g w": "vim::PushRewrap",
"insert": "vim::InsertBefore",
"alt-.": "vim::RepeatFind",
"d": "vim::HelixDelete",
"c": "vim::HelixSubstitute",
"alt-c": "vim::HelixSubstituteNoYank",
// Selection manipulation
"s": "vim::HelixSelectRegex",
"alt-s": ["editor::SplitSelectionIntoLines", { "keep_selections": true }],
";": "vim::HelixCollapseSelection",
"alt-;": "vim::OtherEnd",
",": "vim::HelixKeepNewestSelection",
"shift-c": "vim::HelixDuplicateBelow",
"alt-shift-c": "vim::HelixDuplicateAbove",
"%": "editor::SelectAll",
"x": "vim::HelixSelectLine",
"shift-x": "editor::SelectLine",
"ctrl-c": "editor::ToggleComments",
"alt-o": "editor::SelectLargerSyntaxNode",
"alt-i": "editor::SelectSmallerSyntaxNode",
"alt-p": "editor::SelectPreviousSyntaxNode",
"alt-n": "editor::SelectNextSyntaxNode",
// Goto mode
"g n": "pane::ActivateNextItem",
"g p": "pane::ActivatePreviousItem",
// "tab": "pane::ActivateNextItem",
// "shift-tab": "pane::ActivatePrevItem",
"shift-h": "pane::ActivatePreviousItem",
"shift-l": "pane::ActivateNextItem",
"g l": "vim::EndOfLine",
"g h": "vim::StartOfLine",
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
"g e": "vim::EndOfDocument",
"g .": "vim::HelixGotoLastModification", // go to last modification
"g r": "editor::FindAllReferences", // zed specific
"g h": "vim::StartOfLine",
"g l": "vim::EndOfLine",
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
"g t": "vim::WindowTop",
"g c": "vim::WindowMiddle",
"g b": "vim::WindowBottom",
"g r": "editor::FindAllReferences", // zed specific
"g n": "pane::ActivateNextItem",
"shift-l": "pane::ActivateNextItem",
"g p": "pane::ActivatePreviousItem",
"shift-h": "pane::ActivatePreviousItem",
"g .": "vim::HelixGotoLastModification", // go to last modification
"shift-r": "editor::Paste",
"x": "vim::HelixSelectLine",
"shift-x": "editor::SelectLine",
"%": "editor::SelectAll",
// Window mode
"space w h": "workspace::ActivatePaneLeft",
"space w l": "workspace::ActivatePaneRight",
@@ -482,6 +494,7 @@
"space w r": "pane::SplitRight",
"space w v": "pane::SplitDown",
"space w d": "pane::SplitDown",
// Space mode
"space f": "file_finder::Toggle",
"space k": "editor::Hover",
@@ -492,16 +505,18 @@
"space a": "editor::ToggleCodeActions",
"space h": "editor::SelectAllMatches",
"space c": "editor::ToggleComments",
"space y": "editor::Copy",
"space p": "editor::Paste",
"shift-u": "editor::Redo",
"ctrl-c": "editor::ToggleComments",
"d": "vim::HelixDelete",
"c": "vim::HelixSubstitute",
"alt-c": "vim::HelixSubstituteNoYank",
"shift-c": "vim::HelixDuplicateBelow",
"alt-shift-c": "vim::HelixDuplicateAbove",
",": "vim::HelixKeepNewestSelection"
"space y": "editor::Copy",
// Other
":": "command_palette::Toggle",
"m": "vim::PushHelixMatch",
"]": ["vim::PushHelixNext", { "around": true }],
"[": ["vim::PushHelixPrevious", { "around": true }],
"g q": "vim::PushRewrap",
"g w": "vim::PushRewrap"
// "tab": "pane::ActivateNextItem",
// "shift-tab": "pane::ActivatePrevItem",
}
},
{
@@ -970,7 +985,9 @@
"bindings": {
"ctrl-h": "editor::Backspace",
"ctrl-u": "editor::DeleteToBeginningOfLine",
"ctrl-w": "editor::DeleteToPreviousWordStart"
"ctrl-w": "editor::DeleteToPreviousWordStart",
"ctrl-p": "menu::SelectPrevious",
"ctrl-n": "menu::SelectNext"
}
},
{
@@ -1002,5 +1019,16 @@
// and Windows.
"alt-l": "editor::AcceptEditPrediction"
}
},
{
"context": "SettingsWindow > NavigationMenu && !search",
"bindings": {
"l": "settings_editor::ExpandNavEntry",
"h": "settings_editor::CollapseNavEntry",
"k": "settings_editor::FocusPreviousNavEntry",
"j": "settings_editor::FocusNextNavEntry",
"g g": "settings_editor::FocusFirstNavEntry",
"shift-g": "settings_editor::FocusLastNavEntry"
}
}
]

View File

@@ -1,8 +1,8 @@
{
"$schema": "zed://schemas/settings",
/// The displayed name of this project. If not set or empty, the root directory name
/// The displayed name of this project. If not set or null, the root directory name
/// will be displayed.
"project_name": "",
"project_name": null,
// The name of the Zed theme to use for the UI.
//
// `mode` is one of:
@@ -311,11 +311,11 @@
"use_on_type_format": true,
// Whether to automatically add matching closing characters when typing
// opening parenthesis, bracket, brace, single or double quote characters.
// For example, when you type (, Zed will add a closing ) at the correct position.
// For example, when you type '(', Zed will add a closing ) at the correct position.
"use_autoclose": true,
// Whether to automatically surround selected text when typing opening parenthesis,
// bracket, brace, single or double quote characters.
// For example, when you select text and type (, Zed will surround the text with ().
// For example, when you select text and type '(', Zed will surround the text with ().
"use_auto_surround": true,
// Whether indentation should be adjusted based on the context whilst typing.
"auto_indent": true,
@@ -884,8 +884,6 @@
// Note: This setting has no effect on external agents that support permission modes, such as Claude Code.
// You can set `agent_servers.claude.default_mode` to `bypassPermissions` to skip all permission requests.
"always_allow_tool_actions": false,
// When enabled, the agent will stream edits.
"stream_edits": false,
// When enabled, agent edits will be displayed in single-file editors for review
"single_file_review": true,
// When enabled, show voting thumbs for feedback on agent edits.
@@ -1093,10 +1091,10 @@
// 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
// * "all": Use all gitignored files
// * "indexed": Use only the files Zed had indexed
// * "smart": Be smart and search for ignored when called from a gitignored worktree
"include_ignored": "smart"
},
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
@@ -1352,7 +1350,9 @@
// Whether to show the active language button in the status bar.
"active_language_button": true,
// Whether to show the cursor position button in the status bar.
"cursor_position_button": true
"cursor_position_button": true,
// Whether to show active line endings button in the status bar.
"line_endings_button": false
},
// Settings specific to the terminal
"terminal": {
@@ -1559,6 +1559,7 @@
//
"file_types": {
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json", "tsconfig*.json"],
"Markdown": [".rules", ".cursorrules", ".windsurfrules", ".clinerules"],
"Shell Script": [".env.*"]
},
// Settings for which version of Node.js and NPM to use when installing
@@ -1740,7 +1741,7 @@
}
},
"Kotlin": {
"language_servers": ["kotlin-language-server", "!kotlin-lsp", "..."]
"language_servers": ["!kotlin-language-server", "kotlin-lsp", "..."]
},
"LaTeX": {
"formatter": "language_server",
@@ -1771,12 +1772,16 @@
"allow_rewrap": "anywhere"
},
"Python": {
"code_actions_on_format": {
"source.organizeImports.ruff": true
},
"formatter": {
"language_server": {
"name": "ruff"
}
},
"debuggers": ["Debugpy"]
"debuggers": ["Debugpy"],
"language_servers": ["basedpyright", "ruff", "!ty", "!pyrefly", "!pyright", "!pylsp", "..."]
},
"Ruby": {
"language_servers": ["solargraph", "!ruby-lsp", "!rubocop", "!sorbet", "!steep", "..."]
@@ -1818,10 +1823,11 @@
},
"SystemVerilog": {
"format_on_save": "off",
"language_servers": ["!slang", "..."],
"use_on_type_format": false
},
"Vue.js": {
"language_servers": ["vue-language-server", "..."],
"language_servers": ["vue-language-server", "vtsls", "..."],
"prettier": {
"allowed": true
}

View File

@@ -49,8 +49,9 @@
"panel.background": "#3a3735ff",
"panel.focused_border": "#83a598ff",
"pane.focused_border": null,
"scrollbar.thumb.background": "#fbf1c74c",
"scrollbar.thumb.hover_background": "#494340ff",
"scrollbar.thumb.active_background": "#83a598ac",
"scrollbar.thumb.hover_background": "#fbf1c74c",
"scrollbar.thumb.background": "#a899844c",
"scrollbar.thumb.border": "#494340ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#373432ff",
@@ -454,8 +455,9 @@
"panel.background": "#393634ff",
"panel.focused_border": "#83a598ff",
"pane.focused_border": null,
"scrollbar.thumb.background": "#fbf1c74c",
"scrollbar.thumb.hover_background": "#494340ff",
"scrollbar.thumb.active_background": "#83a598ac",
"scrollbar.thumb.hover_background": "#fbf1c74c",
"scrollbar.thumb.background": "#a899844c",
"scrollbar.thumb.border": "#494340ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#343130ff",
@@ -859,8 +861,9 @@
"panel.background": "#3b3735ff",
"panel.focused_border": null,
"pane.focused_border": null,
"scrollbar.thumb.background": "#fbf1c74c",
"scrollbar.thumb.hover_background": "#494340ff",
"scrollbar.thumb.active_background": "#83a598ac",
"scrollbar.thumb.hover_background": "#fbf1c74c",
"scrollbar.thumb.background": "#a899844c",
"scrollbar.thumb.border": "#494340ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#393634ff",
@@ -1264,8 +1267,9 @@
"panel.background": "#ecddb4ff",
"panel.focused_border": null,
"pane.focused_border": null,
"scrollbar.thumb.background": "#2828284c",
"scrollbar.thumb.hover_background": "#ddcca7ff",
"scrollbar.thumb.active_background": "#458588ac",
"scrollbar.thumb.hover_background": "#2828284c",
"scrollbar.thumb.background": "#7c6f644c",
"scrollbar.thumb.border": "#ddcca7ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#eee0b7ff",
@@ -1669,8 +1673,9 @@
"panel.background": "#ecddb5ff",
"panel.focused_border": null,
"pane.focused_border": null,
"scrollbar.thumb.background": "#2828284c",
"scrollbar.thumb.hover_background": "#ddcca7ff",
"scrollbar.thumb.active_background": "#458588ac",
"scrollbar.thumb.hover_background": "#2828284c",
"scrollbar.thumb.background": "#7c6f644c",
"scrollbar.thumb.border": "#ddcca7ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#eee1bbff",
@@ -2074,8 +2079,9 @@
"panel.background": "#ecdcb3ff",
"panel.focused_border": null,
"pane.focused_border": null,
"scrollbar.thumb.background": "#2828284c",
"scrollbar.thumb.hover_background": "#ddcca7ff",
"scrollbar.thumb.active_background": "#458588ac",
"scrollbar.thumb.hover_background": "#2828284c",
"scrollbar.thumb.background": "#7c6f644c",
"scrollbar.thumb.border": "#ddcca7ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#eddeb5ff",

21
ci/Dockerfile.namespace Normal file
View File

@@ -0,0 +1,21 @@
ARG NAMESPACE_BASE_IMAGE_REF=""
# Your image must build FROM NAMESPACE_BASE_IMAGE_REF
FROM ${NAMESPACE_BASE_IMAGE_REF} AS base
# Remove problematic git-lfs packagecloud source
RUN sudo rm -f /etc/apt/sources.list.d/*git-lfs*.list
# Install git and SSH for cloning private repositories
RUN sudo apt-get update && \
sudo apt-get install -y git openssh-client
# Clone the Zed repository
RUN git clone https://github.com/zed-industries/zed.git ~/zed
# Run the Linux installation script
WORKDIR /home/runner/zed
RUN ./script/linux
# Clean up unnecessary files to reduce image size
RUN sudo apt-get clean && sudo rm -rf \
/home/runner/zed

View File

@@ -9,6 +9,9 @@ disallowed-methods = [
{ path = "std::process::Command::spawn", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::spawn" },
{ path = "std::process::Command::output", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::output" },
{ path = "std::process::Command::status", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::status" },
{ path = "std::process::Command::stdin", reason = "`smol::process::Command::from()` does not preserve stdio configuration", replacement = "smol::process::Command::stdin" },
{ path = "std::process::Command::stdout", reason = "`smol::process::Command::from()` does not preserve stdio configuration", replacement = "smol::process::Command::stdout" },
{ path = "std::process::Command::stderr", reason = "`smol::process::Command::from()` does not preserve stdio configuration", replacement = "smol::process::Command::stderr" },
{ path = "serde_json::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892. Use `serde_json::from_slice` instead." },
{ path = "serde_json_lenient::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892, Use `serde_json_lenient::from_slice` instead." },
]

View File

@@ -33,32 +33,6 @@ services:
volumes:
- ./livekit.yaml:/livekit.yaml
postgrest_app:
image: docker.io/postgrest/postgrest
container_name: postgrest_app
ports:
- 8081:8081
environment:
PGRST_DB_URI: postgres://postgres@postgres:5432/zed
volumes:
- ./crates/collab/postgrest_app.conf:/etc/postgrest.conf
command: postgrest /etc/postgrest.conf
depends_on:
- postgres
postgrest_llm:
image: docker.io/postgrest/postgrest
container_name: postgrest_llm
ports:
- 8082:8082
environment:
PGRST_DB_URI: postgres://postgres@postgres:5432/zed_llm
volumes:
- ./crates/collab/postgrest_llm.conf:/etc/postgrest.conf
command: postgrest /etc/postgrest.conf
depends_on:
- postgres
stripe-mock:
image: docker.io/stripe/stripe-mock:v0.178.0
ports:

View File

@@ -35,7 +35,7 @@ use std::rc::Rc;
use std::time::{Duration, Instant};
use std::{fmt::Display, mem, path::PathBuf, sync::Arc};
use ui::App;
use util::{ResultExt, get_default_system_shell_preferring_bash};
use util::{ResultExt, get_default_system_shell_preferring_bash, paths::PathStyle};
use uuid::Uuid;
#[derive(Debug)]
@@ -95,9 +95,14 @@ pub enum AssistantMessageChunk {
}
impl AssistantMessageChunk {
pub fn from_str(chunk: &str, language_registry: &Arc<LanguageRegistry>, cx: &mut App) -> Self {
pub fn from_str(
chunk: &str,
language_registry: &Arc<LanguageRegistry>,
path_style: PathStyle,
cx: &mut App,
) -> Self {
Self::Message {
block: ContentBlock::new(chunk.into(), language_registry, cx),
block: ContentBlock::new(chunk.into(), language_registry, path_style, cx),
}
}
@@ -186,6 +191,7 @@ impl ToolCall {
tool_call: acp::ToolCall,
status: ToolCallStatus,
language_registry: Arc<LanguageRegistry>,
path_style: PathStyle,
terminals: &HashMap<acp::TerminalId, Entity<Terminal>>,
cx: &mut App,
) -> Result<Self> {
@@ -199,6 +205,7 @@ impl ToolCall {
content.push(ToolCallContent::from_acp(
item,
language_registry.clone(),
path_style,
terminals,
cx,
)?);
@@ -223,6 +230,7 @@ impl ToolCall {
&mut self,
fields: acp::ToolCallUpdateFields,
language_registry: Arc<LanguageRegistry>,
path_style: PathStyle,
terminals: &HashMap<acp::TerminalId, Entity<Terminal>>,
cx: &mut App,
) -> Result<()> {
@@ -260,12 +268,13 @@ impl ToolCall {
// Reuse existing content if we can
for (old, new) in self.content.iter_mut().zip(content.by_ref()) {
old.update_from_acp(new, language_registry.clone(), terminals, cx)?;
old.update_from_acp(new, language_registry.clone(), path_style, terminals, cx)?;
}
for new in content {
self.content.push(ToolCallContent::from_acp(
new,
language_registry.clone(),
path_style,
terminals,
cx,
)?)
@@ -328,7 +337,7 @@ impl ToolCall {
location: acp::ToolCallLocation,
project: WeakEntity<Project>,
cx: &mut AsyncApp,
) -> Option<AgentLocation> {
) -> Option<ResolvedLocation> {
let buffer = project
.update(cx, |project, cx| {
project
@@ -350,17 +359,14 @@ impl ToolCall {
})
.ok()?;
Some(AgentLocation {
buffer: buffer.downgrade(),
position,
})
Some(ResolvedLocation { buffer, position })
}
fn resolve_locations(
&self,
project: Entity<Project>,
cx: &mut App,
) -> Task<Vec<Option<AgentLocation>>> {
) -> Task<Vec<Option<ResolvedLocation>>> {
let locations = self.locations.clone();
project.update(cx, |_, cx| {
cx.spawn(async move |project, cx| {
@@ -374,6 +380,23 @@ impl ToolCall {
}
}
// Separate so we can hold a strong reference to the buffer
// for saving on the thread
#[derive(Clone, Debug, PartialEq, Eq)]
struct ResolvedLocation {
buffer: Entity<Buffer>,
position: Anchor,
}
impl From<&ResolvedLocation> for AgentLocation {
fn from(value: &ResolvedLocation) -> Self {
Self {
buffer: value.buffer.downgrade(),
position: value.position,
}
}
}
#[derive(Debug)]
pub enum ToolCallStatus {
/// The tool call hasn't started running yet, but we start showing it to
@@ -436,21 +459,23 @@ impl ContentBlock {
pub fn new(
block: acp::ContentBlock,
language_registry: &Arc<LanguageRegistry>,
path_style: PathStyle,
cx: &mut App,
) -> Self {
let mut this = Self::Empty;
this.append(block, language_registry, cx);
this.append(block, language_registry, path_style, cx);
this
}
pub fn new_combined(
blocks: impl IntoIterator<Item = acp::ContentBlock>,
language_registry: Arc<LanguageRegistry>,
path_style: PathStyle,
cx: &mut App,
) -> Self {
let mut this = Self::Empty;
for block in blocks {
this.append(block, &language_registry, cx);
this.append(block, &language_registry, path_style, cx);
}
this
}
@@ -459,6 +484,7 @@ impl ContentBlock {
&mut self,
block: acp::ContentBlock,
language_registry: &Arc<LanguageRegistry>,
path_style: PathStyle,
cx: &mut App,
) {
if matches!(self, ContentBlock::Empty)
@@ -468,7 +494,7 @@ impl ContentBlock {
return;
}
let new_content = self.block_string_contents(block);
let new_content = self.block_string_contents(block, path_style);
match self {
ContentBlock::Empty => {
@@ -478,7 +504,7 @@ impl ContentBlock {
markdown.update(cx, |markdown, cx| markdown.append(&new_content, cx));
}
ContentBlock::ResourceLink { resource_link } => {
let existing_content = Self::resource_link_md(&resource_link.uri);
let existing_content = Self::resource_link_md(&resource_link.uri, path_style);
let combined = format!("{}\n{}", existing_content, new_content);
*self = Self::create_markdown_block(combined, language_registry, cx);
@@ -497,11 +523,11 @@ impl ContentBlock {
}
}
fn block_string_contents(&self, block: acp::ContentBlock) -> String {
fn block_string_contents(&self, block: acp::ContentBlock, path_style: PathStyle) -> String {
match block {
acp::ContentBlock::Text(text_content) => text_content.text,
acp::ContentBlock::ResourceLink(resource_link) => {
Self::resource_link_md(&resource_link.uri)
Self::resource_link_md(&resource_link.uri, path_style)
}
acp::ContentBlock::Resource(acp::EmbeddedResource {
resource:
@@ -510,14 +536,14 @@ impl ContentBlock {
..
}),
..
}) => Self::resource_link_md(&uri),
}) => Self::resource_link_md(&uri, path_style),
acp::ContentBlock::Image(image) => Self::image_md(&image),
acp::ContentBlock::Audio(_) | acp::ContentBlock::Resource(_) => String::new(),
}
}
fn resource_link_md(uri: &str) -> String {
if let Some(uri) = MentionUri::parse(uri).log_err() {
fn resource_link_md(uri: &str, path_style: PathStyle) -> String {
if let Some(uri) = MentionUri::parse(uri, path_style).log_err() {
uri.as_link().to_string()
} else {
uri.to_string()
@@ -563,6 +589,7 @@ impl ToolCallContent {
pub fn from_acp(
content: acp::ToolCallContent,
language_registry: Arc<LanguageRegistry>,
path_style: PathStyle,
terminals: &HashMap<acp::TerminalId, Entity<Terminal>>,
cx: &mut App,
) -> Result<Self> {
@@ -570,6 +597,7 @@ impl ToolCallContent {
acp::ToolCallContent::Content { content } => Ok(Self::ContentBlock(ContentBlock::new(
content,
&language_registry,
path_style,
cx,
))),
acp::ToolCallContent::Diff { diff } => Ok(Self::Diff(cx.new(|cx| {
@@ -593,6 +621,7 @@ impl ToolCallContent {
&mut self,
new: acp::ToolCallContent,
language_registry: Arc<LanguageRegistry>,
path_style: PathStyle,
terminals: &HashMap<acp::TerminalId, Entity<Terminal>>,
cx: &mut App,
) -> Result<()> {
@@ -608,7 +637,7 @@ impl ToolCallContent {
};
if needs_update {
*self = Self::from_acp(new, language_registry, terminals, cx)?;
*self = Self::from_acp(new, language_registry, path_style, terminals, cx)?;
}
Ok(())
}
@@ -1091,13 +1120,13 @@ impl AcpThread {
cx: &mut Context<Self>,
) -> Result<(), acp::Error> {
match update {
acp::SessionUpdate::UserMessageChunk { content } => {
acp::SessionUpdate::UserMessageChunk(acp::ContentChunk { content, .. }) => {
self.push_user_content_block(None, content, cx);
}
acp::SessionUpdate::AgentMessageChunk { content } => {
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk { content, .. }) => {
self.push_assistant_content_block(content, false, cx);
}
acp::SessionUpdate::AgentThoughtChunk { content } => {
acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk { content, .. }) => {
self.push_assistant_content_block(content, true, cx);
}
acp::SessionUpdate::ToolCall(tool_call) => {
@@ -1109,12 +1138,14 @@ impl AcpThread {
acp::SessionUpdate::Plan(plan) => {
self.update_plan(plan, cx);
}
acp::SessionUpdate::AvailableCommandsUpdate { available_commands } => {
cx.emit(AcpThreadEvent::AvailableCommandsUpdated(available_commands))
}
acp::SessionUpdate::CurrentModeUpdate { current_mode_id } => {
cx.emit(AcpThreadEvent::ModeUpdated(current_mode_id))
}
acp::SessionUpdate::AvailableCommandsUpdate(acp::AvailableCommandsUpdate {
available_commands,
..
}) => cx.emit(AcpThreadEvent::AvailableCommandsUpdated(available_commands)),
acp::SessionUpdate::CurrentModeUpdate(acp::CurrentModeUpdate {
current_mode_id,
..
}) => cx.emit(AcpThreadEvent::ModeUpdated(current_mode_id)),
}
Ok(())
}
@@ -1126,6 +1157,7 @@ impl AcpThread {
cx: &mut Context<Self>,
) {
let language_registry = self.project.read(cx).languages().clone();
let path_style = self.project.read(cx).path_style(cx);
let entries_len = self.entries.len();
if let Some(last_entry) = self.entries.last_mut()
@@ -1137,12 +1169,12 @@ impl AcpThread {
}) = last_entry
{
*id = message_id.or(id.take());
content.append(chunk.clone(), &language_registry, cx);
content.append(chunk.clone(), &language_registry, path_style, cx);
chunks.push(chunk);
let idx = entries_len - 1;
cx.emit(AcpThreadEvent::EntryUpdated(idx));
} else {
let content = ContentBlock::new(chunk.clone(), &language_registry, cx);
let content = ContentBlock::new(chunk.clone(), &language_registry, path_style, cx);
self.push_entry(
AgentThreadEntry::UserMessage(UserMessage {
id: message_id,
@@ -1162,6 +1194,7 @@ impl AcpThread {
cx: &mut Context<Self>,
) {
let language_registry = self.project.read(cx).languages().clone();
let path_style = self.project.read(cx).path_style(cx);
let entries_len = self.entries.len();
if let Some(last_entry) = self.entries.last_mut()
&& let AgentThreadEntry::AssistantMessage(AssistantMessage { chunks }) = last_entry
@@ -1171,10 +1204,10 @@ impl AcpThread {
match (chunks.last_mut(), is_thought) {
(Some(AssistantMessageChunk::Message { block }), false)
| (Some(AssistantMessageChunk::Thought { block }), true) => {
block.append(chunk, &language_registry, cx)
block.append(chunk, &language_registry, path_style, cx)
}
_ => {
let block = ContentBlock::new(chunk, &language_registry, cx);
let block = ContentBlock::new(chunk, &language_registry, path_style, cx);
if is_thought {
chunks.push(AssistantMessageChunk::Thought { block })
} else {
@@ -1183,7 +1216,7 @@ impl AcpThread {
}
}
} else {
let block = ContentBlock::new(chunk, &language_registry, cx);
let block = ContentBlock::new(chunk, &language_registry, path_style, cx);
let chunk = if is_thought {
AssistantMessageChunk::Thought { block }
} else {
@@ -1235,6 +1268,7 @@ impl AcpThread {
) -> Result<()> {
let update = update.into();
let languages = self.project.read(cx).languages().clone();
let path_style = self.project.read(cx).path_style(cx);
let ix = match self.index_for_tool_call(update.id()) {
Some(ix) => ix,
@@ -1251,6 +1285,7 @@ impl AcpThread {
meta: None,
}),
&languages,
path_style,
cx,
))],
status: ToolCallStatus::Failed,
@@ -1270,7 +1305,7 @@ impl AcpThread {
match update {
ToolCallUpdate::UpdateFields(update) => {
let location_updated = update.fields.locations.is_some();
call.update_fields(update.fields, languages, &self.terminals, cx)?;
call.update_fields(update.fields, languages, path_style, &self.terminals, cx)?;
if location_updated {
self.resolve_locations(update.id, cx);
}
@@ -1309,6 +1344,7 @@ impl AcpThread {
cx: &mut Context<Self>,
) -> Result<(), acp::Error> {
let language_registry = self.project.read(cx).languages().clone();
let path_style = self.project.read(cx).path_style(cx);
let id = update.id.clone();
if let Some(ix) = self.index_for_tool_call(&id) {
@@ -1316,7 +1352,13 @@ impl AcpThread {
unreachable!()
};
call.update_fields(update.fields, language_registry, &self.terminals, cx)?;
call.update_fields(
update.fields,
language_registry,
path_style,
&self.terminals,
cx,
)?;
call.status = status;
cx.emit(AcpThreadEvent::EntryUpdated(ix));
@@ -1325,6 +1367,7 @@ impl AcpThread {
update.try_into()?,
status,
language_registry,
self.project.read(cx).path_style(cx),
&self.terminals,
cx,
)?;
@@ -1393,35 +1436,46 @@ impl AcpThread {
let task = tool_call.resolve_locations(project, cx);
cx.spawn(async move |this, cx| {
let resolved_locations = task.await;
this.update(cx, |this, cx| {
let project = this.project.clone();
for location in resolved_locations.iter().flatten() {
this.shared_buffers
.insert(location.buffer.clone(), location.buffer.read(cx).snapshot());
}
let Some((ix, tool_call)) = this.tool_call_mut(&id) else {
return;
};
if let Some(Some(location)) = resolved_locations.last() {
project.update(cx, |project, cx| {
if let Some(agent_location) = project.agent_location() {
let should_ignore = agent_location.buffer == location.buffer
&& location
.buffer
.update(cx, |buffer, _| {
let snapshot = buffer.snapshot();
let old_position =
agent_location.position.to_point(&snapshot);
let new_position = location.position.to_point(&snapshot);
// ignore this so that when we get updates from the edit tool
// the position doesn't reset to the startof line
old_position.row == new_position.row
&& old_position.column > new_position.column
})
.ok()
.unwrap_or_default();
if !should_ignore {
project.set_agent_location(Some(location.clone()), cx);
}
let should_ignore = if let Some(agent_location) = project
.agent_location()
.filter(|agent_location| agent_location.buffer == location.buffer)
{
let snapshot = location.buffer.read(cx).snapshot();
let old_position = agent_location.position.to_point(&snapshot);
let new_position = location.position.to_point(&snapshot);
// ignore this so that when we get updates from the edit tool
// the position doesn't reset to the startof line
old_position.row == new_position.row
&& old_position.column > new_position.column
} else {
false
};
if !should_ignore {
project.set_agent_location(Some(location.into()), cx);
}
});
}
let resolved_locations = resolved_locations
.iter()
.map(|l| l.as_ref().map(|l| AgentLocation::from(l)))
.collect::<Vec<_>>();
if tool_call.resolved_locations != resolved_locations {
tool_call.resolved_locations = resolved_locations;
cx.emit(AcpThreadEvent::EntryUpdated(ix));
@@ -1593,6 +1647,7 @@ impl AcpThread {
let block = ContentBlock::new_combined(
message.clone(),
self.project.read(cx).languages().clone(),
self.project.read(cx).path_style(cx),
cx,
);
let request = acp::PromptRequest {
@@ -2561,17 +2616,19 @@ mod tests {
thread.update(&mut cx, |thread, cx| {
thread
.handle_session_update(
acp::SessionUpdate::AgentThoughtChunk {
acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk {
content: "Thinking ".into(),
},
meta: None,
}),
cx,
)
.unwrap();
thread
.handle_session_update(
acp::SessionUpdate::AgentThoughtChunk {
acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk {
content: "hard!".into(),
},
meta: None,
}),
cx,
)
.unwrap();
@@ -3070,9 +3127,10 @@ mod tests {
thread.update(&mut cx, |thread, cx| {
thread
.handle_session_update(
acp::SessionUpdate::AgentMessageChunk {
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
content: content.text.to_uppercase().into(),
},
meta: None,
}),
cx,
)
.unwrap();
@@ -3429,9 +3487,10 @@ mod tests {
thread.update(&mut cx, |thread, cx| {
thread
.handle_session_update(
acp::SessionUpdate::AgentMessageChunk {
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
content: content.text.to_uppercase().into(),
},
meta: None,
}),
cx,
)
.unwrap();

View File

@@ -236,21 +236,21 @@ impl PendingDiff {
fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
let ranges = self.excerpt_ranges(cx);
let base_text = self.base_text.clone();
let language_registry = self.new_buffer.read(cx).language_registry();
let new_buffer = self.new_buffer.read(cx);
let language_registry = new_buffer.language_registry();
let path = self
.new_buffer
.read(cx)
let path = new_buffer
.file()
.map(|file| file.path().display(file.path_style(cx)))
.unwrap_or("untitled".into())
.into();
let replica_id = new_buffer.replica_id();
// Replace the buffer in the multibuffer with the snapshot
let buffer = cx.new(|cx| {
let language = self.new_buffer.read(cx).language().cloned();
let buffer = TextBuffer::new_normalized(
0,
replica_id,
cx.entity_id().as_non_zero_u64().into(),
self.new_buffer.read(cx).line_ending(),
self.new_buffer.read(cx).as_rope().clone(),

View File

@@ -7,10 +7,10 @@ use std::{
fmt,
ops::RangeInclusive,
path::{Path, PathBuf},
str::FromStr,
};
use ui::{App, IconName, SharedString};
use url::Url;
use util::paths::PathStyle;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub enum MentionUri {
@@ -49,7 +49,7 @@ pub enum MentionUri {
}
impl MentionUri {
pub fn parse(input: &str) -> Result<Self> {
pub fn parse(input: &str, path_style: PathStyle) -> Result<Self> {
fn parse_line_range(fragment: &str) -> Result<RangeInclusive<u32>> {
let range = fragment
.strip_prefix("L")
@@ -74,25 +74,34 @@ impl MentionUri {
let path = url.path();
match url.scheme() {
"file" => {
let path = url.to_file_path().ok().context("Extracting file path")?;
let path = if path_style.is_windows() {
path.trim_start_matches("/")
} else {
path
};
if let Some(fragment) = url.fragment() {
let line_range = parse_line_range(fragment)?;
if let Some(name) = single_query_param(&url, "symbol")? {
Ok(Self::Symbol {
name,
abs_path: path,
abs_path: path.into(),
line_range,
})
} else {
Ok(Self::Selection {
abs_path: Some(path),
abs_path: Some(path.into()),
line_range,
})
}
} else if input.ends_with("/") {
Ok(Self::Directory { abs_path: path })
Ok(Self::Directory {
abs_path: path.into(),
})
} else {
Ok(Self::File { abs_path: path })
Ok(Self::File {
abs_path: path.into(),
})
}
}
"zed" => {
@@ -213,18 +222,14 @@ impl MentionUri {
pub fn to_uri(&self) -> Url {
match self {
MentionUri::File { abs_path } => {
let mut url = Url::parse("zed:///").unwrap();
url.set_path("/agent/file");
url.query_pairs_mut()
.append_pair("path", &abs_path.to_string_lossy());
let mut url = Url::parse("file:///").unwrap();
url.set_path(&abs_path.to_string_lossy());
url
}
MentionUri::PastedImage => Url::parse("zed:///agent/pasted-image").unwrap(),
MentionUri::Directory { abs_path } => {
let mut url = Url::parse("zed:///").unwrap();
url.set_path("/agent/directory");
url.query_pairs_mut()
.append_pair("path", &abs_path.to_string_lossy());
let mut url = Url::parse("file:///").unwrap();
url.set_path(&abs_path.to_string_lossy());
url
}
MentionUri::Symbol {
@@ -232,10 +237,9 @@ impl MentionUri {
name,
line_range,
} => {
let mut url = Url::parse("zed:///").unwrap();
url.set_path(&format!("/agent/symbol/{name}"));
url.query_pairs_mut()
.append_pair("path", &abs_path.to_string_lossy());
let mut url = Url::parse("file:///").unwrap();
url.set_path(&abs_path.to_string_lossy());
url.query_pairs_mut().append_pair("symbol", name);
url.set_fragment(Some(&format!(
"L{}:{}",
line_range.start() + 1,
@@ -247,13 +251,14 @@ impl MentionUri {
abs_path,
line_range,
} => {
let mut url = Url::parse("zed:///").unwrap();
if let Some(abs_path) = abs_path {
url.set_path("/agent/selection");
url.query_pairs_mut()
.append_pair("path", &abs_path.to_string_lossy());
let mut url = if let Some(path) = abs_path {
let mut url = Url::parse("file:///").unwrap();
url.set_path(&path.to_string_lossy());
url
} else {
let mut url = Url::parse("zed:///").unwrap();
url.set_path("/agent/untitled-buffer");
url
};
url.set_fragment(Some(&format!(
"L{}:{}",
@@ -288,14 +293,6 @@ impl MentionUri {
}
}
impl FromStr for MentionUri {
type Err = anyhow::Error;
fn from_str(s: &str) -> anyhow::Result<Self> {
Self::parse(s)
}
}
pub struct MentionLink<'a>(&'a MentionUri);
impl fmt::Display for MentionLink<'_> {
@@ -338,93 +335,81 @@ mod tests {
#[test]
fn test_parse_file_uri() {
let old_uri = uri!("file:///path/to/file.rs");
let parsed = MentionUri::parse(old_uri).unwrap();
let file_uri = uri!("file:///path/to/file.rs");
let parsed = MentionUri::parse(file_uri, PathStyle::local()).unwrap();
match &parsed {
MentionUri::File { abs_path } => {
assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/file.rs"));
assert_eq!(abs_path, Path::new(path!("/path/to/file.rs")));
}
_ => panic!("Expected File variant"),
}
let new_uri = parsed.to_uri().to_string();
assert!(new_uri.starts_with("zed:///agent/file"));
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
assert_eq!(parsed.to_uri().to_string(), file_uri);
}
#[test]
fn test_parse_directory_uri() {
let old_uri = uri!("file:///path/to/dir/");
let parsed = MentionUri::parse(old_uri).unwrap();
let file_uri = uri!("file:///path/to/dir/");
let parsed = MentionUri::parse(file_uri, PathStyle::local()).unwrap();
match &parsed {
MentionUri::Directory { abs_path } => {
assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/dir/"));
assert_eq!(abs_path, Path::new(path!("/path/to/dir/")));
}
_ => panic!("Expected Directory variant"),
}
let new_uri = parsed.to_uri().to_string();
assert!(new_uri.starts_with("zed:///agent/directory"));
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
assert_eq!(parsed.to_uri().to_string(), file_uri);
}
#[test]
fn test_to_directory_uri_without_slash() {
let uri = MentionUri::Directory {
abs_path: PathBuf::from(path!("/path/to/dir")),
abs_path: PathBuf::from(path!("/path/to/dir/")),
};
let uri_string = uri.to_uri().to_string();
assert!(uri_string.starts_with("zed:///agent/directory"));
assert_eq!(MentionUri::parse(&uri_string).unwrap(), uri);
let expected = uri!("file:///path/to/dir/");
assert_eq!(uri.to_uri().to_string(), expected);
}
#[test]
fn test_parse_symbol_uri() {
let old_uri = uri!("file:///path/to/file.rs?symbol=MySymbol#L10:20");
let parsed = MentionUri::parse(old_uri).unwrap();
let symbol_uri = uri!("file:///path/to/file.rs?symbol=MySymbol#L10:20");
let parsed = MentionUri::parse(symbol_uri, PathStyle::local()).unwrap();
match &parsed {
MentionUri::Symbol {
abs_path: path,
name,
line_range,
} => {
assert_eq!(path.to_str().unwrap(), path!("/path/to/file.rs"));
assert_eq!(path, Path::new(path!("/path/to/file.rs")));
assert_eq!(name, "MySymbol");
assert_eq!(line_range.start(), &9);
assert_eq!(line_range.end(), &19);
}
_ => panic!("Expected Symbol variant"),
}
let new_uri = parsed.to_uri().to_string();
assert!(new_uri.starts_with("zed:///agent/symbol/MySymbol"));
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
assert_eq!(parsed.to_uri().to_string(), symbol_uri);
}
#[test]
fn test_parse_selection_uri() {
let old_uri = uri!("file:///path/to/file.rs#L5:15");
let parsed = MentionUri::parse(old_uri).unwrap();
let selection_uri = uri!("file:///path/to/file.rs#L5:15");
let parsed = MentionUri::parse(selection_uri, PathStyle::local()).unwrap();
match &parsed {
MentionUri::Selection {
abs_path: path,
line_range,
} => {
assert_eq!(
path.as_ref().unwrap().to_str().unwrap(),
path!("/path/to/file.rs")
);
assert_eq!(path.as_ref().unwrap(), Path::new(path!("/path/to/file.rs")));
assert_eq!(line_range.start(), &4);
assert_eq!(line_range.end(), &14);
}
_ => panic!("Expected Selection variant"),
}
let new_uri = parsed.to_uri().to_string();
assert!(new_uri.starts_with("zed:///agent/selection"));
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
assert_eq!(parsed.to_uri().to_string(), selection_uri);
}
#[test]
fn test_parse_untitled_selection_uri() {
let selection_uri = uri!("zed:///agent/untitled-buffer#L1:10");
let parsed = MentionUri::parse(selection_uri).unwrap();
let parsed = MentionUri::parse(selection_uri, PathStyle::local()).unwrap();
match &parsed {
MentionUri::Selection {
abs_path: None,
@@ -441,7 +426,7 @@ mod tests {
#[test]
fn test_parse_thread_uri() {
let thread_uri = "zed:///agent/thread/session123?name=Thread+name";
let parsed = MentionUri::parse(thread_uri).unwrap();
let parsed = MentionUri::parse(thread_uri, PathStyle::local()).unwrap();
match &parsed {
MentionUri::Thread {
id: thread_id,
@@ -458,7 +443,7 @@ mod tests {
#[test]
fn test_parse_rule_uri() {
let rule_uri = "zed:///agent/rule/d8694ff2-90d5-4b6f-be33-33c1763acd52?name=Some+rule";
let parsed = MentionUri::parse(rule_uri).unwrap();
let parsed = MentionUri::parse(rule_uri, PathStyle::local()).unwrap();
match &parsed {
MentionUri::Rule { id, name } => {
assert_eq!(id.to_string(), "d8694ff2-90d5-4b6f-be33-33c1763acd52");
@@ -472,7 +457,7 @@ mod tests {
#[test]
fn test_parse_fetch_http_uri() {
let http_uri = "http://example.com/path?query=value#fragment";
let parsed = MentionUri::parse(http_uri).unwrap();
let parsed = MentionUri::parse(http_uri, PathStyle::local()).unwrap();
match &parsed {
MentionUri::Fetch { url } => {
assert_eq!(url.to_string(), http_uri);
@@ -485,7 +470,7 @@ mod tests {
#[test]
fn test_parse_fetch_https_uri() {
let https_uri = "https://example.com/api/endpoint";
let parsed = MentionUri::parse(https_uri).unwrap();
let parsed = MentionUri::parse(https_uri, PathStyle::local()).unwrap();
match &parsed {
MentionUri::Fetch { url } => {
assert_eq!(url.to_string(), https_uri);
@@ -497,40 +482,55 @@ mod tests {
#[test]
fn test_invalid_scheme() {
assert!(MentionUri::parse("ftp://example.com").is_err());
assert!(MentionUri::parse("ssh://example.com").is_err());
assert!(MentionUri::parse("unknown://example.com").is_err());
assert!(MentionUri::parse("ftp://example.com", PathStyle::local()).is_err());
assert!(MentionUri::parse("ssh://example.com", PathStyle::local()).is_err());
assert!(MentionUri::parse("unknown://example.com", PathStyle::local()).is_err());
}
#[test]
fn test_invalid_zed_path() {
assert!(MentionUri::parse("zed:///invalid/path").is_err());
assert!(MentionUri::parse("zed:///agent/unknown/test").is_err());
assert!(MentionUri::parse("zed:///invalid/path", PathStyle::local()).is_err());
assert!(MentionUri::parse("zed:///agent/unknown/test", PathStyle::local()).is_err());
}
#[test]
fn test_invalid_line_range_format() {
// Missing L prefix
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#10:20")).is_err());
assert!(
MentionUri::parse(uri!("file:///path/to/file.rs#10:20"), PathStyle::local()).is_err()
);
// Missing colon separator
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L1020")).is_err());
assert!(
MentionUri::parse(uri!("file:///path/to/file.rs#L1020"), PathStyle::local()).is_err()
);
// Invalid numbers
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L10:abc")).is_err());
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#Labc:20")).is_err());
assert!(
MentionUri::parse(uri!("file:///path/to/file.rs#L10:abc"), PathStyle::local()).is_err()
);
assert!(
MentionUri::parse(uri!("file:///path/to/file.rs#Labc:20"), PathStyle::local()).is_err()
);
}
#[test]
fn test_invalid_query_parameters() {
// Invalid query parameter name
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L10:20?invalid=test")).is_err());
assert!(
MentionUri::parse(
uri!("file:///path/to/file.rs#L10:20?invalid=test"),
PathStyle::local()
)
.is_err()
);
// Too many query parameters
assert!(
MentionUri::parse(uri!(
"file:///path/to/file.rs#L10:20?symbol=test&another=param"
))
MentionUri::parse(
uri!("file:///path/to/file.rs#L10:20?symbol=test&another=param"),
PathStyle::local()
)
.is_err()
);
}
@@ -538,8 +538,14 @@ mod tests {
#[test]
fn test_zero_based_line_numbers() {
// Test that 0-based line numbers are rejected (should be 1-based)
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L0:10")).is_err());
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L1:0")).is_err());
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L0:0")).is_err());
assert!(
MentionUri::parse(uri!("file:///path/to/file.rs#L0:10"), PathStyle::local()).is_err()
);
assert!(
MentionUri::parse(uri!("file:///path/to/file.rs#L1:0"), PathStyle::local()).is_err()
);
assert!(
MentionUri::parse(uri!("file:///path/to/file.rs#L0:0"), PathStyle::local()).is_err()
);
}
}

View File

@@ -1,10 +1,15 @@
use agent_client_protocol as acp;
use anyhow::Result;
use futures::{FutureExt as _, future::Shared};
use gpui::{App, AppContext, Context, Entity, Task};
use gpui::{App, AppContext, AsyncApp, Context, Entity, Task};
use language::LanguageRegistry;
use markdown::Markdown;
use project::Project;
use settings::{Settings as _, SettingsLocation};
use std::{path::PathBuf, process::ExitStatus, sync::Arc, time::Instant};
use task::Shell;
use terminal::terminal_settings::TerminalSettings;
use util::get_default_system_shell_preferring_bash;
pub struct Terminal {
id: acp::TerminalId,
@@ -170,3 +175,68 @@ impl Terminal {
)
}
}
pub async fn create_terminal_entity(
command: String,
args: &[String],
env_vars: Vec<(String, String)>,
cwd: Option<PathBuf>,
project: &Entity<Project>,
cx: &mut AsyncApp,
) -> Result<Entity<terminal::Terminal>> {
let mut env = if let Some(dir) = &cwd {
project
.update(cx, |project, cx| {
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
project.directory_environment(&shell, dir.clone().into(), cx)
})?
.await
.unwrap_or_default()
} else {
Default::default()
};
// Disables paging for `git` and hopefully other commands
env.insert("PAGER".into(), "".into());
env.extend(env_vars);
// Use remote shell or default system shell, as appropriate
let shell = project
.update(cx, |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
.map(Shell::Program)
})?
.unwrap_or_else(|| Shell::Program(get_default_system_shell_preferring_bash()));
let is_windows = project
.read_with(cx, |project, cx| project.path_style(cx).is_windows())
.unwrap_or(cfg!(windows));
let (task_command, task_args) = task::ShellBuilder::new(&shell, is_windows)
.redirect_stdin_to_dev_null()
.build(Some(command.clone()), &args);
project
.update(cx, |project, cx| {
project.create_terminal_task(
task::SpawnInTerminal {
command: Some(task_command),
args: task_args,
cwd,
env,
..Default::default()
},
cx,
)
})?
.await
}

View File

@@ -93,8 +93,8 @@ struct WatchedConnection {
messages: Vec<WatchedConnectionMessage>,
list_state: ListState,
connection: Weak<acp::ClientSideConnection>,
incoming_request_methods: HashMap<i32, Arc<str>>,
outgoing_request_methods: HashMap<i32, Arc<str>>,
incoming_request_methods: HashMap<acp::RequestId, Arc<str>>,
outgoing_request_methods: HashMap<acp::RequestId, Arc<str>>,
_task: Task<()>,
}
@@ -175,7 +175,7 @@ impl AcpTools {
}
};
method_map.insert(id, method.clone());
method_map.insert(id.clone(), method.clone());
(Some(id), method.into(), MessageType::Request, Ok(params))
}
acp::StreamMessageContent::Response { id, result } => {
@@ -338,6 +338,7 @@ impl AcpTools {
.children(
message
.request_id
.as_ref()
.map(|req_id| div().child(ui::Chip::new(req_id.to_string()))),
),
)
@@ -389,7 +390,7 @@ impl AcpTools {
struct WatchedConnectionMessage {
name: SharedString,
request_id: Option<i32>,
request_id: Option<acp::RequestId>,
direction: acp::StreamMessageDirection,
message_type: MessageType,
params: Result<Option<serde_json::Value>, acp::Error>,

View File

@@ -11,8 +11,7 @@ use language::{
LanguageServerStatusUpdate, ServerHealth,
};
use project::{
EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project,
ProjectEnvironmentEvent,
LanguageServerProgress, LspStoreEvent, Project, ProjectEnvironmentEvent,
git_store::{GitStoreEvent, Repository},
};
use smallvec::SmallVec;
@@ -327,20 +326,20 @@ impl ActivityIndicator {
.flatten()
}
fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a EnvironmentErrorMessage> {
fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a String> {
self.project.read(cx).peek_environment_error(cx)
}
fn content_to_render(&mut self, cx: &mut Context<Self>) -> Option<Content> {
// Show if any direnv calls failed
if let Some(error) = self.pending_environment_error(cx) {
if let Some(message) = self.pending_environment_error(cx) {
return Some(Content {
icon: Some(
Icon::new(IconName::Warning)
.size(IconSize::Small)
.into_any_element(),
),
message: error.0.clone(),
message: message.clone(),
on_click: Some(Arc::new(move |this, window, cx| {
this.project.update(cx, |project, cx| {
project.pop_environment_error(cx);

View File

@@ -10,6 +10,8 @@ path = "src/agent.rs"
[features]
test-support = ["db/test-support"]
eval = []
unit-eval = []
e2e = []
[lints]
@@ -22,7 +24,7 @@ agent-client-protocol.workspace = true
agent_servers.workspace = true
agent_settings.workspace = true
anyhow.workspace = true
assistant_context.workspace = true
assistant_text_thread.workspace = true
chrono.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
@@ -74,7 +76,7 @@ zstd.workspace = true
[dev-dependencies]
agent_servers = { workspace = true, "features" = ["test-support"] }
assistant_context = { workspace = true, "features" = ["test-support"] }
assistant_text_thread = { workspace = true, "features" = ["test-support"] }
client = { workspace = true, "features" = ["test-support"] }
clock = { workspace = true, "features" = ["test-support"] }
context_server = { workspace = true, "features" = ["test-support"] }

View File

@@ -48,24 +48,10 @@ use util::rel_path::RelPath;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectSnapshot {
pub worktree_snapshots: Vec<WorktreeSnapshot>,
pub worktree_snapshots: Vec<project::telemetry_snapshot::TelemetryWorktreeSnapshot>,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WorktreeSnapshot {
pub worktree_path: String,
pub git_state: Option<GitState>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GitState {
pub remote_url: Option<String>,
pub head_sha: Option<String>,
pub current_branch: Option<String>,
pub diff: Option<String>,
}
const RULES_FILE_NAMES: [&str; 9] = [
".rules",
".cursorrules",
@@ -1049,12 +1035,13 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
let session_id = params.session_id.clone();
log::info!("Received prompt request for session: {}", session_id);
log::debug!("Prompt blocks count: {}", params.prompt.len());
let path_style = self.0.read(cx).project.read(cx).path_style(cx);
self.run_turn(session_id, cx, |thread, cx| {
self.run_turn(session_id, cx, move |thread, cx| {
let content: Vec<UserMessageContent> = params
.prompt
.into_iter()
.map(Into::into)
.map(|block| UserMessageContent::from_content_block(block, path_style))
.collect::<Vec<_>>();
log::debug!("Converted prompt to message: {} chars", content.len());
log::debug!("Message id: {:?}", id);
@@ -1280,8 +1267,9 @@ mod internal_tests {
)
.await;
let project = Project::test(fs.clone(), [], cx).await;
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let agent = NativeAgent::new(
project.clone(),
history_store,
@@ -1341,8 +1329,9 @@ mod internal_tests {
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/", json!({ "a": {} })).await;
let project = Project::test(fs.clone(), [], cx).await;
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let connection = NativeAgentConnection(
NativeAgent::new(
project.clone(),
@@ -1416,8 +1405,9 @@ mod internal_tests {
.await;
let project = Project::test(fs.clone(), [], cx).await;
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
// Create the agent and connection
let agent = NativeAgent::new(
@@ -1488,8 +1478,9 @@ mod internal_tests {
)
.await;
let project = Project::test(fs.clone(), [path!("/a").as_ref()], cx).await;
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let agent = NativeAgent::new(
project.clone(),
history_store.clone(),

View File

@@ -31,7 +31,7 @@ use std::{
use util::path;
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "unit-eval"), ignore)]
fn eval_extract_handle_command_output() {
// Test how well agent generates multiple edit hunks.
//
@@ -108,7 +108,7 @@ fn eval_extract_handle_command_output() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "unit-eval"), ignore)]
fn eval_delete_run_git_blame() {
// Model | Pass rate
// ----------------------------|----------
@@ -171,7 +171,7 @@ fn eval_delete_run_git_blame() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "unit-eval"), ignore)]
fn eval_translate_doc_comments() {
// Model | Pass rate
// ============================================
@@ -234,7 +234,7 @@ fn eval_translate_doc_comments() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "unit-eval"), ignore)]
fn eval_use_wasi_sdk_in_compile_parser_to_wasm() {
// Model | Pass rate
// ============================================
@@ -360,7 +360,7 @@ fn eval_use_wasi_sdk_in_compile_parser_to_wasm() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "unit-eval"), ignore)]
fn eval_disable_cursor_blinking() {
// Model | Pass rate
// ============================================
@@ -446,7 +446,7 @@ fn eval_disable_cursor_blinking() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "unit-eval"), ignore)]
fn eval_from_pixels_constructor() {
// Results for 2025-06-13
//
@@ -656,7 +656,7 @@ fn eval_from_pixels_constructor() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "unit-eval"), ignore)]
fn eval_zode() {
// Model | Pass rate
// ============================================
@@ -763,7 +763,7 @@ fn eval_zode() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "unit-eval"), ignore)]
fn eval_add_overwrite_test() {
// Model | Pass rate
// ============================================
@@ -995,7 +995,7 @@ fn eval_add_overwrite_test() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "unit-eval"), ignore)]
fn eval_create_empty_file() {
// Check that Edit Agent can create a file without writing its
// thoughts into it. This issue is not specific to empty files, but
@@ -1483,16 +1483,27 @@ impl EditAgentTest {
fs.insert_tree("/root", json!({})).await;
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
let agent_model = SelectedModel::from_str(
&std::env::var("ZED_AGENT_MODEL").unwrap_or("anthropic/claude-4-sonnet-latest".into()),
&std::env::var("ZED_AGENT_MODEL").unwrap_or("anthropic/claude-sonnet-4-latest".into()),
)
.unwrap();
let judge_model = SelectedModel::from_str(
&std::env::var("ZED_JUDGE_MODEL").unwrap_or("anthropic/claude-4-sonnet-latest".into()),
&std::env::var("ZED_JUDGE_MODEL").unwrap_or("anthropic/claude-sonnet-4-latest".into()),
)
.unwrap();
let authenticate_provider_tasks = cx.update(|cx| {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry
.providers()
.iter()
.map(|p| p.authenticate(cx))
.collect::<Vec<_>>()
})
});
let (agent_model, judge_model) = cx
.update(|cx| {
cx.spawn(async move |cx| {
futures::future::join_all(authenticate_provider_tasks).await;
let agent_model = Self::load_model(&agent_model, cx).await;
let judge_model = Self::load_model(&judge_model, cx).await;
(agent_model.unwrap(), judge_model.unwrap())
@@ -1536,7 +1547,7 @@ impl EditAgentTest {
model.provider_id() == selected_model.provider
&& model.id() == selected_model.model
})
.expect("Model not found");
.unwrap_or_else(|| panic!("Model {} not found", selected_model.model.0));
model
})
}

View File

@@ -308,12 +308,13 @@ mod tests {
use indoc::indoc;
use language::{BufferId, TextBuffer};
use rand::prelude::*;
use text::ReplicaId;
use util::test::{generate_marked_text, marked_text_ranges};
#[test]
fn test_empty_query() {
let buffer = TextBuffer::new(
0,
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
"Hello world\nThis is a test\nFoo bar baz",
);
@@ -327,7 +328,7 @@ mod tests {
#[test]
fn test_streaming_exact_match() {
let buffer = TextBuffer::new(
0,
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
"Hello world\nThis is a test\nFoo bar baz",
);
@@ -351,7 +352,7 @@ mod tests {
#[test]
fn test_streaming_fuzzy_match() {
let buffer = TextBuffer::new(
0,
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
indoc! {"
function foo(a, b) {
@@ -385,7 +386,7 @@ mod tests {
#[test]
fn test_incremental_improvement() {
let buffer = TextBuffer::new(
0,
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
"Line 1\nLine 2\nLine 3\nLine 4\nLine 5",
);
@@ -410,7 +411,7 @@ mod tests {
#[test]
fn test_incomplete_lines_buffering() {
let buffer = TextBuffer::new(
0,
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
indoc! {"
The quick brown fox
@@ -437,7 +438,7 @@ mod tests {
#[test]
fn test_multiline_fuzzy_match() {
let buffer = TextBuffer::new(
0,
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
indoc! {r#"
impl Display for User {
@@ -691,7 +692,11 @@ mod tests {
}
"#};
let buffer = TextBuffer::new(0, BufferId::new(1).unwrap(), text.to_string());
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
text.to_string(),
);
let snapshot = buffer.snapshot();
let mut matcher = StreamingFuzzyMatcher::new(snapshot.clone());
@@ -724,7 +729,7 @@ mod tests {
#[track_caller]
fn assert_location_resolution(text_with_expected_range: &str, query: &str, rng: &mut StdRng) {
let (text, expected_ranges) = marked_text_ranges(text_with_expected_range, false);
let buffer = TextBuffer::new(0, BufferId::new(1).unwrap(), text.clone());
let buffer = TextBuffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), text.clone());
let snapshot = buffer.snapshot();
let mut matcher = StreamingFuzzyMatcher::new(snapshot);

View File

@@ -2,12 +2,12 @@ use crate::{DbThread, DbThreadMetadata, ThreadsDatabase};
use acp_thread::MentionUri;
use agent_client_protocol as acp;
use anyhow::{Context as _, Result, anyhow};
use assistant_context::{AssistantContext, SavedContextMetadata};
use assistant_text_thread::{SavedTextThreadMetadata, TextThread};
use chrono::{DateTime, Utc};
use db::kvp::KEY_VALUE_STORE;
use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
use itertools::Itertools;
use paths::contexts_dir;
use paths::text_threads_dir;
use project::Project;
use serde::{Deserialize, Serialize};
use std::{collections::VecDeque, path::Path, rc::Rc, sync::Arc, time::Duration};
@@ -50,21 +50,23 @@ pub fn load_agent_thread(
#[derive(Clone, Debug)]
pub enum HistoryEntry {
AcpThread(DbThreadMetadata),
TextThread(SavedContextMetadata),
TextThread(SavedTextThreadMetadata),
}
impl HistoryEntry {
pub fn updated_at(&self) -> DateTime<Utc> {
match self {
HistoryEntry::AcpThread(thread) => thread.updated_at,
HistoryEntry::TextThread(context) => context.mtime.to_utc(),
HistoryEntry::TextThread(text_thread) => text_thread.mtime.to_utc(),
}
}
pub fn id(&self) -> HistoryEntryId {
match self {
HistoryEntry::AcpThread(thread) => HistoryEntryId::AcpThread(thread.id.clone()),
HistoryEntry::TextThread(context) => HistoryEntryId::TextThread(context.path.clone()),
HistoryEntry::TextThread(text_thread) => {
HistoryEntryId::TextThread(text_thread.path.clone())
}
}
}
@@ -74,9 +76,9 @@ impl HistoryEntry {
id: thread.id.clone(),
name: thread.title.to_string(),
},
HistoryEntry::TextThread(context) => MentionUri::TextThread {
path: context.path.as_ref().to_owned(),
name: context.title.to_string(),
HistoryEntry::TextThread(text_thread) => MentionUri::TextThread {
path: text_thread.path.as_ref().to_owned(),
name: text_thread.title.to_string(),
},
}
}
@@ -90,7 +92,7 @@ impl HistoryEntry {
&thread.title
}
}
HistoryEntry::TextThread(context) => &context.title,
HistoryEntry::TextThread(text_thread) => &text_thread.title,
}
}
}
@@ -120,7 +122,7 @@ enum SerializedRecentOpen {
pub struct HistoryStore {
threads: Vec<DbThreadMetadata>,
entries: Vec<HistoryEntry>,
text_thread_store: Entity<assistant_context::ContextStore>,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
recently_opened_entries: VecDeque<HistoryEntryId>,
_subscriptions: Vec<gpui::Subscription>,
_save_recently_opened_entries_task: Task<()>,
@@ -128,7 +130,7 @@ pub struct HistoryStore {
impl HistoryStore {
pub fn new(
text_thread_store: Entity<assistant_context::ContextStore>,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
cx: &mut Context<Self>,
) -> Self {
let subscriptions =
@@ -192,16 +194,16 @@ impl HistoryStore {
cx: &mut Context<Self>,
) -> Task<Result<()>> {
self.text_thread_store
.update(cx, |store, cx| store.delete_local_context(path, cx))
.update(cx, |store, cx| store.delete_local(path, cx))
}
pub fn load_text_thread(
&self,
path: Arc<Path>,
cx: &mut Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
) -> Task<Result<Entity<TextThread>>> {
self.text_thread_store
.update(cx, |store, cx| store.open_local_context(path, cx))
.update(cx, |store, cx| store.open_local(path, cx))
}
pub fn reload(&self, cx: &mut Context<Self>) {
@@ -243,7 +245,7 @@ impl HistoryStore {
history_entries.extend(
self.text_thread_store
.read(cx)
.unordered_contexts()
.unordered_text_threads()
.cloned()
.map(HistoryEntry::TextThread),
);
@@ -278,14 +280,14 @@ impl HistoryStore {
let context_entries = self
.text_thread_store
.read(cx)
.unordered_contexts()
.flat_map(|context| {
.unordered_text_threads()
.flat_map(|text_thread| {
self.recently_opened_entries
.iter()
.enumerate()
.flat_map(|(index, entry)| match entry {
HistoryEntryId::TextThread(path) if &context.path == path => {
Some((index, HistoryEntry::TextThread(context.clone())))
HistoryEntryId::TextThread(path) if &text_thread.path == path => {
Some((index, HistoryEntry::TextThread(text_thread.clone())))
}
_ => None,
})
@@ -347,7 +349,7 @@ impl HistoryStore {
acp::SessionId(id.as_str().into()),
)),
SerializedRecentOpen::TextThread(file_name) => Some(
HistoryEntryId::TextThread(contexts_dir().join(file_name).into()),
HistoryEntryId::TextThread(text_threads_dir().join(file_name).into()),
),
})
.collect();

View File

@@ -81,7 +81,7 @@ impl AgentServer for NativeAgentServer {
mod tests {
use super::*;
use assistant_context::ContextStore;
use assistant_text_thread::TextThreadStore;
use gpui::AppContext;
agent_servers::e2e_tests::common_e2e_tests!(
@@ -116,8 +116,9 @@ mod tests {
});
let history = cx.update(|cx| {
let context_store = cx.new(move |cx| ContextStore::fake(project.clone(), cx));
cx.new(move |cx| HistoryStore::new(context_store, cx))
let text_thread_store =
cx.new(move |cx| TextThreadStore::fake(project.clone(), cx));
cx.new(move |cx| HistoryStore::new(text_thread_store, cx))
});
NativeAgentServer::new(fs.clone(), history)

View File

@@ -160,6 +160,42 @@ async fn test_system_prompt(cx: &mut TestAppContext) {
);
}
#[gpui::test]
async fn test_system_prompt_without_tools(cx: &mut TestAppContext) {
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
let fake_model = model.as_fake();
thread
.update(cx, |thread, cx| {
thread.send(UserMessageId::new(), ["abc"], cx)
})
.unwrap();
cx.run_until_parked();
let mut pending_completions = fake_model.pending_completions();
assert_eq!(
pending_completions.len(),
1,
"unexpected pending completions: {:?}",
pending_completions
);
let pending_completion = pending_completions.pop().unwrap();
assert_eq!(pending_completion.messages[0].role, Role::System);
let system_message = &pending_completion.messages[0];
let system_prompt = system_message.content[0].to_str().unwrap();
assert!(
!system_prompt.contains("## Tool Use"),
"unexpected system message: {:?}",
system_message
);
assert!(
!system_prompt.contains("## Fixing Diagnostics"),
"unexpected system message: {:?}",
system_message
);
}
#[gpui::test]
async fn test_prompt_caching(cx: &mut TestAppContext) {
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
@@ -1834,8 +1870,9 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
fake_fs.insert_tree(path!("/test"), json!({})).await;
let project = Project::test(fake_fs.clone(), [Path::new("/test")], cx).await;
let cwd = Path::new("/test");
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
// Create agent and connection
let agent = NativeAgent::new(
@@ -1995,7 +2032,7 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) {
locations: vec![],
raw_input: Some(json!({})),
raw_output: None,
meta: None,
meta: Some(json!({ "tool_name": "thinking" })),
}
);
let update = expect_tool_call_update_fields(&mut events).await;

View File

@@ -1,9 +1,8 @@
use crate::{
ContextServerRegistry, CopyPathTool, CreateDirectoryTool, DbLanguageModel, DbThread,
DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GitState, GrepTool,
DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GrepTool,
ListDirectoryTool, MovePathTool, NowTool, OpenTool, ProjectSnapshot, ReadFileTool,
SystemPromptTemplate, Template, Templates, TerminalTool, ThinkingTool, WebSearchTool,
WorktreeSnapshot,
};
use acp_thread::{MentionUri, UserMessageId};
use action_log::ActionLog;
@@ -26,7 +25,6 @@ use futures::{
future::Shared,
stream::FuturesUnordered,
};
use git::repository::DiffType;
use gpui::{
App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,
};
@@ -37,10 +35,7 @@ use language_model::{
LanguageModelToolResultContent, LanguageModelToolSchemaFormat, LanguageModelToolUse,
LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage, ZED_CLOUD_PROVIDER_ID,
};
use project::{
Project,
git_store::{GitStore, RepositoryState},
};
use project::Project;
use prompt_store::ProjectContext;
use schemars::{JsonSchema, Schema};
use serde::{Deserialize, Serialize};
@@ -55,7 +50,7 @@ use std::{
time::{Duration, Instant},
};
use std::{fmt::Write, path::PathBuf};
use util::{ResultExt, debug_panic, markdown::MarkdownCodeBlock};
use util::{ResultExt, debug_panic, markdown::MarkdownCodeBlock, paths::PathStyle};
use uuid::Uuid;
const TOOL_CANCELED_MESSAGE: &str = "Tool canceled by user";
@@ -750,7 +745,13 @@ impl Thread {
let title = tool.initial_title(tool_use.input.clone(), cx);
let kind = tool.kind();
stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone());
stream.send_tool_call(
&tool_use.id,
&tool_use.name,
title,
kind,
tool_use.input.clone(),
);
let output = tool_result
.as_ref()
@@ -880,101 +881,17 @@ impl Thread {
project: Entity<Project>,
cx: &mut Context<Self>,
) -> Task<Arc<ProjectSnapshot>> {
let git_store = project.read(cx).git_store().clone();
let worktree_snapshots: Vec<_> = project
.read(cx)
.visible_worktrees(cx)
.map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
.collect();
let task = project::telemetry_snapshot::TelemetrySnapshot::new(&project, cx);
cx.spawn(async move |_, _| {
let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
let snapshot = task.await;
Arc::new(ProjectSnapshot {
worktree_snapshots,
worktree_snapshots: snapshot.worktree_snapshots,
timestamp: Utc::now(),
})
})
}
fn worktree_snapshot(
worktree: Entity<project::Worktree>,
git_store: Entity<GitStore>,
cx: &App,
) -> Task<WorktreeSnapshot> {
cx.spawn(async move |cx| {
// Get worktree path and snapshot
let worktree_info = cx.update(|app_cx| {
let worktree = worktree.read(app_cx);
let path = worktree.abs_path().to_string_lossy().into_owned();
let snapshot = worktree.snapshot();
(path, snapshot)
});
let Ok((worktree_path, _snapshot)) = worktree_info else {
return WorktreeSnapshot {
worktree_path: String::new(),
git_state: None,
};
};
let git_state = git_store
.update(cx, |git_store, cx| {
git_store
.repositories()
.values()
.find(|repo| {
repo.read(cx)
.abs_path_to_repo_path(&worktree.read(cx).abs_path())
.is_some()
})
.cloned()
})
.ok()
.flatten()
.map(|repo| {
repo.update(cx, |repo, _| {
let current_branch =
repo.branch.as_ref().map(|branch| branch.name().to_owned());
repo.send_job(None, |state, _| async move {
let RepositoryState::Local { backend, .. } = state else {
return GitState {
remote_url: None,
head_sha: None,
current_branch,
diff: None,
};
};
let remote_url = backend.remote_url("origin");
let head_sha = backend.head_sha().await;
let diff = backend.diff(DiffType::HeadToWorktree).await.ok();
GitState {
remote_url,
head_sha,
current_branch,
diff,
}
})
})
});
let git_state = match git_state {
Some(git_state) => match git_state.ok() {
Some(git_state) => git_state.await.ok(),
None => None,
},
None => None,
};
WorktreeSnapshot {
worktree_path,
git_state,
}
})
}
pub fn project_context(&self) -> &Entity<ProjectContext> {
&self.project_context
}
@@ -1133,14 +1050,18 @@ impl Thread {
Ok(())
}
pub fn latest_token_usage(&self) -> Option<acp_thread::TokenUsage> {
pub fn latest_request_token_usage(&self) -> Option<language_model::TokenUsage> {
let last_user_message = self.last_user_message()?;
let tokens = self.request_token_usage.get(&last_user_message.id)?;
let model = self.model.clone()?;
Some(*tokens)
}
pub fn latest_token_usage(&self) -> Option<acp_thread::TokenUsage> {
let usage = self.latest_request_token_usage()?;
let model = self.model.clone()?;
Some(acp_thread::TokenUsage {
max_tokens: model.max_token_count_for_mode(self.completion_mode.into()),
used_tokens: tokens.total_tokens(),
used_tokens: usage.total_tokens(),
})
}
@@ -1183,6 +1104,14 @@ impl Thread {
self.run_turn(cx)
}
#[cfg(feature = "eval")]
pub fn proceed(
&mut self,
cx: &mut Context<Self>,
) -> Result<mpsc::UnboundedReceiver<Result<ThreadEvent>>> {
self.run_turn(cx)
}
fn run_turn(
&mut self,
cx: &mut Context<Self>,
@@ -1550,7 +1479,13 @@ impl Thread {
});
if push_new_tool_use {
event_stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone());
event_stream.send_tool_call(
&tool_use.id,
&tool_use.name,
title,
kind,
tool_use.input.clone(),
);
last_message
.content
.push(AgentMessageContent::ToolUse(tool_use.clone()));
@@ -1881,9 +1816,15 @@ impl Thread {
log::debug!("Completion intent: {:?}", completion_intent);
log::debug!("Completion mode: {:?}", self.completion_mode);
let messages = self.build_request_messages(cx);
let available_tools: Vec<_> = self
.running_turn
.as_ref()
.map(|turn| turn.tools.keys().cloned().collect())
.unwrap_or_default();
log::debug!("Request includes {} tools", available_tools.len());
let messages = self.build_request_messages(available_tools, cx);
log::debug!("Request will include {} messages", messages.len());
log::debug!("Request includes {} tools", tools.len());
let request = LanguageModelRequest {
thread_id: Some(self.id.to_string()),
@@ -1922,7 +1863,7 @@ impl Thread {
.tools
.iter()
.filter_map(|(tool_name, tool)| {
if tool.supported_provider(&model.provider_id())
if tool.supports_provider(&model.provider_id())
&& profile.is_tool_enabled(tool_name)
{
Some((truncate(tool_name), tool.clone()))
@@ -1974,7 +1915,11 @@ impl Thread {
self.running_turn.as_ref()?.tools.get(name).cloned()
}
fn build_request_messages(&self, cx: &App) -> Vec<LanguageModelRequestMessage> {
fn build_request_messages(
&self,
available_tools: Vec<SharedString>,
cx: &App,
) -> Vec<LanguageModelRequestMessage> {
log::trace!(
"Building request messages from {} thread messages",
self.messages.len()
@@ -1982,7 +1927,7 @@ impl Thread {
let system_prompt = SystemPromptTemplate {
project: self.project_context.read(cx),
available_tools: self.tools.keys().cloned().collect(),
available_tools,
}
.render(&self.templates)
.context("failed to build system prompt")
@@ -2198,7 +2143,7 @@ where
/// Some tools rely on a provider for the underlying billing or other reasons.
/// Allow the tool to check if they are compatible, or should be filtered out.
fn supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
fn supports_provider(_provider: &LanguageModelProviderId) -> bool {
true
}
@@ -2239,7 +2184,7 @@ pub trait AnyAgentTool {
fn kind(&self) -> acp::ToolKind;
fn initial_title(&self, input: serde_json::Value, _cx: &mut App) -> SharedString;
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value>;
fn supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
fn supports_provider(&self, _provider: &LanguageModelProviderId) -> bool {
true
}
fn run(
@@ -2284,8 +2229,8 @@ where
Ok(json)
}
fn supported_provider(&self, provider: &LanguageModelProviderId) -> bool {
self.0.supported_provider(provider)
fn supports_provider(&self, provider: &LanguageModelProviderId) -> bool {
T::supports_provider(provider)
}
fn run(
@@ -2345,6 +2290,7 @@ impl ThreadEventStream {
fn send_tool_call(
&self,
id: &LanguageModelToolUseId,
tool_name: &str,
title: SharedString,
kind: acp::ToolKind,
input: serde_json::Value,
@@ -2352,6 +2298,7 @@ impl ThreadEventStream {
self.0
.unbounded_send(Ok(ThreadEvent::ToolCall(Self::initial_tool_call(
id,
tool_name,
title.to_string(),
kind,
input,
@@ -2361,12 +2308,15 @@ impl ThreadEventStream {
fn initial_tool_call(
id: &LanguageModelToolUseId,
tool_name: &str,
title: String,
kind: acp::ToolKind,
input: serde_json::Value,
) -> acp::ToolCall {
acp::ToolCall {
meta: None,
meta: Some(serde_json::json!({
"tool_name": tool_name
})),
id: acp::ToolCallId(id.to_string().into()),
title,
kind,
@@ -2598,8 +2548,8 @@ impl From<&str> for UserMessageContent {
}
}
impl From<acp::ContentBlock> for UserMessageContent {
fn from(value: acp::ContentBlock) -> Self {
impl UserMessageContent {
pub fn from_content_block(value: acp::ContentBlock, path_style: PathStyle) -> Self {
match value {
acp::ContentBlock::Text(text_content) => Self::Text(text_content.text),
acp::ContentBlock::Image(image_content) => Self::Image(convert_image(image_content)),
@@ -2608,7 +2558,7 @@ impl From<acp::ContentBlock> for UserMessageContent {
Self::Text("[audio]".to_string())
}
acp::ContentBlock::ResourceLink(resource_link) => {
match MentionUri::parse(&resource_link.uri) {
match MentionUri::parse(&resource_link.uri, path_style) {
Ok(uri) => Self::Mention {
uri,
content: String::new(),
@@ -2621,7 +2571,7 @@ impl From<acp::ContentBlock> for UserMessageContent {
}
acp::ContentBlock::Resource(resource) => match resource.resource {
acp::EmbeddedResourceResource::TextResourceContents(resource) => {
match MentionUri::parse(&resource.uri) {
match MentionUri::parse(&resource.uri, path_style) {
Ok(uri) => Self::Mention {
uri,
content: resource.text,

View File

@@ -40,13 +40,19 @@ pub use web_search_tool::*;
macro_rules! tools {
($($tool:ty),* $(,)?) => {
/// A list of all built-in tool names
pub fn built_in_tool_names() -> impl Iterator<Item = String> {
pub fn supported_built_in_tool_names(provider: Option<language_model::LanguageModelProviderId>) -> impl Iterator<Item = String> {
[
$(
<$tool>::name().to_string(),
(if let Some(provider) = provider.as_ref() {
<$tool>::supports_provider(provider)
} else {
true
})
.then(|| <$tool>::name().to_string()),
)*
]
.into_iter()
.flatten()
}
/// A list of all built-in tools

View File

@@ -57,7 +57,7 @@ impl AgentTool for WebSearchTool {
}
/// We currently only support Zed Cloud as a provider.
fn supported_provider(&self, provider: &LanguageModelProviderId) -> bool {
fn supports_provider(provider: &LanguageModelProviderId) -> bool {
provider == &ZED_CLOUD_PROVIDER_ID
}

View File

@@ -38,6 +38,7 @@ language_model.workspace = true
language_models.workspace = true
log.workspace = true
project.workspace = true
release_channel.workspace = true
reqwest_client = { workspace = true, optional = true }
serde.workspace = true
serde_json.workspace = true

View File

@@ -9,9 +9,7 @@ use futures::io::BufReader;
use project::Project;
use project::agent_server_store::AgentServerCommand;
use serde::Deserialize;
use settings::{Settings as _, SettingsLocation};
use task::Shell;
use util::{ResultExt as _, get_default_system_shell_preferring_bash};
use util::ResultExt as _;
use std::path::PathBuf;
use std::{any::Any, cell::RefCell};
@@ -23,7 +21,7 @@ use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString, Task, WeakEntit
use acp_thread::{AcpThread, AuthRequired, LoadError, TerminalProviderEvent};
use terminal::TerminalBuilder;
use terminal::terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
use terminal::terminal_settings::{AlternateScroll, CursorShape};
#[derive(Debug, Error)]
#[error("Unsupported version")]
@@ -40,7 +38,7 @@ pub struct AcpConnection {
// NB: Don't move this into the wait_task, since we need to ensure the process is
// killed on drop (setting kill_on_drop on the command seems to not always work).
child: smol::process::Child,
_io_task: Task<Result<()>>,
_io_task: Task<Result<(), acp::Error>>,
_wait_task: Task<Result<()>>,
_stderr_task: Task<Result<()>>,
}
@@ -107,6 +105,14 @@ impl AcpConnection {
let sessions = Rc::new(RefCell::new(HashMap::default()));
let (release_channel, version) = cx.update(|cx| {
(
release_channel::ReleaseChannel::try_global(cx)
.map(|release_channel| release_channel.display_name()),
release_channel::AppVersion::global(cx).to_string(),
)
})?;
let client = ClientDelegate {
sessions: sessions.clone(),
cx: cx.clone(),
@@ -172,8 +178,14 @@ impl AcpConnection {
meta: Some(serde_json::json!({
// Experimental: Allow for rendering terminal output from the agents
"terminal_output": true,
"terminal-auth": true,
})),
},
client_info: Some(acp::Implementation {
name: "zed".to_owned(),
title: release_channel.map(|c| c.to_owned()),
version,
}),
meta: None,
})
.await?;
@@ -702,7 +714,11 @@ impl acp::Client for ClientDelegate {
.get(&notification.session_id)
.context("Failed to get session")?;
if let acp::SessionUpdate::CurrentModeUpdate { current_mode_id } = &notification.update {
if let acp::SessionUpdate::CurrentModeUpdate(acp::CurrentModeUpdate {
current_mode_id,
..
}) = &notification.update
{
if let Some(session_modes) = &session.session_modes {
session_modes.borrow_mut().current_mode_id = current_mode_id.clone();
} else {
@@ -816,62 +832,18 @@ impl acp::Client for ClientDelegate {
let thread = self.session_thread(&args.session_id)?;
let project = thread.read_with(&self.cx, |thread, _cx| thread.project().clone())?;
let mut env = if let Some(dir) = &args.cwd {
project
.update(&mut self.cx.clone(), |project, cx| {
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
project.directory_environment(&shell, dir.clone().into(), cx)
})?
.await
.unwrap_or_default()
} else {
Default::default()
};
// Disables paging for `git` and hopefully other commands
env.insert("PAGER".into(), "".into());
for var in args.env {
env.insert(var.name, var.value);
}
// Use remote shell or default system shell, as appropriate
let shell = project
.update(&mut self.cx.clone(), |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
.map(Shell::Program)
})?
.unwrap_or_else(|| Shell::Program(get_default_system_shell_preferring_bash()));
let is_windows = project
.read_with(&self.cx, |project, cx| project.path_style(cx).is_windows())
.unwrap_or(cfg!(windows));
let (task_command, task_args) = task::ShellBuilder::new(&shell, is_windows)
.redirect_stdin_to_dev_null()
.build(Some(args.command.clone()), &args.args);
let terminal_entity = project
.update(&mut self.cx.clone(), |project, cx| {
project.create_terminal_task(
task::SpawnInTerminal {
command: Some(task_command),
args: task_args,
cwd: args.cwd.clone(),
env,
..Default::default()
},
cx,
)
})?
.await?;
let terminal_entity = acp_thread::create_terminal_entity(
args.command.clone(),
&args.args,
args.env
.into_iter()
.map(|env| (env.name, env.value))
.collect(),
args.cwd.clone(),
&project,
&mut self.cx.clone(),
)
.await?;
// Register with renderer
let terminal_entity = thread.update(&mut self.cx.clone(), |thread, cx| {

View File

@@ -79,8 +79,6 @@ impl AgentServer for Codex {
root_dir.as_deref(),
extra_env,
delegate.status_tx,
// For now, report that there are no updates.
// (A future PR will use the GitHub Releases API to fetch them.)
delegate.new_version_available,
&mut cx.to_async(),
))

View File

@@ -41,7 +41,6 @@ pub struct AgentSettings {
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>,
pub preferred_completion_mode: CompletionMode,
@@ -174,7 +173,6 @@ impl Settings for AgentSettings {
always_allow_tool_actions: agent.always_allow_tool_actions.unwrap(),
notify_when_agent_waiting: agent.notify_when_agent_waiting.unwrap(),
play_sound_when_agent_done: agent.play_sound_when_agent_done.unwrap(),
stream_edits: agent.stream_edits.unwrap(),
single_file_review: agent.single_file_review.unwrap(),
model_parameters: agent.model_parameters,
preferred_completion_mode: agent.preferred_completion_mode.unwrap().into(),

View File

@@ -25,7 +25,7 @@ agent_settings.workspace = true
ai_onboarding.workspace = true
anyhow.workspace = true
arrayvec.workspace = true
assistant_context.workspace = true
assistant_text_thread.workspace = true
assistant_slash_command.workspace = true
assistant_slash_commands.workspace = true
audio.workspace = true
@@ -102,7 +102,7 @@ zed_actions.workspace = true
[dev-dependencies]
acp_thread = { workspace = true, features = ["test-support"] }
agent = { workspace = true, features = ["test-support"] }
assistant_context = { workspace = true, features = ["test-support"] }
assistant_text_thread = { workspace = true, features = ["test-support"] }
buffer_diff = { workspace = true, features = ["test-support"] }
db = { workspace = true, features = ["test-support"] }
editor = { workspace = true, features = ["test-support"] }

View File

@@ -253,17 +253,22 @@ impl ContextPickerCompletionProvider {
) -> Option<Completion> {
let project = workspace.read(cx).project().clone();
let label = CodeLabel::plain(symbol.name.clone(), None);
let abs_path = match &symbol.path {
SymbolLocation::InProject(project_path) => {
project.read(cx).absolute_path(&project_path, cx)?
}
let (abs_path, file_name) = match &symbol.path {
SymbolLocation::InProject(project_path) => (
project.read(cx).absolute_path(&project_path, cx)?,
project_path.path.file_name()?.to_string().into(),
),
SymbolLocation::OutsideProject {
abs_path,
signature: _,
} => PathBuf::from(abs_path.as_ref()),
} => (
PathBuf::from(abs_path.as_ref()),
abs_path.file_name().map(|f| f.to_string_lossy())?,
),
};
let label = build_symbol_label(&symbol.name, &file_name, symbol.range.start.0.row + 1, cx);
let uri = MentionUri::Symbol {
abs_path,
name: symbol.name.clone(),
@@ -570,6 +575,7 @@ impl ContextPickerCompletionProvider {
.unwrap_or_default();
let workspace = workspace.read(cx);
let project = workspace.project().read(cx);
let include_root_name = workspace.visible_worktrees(cx).count() > 1;
if let Some(agent_panel) = workspace.panel::<AgentPanel>(cx)
&& let Some(thread) = agent_panel.read(cx).active_agent_thread(cx)
@@ -596,7 +602,11 @@ impl ContextPickerCompletionProvider {
project
.worktree_for_id(project_path.worktree_id, cx)
.map(|worktree| {
let path_prefix = worktree.read(cx).root_name().into();
let path_prefix = if include_root_name {
worktree.read(cx).root_name().into()
} else {
RelPath::empty().into()
};
Match::File(FileMatch {
mat: fuzzy::PathMatch {
score: 1.,
@@ -674,6 +684,17 @@ impl ContextPickerCompletionProvider {
}
}
fn build_symbol_label(symbol_name: &str, file_name: &str, line: u32, cx: &App) -> CodeLabel {
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
let mut label = CodeLabelBuilder::default();
label.push_str(symbol_name, None);
label.push_str(" ", None);
label.push_str(&format!("{} L{}", file_name, line), comment_id);
label.build()
}
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
let mut label = CodeLabelBuilder::default();
@@ -812,9 +833,21 @@ impl CompletionProvider for ContextPickerCompletionProvider {
path: mat.path.clone(),
};
// If path is empty, this means we're matching with the root directory itself
// so we use the path_prefix as the name
let path_prefix = if mat.path.is_empty() {
project
.read(cx)
.worktree_for_id(project_path.worktree_id, cx)
.map(|wt| wt.read(cx).root_name().into())
.unwrap_or_else(|| mat.path_prefix.clone())
} else {
mat.path_prefix.clone()
};
Self::completion_for_path(
project_path,
&mat.path_prefix,
&path_prefix,
is_recent,
mat.is_dir,
source_range.clone(),

View File

@@ -402,7 +402,7 @@ mod tests {
use agent::HistoryStore;
use agent_client_protocol as acp;
use agent_settings::AgentSettings;
use assistant_context::ContextStore;
use assistant_text_thread::TextThreadStore;
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
use editor::{EditorSettings, RowInfo};
use fs::FakeFs;
@@ -466,8 +466,8 @@ mod tests {
connection.send_update(session_id, acp::SessionUpdate::ToolCall(tool_call), cx)
});
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let view_state = cx.new(|_cx| {
EntryViewState::new(

View File

@@ -1,4 +1,5 @@
use crate::{
ChatWithFollow,
acp::completion_provider::{ContextPickerCompletionProvider, SlashCommandCompletion},
context_picker::{ContextPickerAction, fetch_context_picker::fetch_url_content},
};
@@ -11,10 +12,10 @@ use assistant_slash_commands::codeblock_fence_for_path;
use collections::{HashMap, HashSet};
use editor::{
Addon, Anchor, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement,
EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, InlayId,
EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, Inlay,
MultiBuffer, ToOffset,
actions::Paste,
display_map::{Crease, CreaseId, FoldId, Inlay},
display_map::{Crease, CreaseId, FoldId},
};
use futures::{
FutureExt as _,
@@ -29,7 +30,8 @@ use language::{Buffer, Language, language_settings::InlayHintKind};
use language_model::LanguageModelImage;
use postage::stream::Stream as _;
use project::{
CompletionIntent, InlayHint, InlayHintLabel, Project, ProjectItem, ProjectPath, Worktree,
CompletionIntent, InlayHint, InlayHintLabel, InlayId, Project, ProjectItem, ProjectPath,
Worktree,
};
use prompt_store::{PromptId, PromptStore};
use rope::Point;
@@ -48,7 +50,7 @@ use text::OffsetRangeExt;
use theme::ThemeSettings;
use ui::{ButtonLike, TintColor, Toggleable, prelude::*};
use util::{ResultExt, debug_panic, rel_path::RelPath};
use workspace::{Workspace, notifications::NotifyResultExt as _};
use workspace::{CollaboratorId, Workspace, notifications::NotifyResultExt as _};
use zed_actions::agent::Chat;
pub struct MessageEditor {
@@ -75,7 +77,7 @@ pub enum MessageEditorEvent {
impl EventEmitter<MessageEditorEvent> for MessageEditor {}
const COMMAND_HINT_INLAY_ID: u32 = 0;
const COMMAND_HINT_INLAY_ID: InlayId = InlayId::Hint(0);
impl MessageEditor {
pub fn new(
@@ -151,7 +153,7 @@ impl MessageEditor {
let has_new_hint = !new_hints.is_empty();
editor.splice_inlays(
if has_hint {
&[InlayId::Hint(COMMAND_HINT_INLAY_ID)]
&[COMMAND_HINT_INLAY_ID]
} else {
&[]
},
@@ -628,12 +630,12 @@ impl MessageEditor {
path: PathBuf,
cx: &mut Context<Self>,
) -> Task<Result<Mention>> {
let context = self.history_store.update(cx, |store, cx| {
let text_thread_task = self.history_store.update(cx, |store, cx| {
store.load_text_thread(path.as_path().into(), cx)
});
cx.spawn(async move |_, cx| {
let context = context.await?;
let xml = context.update(cx, |context, cx| context.to_xml(cx))?;
let text_thread = text_thread_task.await?;
let xml = text_thread.update(cx, |text_thread, cx| text_thread.to_xml(cx))?;
Ok(Mention::Text {
content: xml,
tracked_buffers: Vec::new(),
@@ -701,7 +703,7 @@ impl MessageEditor {
let mut all_tracked_buffers = Vec::new();
let result = editor.update(cx, |editor, cx| {
let mut ix = 0;
let mut ix = text.chars().position(|c| !c.is_whitespace()).unwrap_or(0);
let mut chunks: Vec<acp::ContentBlock> = Vec::new();
let text = editor.text(cx);
editor.display_map.update(cx, |map, cx| {
@@ -713,15 +715,6 @@ impl MessageEditor {
let crease_range = crease.range().to_offset(&snapshot.buffer_snapshot());
if crease_range.start > ix {
//todo(): Custom slash command ContentBlock?
// let chunk = if prevent_slash_commands
// && ix == 0
// && parse_slash_command(&text[ix..]).is_some()
// {
// format!(" {}", &text[ix..crease_range.start]).into()
// } else {
// text[ix..crease_range.start].into()
// };
let chunk = text[ix..crease_range.start].into();
chunks.push(chunk);
}
@@ -782,15 +775,6 @@ impl MessageEditor {
}
if ix < text.len() {
//todo(): Custom slash command ContentBlock?
// let last_chunk = if prevent_slash_commands
// && ix == 0
// && parse_slash_command(&text[ix..]).is_some()
// {
// format!(" {}", text[ix..].trim_end())
// } else {
// text[ix..].trim_end().to_owned()
// };
let last_chunk = text[ix..].trim_end().to_owned();
if !last_chunk.is_empty() {
chunks.push(last_chunk.into());
@@ -830,6 +814,21 @@ impl MessageEditor {
self.send(cx);
}
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.send(cx);
}
fn cancel(&mut self, _: &editor::actions::Cancel, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(MessageEditorEvent::Cancel)
}
@@ -1061,6 +1060,7 @@ impl MessageEditor {
) {
self.clear(window, cx);
let path_style = self.project.read(cx).path_style(cx);
let mut text = String::new();
let mut mentions = Vec::new();
@@ -1073,7 +1073,8 @@ impl MessageEditor {
resource: acp::EmbeddedResourceResource::TextResourceContents(resource),
..
}) => {
let Some(mention_uri) = MentionUri::parse(&resource.uri).log_err() else {
let Some(mention_uri) = MentionUri::parse(&resource.uri, path_style).log_err()
else {
continue;
};
let start = text.len();
@@ -1089,7 +1090,9 @@ impl MessageEditor {
));
}
acp::ContentBlock::ResourceLink(resource) => {
if let Some(mention_uri) = MentionUri::parse(&resource.uri).log_err() {
if let Some(mention_uri) =
MentionUri::parse(&resource.uri, path_style).log_err()
{
let start = text.len();
write!(&mut text, "{}", mention_uri.as_link()).ok();
let end = text.len();
@@ -1104,7 +1107,7 @@ impl MessageEditor {
meta: _,
}) => {
let mention_uri = if let Some(uri) = uri {
MentionUri::parse(&uri)
MentionUri::parse(&uri, path_style)
} else {
Ok(MentionUri::PastedImage)
};
@@ -1289,6 +1292,7 @@ impl Render for MessageEditor {
div()
.key_context("MessageEditor")
.on_action(cx.listener(Self::chat))
.on_action(cx.listener(Self::chat_with_follow))
.on_action(cx.listener(Self::cancel))
.capture_action(cx.listener(Self::paste))
.flex_1()
@@ -1590,7 +1594,7 @@ mod tests {
use acp_thread::MentionUri;
use agent::{HistoryStore, outline};
use agent_client_protocol as acp;
use assistant_context::ContextStore;
use assistant_text_thread::TextThreadStore;
use editor::{AnchorRangeExt as _, Editor, EditorMode};
use fs::FakeFs;
use futures::StreamExt as _;
@@ -1621,8 +1625,8 @@ mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
@@ -1726,8 +1730,8 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
// Start with no available commands - simulating Claude which doesn't support slash commands
let available_commands = Rc::new(RefCell::new(vec![]));
@@ -1890,8 +1894,8 @@ mod tests {
let mut cx = VisualTestContext::from_window(*window, cx);
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
let available_commands = Rc::new(RefCell::new(vec![
acp::AvailableCommand {
@@ -2130,8 +2134,8 @@ mod tests {
opened_editors.push(buffer);
}
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| {
@@ -2178,10 +2182,10 @@ mod tests {
assert_eq!(
current_completion_labels(editor),
&[
format!("eight.txt dir{slash}b{slash}"),
format!("seven.txt dir{slash}b{slash}"),
format!("six.txt dir{slash}b{slash}"),
format!("five.txt dir{slash}b{slash}"),
format!("eight.txt b{slash}"),
format!("seven.txt b{slash}"),
format!("six.txt b{slash}"),
format!("five.txt b{slash}"),
]
);
editor.set_text("", window, cx);
@@ -2209,10 +2213,10 @@ mod tests {
assert_eq!(
current_completion_labels(editor),
&[
format!("eight.txt dir{slash}b{slash}"),
format!("seven.txt dir{slash}b{slash}"),
format!("six.txt dir{slash}b{slash}"),
format!("five.txt dir{slash}b{slash}"),
format!("eight.txt b{slash}"),
format!("seven.txt b{slash}"),
format!("six.txt b{slash}"),
format!("five.txt b{slash}"),
"Files & Directories".into(),
"Symbols".into(),
"Threads".into(),
@@ -2245,7 +2249,7 @@ mod tests {
assert!(editor.has_visible_completions_menu());
assert_eq!(
current_completion_labels(editor),
vec![format!("one.txt dir{slash}a{slash}")]
vec![format!("one.txt a{slash}")]
);
});
@@ -2292,7 +2296,10 @@ mod tests {
panic!("Unexpected mentions");
};
pretty_assertions::assert_eq!(content, "1");
pretty_assertions::assert_eq!(uri, &url_one.parse::<MentionUri>().unwrap());
pretty_assertions::assert_eq!(
uri,
&MentionUri::parse(&url_one, PathStyle::local()).unwrap()
);
}
let contents = message_editor
@@ -2313,7 +2320,10 @@ mod tests {
let [(uri, Mention::UriOnly)] = contents.as_slice() else {
panic!("Unexpected mentions");
};
pretty_assertions::assert_eq!(uri, &url_one.parse::<MentionUri>().unwrap());
pretty_assertions::assert_eq!(
uri,
&MentionUri::parse(&url_one, PathStyle::local()).unwrap()
);
}
cx.simulate_input(" ");
@@ -2374,7 +2384,10 @@ mod tests {
panic!("Unexpected mentions");
};
pretty_assertions::assert_eq!(content, "8");
pretty_assertions::assert_eq!(uri, &url_eight.parse::<MentionUri>().unwrap());
pretty_assertions::assert_eq!(
uri,
&MentionUri::parse(&url_eight, PathStyle::local()).unwrap()
);
}
editor.update(&mut cx, |editor, cx| {
@@ -2459,7 +2472,7 @@ mod tests {
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) @symbol ")
);
assert!(editor.has_visible_completions_menu());
assert_eq!(current_completion_labels(editor), &["MySymbol"]);
assert_eq!(current_completion_labels(editor), &["MySymbol one.txt L1"]);
});
editor.update_in(&mut cx, |editor, window, cx| {
@@ -2515,7 +2528,7 @@ mod tests {
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) @file x.png", symbol.to_uri())
);
assert!(editor.has_visible_completions_menu());
assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]);
assert_eq!(current_completion_labels(editor), &["x.png "]);
});
editor.update_in(&mut cx, |editor, window, cx| {
@@ -2557,7 +2570,7 @@ mod tests {
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) @file x.png", symbol.to_uri())
);
assert!(editor.has_visible_completions_menu());
assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]);
assert_eq!(current_completion_labels(editor), &["x.png "]);
});
editor.update_in(&mut cx, |editor, window, cx| {
@@ -2657,8 +2670,8 @@ mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
@@ -2733,4 +2746,62 @@ mod tests {
_ => panic!("Expected Text mention for small file"),
}
}
#[gpui::test]
async fn test_whitespace_trimming(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/project", json!({"file.rs": "fn main() {}"}))
.await;
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
MessageEditor::new(
workspace.downgrade(),
project.clone(),
history_store.clone(),
None,
Default::default(),
Default::default(),
"Test Agent".into(),
"Test",
EditorMode::AutoHeight {
min_lines: 1,
max_lines: None,
},
window,
cx,
)
})
});
let editor = message_editor.update(cx, |message_editor, _| message_editor.editor.clone());
cx.run_until_parked();
editor.update_in(cx, |editor, window, cx| {
editor.set_text(" hello world ", window, cx);
});
let (content, _) = message_editor
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
.await
.unwrap();
assert_eq!(
content,
vec![acp::ContentBlock::Text(acp::TextContent {
text: "hello world".into(),
annotations: None,
meta: None
})]
);
}
}

View File

@@ -194,7 +194,7 @@ impl Render for ModeSelector {
trigger_button,
Tooltip::element({
let focus_handle = self.focus_handle.clone();
move |window, cx| {
move |_window, cx| {
v_flex()
.gap_1()
.child(
@@ -205,10 +205,9 @@ impl Render for ModeSelector {
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.child(Label::new("Cycle Through Modes"))
.children(KeyBinding::for_action_in(
.child(KeyBinding::for_action_in(
&CycleModeSelector,
&focus_handle,
window,
cx,
)),
)
@@ -217,10 +216,9 @@ impl Render for ModeSelector {
.gap_2()
.justify_between()
.child(Label::new("Toggle Mode Menu"))
.children(KeyBinding::for_action_in(
.child(KeyBinding::for_action_in(
&ToggleProfileSelector,
&focus_handle,
window,
cx,
)),
)

View File

@@ -77,14 +77,8 @@ impl Render for AcpModelSelectorPopover {
.ml_0p5(),
)
.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
move |_window, cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
},
gpui::Corner::BottomRight,
cx,

View File

@@ -324,8 +324,8 @@ impl AcpThreadHistory {
HistoryEntry::AcpThread(thread) => self
.history_store
.update(cx, |this, cx| this.delete_thread(thread.id.clone(), cx)),
HistoryEntry::TextThread(context) => self.history_store.update(cx, |this, cx| {
this.delete_text_thread(context.path.clone(), cx)
HistoryEntry::TextThread(text_thread) => self.history_store.update(cx, |this, cx| {
this.delete_text_thread(text_thread.path.clone(), cx)
}),
};
task.detach_and_log_err(cx);
@@ -423,8 +423,8 @@ impl AcpThreadHistory {
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.tooltip(move |window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
.tooltip(move |_window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
})
.on_click(
cx.listener(move |this, _, _, cx| this.remove_thread(ix, cx)),
@@ -595,8 +595,8 @@ impl RenderOnce for AcpHistoryEntryElement {
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.tooltip(move |window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
.tooltip(move |_window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
})
.on_click({
let thread_view = self.thread_view.clone();
@@ -635,12 +635,12 @@ impl RenderOnce for AcpHistoryEntryElement {
});
}
}
HistoryEntry::TextThread(context) => {
HistoryEntry::TextThread(text_thread) => {
if let Some(panel) = workspace.read(cx).panel::<AgentPanel>(cx) {
panel.update(cx, |panel, cx| {
panel
.open_saved_text_thread(
context.path.clone(),
text_thread.path.clone(),
window,
cx,
)

View File

@@ -1259,6 +1259,7 @@ impl AcpThreadView {
.await?;
this.update_in(cx, |this, window, cx| {
this.send_impl(message_editor, window, cx);
this.focus_handle(cx).focus(window);
})?;
anyhow::Ok(())
})
@@ -1468,6 +1469,114 @@ impl AcpThreadView {
return;
};
// Check for the experimental "terminal-auth" _meta field
let auth_method = connection.auth_methods().iter().find(|m| m.id == method);
if let Some(auth_method) = auth_method {
if let Some(meta) = &auth_method.meta {
if let Some(terminal_auth) = meta.get("terminal-auth") {
// Extract terminal auth details from meta
if let (Some(command), Some(label)) = (
terminal_auth.get("command").and_then(|v| v.as_str()),
terminal_auth.get("label").and_then(|v| v.as_str()),
) {
let args = terminal_auth
.get("args")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
let env = terminal_auth
.get("env")
.and_then(|v| v.as_object())
.map(|obj| {
obj.iter()
.filter_map(|(k, v)| {
v.as_str().map(|val| (k.clone(), val.to_string()))
})
.collect::<HashMap<String, String>>()
})
.unwrap_or_default();
// Run SpawnInTerminal in the same dir as the ACP server
let cwd = connection
.clone()
.downcast::<agent_servers::AcpConnection>()
.map(|acp_conn| acp_conn.root_dir().to_path_buf());
// Build SpawnInTerminal from _meta
let login = task::SpawnInTerminal {
id: task::TaskId(format!("external-agent-{}-login", label)),
full_label: label.to_string(),
label: label.to_string(),
command: Some(command.to_string()),
args,
command_label: label.to_string(),
cwd,
env,
use_new_terminal: true,
allow_concurrent_runs: true,
hide: task::HideStrategy::Always,
..Default::default()
};
self.thread_error.take();
configuration_view.take();
pending_auth_method.replace(method.clone());
if let Some(workspace) = self.workspace.upgrade() {
let project = self.project.clone();
let authenticate = Self::spawn_external_agent_login(
login, workspace, project, false, true, window, cx,
);
cx.notify();
self.auth_task = Some(cx.spawn_in(window, {
let agent = self.agent.clone();
async move |this, cx| {
let result = authenticate.await;
match &result {
Ok(_) => telemetry::event!(
"Authenticate Agent Succeeded",
agent = agent.telemetry_id()
),
Err(_) => {
telemetry::event!(
"Authenticate Agent Failed",
agent = agent.telemetry_id(),
)
}
}
this.update_in(cx, |this, window, cx| {
if let Err(err) = result {
if let ThreadState::Unauthenticated {
pending_auth_method,
..
} = &mut this.thread_state
{
pending_auth_method.take();
}
this.handle_thread_error(err, cx);
} else {
this.reset(window, cx);
}
this.auth_task.take()
})
.ok();
}
}));
}
return;
}
}
}
}
if method.0.as_ref() == "gemini-api-key" {
let registry = LanguageModelRegistry::global(cx);
let provider = registry
@@ -1566,7 +1675,10 @@ impl AcpThreadView {
&& let Some(login) = self.login.clone()
{
if let Some(workspace) = self.workspace.upgrade() {
Self::spawn_external_agent_login(login, workspace, false, window, cx)
let project = self.project.clone();
Self::spawn_external_agent_login(
login, workspace, project, false, false, window, cx,
)
} else {
Task::ready(Ok(()))
}
@@ -1616,17 +1728,40 @@ impl AcpThreadView {
fn spawn_external_agent_login(
login: task::SpawnInTerminal,
workspace: Entity<Workspace>,
project: Entity<Project>,
previous_attempt: bool,
check_exit_code: bool,
window: &mut Window,
cx: &mut App,
) -> Task<Result<()>> {
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
return Task::ready(Ok(()));
};
let project = workspace.read(cx).project().clone();
window.spawn(cx, async move |cx| {
let mut task = login.clone();
if let Some(cmd) = &task.command {
// Have "node" command use Zed's managed Node runtime by default
if cmd == "node" {
let resolved_node_runtime = project
.update(cx, |project, cx| {
let agent_server_store = project.agent_server_store().clone();
agent_server_store.update(cx, |store, cx| {
store.node_runtime().map(|node_runtime| {
cx.background_spawn(async move {
node_runtime.binary_path().await
})
})
})
});
if let Ok(Some(resolve_task)) = resolved_node_runtime {
if let Ok(node_path) = resolve_task.await {
task.command = Some(node_path.to_string_lossy().to_string());
}
}
}
}
task.shell = task::Shell::WithArguments {
program: task.command.take().expect("login command should be set"),
args: std::mem::take(&mut task.args),
@@ -1644,44 +1779,65 @@ impl AcpThreadView {
})?;
let terminal = terminal.await?;
let mut exit_status = terminal
.read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
.fuse();
let logged_in = cx
.spawn({
let terminal = terminal.clone();
async move |cx| {
loop {
cx.background_executor().timer(Duration::from_secs(1)).await;
let content =
terminal.update(cx, |terminal, _cx| terminal.get_content())?;
if content.contains("Login successful")
|| content.contains("Type your message")
{
return anyhow::Ok(());
if check_exit_code {
// For extension-based auth, wait for the process to exit and check exit code
let exit_status = terminal
.read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
.await;
match exit_status {
Some(status) if status.success() => {
Ok(())
}
Some(status) => {
Err(anyhow!("Login command failed with exit code: {:?}", status.code()))
}
None => {
Err(anyhow!("Login command terminated without exit status"))
}
}
} else {
// For hardcoded agents (claude-login, gemini-cli): look for specific output
let mut exit_status = terminal
.read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
.fuse();
let logged_in = cx
.spawn({
let terminal = terminal.clone();
async move |cx| {
loop {
cx.background_executor().timer(Duration::from_secs(1)).await;
let content =
terminal.update(cx, |terminal, _cx| terminal.get_content())?;
if content.contains("Login successful")
|| content.contains("Type your message")
{
return anyhow::Ok(());
}
}
}
})
.fuse();
futures::pin_mut!(logged_in);
futures::select_biased! {
result = logged_in => {
if let Err(e) = result {
log::error!("{e}");
return Err(anyhow!("exited before logging in"));
}
}
})
.fuse();
futures::pin_mut!(logged_in);
futures::select_biased! {
result = logged_in => {
if let Err(e) = result {
log::error!("{e}");
_ = exit_status => {
if !previous_attempt && project.read_with(cx, |project, _| project.is_via_remote_server())? && login.label.contains("gemini") {
return cx.update(|window, cx| Self::spawn_external_agent_login(login, workspace, project.clone(), true, false, window, cx))?.await
}
return Err(anyhow!("exited before logging in"));
}
}
_ = exit_status => {
if !previous_attempt && project.read_with(cx, |project, _| project.is_via_remote_server())? && login.label.contains("gemini") {
return cx.update(|window, cx| Self::spawn_external_agent_login(login, workspace, true, window, cx))?.await
}
return Err(anyhow!("exited before logging in"));
}
terminal.update(cx, |terminal, _| terminal.kill_active_task())?;
Ok(())
}
terminal.update(cx, |terminal, _| terminal.kill_active_task())?;
Ok(())
})
}
@@ -2157,7 +2313,6 @@ impl AcpThreadView {
options,
entry_ix,
tool_call.id.clone(),
window,
cx,
))
.into_any(),
@@ -2558,7 +2713,6 @@ impl AcpThreadView {
options: &[acp::PermissionOption],
entry_ix: usize,
tool_call_id: acp::ToolCallId,
window: &Window,
cx: &Context<Self>,
) -> Div {
let is_first = self.thread().is_some_and(|thread| {
@@ -2615,7 +2769,7 @@ impl AcpThreadView {
seen_kinds.push(option.kind);
this.key_binding(
KeyBinding::for_action_in(action, &self.focus_handle, window, cx)
KeyBinding::for_action_in(action, &self.focus_handle, cx)
.map(|kb| kb.size(rems_from_px(10.))),
)
})
@@ -2796,12 +2950,11 @@ impl AcpThreadView {
.icon_size(IconSize::Small)
.icon_color(Color::Error)
.label_size(LabelSize::Small)
.tooltip(move |window, cx| {
.tooltip(move |_window, cx| {
Tooltip::with_meta(
"Stop This Command",
None,
"Also possible by placing your cursor inside the terminal and using regular terminal bindings.",
window,
cx,
)
})
@@ -3102,7 +3255,7 @@ impl AcpThreadView {
)
}
fn render_recent_history(&self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
fn render_recent_history(&self, cx: &mut Context<Self>) -> AnyElement {
let render_history = self
.agent
.clone()
@@ -3131,7 +3284,6 @@ impl AcpThreadView {
KeyBinding::for_action_in(
&OpenHistory,
&self.focus_handle(cx),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -3459,7 +3611,6 @@ impl AcpThreadView {
&changed_buffers,
self.edits_expanded,
pending_edits,
window,
cx,
))
.when(self.edits_expanded, |parent| {
@@ -3619,7 +3770,6 @@ impl AcpThreadView {
changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
expanded: bool,
pending_edits: bool,
window: &mut Window,
cx: &Context<Self>,
) -> Div {
const EDIT_NOT_READY_TOOLTIP_LABEL: &str = "Wait until file edits are complete.";
@@ -3695,12 +3845,11 @@ impl AcpThreadView {
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Review Changes",
&OpenAgentDiff,
&focus_handle,
window,
cx,
)
}
@@ -3718,13 +3867,8 @@ impl AcpThreadView {
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
})
.key_binding(
KeyBinding::for_action_in(
&RejectAll,
&focus_handle.clone(),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
KeyBinding::for_action_in(&RejectAll, &focus_handle.clone(), cx)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(move |this, _, window, cx| {
this.reject_all(&RejectAll, window, cx);
@@ -3738,7 +3882,7 @@ impl AcpThreadView {
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
})
.key_binding(
KeyBinding::for_action_in(&KeepAll, &focus_handle, window, cx)
KeyBinding::for_action_in(&KeepAll, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(move |this, _, window, cx| {
@@ -3968,12 +4112,11 @@ impl AcpThreadView {
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip({
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
expand_tooltip,
&ExpandMessageEditor,
&focus_handle,
window,
cx,
)
}
@@ -4198,8 +4341,8 @@ impl AcpThreadView {
IconButton::new("stop-generation", IconName::Stop)
.icon_color(Color::Error)
.style(ButtonStyle::Tinted(ui::TintColor::Error))
.tooltip(move |window, cx| {
Tooltip::for_action("Stop Generation", &editor::actions::Cancel, window, cx)
.tooltip(move |_window, cx| {
Tooltip::for_action("Stop Generation", &editor::actions::Cancel, cx)
})
.on_click(cx.listener(|this, _event, _, cx| this.cancel_generation(cx)))
.into_any_element()
@@ -4221,7 +4364,7 @@ impl AcpThreadView {
this.icon_color(Color::Accent)
}
})
.tooltip(move |window, cx| Tooltip::for_action(send_btn_tooltip, &Chat, window, cx))
.tooltip(move |_window, cx| Tooltip::for_action(send_btn_tooltip, &Chat, cx))
.on_click(cx.listener(|this, _, window, cx| {
this.send(window, cx);
}))
@@ -4282,15 +4425,14 @@ impl AcpThreadView {
.icon_color(Color::Muted)
.toggle_state(following)
.selected_icon_color(Some(Color::Custom(cx.theme().players().agent().cursor)))
.tooltip(move |window, cx| {
.tooltip(move |_window, cx| {
if following {
Tooltip::for_action(tooltip_label.clone(), &Follow, window, cx)
Tooltip::for_action(tooltip_label.clone(), &Follow, cx)
} else {
Tooltip::with_meta(
tooltip_label.clone(),
Some(&Follow),
"Track the agent's location as it reads and edits files.",
window,
cx,
)
}
@@ -4318,7 +4460,8 @@ impl AcpThreadView {
return;
};
if let Some(mention) = MentionUri::parse(&url).log_err() {
if let Some(mention) = MentionUri::parse(&url, workspace.read(cx).path_style(cx)).log_err()
{
workspace.update(cx, |workspace, cx| match mention {
MentionUri::File { abs_path } => {
let project = workspace.project();
@@ -5079,7 +5222,7 @@ impl AcpThreadView {
}
}
fn render_thread_error(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
fn render_thread_error(&self, cx: &mut Context<Self>) -> Option<Div> {
let content = match self.thread_error.as_ref()? {
ThreadError::Other(error) => self.render_any_thread_error(error.clone(), cx),
ThreadError::Refusal => self.render_refusal_error(cx),
@@ -5090,9 +5233,7 @@ impl AcpThreadView {
ThreadError::ModelRequestLimitReached(plan) => {
self.render_model_request_limit_reached_error(*plan, cx)
}
ThreadError::ToolUseLimitReached => {
self.render_tool_use_limit_reached_error(window, cx)?
}
ThreadError::ToolUseLimitReached => self.render_tool_use_limit_reached_error(cx)?,
};
Some(div().child(content))
@@ -5283,11 +5424,7 @@ impl AcpThreadView {
.dismiss_action(self.dismiss_error_button(cx))
}
fn render_tool_use_limit_reached_error(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Callout> {
fn render_tool_use_limit_reached_error(&self, cx: &mut Context<Self>) -> Option<Callout> {
let thread = self.as_native_thread(cx)?;
let supports_burn_mode = thread
.read(cx)
@@ -5314,7 +5451,6 @@ impl AcpThreadView {
KeyBinding::for_action_in(
&ContinueWithBurnMode,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
@@ -5338,13 +5474,8 @@ impl AcpThreadView {
.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.))),
KeyBinding::for_action_in(&ContinueThread, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(|this, _, _window, cx| {
this.resume_chat(cx);
@@ -5439,9 +5570,11 @@ impl AcpThreadView {
HistoryEntry::AcpThread(thread) => self.history_store.update(cx, |history, cx| {
history.delete_thread(thread.id.clone(), cx)
}),
HistoryEntry::TextThread(context) => self.history_store.update(cx, |history, cx| {
history.delete_text_thread(context.path.clone(), cx)
}),
HistoryEntry::TextThread(text_thread) => {
self.history_store.update(cx, |history, cx| {
history.delete_text_thread(text_thread.path.clone(), cx)
})
}
};
task.detach_and_log_err(cx);
}
@@ -5520,7 +5653,7 @@ impl Render for AcpThreadView {
.into_any(),
ThreadState::Loading { .. } => v_flex()
.flex_1()
.child(self.render_recent_history(window, cx))
.child(self.render_recent_history(cx))
.into_any(),
ThreadState::LoadError(e) => v_flex()
.flex_1()
@@ -5551,8 +5684,7 @@ impl Render for AcpThreadView {
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
.into_any()
} else {
this.child(self.render_recent_history(window, cx))
.into_any()
this.child(self.render_recent_history(cx)).into_any()
}
}),
})
@@ -5576,7 +5708,7 @@ impl Render for AcpThreadView {
Vec::<Empty>::new()
}
})
.children(self.render_thread_error(window, cx))
.children(self.render_thread_error(cx))
.when_some(
self.new_server_version_available.as_ref().filter(|_| {
!has_messages || !matches!(self.thread_state, ThreadState::Ready { .. })
@@ -5761,7 +5893,7 @@ fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
pub(crate) mod tests {
use acp_thread::StubAgentConnection;
use agent_client_protocol::SessionId;
use assistant_context::ContextStore;
use assistant_text_thread::TextThreadStore;
use editor::EditorSettings;
use fs::FakeFs;
use gpui::{EventEmitter, SemanticVersion, TestAppContext, VisualTestContext};
@@ -5924,10 +6056,10 @@ pub(crate) mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let context_store =
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
let text_thread_store =
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
let history_store =
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx)));
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(text_thread_store, cx)));
let thread_view = cx.update(|window, cx| {
cx.new(|cx| {
@@ -6005,9 +6137,12 @@ pub(crate) mod tests {
impl StubAgentServer<StubAgentConnection> {
fn default_response() -> Self {
let conn = StubAgentConnection::new();
conn.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
content: "Default response".into(),
}]);
conn.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
acp::ContentChunk {
content: "Default response".into(),
meta: None,
},
)]);
Self::new(conn)
}
}
@@ -6196,10 +6331,10 @@ pub(crate) mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let context_store =
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
let text_thread_store =
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
let history_store =
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx)));
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(text_thread_store, cx)));
let connection = Rc::new(StubAgentConnection::new());
let thread_view = cx.update(|window, cx| {
@@ -6358,13 +6493,16 @@ pub(crate) mod tests {
let connection = StubAgentConnection::new();
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
content: acp::ContentBlock::Text(acp::TextContent {
text: "Response".into(),
annotations: None,
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
acp::ContentChunk {
content: acp::ContentBlock::Text(acp::TextContent {
text: "Response".into(),
annotations: None,
meta: None,
}),
meta: None,
}),
}]);
},
)]);
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
add_to_workspace(thread_view.clone(), cx);
@@ -6448,13 +6586,16 @@ pub(crate) mod tests {
let connection = StubAgentConnection::new();
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
content: acp::ContentBlock::Text(acp::TextContent {
text: "Response".into(),
annotations: None,
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
acp::ContentChunk {
content: acp::ContentBlock::Text(acp::TextContent {
text: "Response".into(),
annotations: None,
meta: None,
}),
meta: None,
}),
}]);
},
)]);
let (thread_view, cx) =
setup_thread_view(StubAgentServer::new(connection.clone()), cx).await;
@@ -6492,13 +6633,16 @@ pub(crate) mod tests {
});
// Send
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
content: acp::ContentBlock::Text(acp::TextContent {
text: "New Response".into(),
annotations: None,
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
acp::ContentChunk {
content: acp::ContentBlock::Text(acp::TextContent {
text: "New Response".into(),
annotations: None,
meta: None,
}),
meta: None,
}),
}]);
},
)]);
user_message_editor.update_in(cx, |_editor, window, cx| {
window.dispatch_action(Box::new(Chat), cx);
@@ -6585,13 +6729,14 @@ pub(crate) mod tests {
cx.update(|_, cx| {
connection.send_update(
session_id.clone(),
acp::SessionUpdate::AgentMessageChunk {
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
content: acp::ContentBlock::Text(acp::TextContent {
text: "Response".into(),
annotations: None,
meta: None,
}),
},
meta: None,
}),
cx,
);
connection.end_turn(session_id, acp::StopReason::EndTurn);
@@ -6643,9 +6788,10 @@ pub(crate) mod tests {
cx.update(|_, cx| {
connection.send_update(
session_id.clone(),
acp::SessionUpdate::AgentMessageChunk {
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
content: "Message 1 resp".into(),
},
meta: None,
}),
cx,
);
});
@@ -6679,9 +6825,10 @@ pub(crate) mod tests {
// Simulate a response sent after beginning to cancel
connection.send_update(
session_id.clone(),
acp::SessionUpdate::AgentMessageChunk {
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
content: "onse".into(),
},
meta: None,
}),
cx,
);
});
@@ -6712,9 +6859,10 @@ pub(crate) mod tests {
cx.update(|_, cx| {
connection.send_update(
session_id.clone(),
acp::SessionUpdate::AgentMessageChunk {
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
content: "Message 2 response".into(),
},
meta: None,
}),
cx,
);
connection.end_turn(session_id.clone(), acp::StopReason::EndTurn);
@@ -6752,13 +6900,16 @@ pub(crate) mod tests {
init_test(cx);
let connection = StubAgentConnection::new();
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
content: acp::ContentBlock::Text(acp::TextContent {
text: "Response".into(),
annotations: None,
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
acp::ContentChunk {
content: acp::ContentBlock::Text(acp::TextContent {
text: "Response".into(),
annotations: None,
meta: None,
}),
meta: None,
}),
}]);
},
)]);
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
add_to_workspace(thread_view.clone(), cx);
@@ -6835,13 +6986,16 @@ pub(crate) mod tests {
init_test(cx);
let connection = StubAgentConnection::new();
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
content: acp::ContentBlock::Text(acp::TextContent {
text: "Response".into(),
annotations: None,
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
acp::ContentChunk {
content: acp::ContentBlock::Text(acp::TextContent {
text: "Response".into(),
annotations: None,
meta: None,
}),
meta: None,
}),
}]);
},
)]);
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
add_to_workspace(thread_view.clone(), cx);

View File

@@ -10,7 +10,7 @@ use settings::{OpenAiCompatibleSettingsContent, update_settings_file};
use ui::{
Banner, Checkbox, KeyBinding, Modal, ModalFooter, ModalHeader, Section, ToggleState, prelude::*,
};
use ui_input::SingleLineInput;
use ui_input::InputField;
use workspace::{ModalView, Workspace};
#[derive(Clone, Copy)]
@@ -33,9 +33,9 @@ impl LlmCompatibleProvider {
}
struct AddLlmProviderInput {
provider_name: Entity<SingleLineInput>,
api_url: Entity<SingleLineInput>,
api_key: Entity<SingleLineInput>,
provider_name: Entity<InputField>,
api_url: Entity<InputField>,
api_key: Entity<InputField>,
models: Vec<ModelInput>,
}
@@ -76,10 +76,10 @@ struct ModelCapabilityToggles {
}
struct ModelInput {
name: Entity<SingleLineInput>,
max_completion_tokens: Entity<SingleLineInput>,
max_output_tokens: Entity<SingleLineInput>,
max_tokens: Entity<SingleLineInput>,
name: Entity<InputField>,
max_completion_tokens: Entity<InputField>,
max_output_tokens: Entity<InputField>,
max_tokens: Entity<InputField>,
capabilities: ModelCapabilityToggles,
}
@@ -171,9 +171,9 @@ fn single_line_input(
text: Option<&str>,
window: &mut Window,
cx: &mut App,
) -> Entity<SingleLineInput> {
) -> Entity<InputField> {
cx.new(|cx| {
let input = SingleLineInput::new(window, cx, placeholder).label(label);
let input = InputField::new(window, cx, placeholder).label(label);
if let Some(text) = text {
input
.editor()
@@ -431,7 +431,7 @@ impl Focusable for AddLlmProviderModal {
impl ModalView for AddLlmProviderModal {}
impl Render for AddLlmProviderModal {
fn render(&mut self, window: &mut ui::Window, cx: &mut ui::Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut ui::Window, cx: &mut ui::Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
div()
@@ -484,7 +484,6 @@ impl Render for AddLlmProviderModal {
KeyBinding::for_action_in(
&menu::Cancel,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -499,7 +498,6 @@ impl Render for AddLlmProviderModal {
KeyBinding::for_action_in(
&menu::Confirm,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -757,12 +755,7 @@ mod tests {
models: Vec<(&str, &str, &str, &str)>,
cx: &mut VisualTestContext,
) -> Option<SharedString> {
fn set_text(
input: &Entity<SingleLineInput>,
text: &str,
window: &mut Window,
cx: &mut App,
) {
fn set_text(input: &Entity<InputField>, text: &str, window: &mut Window, cx: &mut App) {
input.update(cx, |input, cx| {
input.editor().update(cx, |editor, cx| {
editor.set_text(text, window, cx);

View File

@@ -566,7 +566,7 @@ impl ConfigureContextServerModal {
.into_any_element()
}
fn render_modal_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> ModalFooter {
fn render_modal_footer(&self, cx: &mut Context<Self>) -> ModalFooter {
let focus_handle = self.focus_handle(cx);
let is_connecting = matches!(self.state, State::Waiting);
@@ -584,12 +584,11 @@ impl ConfigureContextServerModal {
.icon_size(IconSize::Small)
.tooltip({
let repository_url = repository_url.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::with_meta(
"Open Repository",
None,
repository_url.clone(),
window,
cx,
)
}
@@ -616,7 +615,7 @@ impl ConfigureContextServerModal {
},
)
.key_binding(
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, window, cx)
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(
@@ -634,7 +633,7 @@ impl ConfigureContextServerModal {
)
.disabled(is_connecting)
.key_binding(
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, window, cx)
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(
@@ -709,7 +708,7 @@ impl Render for ConfigureContextServerModal {
State::Error(error) => Self::render_modal_error(error.clone()),
}),
)
.footer(self.render_modal_footer(window, cx)),
.footer(self.render_modal_footer(cx)),
)
}
}

View File

@@ -7,6 +7,7 @@ use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profil
use editor::Editor;
use fs::Fs;
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, prelude::*};
use language_model::LanguageModel;
use settings::Settings as _;
use ui::{
KeyBinding, ListItem, ListItemSpacing, ListSeparator, Navigable, NavigableEntry, prelude::*,
@@ -96,6 +97,7 @@ pub struct NewProfileMode {
pub struct ManageProfilesModal {
fs: Arc<dyn Fs>,
context_server_registry: Entity<ContextServerRegistry>,
active_model: Option<Arc<dyn LanguageModel>>,
focus_handle: FocusHandle,
mode: Mode,
}
@@ -109,9 +111,14 @@ impl ManageProfilesModal {
workspace.register_action(|workspace, action: &ManageProfiles, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
let fs = workspace.app_state().fs.clone();
let active_model = panel
.read(cx)
.active_native_agent_thread(cx)
.and_then(|thread| thread.read(cx).model().cloned());
let context_server_registry = panel.read(cx).context_server_registry().clone();
workspace.toggle_modal(window, cx, |window, cx| {
let mut this = Self::new(fs, context_server_registry, window, cx);
let mut this = Self::new(fs, active_model, context_server_registry, window, cx);
if let Some(profile_id) = action.customize_tools.clone() {
this.configure_builtin_tools(profile_id, window, cx);
@@ -125,6 +132,7 @@ impl ManageProfilesModal {
pub fn new(
fs: Arc<dyn Fs>,
active_model: Option<Arc<dyn LanguageModel>>,
context_server_registry: Entity<ContextServerRegistry>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -133,6 +141,7 @@ impl ManageProfilesModal {
Self {
fs,
active_model,
context_server_registry,
focus_handle,
mode: Mode::choose_profile(window, cx),
@@ -228,9 +237,11 @@ impl ManageProfilesModal {
let tool_picker = cx.new(|cx| {
let delegate = ToolPickerDelegate::builtin_tools(
//todo: This causes the web search tool to show up even it only works when using zed hosted models
agent::built_in_tool_names()
.map(|s| s.into())
.collect::<Vec<_>>(),
agent::supported_built_in_tool_names(
self.active_model.as_ref().map(|model| model.provider_id()),
)
.map(|s| s.into())
.collect::<Vec<_>>(),
self.fs.clone(),
profile_id.clone(),
profile,
@@ -341,10 +352,9 @@ impl ManageProfilesModal {
.size(LabelSize::Small)
.color(Color::Muted),
)
.children(KeyBinding::for_action_in(
.child(KeyBinding::for_action_in(
&menu::Confirm,
&self.focus_handle,
window,
cx,
)),
)
@@ -638,14 +648,13 @@ impl ManageProfilesModal {
)
.child(Label::new("Go Back"))
.end_slot(
div().children(
div().child(
KeyBinding::for_action_in(
&menu::Cancel,
&self.focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
.size(rems_from_px(12.)),
),
)
.on_click({
@@ -689,14 +698,9 @@ impl Render for ManageProfilesModal {
)
.child(Label::new("Go Back"))
.end_slot(
div().children(
KeyBinding::for_action_in(
&menu::Cancel,
&self.focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
div().child(
KeyBinding::for_action_in(&menu::Cancel, &self.focus_handle, cx)
.size(rems_from_px(12.)),
),
)
.on_click({

View File

@@ -576,16 +576,22 @@ impl Item for AgentDiffPane {
});
}
fn can_split(&self) -> bool {
true
}
fn clone_on_split(
&self,
_workspace_id: Option<workspace::WorkspaceId>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Entity<Self>>
) -> Task<Option<Entity<Self>>>
where
Self: Sized,
{
Some(cx.new(|cx| Self::new(self.thread.clone(), self.workspace.clone(), window, cx)))
Task::ready(Some(cx.new(|cx| {
Self::new(self.thread.clone(), self.workspace.clone(), window, cx)
})))
}
fn is_dirty(&self, cx: &App) -> bool {
@@ -669,7 +675,7 @@ impl Item for AgentDiffPane {
}
impl Render for AgentDiffPane {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_empty = self.multibuffer.read(cx).is_empty();
let focus_handle = &self.focus_handle;
@@ -702,7 +708,6 @@ impl Render for AgentDiffPane {
.key_binding(KeyBinding::for_action_in(
&ToggleFocus,
&focus_handle.clone(),
window,
cx,
))
.on_click(|_event, window, cx| {
@@ -719,14 +724,7 @@ fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControl
let thread = thread.clone();
Arc::new(
move |row,
status: &DiffHunkStatus,
hunk_range,
is_created_file,
line_height,
editor: &Entity<Editor>,
window: &mut Window,
cx: &mut App| {
move |row, status, hunk_range, is_created_file, line_height, editor, _, cx| {
{
render_diff_hunk_controls(
row,
@@ -736,7 +734,6 @@ fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControl
line_height,
&thread,
editor,
window,
cx,
)
}
@@ -752,7 +749,6 @@ fn render_diff_hunk_controls(
line_height: Pixels,
thread: &AgentDiffThread,
editor: &Entity<Editor>,
window: &mut Window,
cx: &mut App,
) -> AnyElement {
let editor = editor.clone();
@@ -775,13 +771,8 @@ fn render_diff_hunk_controls(
Button::new(("reject", row as u64), "Reject")
.disabled(is_created_file)
.key_binding(
KeyBinding::for_action_in(
&Reject,
&editor.read(cx).focus_handle(cx),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
KeyBinding::for_action_in(&Reject, &editor.read(cx).focus_handle(cx), cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click({
let editor = editor.clone();
@@ -802,7 +793,7 @@ fn render_diff_hunk_controls(
}),
Button::new(("keep", row as u64), "Keep")
.key_binding(
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), window, cx)
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click({
@@ -833,14 +824,8 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Next Hunk",
&GoToHunk,
&focus_handle,
window,
cx,
)
move |_window, cx| {
Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
}
})
.on_click({
@@ -869,12 +854,11 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Previous Hunk",
&GoToPreviousHunk,
&focus_handle,
window,
cx,
)
}
@@ -1039,7 +1023,7 @@ impl ToolbarItemView for AgentDiffToolbar {
}
impl Render for AgentDiffToolbar {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let spinner_icon = div()
.px_0p5()
.id("generating")
@@ -1114,7 +1098,6 @@ impl Render for AgentDiffToolbar {
KeyBinding::for_action_in(
&RejectAll,
&editor_focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
@@ -1129,7 +1112,6 @@ impl Render for AgentDiffToolbar {
KeyBinding::for_action_in(
&KeepAll,
&editor_focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
@@ -1206,13 +1188,8 @@ impl Render for AgentDiffToolbar {
.child(
Button::new("reject-all", "Reject All")
.key_binding({
KeyBinding::for_action_in(
&RejectAll,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
KeyBinding::for_action_in(&RejectAll, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.)))
})
.on_click(cx.listener(|this, _, window, cx| {
this.dispatch_action(&RejectAll, window, cx)
@@ -1221,13 +1198,8 @@ impl Render for AgentDiffToolbar {
.child(
Button::new("keep-all", "Keep All")
.key_binding({
KeyBinding::for_action_in(
&KeepAll,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
KeyBinding::for_action_in(&KeepAll, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.)))
})
.on_click(cx.listener(|this, _, window, cx| {
this.dispatch_action(&KeepAll, window, cx)

View File

@@ -96,14 +96,8 @@ impl Render for AgentModelSelector {
.color(color)
.size(IconSize::XSmall),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
move |_window, cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
},
gpui::Corner::TopRight,
cx,

View File

@@ -6,8 +6,11 @@ use std::sync::Arc;
use acp_thread::AcpThread;
use agent::{ContextServerRegistry, DbThreadMetadata, HistoryEntry, HistoryStore};
use db::kvp::{Dismissable, KEY_VALUE_STORE};
use project::agent_server_store::{
AgentServerCommand, AllAgentServersSettings, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME,
use project::{
ExternalAgentServerName,
agent_server_store::{
AgentServerCommand, AllAgentServersSettings, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME,
},
};
use serde::{Deserialize, Serialize};
use settings::{
@@ -36,11 +39,13 @@ use crate::{
use agent_settings::AgentSettings;
use ai_onboarding::AgentPanelOnboarding;
use anyhow::{Result, anyhow};
use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
use assistant_slash_command::SlashCommandWorkingSet;
use assistant_text_thread::{TextThread, TextThreadEvent, TextThreadSummary};
use client::{UserStore, zed_urls};
use cloud_llm_client::{Plan, PlanV1, PlanV2, UsageLimit};
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
use extension::ExtensionEvents;
use extension_host::ExtensionStore;
use fs::Fs;
use gpui::{
Action, AnyElement, App, AsyncWindowContext, Corner, DismissEvent, Entity, EventEmitter,
@@ -67,7 +72,9 @@ use workspace::{
};
use zed_actions::{
DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,
agent::{OpenAcpOnboardingModal, OpenOnboardingModal, OpenSettings, ResetOnboarding},
agent::{
OpenAcpOnboardingModal, OpenOnboardingModal, OpenSettings, ResetAgentZoom, ResetOnboarding,
},
assistant::{OpenRulesLibrary, ToggleFocus},
};
@@ -188,6 +195,13 @@ pub fn init(cx: &mut App) {
})
.register_action(|_workspace, _: &ResetTrialEndUpsell, _window, cx| {
TrialEndUpsell::set_dismissed(false, cx);
})
.register_action(|workspace, _: &ResetAgentZoom, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
panel.update(cx, |panel, cx| {
panel.reset_agent_zoom(window, cx);
});
}
});
},
)
@@ -199,7 +213,7 @@ enum ActiveView {
thread_view: Entity<AcpThreadView>,
},
TextThread {
context_editor: Entity<TextThreadEditor>,
text_thread_editor: Entity<TextThreadEditor>,
title_editor: Entity<Editor>,
buffer_search_bar: Entity<BufferSearchBar>,
_subscriptions: Vec<gpui::Subscription>,
@@ -301,13 +315,13 @@ impl ActiveView {
}
pub fn text_thread(
context_editor: Entity<TextThreadEditor>,
text_thread_editor: Entity<TextThreadEditor>,
acp_history_store: Entity<agent::HistoryStore>,
language_registry: Arc<LanguageRegistry>,
window: &mut Window,
cx: &mut App,
) -> Self {
let title = context_editor.read(cx).title(cx).to_string();
let title = text_thread_editor.read(cx).title(cx).to_string();
let editor = cx.new(|cx| {
let mut editor = Editor::single_line(window, cx);
@@ -323,7 +337,7 @@ impl ActiveView {
let subscriptions = vec![
window.subscribe(&editor, cx, {
{
let context_editor = context_editor.clone();
let text_thread_editor = text_thread_editor.clone();
move |editor, event, window, cx| match event {
EditorEvent::BufferEdited => {
if suppress_first_edit {
@@ -332,19 +346,19 @@ impl ActiveView {
}
let new_summary = editor.read(cx).text(cx);
context_editor.update(cx, |context_editor, cx| {
context_editor
.context()
.update(cx, |assistant_context, cx| {
assistant_context.set_custom_summary(new_summary, cx);
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor
.text_thread()
.update(cx, |text_thread, cx| {
text_thread.set_custom_summary(new_summary, cx);
})
})
}
EditorEvent::Blurred => {
if editor.read(cx).text(cx).is_empty() {
let summary = context_editor
let summary = text_thread_editor
.read(cx)
.context()
.text_thread()
.read(cx)
.summary()
.or_default();
@@ -358,17 +372,17 @@ impl ActiveView {
}
}
}),
window.subscribe(&context_editor.read(cx).context().clone(), cx, {
window.subscribe(&text_thread_editor.read(cx).text_thread().clone(), cx, {
let editor = editor.clone();
move |assistant_context, event, window, cx| match event {
ContextEvent::SummaryGenerated => {
let summary = assistant_context.read(cx).summary().or_default();
move |text_thread, event, window, cx| match event {
TextThreadEvent::SummaryGenerated => {
let summary = text_thread.read(cx).summary().or_default();
editor.update(cx, |editor, cx| {
editor.set_text(summary, window, cx);
})
}
ContextEvent::PathChanged { old_path, new_path } => {
TextThreadEvent::PathChanged { old_path, new_path } => {
acp_history_store.update(cx, |history_store, cx| {
if let Some(old_path) = old_path {
history_store
@@ -389,11 +403,11 @@ impl ActiveView {
let buffer_search_bar =
cx.new(|cx| BufferSearchBar::new(Some(language_registry), window, cx));
buffer_search_bar.update(cx, |buffer_search_bar, cx| {
buffer_search_bar.set_active_pane_item(Some(&context_editor), window, cx)
buffer_search_bar.set_active_pane_item(Some(&text_thread_editor), window, cx)
});
Self::TextThread {
context_editor,
text_thread_editor,
title_editor: editor,
buffer_search_bar,
_subscriptions: subscriptions,
@@ -410,7 +424,7 @@ pub struct AgentPanel {
language_registry: Arc<LanguageRegistry>,
acp_history: Entity<AcpThreadHistory>,
history_store: Entity<agent::HistoryStore>,
text_thread_store: Entity<assistant_context::ContextStore>,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
context_server_registry: Entity<ContextServerRegistry>,
inline_assist_context_store: Entity<ContextStore>,
@@ -422,6 +436,7 @@ pub struct AgentPanel {
agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
agent_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
agent_navigation_menu: Option<Entity<ContextMenu>>,
_extension_subscription: Option<Subscription>,
width: Option<Pixels>,
height: Option<Pixels>,
zoomed: bool,
@@ -474,7 +489,7 @@ impl AgentPanel {
let text_thread_store = workspace
.update(cx, |workspace, cx| {
let project = workspace.project().clone();
assistant_context::ContextStore::new(
assistant_text_thread::TextThreadStore::new(
project,
prompt_builder,
slash_commands,
@@ -512,7 +527,7 @@ impl AgentPanel {
fn new(
workspace: &Workspace,
text_thread_store: Entity<assistant_context::ContextStore>,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -565,8 +580,8 @@ impl AgentPanel {
DefaultView::TextThread => {
let context = text_thread_store.update(cx, |store, cx| store.create(cx));
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project.clone(), cx).unwrap();
let context_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_context(
let text_thread_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_text_thread(
context,
fs.clone(),
workspace.clone(),
@@ -579,7 +594,7 @@ impl AgentPanel {
editor
});
ActiveView::text_thread(
context_editor,
text_thread_editor,
history_store.clone(),
language_registry.clone(),
window,
@@ -632,7 +647,24 @@ impl AgentPanel {
)
});
Self {
// Subscribe to extension events to sync agent servers when extensions change
let extension_subscription = if let Some(extension_events) = ExtensionEvents::try_global(cx)
{
Some(
cx.subscribe(&extension_events, |this, _source, event, cx| match event {
extension::Event::ExtensionInstalled(_)
| extension::Event::ExtensionUninstalled(_)
| extension::Event::ExtensionsInstalledChanged => {
this.sync_agent_servers_from_extensions(cx);
}
_ => {}
}),
)
} else {
None
};
let mut panel = Self {
active_view,
workspace,
user_store,
@@ -650,6 +682,7 @@ impl AgentPanel {
agent_panel_menu_handle: PopoverMenuHandle::default(),
agent_navigation_menu_handle: PopoverMenuHandle::default(),
agent_navigation_menu: None,
_extension_subscription: extension_subscription,
width: None,
height: None,
zoomed: false,
@@ -659,7 +692,11 @@ impl AgentPanel {
history_store,
selected_agent: AgentType::default(),
loading: false,
}
};
// Initial sync of agent servers from extensions
panel.sync_agent_servers_from_extensions(cx);
panel
}
pub fn toggle_focus(
@@ -736,8 +773,8 @@ impl AgentPanel {
.log_err()
.flatten();
let context_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_context(
let text_thread_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_text_thread(
context,
self.fs.clone(),
self.workspace.clone(),
@@ -757,7 +794,7 @@ impl AgentPanel {
self.set_active_view(
ActiveView::text_thread(
context_editor.clone(),
text_thread_editor.clone(),
self.history_store.clone(),
self.language_registry.clone(),
window,
@@ -766,7 +803,7 @@ impl AgentPanel {
window,
cx,
);
context_editor.focus_handle(cx).focus(window);
text_thread_editor.focus_handle(cx).focus(window);
}
fn external_thread(
@@ -905,20 +942,20 @@ impl AgentPanel {
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let context = self
let text_thread_task = self
.history_store
.update(cx, |store, cx| store.load_text_thread(path, cx));
cx.spawn_in(window, async move |this, cx| {
let context = context.await?;
let text_thread = text_thread_task.await?;
this.update_in(cx, |this, window, cx| {
this.open_text_thread(context, window, cx);
this.open_text_thread(text_thread, window, cx);
})
})
}
pub(crate) fn open_text_thread(
&mut self,
context: Entity<AssistantContext>,
text_thread: Entity<TextThread>,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -926,8 +963,8 @@ impl AgentPanel {
.log_err()
.flatten();
let editor = cx.new(|cx| {
TextThreadEditor::for_context(
context,
TextThreadEditor::for_text_thread(
text_thread,
self.fs.clone(),
self.workspace.clone(),
self.project.clone(),
@@ -965,8 +1002,10 @@ impl AgentPanel {
ActiveView::ExternalAgentThread { thread_view } => {
thread_view.focus_handle(cx).focus(window);
}
ActiveView::TextThread { context_editor, .. } => {
context_editor.focus_handle(cx).focus(window);
ActiveView::TextThread {
text_thread_editor, ..
} => {
text_thread_editor.focus_handle(cx).focus(window);
}
ActiveView::History | ActiveView::Configuration => {}
}
@@ -1029,13 +1068,21 @@ impl AgentPanel {
update_settings_file(self.fs.clone(), cx, move |settings, cx| {
let agent_ui_font_size =
ThemeSettings::get_global(cx).agent_ui_font_size(cx) + delta;
let agent_buffer_font_size =
ThemeSettings::get_global(cx).agent_buffer_font_size(cx) + delta;
let _ = settings
.theme
.agent_ui_font_size
.insert(theme::clamp_font_size(agent_ui_font_size).into());
let _ = settings
.theme
.agent_buffer_font_size
.insert(theme::clamp_font_size(agent_buffer_font_size).into());
});
} else {
theme::adjust_agent_ui_font_size(cx, |size| size + delta);
theme::adjust_agent_buffer_font_size(cx, |size| size + delta);
}
}
WhichFontSize::BufferFont => {
@@ -1056,12 +1103,19 @@ impl AgentPanel {
if action.persist {
update_settings_file(self.fs.clone(), cx, move |settings, _| {
settings.theme.agent_ui_font_size = None;
settings.theme.agent_buffer_font_size = None;
});
} else {
theme::reset_agent_ui_font_size(cx);
theme::reset_agent_buffer_font_size(cx);
}
}
pub fn reset_agent_zoom(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
theme::reset_agent_ui_font_size(cx);
theme::reset_agent_buffer_font_size(cx);
}
pub fn toggle_zoom(&mut self, _: &ToggleZoom, window: &mut Window, cx: &mut Context<Self>) {
if self.zoomed {
cx.emit(PanelEvent::ZoomOut);
@@ -1183,9 +1237,11 @@ impl AgentPanel {
}
}
pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
pub(crate) fn active_text_thread_editor(&self) -> Option<Entity<TextThreadEditor>> {
match &self.active_view {
ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
ActiveView::TextThread {
text_thread_editor, ..
} => Some(text_thread_editor.clone()),
_ => None,
}
}
@@ -1206,16 +1262,16 @@ impl AgentPanel {
let new_is_special = new_is_history || new_is_config;
match &new_view {
ActiveView::TextThread { context_editor, .. } => {
self.history_store.update(cx, |store, cx| {
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
store.push_recently_opened_entry(
agent::HistoryEntryId::TextThread(path.clone()),
cx,
)
}
})
}
ActiveView::TextThread {
text_thread_editor, ..
} => self.history_store.update(cx, |store, cx| {
if let Some(path) = text_thread_editor.read(cx).text_thread().read(cx).path() {
store.push_recently_opened_entry(
agent::HistoryEntryId::TextThread(path.clone()),
cx,
)
}
}),
ActiveView::ExternalAgentThread { .. } => {}
ActiveView::History | ActiveView::Configuration => {}
}
@@ -1305,6 +1361,31 @@ impl AgentPanel {
self.selected_agent.clone()
}
fn sync_agent_servers_from_extensions(&mut self, cx: &mut Context<Self>) {
if let Some(extension_store) = ExtensionStore::try_global(cx) {
let (manifests, extensions_dir) = {
let store = extension_store.read(cx);
let installed = store.installed_extensions();
let manifests: Vec<_> = installed
.iter()
.map(|(id, entry)| (id.clone(), entry.manifest.clone()))
.collect();
let extensions_dir = paths::extensions_dir().join("installed");
(manifests, extensions_dir)
};
self.project.update(cx, |project, cx| {
project.agent_server_store().update(cx, |store, cx| {
let manifest_refs: Vec<_> = manifests
.iter()
.map(|(id, manifest)| (id.as_ref(), manifest.as_ref()))
.collect();
store.sync_extension_agents(manifest_refs, extensions_dir, cx);
});
});
}
}
pub fn new_agent_thread(
&mut self,
agent: AgentType,
@@ -1372,7 +1453,9 @@ impl Focusable for AgentPanel {
match &self.active_view {
ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
ActiveView::History => self.acp_history.focus_handle(cx),
ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
ActiveView::TextThread {
text_thread_editor, ..
} => text_thread_editor.focus_handle(cx),
ActiveView::Configuration => {
if let Some(configuration) = self.configuration.as_ref() {
configuration.focus_handle(cx)
@@ -1395,6 +1478,10 @@ impl Panel for AgentPanel {
"AgentPanel"
}
fn panel_key() -> &'static str {
AGENT_PANEL_KEY
}
fn position(&self, _window: &Window, cx: &App) -> DockPosition {
agent_panel_dock_position(cx)
}
@@ -1503,17 +1590,17 @@ impl AgentPanel {
}
ActiveView::TextThread {
title_editor,
context_editor,
text_thread_editor,
..
} => {
let summary = context_editor.read(cx).context().read(cx).summary();
let summary = text_thread_editor.read(cx).text_thread().read(cx).summary();
match summary {
ContextSummary::Pending => Label::new(ContextSummary::DEFAULT)
TextThreadSummary::Pending => Label::new(TextThreadSummary::DEFAULT)
.color(Color::Muted)
.truncate()
.into_any_element(),
ContextSummary::Content(summary) => {
TextThreadSummary::Content(summary) => {
if summary.done {
div()
.w_full()
@@ -1526,17 +1613,17 @@ impl AgentPanel {
.into_any_element()
}
}
ContextSummary::Error => h_flex()
TextThreadSummary::Error => h_flex()
.w_full()
.child(title_editor.clone())
.child(
IconButton::new("retry-summary-generation", IconName::RotateCcw)
.icon_size(IconSize::Small)
.on_click({
let context_editor = context_editor.clone();
let text_thread_editor = text_thread_editor.clone();
move |_, _window, cx| {
context_editor.update(cx, |context_editor, cx| {
context_editor.regenerate_summary(cx);
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor.regenerate_summary(cx);
});
}
})
@@ -1591,12 +1678,11 @@ impl AgentPanel {
.icon_size(IconSize::Small),
{
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Toggle Agent Menu",
&ToggleOptionsMenu,
&focus_handle,
window,
cx,
)
}
@@ -1660,7 +1746,7 @@ impl AgentPanel {
.separator();
menu = menu
.action("Rules", Box::new(OpenRulesLibrary::default()))
.action("Rules", Box::new(OpenRulesLibrary::default()))
.action("Settings", Box::new(OpenSettings))
.separator()
.action(full_screen_label, Box::new(ToggleZoom));
@@ -1687,12 +1773,11 @@ impl AgentPanel {
.trigger_with_tooltip(
IconButton::new("agent-nav-menu", icon).icon_size(IconSize::Small),
{
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Toggle Recent Threads",
&ToggleNavigationMenu,
&focus_handle,
window,
cx,
)
}
@@ -1726,8 +1811,8 @@ impl AgentPanel {
this.go_back(&workspace::GoBack, window, cx);
}))
.tooltip({
move |window, cx| {
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, window, cx)
move |_window, cx| {
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, cx)
}
})
}
@@ -1736,6 +1821,16 @@ impl AgentPanel {
let agent_server_store = self.project.read(cx).agent_server_store().clone();
let focus_handle = self.focus_handle(cx);
// Get custom icon path for selected agent before building menu (to avoid borrow issues)
let selected_agent_custom_icon =
if let AgentType::Custom { name, .. } = &self.selected_agent {
agent_server_store
.read(cx)
.agent_icon(&ExternalAgentServerName(name.clone()))
} else {
None
};
let active_thread = match &self.active_view {
ActiveView::ExternalAgentThread { thread_view } => {
thread_view.read(cx).as_native_thread(cx)
@@ -1748,14 +1843,8 @@ impl AgentPanel {
IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
{
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"New…",
&ToggleNewThreadMenu,
&focus_handle,
window,
cx,
)
move |_window, cx| {
Tooltip::for_action_in("New…", &ToggleNewThreadMenu, &focus_handle, cx)
}
},
)
@@ -1774,8 +1863,7 @@ impl AgentPanel {
let active_thread = active_thread.clone();
Some(ContextMenu::build(window, cx, |menu, _window, cx| {
menu
.context(focus_handle.clone())
menu.context(focus_handle.clone())
.header("Zed Agent")
.when_some(active_thread, |this, active_thread| {
let thread = active_thread.read(cx);
@@ -1932,83 +2020,110 @@ impl AgentPanel {
}),
)
.map(|mut menu| {
let agent_names = agent_server_store
.read(cx)
let agent_server_store_read = agent_server_store.read(cx);
let agent_names = agent_server_store_read
.external_agents()
.filter(|name| {
name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME && name.0 != CODEX_NAME
name.0 != GEMINI_NAME
&& name.0 != CLAUDE_CODE_NAME
&& name.0 != CODEX_NAME
})
.cloned()
.collect::<Vec<_>>();
let custom_settings = cx.global::<SettingsStore>().get::<AllAgentServersSettings>(None).custom.clone();
let custom_settings = cx
.global::<SettingsStore>()
.get::<AllAgentServersSettings>(None)
.custom
.clone();
for agent_name in agent_names {
menu = menu.item(
ContextMenuEntry::new(format!("New {} Thread", agent_name))
.icon(IconName::Terminal)
.icon_color(Color::Muted)
.disabled(is_via_collab)
.handler({
let workspace = workspace.clone();
let agent_name = agent_name.clone();
let custom_settings = custom_settings.clone();
move |window, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(panel) =
workspace.panel::<AgentPanel>(cx)
{
panel.update(cx, |panel, cx| {
panel.new_agent_thread(
AgentType::Custom {
name: agent_name.clone().into(),
command: custom_settings
.get(&agent_name.0)
.map(|settings| {
settings.command.clone()
})
.unwrap_or(placeholder_command()),
},
window,
cx,
);
});
}
});
}
let icon_path = agent_server_store_read.agent_icon(&agent_name);
let mut entry =
ContextMenuEntry::new(format!("New {} Thread", agent_name));
if let Some(icon_path) = icon_path {
entry = entry.custom_icon_svg(icon_path);
} else {
entry = entry.icon(IconName::Terminal);
}
entry = entry
.icon_color(Color::Muted)
.disabled(is_via_collab)
.handler({
let workspace = workspace.clone();
let agent_name = agent_name.clone();
let custom_settings = custom_settings.clone();
move |window, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(panel) =
workspace.panel::<AgentPanel>(cx)
{
panel.update(cx, |panel, cx| {
panel.new_agent_thread(
AgentType::Custom {
name: agent_name
.clone()
.into(),
command: custom_settings
.get(&agent_name.0)
.map(|settings| {
settings
.command
.clone()
})
.unwrap_or(
placeholder_command(
),
),
},
window,
cx,
);
});
}
});
}
}),
);
}
});
menu = menu.item(entry);
}
menu
})
.separator().link(
"Add Other Agents",
OpenBrowser {
url: zed_urls::external_agents_docs(cx),
}
.boxed_clone(),
)
.separator()
.link(
"Add Other Agents",
OpenBrowser {
url: zed_urls::external_agents_docs(cx),
}
.boxed_clone(),
)
}))
}
});
let selected_agent_label = self.selected_agent.label();
let has_custom_icon = selected_agent_custom_icon.is_some();
let selected_agent = div()
.id("selected_agent_icon")
.when_some(self.selected_agent.icon(), |this, icon| {
.when_some(selected_agent_custom_icon, |this, icon_path| {
let label = selected_agent_label.clone();
this.px(DynamicSpacing::Base02.rems(cx))
.child(Icon::new(icon).color(Color::Muted))
.tooltip(move |window, cx| {
Tooltip::with_meta(
selected_agent_label.clone(),
None,
"Selected Agent",
window,
cx,
)
.child(Icon::from_external_svg(icon_path).color(Color::Muted))
.tooltip(move |_window, cx| {
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
})
})
.when(!has_custom_icon, |this| {
this.when_some(self.selected_agent.icon(), |this, icon| {
let label = selected_agent_label.clone();
this.px(DynamicSpacing::Base02.rems(cx))
.child(Icon::new(icon).color(Color::Muted))
.tooltip(move |_window, cx| {
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
})
})
})
.into_any_element();
h_flex()
@@ -2182,7 +2297,6 @@ impl AgentPanel {
border_bottom: bool,
configuration_error: &ConfigurationError,
focus_handle: &FocusHandle,
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
let zed_provider_configured = AgentSettings::get_global(cx)
@@ -2231,7 +2345,7 @@ impl AgentPanel {
.style(ButtonStyle::Tinted(ui::TintColor::Warning))
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(&OpenSettings, focus_handle, window, cx)
KeyBinding::for_action_in(&OpenSettings, focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(|_event, window, cx| {
@@ -2249,7 +2363,7 @@ impl AgentPanel {
fn render_text_thread(
&self,
context_editor: &Entity<TextThreadEditor>,
text_thread_editor: &Entity<TextThreadEditor>,
buffer_search_bar: &Entity<BufferSearchBar>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -2283,7 +2397,7 @@ impl AgentPanel {
)
})
})
.child(context_editor.clone())
.child(text_thread_editor.clone())
.child(self.render_drag_target(cx))
}
@@ -2359,10 +2473,12 @@ impl AgentPanel {
thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
});
}
ActiveView::TextThread { context_editor, .. } => {
context_editor.update(cx, |context_editor, cx| {
ActiveView::TextThread {
text_thread_editor, ..
} => {
text_thread_editor.update(cx, |text_thread_editor, cx| {
TextThreadEditor::insert_dragged_files(
context_editor,
text_thread_editor,
paths,
added_worktrees,
window,
@@ -2433,7 +2549,7 @@ impl Render for AgentPanel {
.child(self.render_drag_target(cx)),
ActiveView::History => parent.child(self.acp_history.clone()),
ActiveView::TextThread {
context_editor,
text_thread_editor,
buffer_search_bar,
..
} => {
@@ -2449,7 +2565,6 @@ impl Render for AgentPanel {
true,
err,
&self.focus_handle(cx),
window,
cx,
))
} else {
@@ -2457,7 +2572,7 @@ impl Render for AgentPanel {
}
})
.child(self.render_text_thread(
context_editor,
text_thread_editor,
buffer_search_bar,
window,
cx,
@@ -2535,17 +2650,17 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
pub struct ConcreteAssistantPanelDelegate;
impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
fn active_context_editor(
fn active_text_thread_editor(
&self,
workspace: &mut Workspace,
_window: &mut Window,
cx: &mut Context<Workspace>,
) -> Option<Entity<TextThreadEditor>> {
let panel = workspace.panel::<AgentPanel>(cx)?;
panel.read(cx).active_context_editor()
panel.read(cx).active_text_thread_editor()
}
fn open_saved_context(
fn open_local_text_thread(
&self,
workspace: &mut Workspace,
path: Arc<Path>,
@@ -2561,10 +2676,10 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
})
}
fn open_remote_context(
fn open_remote_text_thread(
&self,
_workspace: &mut Workspace,
_context_id: assistant_context::ContextId,
_text_thread_id: assistant_text_thread::TextThreadId,
_window: &mut Window,
_cx: &mut Context<Workspace>,
) -> Task<Result<Entity<TextThreadEditor>>> {
@@ -2595,15 +2710,15 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
thread_view.update(cx, |thread_view, cx| {
thread_view.insert_selections(window, cx);
});
} else if let Some(context_editor) = panel.active_context_editor() {
} else if let Some(text_thread_editor) = panel.active_text_thread_editor() {
let snapshot = buffer.read(cx).snapshot(cx);
let selection_ranges = selection_ranges
.into_iter()
.map(|range| range.to_point(&snapshot))
.collect::<Vec<_>>();
context_editor.update(cx, |context_editor, cx| {
context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor.quote_ranges(selection_ranges, snapshot, window, cx)
});
}
});

View File

@@ -130,12 +130,6 @@ actions!(
]
);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Action)]
#[action(namespace = agent)]
#[action(deprecated_aliases = ["assistant::QuoteSelection"])]
/// Quotes the current selection in the agent panel's message editor.
pub struct QuoteSelection;
/// Creates a new conversation thread, optionally based on an existing thread.
#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
#[action(namespace = agent)]
@@ -256,7 +250,7 @@ pub fn init(
) {
AgentSettings::register(cx);
assistant_context::init(client.clone(), cx);
assistant_text_thread::init(client.clone(), cx);
rules_library::init(cx);
if !is_eval {
// Initializing the language model from the user settings messes with the eval, so we only initialize them when

View File

@@ -1,5 +1,5 @@
use agent::outline;
use assistant_context::AssistantContext;
use assistant_text_thread::TextThread;
use futures::future;
use futures::{FutureExt, future::Shared};
use gpui::{App, AppContext as _, ElementId, Entity, SharedString, Task};
@@ -581,7 +581,7 @@ impl Display for ThreadContext {
#[derive(Debug, Clone)]
pub struct TextThreadContextHandle {
pub context: Entity<AssistantContext>,
pub text_thread: Entity<TextThread>,
pub context_id: ContextId,
}
@@ -595,20 +595,20 @@ pub struct TextThreadContext {
impl TextThreadContextHandle {
// pub fn lookup_key() ->
pub fn eq_for_key(&self, other: &Self) -> bool {
self.context == other.context
self.text_thread == other.text_thread
}
pub fn hash_for_key<H: Hasher>(&self, state: &mut H) {
self.context.hash(state)
self.text_thread.hash(state)
}
pub fn title(&self, cx: &App) -> SharedString {
self.context.read(cx).summary().or_default()
self.text_thread.read(cx).summary().or_default()
}
fn load(self, cx: &App) -> Task<Option<AgentContext>> {
let title = self.title(cx);
let text = self.context.read(cx).to_xml(cx);
let text = self.text_thread.read(cx).to_xml(cx);
let context = AgentContext::TextThread(TextThreadContext {
title,
text: text.into(),

View File

@@ -662,6 +662,7 @@ pub(crate) fn recent_context_picker_entries(
let mut recent = Vec::with_capacity(6);
let workspace = workspace.read(cx);
let project = workspace.project().read(cx);
let include_root_name = workspace.visible_worktrees(cx).count() > 1;
recent.extend(
workspace
@@ -675,9 +676,16 @@ pub(crate) fn recent_context_picker_entries(
.filter_map(|(project_path, _)| {
project
.worktree_for_id(project_path.worktree_id, cx)
.map(|worktree| RecentEntry::File {
project_path,
path_prefix: worktree.read(cx).root_name().into(),
.map(|worktree| {
let path_prefix = if include_root_name {
worktree.read(cx).root_name().into()
} else {
RelPath::empty().into()
};
RecentEntry::File {
project_path,
path_prefix,
}
})
}),
);

View File

@@ -655,13 +655,12 @@ impl ContextPickerCompletionProvider {
let SymbolLocation::InProject(symbol_path) = &symbol.path else {
return None;
};
let path_prefix = workspace
let _path_prefix = workspace
.read(cx)
.project()
.read(cx)
.worktree_for_id(symbol_path.worktree_id, cx)?
.read(cx)
.root_name();
.worktree_for_id(symbol_path.worktree_id, cx)?;
let path_prefix = RelPath::empty();
let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory(
&symbol_path.path,
@@ -818,9 +817,21 @@ impl CompletionProvider for ContextPickerCompletionProvider {
return None;
}
// If path is empty, this means we're matching with the root directory itself
// so we use the path_prefix as the name
let path_prefix = if mat.path.is_empty() {
project
.read(cx)
.worktree_for_id(project_path.worktree_id, cx)
.map(|wt| wt.read(cx).root_name().into())
.unwrap_or_else(|| mat.path_prefix.clone())
} else {
mat.path_prefix.clone()
};
Some(Self::completion_for_path(
project_path,
&mat.path_prefix,
&path_prefix,
is_recent,
mat.is_dir,
excerpt_id,
@@ -1309,10 +1320,10 @@ mod tests {
assert_eq!(
current_completion_labels(editor),
&[
format!("seven.txt dir{slash}b{slash}"),
format!("six.txt dir{slash}b{slash}"),
format!("five.txt dir{slash}b{slash}"),
format!("four.txt dir{slash}a{slash}"),
format!("seven.txt b{slash}"),
format!("six.txt b{slash}"),
format!("five.txt b{slash}"),
format!("four.txt a{slash}"),
"Files & Directories".into(),
"Symbols".into(),
"Fetch".into()
@@ -1344,7 +1355,7 @@ mod tests {
assert!(editor.has_visible_completions_menu());
assert_eq!(
current_completion_labels(editor),
vec![format!("one.txt dir{slash}a{slash}")]
vec![format!("one.txt a{slash}")]
);
});
@@ -1356,12 +1367,12 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) ")
format!("Lorem [@one.txt](@file:a{slash}one.txt) ")
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
vec![Point::new(0, 6)..Point::new(0, 37)]
vec![Point::new(0, 6)..Point::new(0, 33)]
);
});
@@ -1370,12 +1381,12 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) ")
format!("Lorem [@one.txt](@file:a{slash}one.txt) ")
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
vec![Point::new(0, 6)..Point::new(0, 37)]
vec![Point::new(0, 6)..Point::new(0, 33)]
);
});
@@ -1384,12 +1395,12 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum "),
format!("Lorem [@one.txt](@file:a{slash}one.txt) Ipsum "),
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
vec![Point::new(0, 6)..Point::new(0, 37)]
vec![Point::new(0, 6)..Point::new(0, 33)]
);
});
@@ -1398,12 +1409,12 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum @file "),
format!("Lorem [@one.txt](@file:a{slash}one.txt) Ipsum @file "),
);
assert!(editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
vec![Point::new(0, 6)..Point::new(0, 37)]
vec![Point::new(0, 6)..Point::new(0, 33)]
);
});
@@ -1416,14 +1427,14 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) ")
format!("Lorem [@one.txt](@file:a{slash}one.txt) Ipsum [@seven.txt](@file:b{slash}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, 45)..Point::new(0, 80)
Point::new(0, 6)..Point::new(0, 33),
Point::new(0, 41)..Point::new(0, 72)
]
);
});
@@ -1433,14 +1444,14 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) \n@")
format!("Lorem [@one.txt](@file:a{slash}one.txt) Ipsum [@seven.txt](@file:b{slash}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, 45)..Point::new(0, 80)
Point::new(0, 6)..Point::new(0, 33),
Point::new(0, 41)..Point::new(0, 72)
]
);
});
@@ -1454,20 +1465,203 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) \n[@six.txt](@file:dir{slash}b{slash}six.txt) ")
format!("Lorem [@one.txt](@file:a{slash}one.txt) Ipsum [@seven.txt](@file:b{slash}seven.txt) \n[@six.txt](@file:b{slash}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, 45)..Point::new(0, 80),
Point::new(1, 0)..Point::new(1, 31)
Point::new(0, 6)..Point::new(0, 33),
Point::new(0, 41)..Point::new(0, 72),
Point::new(1, 0)..Point::new(1, 27)
]
);
});
}
#[gpui::test]
async fn test_context_completion_provider_multiple_worktrees(cx: &mut TestAppContext) {
init_test(cx);
let app_state = cx.update(AppState::test);
cx.update(|cx| {
language::init(cx);
editor::init(cx);
workspace::init(app_state.clone(), cx);
Project::init_settings(cx);
});
app_state
.fs
.as_fake()
.insert_tree(
path!("/project1"),
json!({
"a": {
"one.txt": "",
"two.txt": "",
}
}),
)
.await;
app_state
.fs
.as_fake()
.insert_tree(
path!("/project2"),
json!({
"b": {
"three.txt": "",
"four.txt": "",
}
}),
)
.await;
let project = Project::test(
app_state.fs.clone(),
[path!("/project1").as_ref(), path!("/project2").as_ref()],
cx,
)
.await;
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let workspace = window.root(cx).unwrap();
let worktrees = project.update(cx, |project, cx| {
let worktrees = project.worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 2);
worktrees
});
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
let slash = PathStyle::local().separator();
for (worktree_idx, paths) in [
vec![rel_path("a/one.txt"), rel_path("a/two.txt")],
vec![rel_path("b/three.txt"), rel_path("b/four.txt")],
]
.iter()
.enumerate()
{
let worktree_id = worktrees[worktree_idx].read_with(&cx, |wt, _| wt.id());
for path in paths {
workspace
.update_in(&mut cx, |workspace, window, cx| {
workspace.open_path(
ProjectPath {
worktree_id,
path: (*path).into(),
},
None,
false,
window,
cx,
)
})
.await
.unwrap();
}
}
let editor = workspace.update_in(&mut cx, |workspace, window, cx| {
let editor = cx.new(|cx| {
Editor::new(
editor::EditorMode::full(),
multi_buffer::MultiBuffer::build_simple("", cx),
None,
window,
cx,
)
});
workspace.active_pane().update(cx, |pane, cx| {
pane.add_item(
Box::new(cx.new(|_| AtMentionEditor(editor.clone()))),
true,
true,
None,
window,
cx,
);
});
editor
});
let context_store = cx.new(|_| ContextStore::new(project.downgrade()));
let editor_entity = editor.downgrade();
editor.update_in(&mut cx, |editor, window, cx| {
window.focus(&editor.focus_handle(cx));
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
workspace.downgrade(),
context_store.downgrade(),
None,
None,
editor_entity,
None,
))));
});
cx.simulate_input("@");
// With multiple worktrees, we should see the project name as prefix
editor.update(&mut cx, |editor, cx| {
assert_eq!(editor.text(cx), "@");
assert!(editor.has_visible_completions_menu());
let labels = current_completion_labels(editor);
assert!(
labels.contains(&format!("four.txt project2{slash}b{slash}")),
"Expected 'four.txt project2{slash}b{slash}' in labels: {:?}",
labels
);
assert!(
labels.contains(&format!("three.txt project2{slash}b{slash}")),
"Expected 'three.txt project2{slash}b{slash}' in labels: {:?}",
labels
);
});
editor.update_in(&mut cx, |editor, window, cx| {
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
});
cx.run_until_parked();
editor.update(&mut cx, |editor, cx| {
assert_eq!(editor.text(cx), "@file ");
assert!(editor.has_visible_completions_menu());
});
cx.simulate_input("one");
editor.update(&mut cx, |editor, cx| {
assert_eq!(editor.text(cx), "@file one");
assert!(editor.has_visible_completions_menu());
assert_eq!(
current_completion_labels(editor),
vec![format!("one.txt project1{slash}a{slash}")]
);
});
editor.update_in(&mut cx, |editor, window, cx| {
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
});
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
format!("[@one.txt](@file:project1{slash}a{slash}one.txt) ")
);
assert!(!editor.has_visible_completions_menu());
});
}
fn fold_ranges(editor: &Editor, cx: &mut App) -> Vec<Range<Point>> {
let snapshot = editor.buffer().read(cx).snapshot(cx);
editor.display_map.update(cx, |display_map, cx| {

View File

@@ -197,34 +197,50 @@ pub(crate) fn search_files(
if query.is_empty() {
let workspace = workspace.read(cx);
let project = workspace.project().read(cx);
let visible_worktrees = workspace.visible_worktrees(cx).collect::<Vec<_>>();
let include_root_name = visible_worktrees.len() > 1;
let recent_matches = workspace
.recent_navigation_history(Some(10), cx)
.into_iter()
.filter_map(|(project_path, _)| {
let worktree = project.worktree_for_id(project_path.worktree_id, cx)?;
Some(FileMatch {
.map(|(project_path, _)| {
let path_prefix = if include_root_name {
project
.worktree_for_id(project_path.worktree_id, cx)
.map(|wt| wt.read(cx).root_name().into())
.unwrap_or_else(|| RelPath::empty().into())
} else {
RelPath::empty().into()
};
FileMatch {
mat: PathMatch {
score: 0.,
positions: Vec::new(),
worktree_id: project_path.worktree_id.to_usize(),
path: project_path.path,
path_prefix: worktree.read(cx).root_name().into(),
path_prefix,
distance_to_relative_ancestor: 0,
is_dir: false,
},
is_recent: true,
})
}
});
let file_matches = project.worktrees(cx).flat_map(|worktree| {
let file_matches = visible_worktrees.into_iter().flat_map(|worktree| {
let worktree = worktree.read(cx);
let path_prefix: Arc<RelPath> = if include_root_name {
worktree.root_name().into()
} else {
RelPath::empty().into()
};
worktree.entries(false, 0).map(move |entry| FileMatch {
mat: PathMatch {
score: 0.,
positions: Vec::new(),
worktree_id: worktree.id().to_usize(),
path: entry.path.clone(),
path_prefix: worktree.root_name().into(),
path_prefix: path_prefix.clone(),
distance_to_relative_ancestor: 0,
is_dir: entry.is_dir(),
},
@@ -235,6 +251,7 @@ pub(crate) fn search_files(
Task::ready(recent_matches.chain(file_matches).collect())
} else {
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
let include_root_name = worktrees.len() > 1;
let candidate_sets = worktrees
.into_iter()
.map(|worktree| {
@@ -243,7 +260,7 @@ pub(crate) fn search_files(
PathMatchCandidateSet {
snapshot: worktree.snapshot(),
include_ignored: worktree.root_entry().is_some_and(|entry| entry.is_ignored),
include_root_name: true,
include_root_name,
candidates: project::Candidates::Entries,
}
})
@@ -276,6 +293,12 @@ pub fn extract_file_name_and_directory(
path_prefix: &RelPath,
path_style: PathStyle,
) -> (SharedString, Option<SharedString>) {
// If path is empty, this means we're matching with the root directory itself
// so we use the path_prefix as the name
if path.is_empty() && !path_prefix.is_empty() {
return (path_prefix.display(path_style).to_string().into(), None);
}
let full_path = path_prefix.join(path);
let file_name = full_path.file_name().unwrap_or_default();
let display_path = full_path.display(path_style);

View File

@@ -5,7 +5,7 @@ use crate::context::{
};
use agent_client_protocol as acp;
use anyhow::{Context as _, Result, anyhow};
use assistant_context::AssistantContext;
use assistant_text_thread::TextThread;
use collections::{HashSet, IndexSet};
use futures::{self, FutureExt};
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
@@ -200,13 +200,13 @@ impl ContextStore {
pub fn add_text_thread(
&mut self,
context: Entity<AssistantContext>,
text_thread: Entity<TextThread>,
remove_if_exists: bool,
cx: &mut Context<Self>,
) -> Option<AgentContextHandle> {
let context_id = self.next_context_id.post_inc();
let context = AgentContextHandle::TextThread(TextThreadContextHandle {
context,
text_thread,
context_id,
});
@@ -353,21 +353,15 @@ impl ContextStore {
);
};
}
// SuggestedContext::Thread { thread, name: _ } => {
// if let Some(thread) = thread.upgrade() {
// let context_id = self.next_context_id.post_inc();
// self.insert_context(
// AgentContextHandle::Thread(ThreadContextHandle { thread, context_id }),
// cx,
// );
// }
// }
SuggestedContext::TextThread { context, name: _ } => {
if let Some(context) = context.upgrade() {
SuggestedContext::TextThread {
text_thread,
name: _,
} => {
if let Some(text_thread) = text_thread.upgrade() {
let context_id = self.next_context_id.post_inc();
self.insert_context(
AgentContextHandle::TextThread(TextThreadContextHandle {
context,
text_thread,
context_id,
}),
cx,
@@ -392,7 +386,7 @@ impl ContextStore {
// }
AgentContextHandle::TextThread(text_thread_context) => {
self.context_text_thread_paths
.extend(text_thread_context.context.read(cx).path().cloned());
.extend(text_thread_context.text_thread.read(cx).path().cloned());
}
_ => {}
}
@@ -414,7 +408,7 @@ impl ContextStore {
.remove(thread_context.thread.read(cx).id());
}
AgentContextHandle::TextThread(text_thread_context) => {
if let Some(path) = text_thread_context.context.read(cx).path() {
if let Some(path) = text_thread_context.text_thread.read(cx).path() {
self.context_text_thread_paths.remove(path);
}
}
@@ -538,13 +532,9 @@ pub enum SuggestedContext {
icon_path: Option<SharedString>,
buffer: WeakEntity<Buffer>,
},
// Thread {
// name: SharedString,
// thread: WeakEntity<Thread>,
// },
TextThread {
name: SharedString,
context: WeakEntity<AssistantContext>,
text_thread: WeakEntity<TextThread>,
},
}
@@ -552,7 +542,6 @@ impl SuggestedContext {
pub fn name(&self) -> &SharedString {
match self {
Self::File { name, .. } => name,
// Self::Thread { name, .. } => name,
Self::TextThread { name, .. } => name,
}
}
@@ -560,7 +549,6 @@ impl SuggestedContext {
pub fn icon_path(&self) -> Option<SharedString> {
match self {
Self::File { icon_path, .. } => icon_path.clone(),
// Self::Thread { .. } => None,
Self::TextThread { .. } => None,
}
}
@@ -568,7 +556,6 @@ impl SuggestedContext {
pub fn kind(&self) -> ContextKind {
match self {
Self::File { .. } => ContextKind::File,
// Self::Thread { .. } => ContextKind::Thread,
Self::TextThread { .. } => ContextKind::TextThread,
}
}

View File

@@ -132,19 +132,19 @@ impl ContextStrip {
let workspace = self.workspace.upgrade()?;
let panel = workspace.read(cx).panel::<AgentPanel>(cx)?.read(cx);
if let Some(active_context_editor) = panel.active_context_editor() {
let context = active_context_editor.read(cx).context();
let weak_context = context.downgrade();
let context = context.read(cx);
let path = context.path()?;
if let Some(active_text_thread_editor) = panel.active_text_thread_editor() {
let text_thread = active_text_thread_editor.read(cx).text_thread();
let weak_text_thread = text_thread.downgrade();
let text_thread = text_thread.read(cx);
let path = text_thread.path()?;
if self.context_store.read(cx).includes_text_thread(path) {
return None;
}
Some(SuggestedContext::TextThread {
name: context.summary().or_default(),
context: weak_context,
name: text_thread.summary().or_default(),
text_thread: weak_text_thread,
})
} else {
None
@@ -332,7 +332,7 @@ impl ContextStrip {
AgentContextHandle::TextThread(text_thread_context) => {
workspace.update(cx, |workspace, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
let context = text_thread_context.context.clone();
let context = text_thread_context.text_thread.clone();
window.defer(cx, move |window, cx| {
panel.update(cx, |panel, cx| {
panel.open_text_thread(context, window, cx)
@@ -483,12 +483,11 @@ impl Render for ContextStrip {
.style(ui::ButtonStyle::Filled),
{
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
window,
cx,
)
}
@@ -558,12 +557,11 @@ impl Render for ContextStrip {
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Remove All Context",
&RemoveAllContext,
&focus_handle,
window,
cx,
)
}

View File

@@ -1508,8 +1508,8 @@ impl InlineAssistant {
return Some(InlineAssistTarget::Terminal(terminal_view));
}
let context_editor = agent_panel
.and_then(|panel| panel.read(cx).active_context_editor())
let text_thread_editor = agent_panel
.and_then(|panel| panel.read(cx).active_text_thread_editor())
.and_then(|editor| {
let editor = &editor.read(cx).editor().clone();
if editor.read(cx).is_focused(window) {
@@ -1519,8 +1519,8 @@ impl InlineAssistant {
}
});
if let Some(context_editor) = context_editor {
Some(InlineAssistTarget::Editor(context_editor))
if let Some(text_thread_editor) = text_thread_editor {
Some(InlineAssistTarget::Editor(text_thread_editor))
} else if let Some(workspace_editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))

View File

@@ -468,12 +468,11 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
.tooltip(move |_window, cx| {
Tooltip::with_meta(
mode.tooltip_interrupt(),
Some(&menu::Cancel),
"Changes won't be discarded",
window,
cx,
)
})
@@ -487,12 +486,11 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
.tooltip(move |_window, cx| {
Tooltip::with_meta(
mode.tooltip_restart(),
Some(&menu::Confirm),
"Changes will be discarded",
window,
cx,
)
})
@@ -505,8 +503,8 @@ impl<T: 'static> PromptEditor<T> {
let accept = IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, window, cx)
.tooltip(move |_window, cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, cx)
})
.on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
@@ -519,11 +517,10 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("confirm", IconName::PlayFilled)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
.tooltip(|_window, cx| {
Tooltip::for_action(
"Execute Generated Command",
&menu::SecondaryConfirm,
window,
cx,
)
})
@@ -615,13 +612,12 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |window, cx| {
move |_window, cx| {
cx.new(|cx| {
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
KeyBinding::for_action_in(
&CyclePreviousInlineAssist,
&focus_handle,
window,
cx,
),
);
@@ -657,13 +653,12 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |window, cx| {
move |_window, cx| {
cx.new(|cx| {
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
KeyBinding::for_action_in(
&CycleNextInlineAssist,
&focus_handle,
window,
cx,
),
);

View File

@@ -162,12 +162,11 @@ impl Render for ProfileSelector {
PickerPopoverMenu::new(
picker,
trigger_button,
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Toggle Profile Menu",
&ToggleProfileSelector,
&focus_handle,
window,
cx,
)
},

View File

@@ -155,8 +155,8 @@ impl PickerDelegate for SlashCommandDelegate {
match command {
SlashCommandEntry::Info(info) => {
self.active_context_editor
.update(cx, |context_editor, cx| {
context_editor.insert_command(&info.name, window, cx)
.update(cx, |text_thread_editor, cx| {
text_thread_editor.insert_command(&info.name, window, cx)
})
.ok();
}

View File

@@ -1,5 +1,4 @@
use crate::{
QuoteSelection,
language_model_selector::{LanguageModelSelector, language_model_selector},
ui::BurnModeTooltip,
};
@@ -72,13 +71,13 @@ use workspace::{
pane,
searchable::{SearchEvent, SearchableItem},
};
use zed_actions::agent::ToggleModelSelector;
use zed_actions::agent::{AddSelectionToThread, ToggleModelSelector};
use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};
use assistant_context::{
AssistantContext, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId,
InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus,
PendingSlashCommandStatus, ThoughtProcessOutputSection,
use assistant_text_thread::{
CacheStatus, Content, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId,
MessageMetadata, MessageStatus, PendingSlashCommandStatus, TextThread, TextThreadEvent,
TextThreadId, ThoughtProcessOutputSection,
};
actions!(
@@ -127,14 +126,14 @@ pub enum ThoughtProcessStatus {
}
pub trait AgentPanelDelegate {
fn active_context_editor(
fn active_text_thread_editor(
&self,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Option<Entity<TextThreadEditor>>;
fn open_saved_context(
fn open_local_text_thread(
&self,
workspace: &mut Workspace,
path: Arc<Path>,
@@ -142,10 +141,10 @@ pub trait AgentPanelDelegate {
cx: &mut Context<Workspace>,
) -> Task<Result<()>>;
fn open_remote_context(
fn open_remote_text_thread(
&self,
workspace: &mut Workspace,
context_id: ContextId,
text_thread_id: TextThreadId,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Task<Result<Entity<TextThreadEditor>>>;
@@ -178,7 +177,7 @@ struct GlobalAssistantPanelDelegate(Arc<dyn AgentPanelDelegate>);
impl Global for GlobalAssistantPanelDelegate {}
pub struct TextThreadEditor {
context: Entity<AssistantContext>,
text_thread: Entity<TextThread>,
fs: Arc<dyn Fs>,
slash_commands: Arc<SlashCommandWorkingSet>,
workspace: WeakEntity<Workspace>,
@@ -224,8 +223,8 @@ impl TextThreadEditor {
.detach();
}
pub fn for_context(
context: Entity<AssistantContext>,
pub fn for_text_thread(
text_thread: Entity<TextThread>,
fs: Arc<dyn Fs>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
@@ -234,14 +233,14 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) -> Self {
let completion_provider = SlashCommandCompletionProvider::new(
context.read(cx).slash_commands().clone(),
text_thread.read(cx).slash_commands().clone(),
Some(cx.entity().downgrade()),
Some(workspace.clone()),
);
let editor = cx.new(|cx| {
let mut editor =
Editor::for_buffer(context.read(cx).buffer().clone(), None, window, cx);
Editor::for_buffer(text_thread.read(cx).buffer().clone(), None, window, cx);
editor.disable_scrollbars_and_minimap(window, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_line_numbers(false, cx);
@@ -265,18 +264,24 @@ impl TextThreadEditor {
});
let _subscriptions = vec![
cx.observe(&context, |_, _, cx| cx.notify()),
cx.subscribe_in(&context, window, Self::handle_context_event),
cx.observe(&text_thread, |_, _, cx| cx.notify()),
cx.subscribe_in(&text_thread, window, Self::handle_text_thread_event),
cx.subscribe_in(&editor, window, Self::handle_editor_event),
cx.subscribe_in(&editor, window, Self::handle_editor_search_event),
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
];
let slash_command_sections = context.read(cx).slash_command_output_sections().to_vec();
let thought_process_sections = context.read(cx).thought_process_output_sections().to_vec();
let slash_commands = context.read(cx).slash_commands().clone();
let slash_command_sections = text_thread
.read(cx)
.slash_command_output_sections()
.to_vec();
let thought_process_sections = text_thread
.read(cx)
.thought_process_output_sections()
.to_vec();
let slash_commands = text_thread.read(cx).slash_commands().clone();
let mut this = Self {
context,
text_thread,
slash_commands,
editor,
lsp_adapter_delegate,
@@ -338,8 +343,8 @@ impl TextThreadEditor {
});
}
pub fn context(&self) -> &Entity<AssistantContext> {
&self.context
pub fn text_thread(&self) -> &Entity<TextThread> {
&self.text_thread
}
pub fn editor(&self) -> &Entity<Editor> {
@@ -351,9 +356,9 @@ impl TextThreadEditor {
self.editor.update(cx, |editor, cx| {
editor.insert(&format!("/{command_name}\n\n"), window, cx)
});
let command = self.context.update(cx, |context, cx| {
context.reparse(cx);
context.parsed_slash_commands()[0].clone()
let command = self.text_thread.update(cx, |text_thread, cx| {
text_thread.reparse(cx);
text_thread.parsed_slash_commands()[0].clone()
});
self.run_command(
command.source_range,
@@ -376,11 +381,14 @@ impl TextThreadEditor {
fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.last_error = None;
if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) {
if let Some(user_message) = self
.text_thread
.update(cx, |text_thread, cx| text_thread.assist(cx))
{
let new_selection = {
let cursor = user_message
.start
.to_offset(self.context.read(cx).buffer().read(cx));
.to_offset(self.text_thread.read(cx).buffer().read(cx));
cursor..cursor
};
self.editor.update(cx, |editor, cx| {
@@ -404,8 +412,8 @@ impl TextThreadEditor {
self.last_error = None;
if self
.context
.update(cx, |context, cx| context.cancel_last_assist(cx))
.text_thread
.update(cx, |text_thread, cx| text_thread.cancel_last_assist(cx))
{
return;
}
@@ -420,13 +428,13 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) {
let cursors = self.cursors(cx);
self.context.update(cx, |context, cx| {
let messages = context
self.text_thread.update(cx, |text_thread, cx| {
let messages = text_thread
.messages_for_offsets(cursors, cx)
.into_iter()
.map(|message| message.id)
.collect();
context.cycle_message_roles(messages, cx)
text_thread.cycle_message_roles(messages, cx)
});
}
@@ -492,11 +500,11 @@ impl TextThreadEditor {
let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
let mut commands_by_range = HashMap::default();
let workspace = self.workspace.clone();
self.context.update(cx, |context, cx| {
context.reparse(cx);
self.text_thread.update(cx, |text_thread, cx| {
text_thread.reparse(cx);
for selection in selections.iter() {
if let Some(command) =
context.pending_command_for_position(selection.head().text_anchor, cx)
text_thread.pending_command_for_position(selection.head().text_anchor, cx)
{
commands_by_range
.entry(command.source_range.clone())
@@ -534,14 +542,14 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) {
if let Some(command) = self.slash_commands.command(name, cx) {
let context = self.context.read(cx);
let sections = context
let text_thread = self.text_thread.read(cx);
let sections = text_thread
.slash_command_output_sections()
.iter()
.filter(|section| section.is_valid(context.buffer().read(cx)))
.filter(|section| section.is_valid(text_thread.buffer().read(cx)))
.cloned()
.collect::<Vec<_>>();
let snapshot = context.buffer().read(cx).snapshot();
let snapshot = text_thread.buffer().read(cx).snapshot();
let output = command.run(
arguments,
&sections,
@@ -551,8 +559,8 @@ impl TextThreadEditor {
window,
cx,
);
self.context.update(cx, |context, cx| {
context.insert_command_output(
self.text_thread.update(cx, |text_thread, cx| {
text_thread.insert_command_output(
command_range,
name,
output,
@@ -563,32 +571,32 @@ impl TextThreadEditor {
}
}
fn handle_context_event(
fn handle_text_thread_event(
&mut self,
_: &Entity<AssistantContext>,
event: &ContextEvent,
_: &Entity<TextThread>,
event: &TextThreadEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
let context_editor = cx.entity().downgrade();
let text_thread_editor = cx.entity().downgrade();
match event {
ContextEvent::MessagesEdited => {
TextThreadEvent::MessagesEdited => {
self.update_message_headers(cx);
self.update_image_blocks(cx);
self.context.update(cx, |context, cx| {
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
self.text_thread.update(cx, |text_thread, cx| {
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
});
}
ContextEvent::SummaryChanged => {
TextThreadEvent::SummaryChanged => {
cx.emit(EditorEvent::TitleChanged);
self.context.update(cx, |context, cx| {
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
self.text_thread.update(cx, |text_thread, cx| {
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
});
}
ContextEvent::SummaryGenerated => {}
ContextEvent::PathChanged { .. } => {}
ContextEvent::StartedThoughtProcess(range) => {
TextThreadEvent::SummaryGenerated => {}
TextThreadEvent::PathChanged { .. } => {}
TextThreadEvent::StartedThoughtProcess(range) => {
let creases = self.insert_thought_process_output_sections(
[(
ThoughtProcessOutputSection {
@@ -601,7 +609,7 @@ impl TextThreadEditor {
);
self.pending_thought_process = Some((creases[0], range.start));
}
ContextEvent::EndedThoughtProcess(end) => {
TextThreadEvent::EndedThoughtProcess(end) => {
if let Some((crease_id, start)) = self.pending_thought_process.take() {
self.editor.update(cx, |editor, cx| {
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
@@ -627,7 +635,7 @@ impl TextThreadEditor {
);
}
}
ContextEvent::StreamedCompletion => {
TextThreadEvent::StreamedCompletion => {
self.editor.update(cx, |editor, cx| {
if let Some(scroll_position) = self.scroll_position {
let snapshot = editor.snapshot(window, cx);
@@ -642,7 +650,7 @@ impl TextThreadEditor {
}
});
}
ContextEvent::ParsedSlashCommandsUpdated { removed, updated } => {
TextThreadEvent::ParsedSlashCommandsUpdated { removed, updated } => {
self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx).snapshot(cx);
let (&excerpt_id, _, _) = buffer.as_singleton().unwrap();
@@ -658,12 +666,12 @@ impl TextThreadEditor {
updated.iter().map(|command| {
let workspace = self.workspace.clone();
let confirm_command = Arc::new({
let context_editor = context_editor.clone();
let text_thread_editor = text_thread_editor.clone();
let command = command.clone();
move |window: &mut Window, cx: &mut App| {
context_editor
.update(cx, |context_editor, cx| {
context_editor.run_command(
text_thread_editor
.update(cx, |text_thread_editor, cx| {
text_thread_editor.run_command(
command.source_range.clone(),
&command.name,
&command.arguments,
@@ -713,17 +721,17 @@ impl TextThreadEditor {
);
})
}
ContextEvent::InvokedSlashCommandChanged { command_id } => {
TextThreadEvent::InvokedSlashCommandChanged { command_id } => {
self.update_invoked_slash_command(*command_id, window, cx);
}
ContextEvent::SlashCommandOutputSectionAdded { section } => {
TextThreadEvent::SlashCommandOutputSectionAdded { section } => {
self.insert_slash_command_output_sections([section.clone()], false, window, cx);
}
ContextEvent::Operation(_) => {}
ContextEvent::ShowAssistError(error_message) => {
TextThreadEvent::Operation(_) => {}
TextThreadEvent::ShowAssistError(error_message) => {
self.last_error = Some(AssistError::Message(error_message.clone()));
}
ContextEvent::ShowPaymentRequiredError => {
TextThreadEvent::ShowPaymentRequiredError => {
self.last_error = Some(AssistError::PaymentRequired);
}
}
@@ -736,14 +744,14 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) {
if let Some(invoked_slash_command) =
self.context.read(cx).invoked_slash_command(&command_id)
self.text_thread.read(cx).invoked_slash_command(&command_id)
&& let InvokedSlashCommandStatus::Finished = invoked_slash_command.status
{
let run_commands_in_ranges = invoked_slash_command.run_commands_in_ranges.clone();
for range in run_commands_in_ranges {
let commands = self.context.update(cx, |context, cx| {
context.reparse(cx);
context
let commands = self.text_thread.update(cx, |text_thread, cx| {
text_thread.reparse(cx);
text_thread
.pending_commands_for_range(range.clone(), cx)
.to_vec()
});
@@ -764,7 +772,7 @@ impl TextThreadEditor {
self.editor.update(cx, |editor, cx| {
if let Some(invoked_slash_command) =
self.context.read(cx).invoked_slash_command(&command_id)
self.text_thread.read(cx).invoked_slash_command(&command_id)
{
if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status {
let buffer = editor.buffer().read(cx).snapshot(cx);
@@ -791,7 +799,7 @@ impl TextThreadEditor {
let buffer = editor.buffer().read(cx).snapshot(cx);
let (&excerpt_id, _buffer_id, _buffer_snapshot) =
buffer.as_singleton().unwrap();
let context = self.context.downgrade();
let context = self.text_thread.downgrade();
let range = buffer
.anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
.unwrap();
@@ -1021,7 +1029,7 @@ impl TextThreadEditor {
let render_block = |message: MessageMetadata| -> RenderBlock {
Arc::new({
let context = self.context.clone();
let text_thread = self.text_thread.clone();
move |cx| {
let message_id = MessageId(message.timestamp);
@@ -1085,20 +1093,19 @@ impl TextThreadEditor {
.child(label)
.children(spinner),
)
.tooltip(|window, cx| {
.tooltip(|_window, cx| {
Tooltip::with_meta(
"Toggle message role",
None,
"Available roles: You (User), Agent, System",
window,
cx,
)
})
.on_click({
let context = context.clone();
let text_thread = text_thread.clone();
move |_, _window, cx| {
context.update(cx, |context, cx| {
context.cycle_message_roles(
text_thread.update(cx, |text_thread, cx| {
text_thread.cycle_message_roles(
HashSet::from_iter(Some(message_id)),
cx,
)
@@ -1126,12 +1133,11 @@ impl TextThreadEditor {
.size(IconSize::XSmall)
.color(Color::Hint),
)
.tooltip(|window, cx| {
.tooltip(|_window, cx| {
Tooltip::with_meta(
"Context Cached",
None,
"Large messages cached to optimize performance",
window,
cx,
)
})
@@ -1161,11 +1167,11 @@ impl TextThreadEditor {
.icon_position(IconPosition::Start)
.tooltip(Tooltip::text("View Details"))
.on_click({
let context = context.clone();
let text_thread = text_thread.clone();
let error = error.clone();
move |_, _window, cx| {
context.update(cx, |_, cx| {
cx.emit(ContextEvent::ShowAssistError(
text_thread.update(cx, |_, cx| {
cx.emit(TextThreadEvent::ShowAssistError(
error.clone(),
));
});
@@ -1208,7 +1214,7 @@ impl TextThreadEditor {
};
let mut new_blocks = vec![];
let mut block_index_to_message = vec![];
for message in self.context.read(cx).messages(cx) {
for message in self.text_thread.read(cx).messages(cx) {
if blocks_to_remove.remove(&message.id).is_some() {
// This is an old message that we might modify.
let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
@@ -1249,18 +1255,18 @@ impl TextThreadEditor {
) -> Option<(String, bool)> {
const CODE_FENCE_DELIMITER: &str = "```";
let context_editor = context_editor_view.read(cx).editor.clone();
context_editor.update(cx, |context_editor, cx| {
let display_map = context_editor.display_snapshot(cx);
if context_editor
let text_thread_editor = context_editor_view.read(cx).editor.clone();
text_thread_editor.update(cx, |text_thread_editor, cx| {
let display_map = text_thread_editor.display_snapshot(cx);
if text_thread_editor
.selections
.newest::<Point>(&display_map)
.is_empty()
{
let snapshot = context_editor.buffer().read(cx).snapshot(cx);
let snapshot = text_thread_editor.buffer().read(cx).snapshot(cx);
let (_, _, snapshot) = snapshot.as_singleton()?;
let head = context_editor
let head = text_thread_editor
.selections
.newest::<Point>(&display_map)
.head();
@@ -1280,8 +1286,8 @@ impl TextThreadEditor {
(!text.is_empty()).then_some((text, true))
} else {
let selection = context_editor.selections.newest_adjusted(&display_map);
let buffer = context_editor.buffer().read(cx).snapshot(cx);
let selection = text_thread_editor.selections.newest_adjusted(&display_map);
let buffer = text_thread_editor.buffer().read(cx).snapshot(cx);
let selected_text = buffer.text_for_range(selection.range()).collect::<String>();
(!selected_text.is_empty()).then_some((selected_text, false))
@@ -1299,7 +1305,7 @@ impl TextThreadEditor {
return;
};
let Some(context_editor_view) =
agent_panel_delegate.active_context_editor(workspace, window, cx)
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
else {
return;
};
@@ -1327,7 +1333,7 @@ impl TextThreadEditor {
let result = maybe!({
let agent_panel_delegate = <dyn AgentPanelDelegate>::try_global(cx)?;
let context_editor_view =
agent_panel_delegate.active_context_editor(workspace, window, cx)?;
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)?;
Self::get_selection_or_code_block(&context_editor_view, cx)
});
let Some((text, is_code_block)) = result else {
@@ -1364,7 +1370,7 @@ impl TextThreadEditor {
return;
};
let Some(context_editor_view) =
agent_panel_delegate.active_context_editor(workspace, window, cx)
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
else {
return;
};
@@ -1450,7 +1456,7 @@ impl TextThreadEditor {
pub fn quote_selection(
workspace: &mut Workspace,
_: &QuoteSelection,
_: &AddSelectionToThread,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
@@ -1625,29 +1631,33 @@ impl TextThreadEditor {
)
});
let context = self.context.read(cx);
let text_thread = self.text_thread.read(cx);
let mut text = String::new();
// If selection is empty, we want to copy the entire line
if selection.range().is_empty() {
let snapshot = context.buffer().read(cx).snapshot();
let snapshot = text_thread.buffer().read(cx).snapshot();
let point = snapshot.offset_to_point(selection.range().start);
selection.start = snapshot.point_to_offset(Point::new(point.row, 0));
selection.end = snapshot
.point_to_offset(cmp::min(Point::new(point.row + 1, 0), snapshot.max_point()));
for chunk in context.buffer().read(cx).text_for_range(selection.range()) {
for chunk in text_thread
.buffer()
.read(cx)
.text_for_range(selection.range())
{
text.push_str(chunk);
}
} else {
for message in context.messages(cx) {
for message in text_thread.messages(cx) {
if message.offset_range.start >= selection.range().end {
break;
} else if message.offset_range.end >= selection.range().start {
let range = cmp::max(message.offset_range.start, selection.range().start)
..cmp::min(message.offset_range.end, selection.range().end);
if !range.is_empty() {
for chunk in context.buffer().read(cx).text_for_range(range) {
for chunk in text_thread.buffer().read(cx).text_for_range(range) {
text.push_str(chunk);
}
if message.offset_range.end < selection.range().end {
@@ -1758,7 +1768,7 @@ impl TextThreadEditor {
});
});
self.context.update(cx, |context, cx| {
self.text_thread.update(cx, |text_thread, cx| {
for image in images {
let Some(render_image) = image.to_image_data(cx.svg_renderer()).log_err()
else {
@@ -1768,7 +1778,7 @@ impl TextThreadEditor {
let image_task = LanguageModelImage::from_image(Arc::new(image), cx).shared();
for image_position in image_positions.iter() {
context.insert_content(
text_thread.insert_content(
Content::Image {
anchor: image_position.text_anchor,
image_id,
@@ -1789,7 +1799,7 @@ impl TextThreadEditor {
let excerpt_id = *buffer.as_singleton().unwrap().0;
let old_blocks = std::mem::take(&mut self.image_blocks);
let new_blocks = self
.context
.text_thread
.read(cx)
.contents(cx)
.map(
@@ -1837,36 +1847,36 @@ impl TextThreadEditor {
}
fn split(&mut self, _: &Split, _window: &mut Window, cx: &mut Context<Self>) {
self.context.update(cx, |context, cx| {
self.text_thread.update(cx, |text_thread, cx| {
let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
for selection in selections.as_ref() {
let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
let range = selection
.map(|endpoint| endpoint.to_offset(&buffer))
.range();
context.split_message(range, cx);
text_thread.split_message(range, cx);
}
});
}
fn save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context<Self>) {
self.context.update(cx, |context, cx| {
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
self.text_thread.update(cx, |text_thread, cx| {
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
});
}
pub fn title(&self, cx: &App) -> SharedString {
self.context.read(cx).summary().or_default()
self.text_thread.read(cx).summary().or_default()
}
pub fn regenerate_summary(&mut self, cx: &mut Context<Self>) {
self.context
.update(cx, |context, cx| context.summarize(true, cx));
self.text_thread
.update(cx, |text_thread, cx| text_thread.summarize(true, cx));
}
fn render_remaining_tokens(&self, cx: &App) -> Option<impl IntoElement + use<>> {
let (token_count_color, token_count, max_token_count, tooltip) =
match token_state(&self.context, cx)? {
match token_state(&self.text_thread, cx)? {
TokenState::NoTokensLeft {
max_token_count,
token_count,
@@ -1914,7 +1924,7 @@ impl TextThreadEditor {
fn render_send_button(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
let (style, tooltip) = match token_state(&self.context, cx) {
let (style, tooltip) = match token_state(&self.text_thread, cx) {
Some(TokenState::NoTokensLeft { .. }) => (
ButtonStyle::Tinted(TintColor::Error),
Some(Tooltip::text("Token limit reached")(window, cx)),
@@ -1947,7 +1957,7 @@ impl TextThreadEditor {
})
.layer(ElevationIndex::ModalSurface)
.key_binding(
KeyBinding::for_action_in(&Assist, &focus_handle, window, cx)
KeyBinding::for_action_in(&Assist, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(move |_event, window, cx| {
@@ -1982,20 +1992,14 @@ impl TextThreadEditor {
.icon_color(Color::Muted)
.selected_icon_color(Color::Accent)
.selected_style(ButtonStyle::Filled),
move |window, cx| {
Tooltip::with_meta(
"Add Context",
None,
"Type / to insert via keyboard",
window,
cx,
)
move |_window, cx| {
Tooltip::with_meta("Add Context", None, "Type / to insert via keyboard", cx)
},
)
}
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let context = self.context().read(cx);
let text_thread = self.text_thread().read(cx);
let active_model = LanguageModelRegistry::read_global(cx)
.default_model()
.map(|default| default.model)?;
@@ -2003,7 +2007,7 @@ impl TextThreadEditor {
return None;
}
let active_completion_mode = context.completion_mode();
let active_completion_mode = text_thread.completion_mode();
let burn_mode_enabled = active_completion_mode == CompletionMode::Burn;
let icon = if burn_mode_enabled {
IconName::ZedBurnModeOn
@@ -2018,8 +2022,8 @@ impl TextThreadEditor {
.toggle_state(burn_mode_enabled)
.selected_icon_color(Color::Error)
.on_click(cx.listener(move |this, _event, _window, cx| {
this.context().update(cx, |context, _cx| {
context.set_completion_mode(match active_completion_mode {
this.text_thread().update(cx, |text_thread, _cx| {
text_thread.set_completion_mode(match active_completion_mode {
CompletionMode::Burn => CompletionMode::Normal,
CompletionMode::Normal => CompletionMode::Burn,
});
@@ -2078,14 +2082,8 @@ impl TextThreadEditor {
)
.child(Icon::new(icon).color(color).size(IconSize::XSmall)),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
move |_window, cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
},
gpui::Corner::BottomRight,
cx,
@@ -2652,10 +2650,10 @@ impl FollowableItem for TextThreadEditor {
}
fn to_state_proto(&self, window: &Window, cx: &App) -> Option<proto::view::Variant> {
let context = self.context.read(cx);
let text_thread = self.text_thread.read(cx);
Some(proto::view::Variant::ContextEditor(
proto::view::ContextEditor {
context_id: context.id().to_proto(),
context_id: text_thread.id().to_proto(),
editor: if let Some(proto::view::Variant::Editor(proto)) =
self.editor.read(cx).to_state_proto(window, cx)
{
@@ -2681,22 +2679,22 @@ impl FollowableItem for TextThreadEditor {
unreachable!()
};
let context_id = ContextId::from_proto(state.context_id);
let text_thread_id = TextThreadId::from_proto(state.context_id);
let editor_state = state.editor?;
let project = workspace.read(cx).project().clone();
let agent_panel_delegate = <dyn AgentPanelDelegate>::try_global(cx)?;
let context_editor_task = workspace.update(cx, |workspace, cx| {
agent_panel_delegate.open_remote_context(workspace, context_id, window, cx)
let text_thread_editor_task = workspace.update(cx, |workspace, cx| {
agent_panel_delegate.open_remote_text_thread(workspace, text_thread_id, window, cx)
});
Some(window.spawn(cx, async move |cx| {
let context_editor = context_editor_task.await?;
context_editor
.update_in(cx, |context_editor, window, cx| {
context_editor.remote_id = Some(id);
context_editor.editor.update(cx, |editor, cx| {
let text_thread_editor = text_thread_editor_task.await?;
text_thread_editor
.update_in(cx, |text_thread_editor, window, cx| {
text_thread_editor.remote_id = Some(id);
text_thread_editor.editor.update(cx, |editor, cx| {
editor.apply_update_proto(
&project,
proto::update_view::Variant::Editor(proto::update_view::Editor {
@@ -2713,7 +2711,7 @@ impl FollowableItem for TextThreadEditor {
})
})?
.await?;
Ok(context_editor)
Ok(text_thread_editor)
}))
}
@@ -2760,7 +2758,7 @@ impl FollowableItem for TextThreadEditor {
}
fn dedup(&self, existing: &Self, _window: &Window, cx: &App) -> Option<item::Dedup> {
if existing.context.read(cx).id() == self.context.read(cx).id() {
if existing.text_thread.read(cx).id() == self.text_thread.read(cx).id() {
Some(item::Dedup::KeepExisting)
} else {
None
@@ -2772,17 +2770,17 @@ enum PendingSlashCommand {}
fn invoked_slash_command_fold_placeholder(
command_id: InvokedSlashCommandId,
context: WeakEntity<AssistantContext>,
text_thread: WeakEntity<TextThread>,
) -> FoldPlaceholder {
FoldPlaceholder {
constrain_width: false,
merge_adjacent: false,
render: Arc::new(move |fold_id, _, cx| {
let Some(context) = context.upgrade() else {
let Some(text_thread) = text_thread.upgrade() else {
return Empty.into_any();
};
let Some(command) = context.read(cx).invoked_slash_command(&command_id) else {
let Some(command) = text_thread.read(cx).invoked_slash_command(&command_id) else {
return Empty.into_any();
};
@@ -2823,14 +2821,15 @@ enum TokenState {
},
}
fn token_state(context: &Entity<AssistantContext>, cx: &App) -> Option<TokenState> {
fn token_state(text_thread: &Entity<TextThread>, cx: &App) -> Option<TokenState> {
const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
let model = LanguageModelRegistry::read_global(cx)
.default_model()?
.model;
let token_count = context.read(cx).token_count()?;
let max_token_count = model.max_token_count_for_mode(context.read(cx).completion_mode().into());
let token_count = text_thread.read(cx).token_count()?;
let max_token_count =
model.max_token_count_for_mode(text_thread.read(cx).completion_mode().into());
let token_state = if max_token_count.saturating_sub(token_count) == 0 {
TokenState::NoTokensLeft {
max_token_count,
@@ -2942,7 +2941,7 @@ mod tests {
#[gpui::test]
async fn test_copy_paste_whole_message(cx: &mut TestAppContext) {
let (context, context_editor, mut cx) = setup_context_editor_text(vec![
let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(vec![
(Role::User, "What is the Zed editor?"),
(
Role::Assistant,
@@ -2952,8 +2951,8 @@ mod tests {
],cx).await;
// Select & Copy whole user message
assert_copy_paste_context_editor(
&context_editor,
assert_copy_paste_text_thread_editor(
&text_thread_editor,
message_range(&context, 0, &mut cx),
indoc! {"
What is the Zed editor?
@@ -2964,8 +2963,8 @@ mod tests {
);
// Select & Copy whole assistant message
assert_copy_paste_context_editor(
&context_editor,
assert_copy_paste_text_thread_editor(
&text_thread_editor,
message_range(&context, 1, &mut cx),
indoc! {"
What is the Zed editor?
@@ -2979,7 +2978,7 @@ mod tests {
#[gpui::test]
async fn test_copy_paste_no_selection(cx: &mut TestAppContext) {
let (context, context_editor, mut cx) = setup_context_editor_text(
let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(
vec![
(Role::User, "user1"),
(Role::Assistant, "assistant1"),
@@ -2992,8 +2991,8 @@ mod tests {
// Copy and paste first assistant message
let message_2_range = message_range(&context, 1, &mut cx);
assert_copy_paste_context_editor(
&context_editor,
assert_copy_paste_text_thread_editor(
&text_thread_editor,
message_2_range.start..message_2_range.start,
indoc! {"
user1
@@ -3006,8 +3005,8 @@ mod tests {
// Copy and cut second assistant message
let message_3_range = message_range(&context, 2, &mut cx);
assert_copy_paste_context_editor(
&context_editor,
assert_copy_paste_text_thread_editor(
&text_thread_editor,
message_3_range.start..message_3_range.start,
indoc! {"
user1
@@ -3094,29 +3093,29 @@ mod tests {
}
}
async fn setup_context_editor_text(
async fn setup_text_thread_editor_text(
messages: Vec<(Role, &str)>,
cx: &mut TestAppContext,
) -> (
Entity<AssistantContext>,
Entity<TextThread>,
Entity<TextThreadEditor>,
VisualTestContext,
) {
cx.update(init_test);
let fs = FakeFs::new(cx.executor());
let context = create_context_with_messages(messages, cx);
let text_thread = create_text_thread_with_messages(messages, cx);
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let workspace = window.root(cx).unwrap();
let mut cx = VisualTestContext::from_window(*window, cx);
let context_editor = window
let text_thread_editor = window
.update(&mut cx, |_, window, cx| {
cx.new(|cx| {
TextThreadEditor::for_context(
context.clone(),
TextThreadEditor::for_text_thread(
text_thread.clone(),
fs,
workspace.downgrade(),
project,
@@ -3128,59 +3127,59 @@ mod tests {
})
.unwrap();
(context, context_editor, cx)
(text_thread, text_thread_editor, cx)
}
fn message_range(
context: &Entity<AssistantContext>,
text_thread: &Entity<TextThread>,
message_ix: usize,
cx: &mut TestAppContext,
) -> Range<usize> {
context.update(cx, |context, cx| {
context
text_thread.update(cx, |text_thread, cx| {
text_thread
.messages(cx)
.nth(message_ix)
.unwrap()
.anchor_range
.to_offset(&context.buffer().read(cx).snapshot())
.to_offset(&text_thread.buffer().read(cx).snapshot())
})
}
fn assert_copy_paste_context_editor<T: editor::ToOffset>(
context_editor: &Entity<TextThreadEditor>,
fn assert_copy_paste_text_thread_editor<T: editor::ToOffset>(
text_thread_editor: &Entity<TextThreadEditor>,
range: Range<T>,
expected_text: &str,
cx: &mut VisualTestContext,
) {
context_editor.update_in(cx, |context_editor, window, cx| {
context_editor.editor.update(cx, |editor, cx| {
text_thread_editor.update_in(cx, |text_thread_editor, window, cx| {
text_thread_editor.editor.update(cx, |editor, cx| {
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
s.select_ranges([range])
});
});
context_editor.copy(&Default::default(), window, cx);
text_thread_editor.copy(&Default::default(), window, cx);
context_editor.editor.update(cx, |editor, cx| {
text_thread_editor.editor.update(cx, |editor, cx| {
editor.move_to_end(&Default::default(), window, cx);
});
context_editor.paste(&Default::default(), window, cx);
text_thread_editor.paste(&Default::default(), window, cx);
context_editor.editor.update(cx, |editor, cx| {
text_thread_editor.editor.update(cx, |editor, cx| {
assert_eq!(editor.text(cx), expected_text);
});
});
}
fn create_context_with_messages(
fn create_text_thread_with_messages(
mut messages: Vec<(Role, &str)>,
cx: &mut TestAppContext,
) -> Entity<AssistantContext> {
) -> Entity<TextThread> {
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
cx.new(|cx| {
let mut context = AssistantContext::local(
let mut text_thread = TextThread::local(
registry,
None,
None,
@@ -3188,33 +3187,33 @@ mod tests {
Arc::new(SlashCommandWorkingSet::default()),
cx,
);
let mut message_1 = context.messages(cx).next().unwrap();
let mut message_1 = text_thread.messages(cx).next().unwrap();
let (role, text) = messages.remove(0);
loop {
if role == message_1.role {
context.buffer().update(cx, |buffer, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
buffer.edit([(message_1.offset_range, text)], None, cx);
});
break;
}
let mut ids = HashSet::default();
ids.insert(message_1.id);
context.cycle_message_roles(ids, cx);
message_1 = context.messages(cx).next().unwrap();
text_thread.cycle_message_roles(ids, cx);
message_1 = text_thread.messages(cx).next().unwrap();
}
let mut last_message_id = message_1.id;
for (role, text) in messages {
context.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
let message = context.messages(cx).last().unwrap();
text_thread.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
let message = text_thread.messages(cx).last().unwrap();
last_message_id = message.id;
context.buffer().update(cx, |buffer, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
buffer.edit([(message.offset_range, text)], None, cx);
})
}
context
text_thread
})
}

View File

@@ -18,7 +18,7 @@ impl BurnModeTooltip {
}
impl Render for BurnModeTooltip {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (icon, color) = if self.selected {
(IconName::ZedBurnModeOn, Color::Error)
} else {
@@ -45,8 +45,7 @@ impl Render for BurnModeTooltip {
.child(Label::new("Burn Mode"))
.when(self.selected, |title| title.child(turned_on));
let keybinding = KeyBinding::for_action(&ToggleBurnMode, window, cx)
.map(|kb| kb.size(rems_from_px(12.)));
let keybinding = KeyBinding::for_action(&ToggleBurnMode, cx).size(rems_from_px(12.));
tooltip_container(cx, |this, _| {
this
@@ -54,7 +53,7 @@ impl Render for BurnModeTooltip {
h_flex()
.justify_between()
.child(title)
.children(keybinding)
.child(keybinding)
)
.child(
div()

View File

@@ -244,8 +244,8 @@ impl RenderOnce for ContextPill {
.truncate(),
),
)
.tooltip(|window, cx| {
Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)
.tooltip(|_window, cx| {
Tooltip::with_meta("Suggested Context", None, "Click to add it", cx)
})
.when_some(on_click.as_ref(), |element, on_click| {
let on_click = on_click.clone();
@@ -497,9 +497,9 @@ impl AddedContext {
icon_path: None,
status: ContextStatus::Ready,
render_hover: {
let context = handle.context.clone();
let text_thread = handle.text_thread.clone();
Some(Rc::new(move |_, cx| {
let text = context.read(cx).to_xml(cx);
let text = text_thread.read(cx).to_xml(cx);
ContextPillHover::new_text(text.into(), cx).into()
}))
},

View File

@@ -84,10 +84,32 @@ impl ZedAiOnboarding {
self
}
fn render_dismiss_button(&self) -> Option<AnyElement> {
self.dismiss_onboarding.as_ref().map(|dismiss_callback| {
let callback = dismiss_callback.clone();
h_flex()
.absolute()
.top_0()
.right_0()
.child(
IconButton::new("dismiss_onboarding", IconName::Close)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Dismiss"))
.on_click(move |_, window, cx| {
telemetry::event!("Banner Dismissed", source = "AI Onboarding",);
callback(window, cx)
}),
)
.into_any_element()
})
}
fn render_sign_in_disclaimer(&self, _cx: &mut App) -> AnyElement {
let signing_in = matches!(self.sign_in_status, SignInStatus::SigningIn);
v_flex()
.relative()
.gap_1()
.child(Headline::new("Welcome to Zed AI"))
.child(
@@ -109,6 +131,7 @@ impl ZedAiOnboarding {
}
}),
)
.children(self.render_dismiss_button())
.into_any_element()
}
@@ -180,27 +203,7 @@ impl ZedAiOnboarding {
)
.child(PlanDefinitions.free_plan(is_v2)),
)
.when_some(
self.dismiss_onboarding.as_ref(),
|this, dismiss_callback| {
let callback = dismiss_callback.clone();
this.child(
h_flex().absolute().top_0().right_0().child(
IconButton::new("dismiss_onboarding", IconName::Close)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Dismiss"))
.on_click(move |_, window, cx| {
telemetry::event!(
"Banner Dismissed",
source = "AI Onboarding",
);
callback(window, cx)
}),
),
)
},
)
.children(self.render_dismiss_button())
.child(
v_flex()
.mt_2()
@@ -245,26 +248,7 @@ impl ZedAiOnboarding {
.mb_2(),
)
.child(PlanDefinitions.pro_trial(is_v2, false))
.when_some(
self.dismiss_onboarding.as_ref(),
|this, dismiss_callback| {
let callback = dismiss_callback.clone();
this.child(
h_flex().absolute().top_0().right_0().child(
IconButton::new("dismiss_onboarding", IconName::Close)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Dismiss"))
.on_click(move |_, window, cx| {
telemetry::event!(
"Banner Dismissed",
source = "AI Onboarding",
);
callback(window, cx)
}),
),
)
},
)
.children(self.render_dismiss_button())
.into_any_element()
}
@@ -278,26 +262,7 @@ impl ZedAiOnboarding {
.mb_2(),
)
.child(PlanDefinitions.pro_plan(is_v2, false))
.when_some(
self.dismiss_onboarding.as_ref(),
|this, dismiss_callback| {
let callback = dismiss_callback.clone();
this.child(
h_flex().absolute().top_0().right_0().child(
IconButton::new("dismiss_onboarding", IconName::Close)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Dismiss"))
.on_click(move |_, window, cx| {
telemetry::event!(
"Banner Dismissed",
source = "AI Onboarding",
);
callback(window, cx)
}),
),
)
},
)
.children(self.render_dismiss_button())
.into_any_element()
}
}

View File

@@ -20,7 +20,7 @@ use futures::{
};
use gpui::{AsyncApp, BackgroundExecutor, Task};
use smol::fs;
use util::{ResultExt as _, debug_panic, maybe, paths::PathExt};
use util::{ResultExt as _, debug_panic, maybe, paths::PathExt, shell::ShellKind};
/// Path to the program used for askpass
///
@@ -199,9 +199,15 @@ impl PasswordProxy {
let current_exec =
std::env::current_exe().context("Failed to determine current zed executable path.")?;
// TODO: inferred from the use of powershell.exe in askpass_helper_script
let shell_kind = if cfg!(windows) {
ShellKind::PowerShell
} else {
ShellKind::Posix
};
let askpass_program = ASKPASS_PROGRAM
.get_or_init(|| current_exec)
.try_shell_safe()
.try_shell_safe(shell_kind)
.context("Failed to shell-escape Askpass program path.")?
.to_string();
// Create an askpass script that communicates back to this process.
@@ -343,7 +349,7 @@ fn generate_askpass_script(askpass_program: &str, askpass_socket: &std::path::Pa
format!(
r#"
$ErrorActionPreference = 'Stop';
($args -join [char]0) | & "{askpass_program}" --askpass={askpass_socket} 2> $null
($args -join [char]0) | & {askpass_program} --askpass={askpass_socket} 2> $null
"#,
askpass_socket = askpass_socket.display(),
)

View File

@@ -1,5 +1,5 @@
[package]
name = "assistant_context"
name = "assistant_text_thread"
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_context.rs"
path = "src/assistant_text_thread.rs"
[features]
test-support = []

View File

@@ -0,0 +1,15 @@
#[cfg(test)]
mod assistant_text_thread_tests;
mod text_thread;
mod text_thread_store;
pub use crate::text_thread::*;
pub use crate::text_thread_store::*;
use client::Client;
use gpui::App;
use std::sync::Arc;
pub fn init(client: Arc<Client>, _: &mut App) {
text_thread_store::init(&client.into());
}

View File

@@ -1,7 +1,3 @@
#[cfg(test)]
mod assistant_context_tests;
mod context_store;
use agent_settings::{AgentSettings, SUMMARIZE_THREAD_PROMPT};
use anyhow::{Context as _, Result, bail};
use assistant_slash_command::{
@@ -9,7 +5,7 @@ use assistant_slash_command::{
SlashCommandResult, SlashCommandWorkingSet,
};
use assistant_slash_commands::FileCommandMetadata;
use client::{self, Client, ModelRequestUsage, RequestUsage, proto, telemetry::Telemetry};
use client::{self, ModelRequestUsage, RequestUsage, proto, telemetry::Telemetry};
use clock::ReplicaId;
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
use collections::{HashMap, HashSet};
@@ -27,7 +23,7 @@ use language_model::{
report_assistant_event,
};
use open_ai::Model as OpenAiModel;
use paths::contexts_dir;
use paths::text_threads_dir;
use project::Project;
use prompt_store::PromptBuilder;
use serde::{Deserialize, Serialize};
@@ -48,16 +44,10 @@ use ui::IconName;
use util::{ResultExt, TryFutureExt, post_inc};
use uuid::Uuid;
pub use crate::context_store::*;
pub fn init(client: Arc<Client>, _: &mut App) {
context_store::init(&client.into());
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ContextId(String);
pub struct TextThreadId(String);
impl ContextId {
impl TextThreadId {
pub fn new() -> Self {
Self(Uuid::new_v4().to_string())
}
@@ -130,7 +120,7 @@ impl MessageStatus {
}
#[derive(Clone, Debug)]
pub enum ContextOperation {
pub enum TextThreadOperation {
InsertMessage {
anchor: MessageAnchor,
metadata: MessageMetadata,
@@ -142,7 +132,7 @@ pub enum ContextOperation {
version: clock::Global,
},
UpdateSummary {
summary: ContextSummaryContent,
summary: TextThreadSummaryContent,
version: clock::Global,
},
SlashCommandStarted {
@@ -170,7 +160,7 @@ pub enum ContextOperation {
BufferOperation(language::Operation),
}
impl ContextOperation {
impl TextThreadOperation {
pub fn from_proto(op: proto::ContextOperation) -> Result<Self> {
match op.variant.context("invalid variant")? {
proto::context_operation::Variant::InsertMessage(insert) => {
@@ -212,7 +202,7 @@ impl ContextOperation {
version: language::proto::deserialize_version(&update.version),
}),
proto::context_operation::Variant::UpdateSummary(update) => Ok(Self::UpdateSummary {
summary: ContextSummaryContent {
summary: TextThreadSummaryContent {
text: update.summary,
done: update.done,
timestamp: language::proto::deserialize_timestamp(
@@ -453,7 +443,7 @@ impl ContextOperation {
}
#[derive(Debug, Clone)]
pub enum ContextEvent {
pub enum TextThreadEvent {
ShowAssistError(SharedString),
ShowPaymentRequiredError,
MessagesEdited,
@@ -476,24 +466,24 @@ pub enum ContextEvent {
SlashCommandOutputSectionAdded {
section: SlashCommandOutputSection<language::Anchor>,
},
Operation(ContextOperation),
Operation(TextThreadOperation),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ContextSummary {
pub enum TextThreadSummary {
Pending,
Content(ContextSummaryContent),
Content(TextThreadSummaryContent),
Error,
}
#[derive(Default, Clone, Debug, Eq, PartialEq)]
pub struct ContextSummaryContent {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TextThreadSummaryContent {
pub text: String,
pub done: bool,
pub timestamp: clock::Lamport,
}
impl ContextSummary {
impl TextThreadSummary {
pub const DEFAULT: &str = "New Text Thread";
pub fn or_default(&self) -> SharedString {
@@ -505,44 +495,48 @@ impl ContextSummary {
.map_or_else(|| message.into(), |content| content.text.clone().into())
}
pub fn content(&self) -> Option<&ContextSummaryContent> {
pub fn content(&self) -> Option<&TextThreadSummaryContent> {
match self {
ContextSummary::Content(content) => Some(content),
ContextSummary::Pending | ContextSummary::Error => None,
TextThreadSummary::Content(content) => Some(content),
TextThreadSummary::Pending | TextThreadSummary::Error => None,
}
}
fn content_as_mut(&mut self) -> Option<&mut ContextSummaryContent> {
fn content_as_mut(&mut self) -> Option<&mut TextThreadSummaryContent> {
match self {
ContextSummary::Content(content) => Some(content),
ContextSummary::Pending | ContextSummary::Error => None,
TextThreadSummary::Content(content) => Some(content),
TextThreadSummary::Pending | TextThreadSummary::Error => None,
}
}
fn content_or_set_empty(&mut self) -> &mut ContextSummaryContent {
fn content_or_set_empty(&mut self) -> &mut TextThreadSummaryContent {
match self {
ContextSummary::Content(content) => content,
ContextSummary::Pending | ContextSummary::Error => {
let content = ContextSummaryContent::default();
*self = ContextSummary::Content(content);
TextThreadSummary::Content(content) => content,
TextThreadSummary::Pending | TextThreadSummary::Error => {
let content = TextThreadSummaryContent {
text: "".to_string(),
done: false,
timestamp: clock::Lamport::MIN,
};
*self = TextThreadSummary::Content(content);
self.content_as_mut().unwrap()
}
}
}
pub fn is_pending(&self) -> bool {
matches!(self, ContextSummary::Pending)
matches!(self, TextThreadSummary::Pending)
}
fn timestamp(&self) -> Option<clock::Lamport> {
match self {
ContextSummary::Content(content) => Some(content.timestamp),
ContextSummary::Pending | ContextSummary::Error => None,
TextThreadSummary::Content(content) => Some(content.timestamp),
TextThreadSummary::Pending | TextThreadSummary::Error => None,
}
}
}
impl PartialOrd for ContextSummary {
impl PartialOrd for TextThreadSummary {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.timestamp().partial_cmp(&other.timestamp())
}
@@ -664,27 +658,27 @@ struct PendingCompletion {
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct InvokedSlashCommandId(clock::Lamport);
pub struct AssistantContext {
id: ContextId,
pub struct TextThread {
id: TextThreadId,
timestamp: clock::Lamport,
version: clock::Global,
pending_ops: Vec<ContextOperation>,
operations: Vec<ContextOperation>,
pub(crate) pending_ops: Vec<TextThreadOperation>,
operations: Vec<TextThreadOperation>,
buffer: Entity<Buffer>,
parsed_slash_commands: Vec<ParsedSlashCommand>,
pub(crate) parsed_slash_commands: Vec<ParsedSlashCommand>,
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
edits_since_last_parse: language::Subscription,
slash_commands: Arc<SlashCommandWorkingSet>,
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
pub(crate) slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
thought_process_output_sections: Vec<ThoughtProcessOutputSection<language::Anchor>>,
message_anchors: Vec<MessageAnchor>,
pub(crate) message_anchors: Vec<MessageAnchor>,
contents: Vec<Content>,
messages_metadata: HashMap<MessageId, MessageMetadata>,
summary: ContextSummary,
pub(crate) messages_metadata: HashMap<MessageId, MessageMetadata>,
summary: TextThreadSummary,
summary_task: Task<Option<()>>,
completion_count: usize,
pending_completions: Vec<PendingCompletion>,
token_count: Option<u64>,
pub(crate) token_count: Option<u64>,
pending_token_count: Task<Option<()>>,
pending_save: Task<Result<()>>,
pending_cache_warming_task: Task<Option<()>>,
@@ -707,9 +701,9 @@ impl ContextAnnotation for ParsedSlashCommand {
}
}
impl EventEmitter<ContextEvent> for AssistantContext {}
impl EventEmitter<TextThreadEvent> for TextThread {}
impl AssistantContext {
impl TextThread {
pub fn local(
language_registry: Arc<LanguageRegistry>,
project: Option<Entity<Project>>,
@@ -719,7 +713,7 @@ impl AssistantContext {
cx: &mut Context<Self>,
) -> Self {
Self::new(
ContextId::new(),
TextThreadId::new(),
ReplicaId::default(),
language::Capability::ReadWrite,
language_registry,
@@ -740,7 +734,7 @@ impl AssistantContext {
}
pub fn new(
id: ContextId,
id: TextThreadId,
replica_id: ReplicaId,
capability: language::Capability,
language_registry: Arc<LanguageRegistry>,
@@ -776,7 +770,7 @@ impl AssistantContext {
slash_command_output_sections: Vec::new(),
thought_process_output_sections: Vec::new(),
edits_since_last_parse: edits_since_last_slash_command_parse,
summary: ContextSummary::Pending,
summary: TextThreadSummary::Pending,
summary_task: Task::ready(None),
completion_count: Default::default(),
pending_completions: Default::default(),
@@ -796,7 +790,7 @@ impl AssistantContext {
};
let first_message_id = MessageId(clock::Lamport {
replica_id: 0,
replica_id: ReplicaId::LOCAL,
value: 0,
});
let message = MessageAnchor {
@@ -819,12 +813,12 @@ impl AssistantContext {
this
}
pub(crate) fn serialize(&self, cx: &App) -> SavedContext {
pub(crate) fn serialize(&self, cx: &App) -> SavedTextThread {
let buffer = self.buffer.read(cx);
SavedContext {
SavedTextThread {
id: Some(self.id.clone()),
zed: "context".into(),
version: SavedContext::VERSION.into(),
version: SavedTextThread::VERSION.into(),
text: buffer.text(),
messages: self
.messages(cx)
@@ -872,7 +866,7 @@ impl AssistantContext {
}
pub fn deserialize(
saved_context: SavedContext,
saved_context: SavedTextThread,
path: Arc<Path>,
language_registry: Arc<LanguageRegistry>,
prompt_builder: Arc<PromptBuilder>,
@@ -881,7 +875,7 @@ impl AssistantContext {
telemetry: Option<Arc<Telemetry>>,
cx: &mut Context<Self>,
) -> Self {
let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
let id = saved_context.id.clone().unwrap_or_else(TextThreadId::new);
let mut this = Self::new(
id,
ReplicaId::default(),
@@ -902,7 +896,7 @@ impl AssistantContext {
this
}
pub fn id(&self) -> &ContextId {
pub fn id(&self) -> &TextThreadId {
&self.id
}
@@ -910,9 +904,9 @@ impl AssistantContext {
self.timestamp.replica_id
}
pub fn version(&self, cx: &App) -> ContextVersion {
ContextVersion {
context: self.version.clone(),
pub fn version(&self, cx: &App) -> TextThreadVersion {
TextThreadVersion {
text_thread: self.version.clone(),
buffer: self.buffer.read(cx).version(),
}
}
@@ -934,7 +928,7 @@ impl AssistantContext {
pub fn serialize_ops(
&self,
since: &ContextVersion,
since: &TextThreadVersion,
cx: &App,
) -> Task<Vec<proto::ContextOperation>> {
let buffer_ops = self
@@ -945,7 +939,7 @@ impl AssistantContext {
let mut context_ops = self
.operations
.iter()
.filter(|op| !since.context.observed(op.timestamp()))
.filter(|op| !since.text_thread.observed(op.timestamp()))
.cloned()
.collect::<Vec<_>>();
context_ops.extend(self.pending_ops.iter().cloned());
@@ -969,13 +963,13 @@ impl AssistantContext {
pub fn apply_ops(
&mut self,
ops: impl IntoIterator<Item = ContextOperation>,
ops: impl IntoIterator<Item = TextThreadOperation>,
cx: &mut Context<Self>,
) {
let mut buffer_ops = Vec::new();
for op in ops {
match op {
ContextOperation::BufferOperation(buffer_op) => buffer_ops.push(buffer_op),
TextThreadOperation::BufferOperation(buffer_op) => buffer_ops.push(buffer_op),
op @ _ => self.pending_ops.push(op),
}
}
@@ -984,7 +978,7 @@ impl AssistantContext {
self.flush_ops(cx);
}
fn flush_ops(&mut self, cx: &mut Context<AssistantContext>) {
fn flush_ops(&mut self, cx: &mut Context<TextThread>) {
let mut changed_messages = HashSet::default();
let mut summary_generated = false;
@@ -997,7 +991,7 @@ impl AssistantContext {
let timestamp = op.timestamp();
match op.clone() {
ContextOperation::InsertMessage {
TextThreadOperation::InsertMessage {
anchor, metadata, ..
} => {
if self.messages_metadata.contains_key(&anchor.id) {
@@ -1007,7 +1001,7 @@ impl AssistantContext {
self.insert_message(anchor, metadata, cx);
}
}
ContextOperation::UpdateMessage {
TextThreadOperation::UpdateMessage {
message_id,
metadata: new_metadata,
..
@@ -1018,7 +1012,7 @@ impl AssistantContext {
changed_messages.insert(message_id);
}
}
ContextOperation::UpdateSummary {
TextThreadOperation::UpdateSummary {
summary: new_summary,
..
} => {
@@ -1027,11 +1021,11 @@ impl AssistantContext {
.timestamp()
.is_none_or(|current_timestamp| new_summary.timestamp > current_timestamp)
{
self.summary = ContextSummary::Content(new_summary);
self.summary = TextThreadSummary::Content(new_summary);
summary_generated = true;
}
}
ContextOperation::SlashCommandStarted {
TextThreadOperation::SlashCommandStarted {
id,
output_range,
name,
@@ -1048,9 +1042,9 @@ impl AssistantContext {
timestamp: id.0,
},
);
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id: id });
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id: id });
}
ContextOperation::SlashCommandOutputSectionAdded { section, .. } => {
TextThreadOperation::SlashCommandOutputSectionAdded { section, .. } => {
let buffer = self.buffer.read(cx);
if let Err(ix) = self
.slash_command_output_sections
@@ -1058,10 +1052,10 @@ impl AssistantContext {
{
self.slash_command_output_sections
.insert(ix, section.clone());
cx.emit(ContextEvent::SlashCommandOutputSectionAdded { section });
cx.emit(TextThreadEvent::SlashCommandOutputSectionAdded { section });
}
}
ContextOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
TextThreadOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
let buffer = self.buffer.read(cx);
if let Err(ix) = self
.thought_process_output_sections
@@ -1071,7 +1065,7 @@ impl AssistantContext {
.insert(ix, section.clone());
}
}
ContextOperation::SlashCommandFinished {
TextThreadOperation::SlashCommandFinished {
id,
error_message,
timestamp,
@@ -1090,10 +1084,10 @@ impl AssistantContext {
slash_command.status = InvokedSlashCommandStatus::Finished;
}
}
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id: id });
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id: id });
}
}
ContextOperation::BufferOperation(_) => unreachable!(),
TextThreadOperation::BufferOperation(_) => unreachable!(),
}
self.version.observe(timestamp);
@@ -1103,43 +1097,43 @@ impl AssistantContext {
if !changed_messages.is_empty() {
self.message_roles_updated(changed_messages, cx);
cx.emit(ContextEvent::MessagesEdited);
cx.emit(TextThreadEvent::MessagesEdited);
cx.notify();
}
if summary_generated {
cx.emit(ContextEvent::SummaryChanged);
cx.emit(ContextEvent::SummaryGenerated);
cx.emit(TextThreadEvent::SummaryChanged);
cx.emit(TextThreadEvent::SummaryGenerated);
cx.notify();
}
}
fn can_apply_op(&self, op: &ContextOperation, cx: &App) -> bool {
fn can_apply_op(&self, op: &TextThreadOperation, cx: &App) -> bool {
if !self.version.observed_all(op.version()) {
return false;
}
match op {
ContextOperation::InsertMessage { anchor, .. } => self
TextThreadOperation::InsertMessage { anchor, .. } => self
.buffer
.read(cx)
.version
.observed(anchor.start.timestamp),
ContextOperation::UpdateMessage { message_id, .. } => {
TextThreadOperation::UpdateMessage { message_id, .. } => {
self.messages_metadata.contains_key(message_id)
}
ContextOperation::UpdateSummary { .. } => true,
ContextOperation::SlashCommandStarted { output_range, .. } => {
TextThreadOperation::UpdateSummary { .. } => true,
TextThreadOperation::SlashCommandStarted { output_range, .. } => {
self.has_received_operations_for_anchor_range(output_range.clone(), cx)
}
ContextOperation::SlashCommandOutputSectionAdded { section, .. } => {
TextThreadOperation::SlashCommandOutputSectionAdded { section, .. } => {
self.has_received_operations_for_anchor_range(section.range.clone(), cx)
}
ContextOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
TextThreadOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
self.has_received_operations_for_anchor_range(section.range.clone(), cx)
}
ContextOperation::SlashCommandFinished { .. } => true,
ContextOperation::BufferOperation(_) => {
TextThreadOperation::SlashCommandFinished { .. } => true,
TextThreadOperation::BufferOperation(_) => {
panic!("buffer operations should always be applied")
}
}
@@ -1160,9 +1154,9 @@ impl AssistantContext {
observed_start && observed_end
}
fn push_op(&mut self, op: ContextOperation, cx: &mut Context<Self>) {
fn push_op(&mut self, op: TextThreadOperation, cx: &mut Context<Self>) {
self.operations.push(op.clone());
cx.emit(ContextEvent::Operation(op));
cx.emit(TextThreadEvent::Operation(op));
}
pub fn buffer(&self) -> &Entity<Buffer> {
@@ -1185,7 +1179,7 @@ impl AssistantContext {
self.path.as_ref()
}
pub fn summary(&self) -> &ContextSummary {
pub fn summary(&self) -> &TextThreadSummary {
&self.summary
}
@@ -1246,13 +1240,13 @@ impl AssistantContext {
language::BufferEvent::Operation {
operation,
is_local: true,
} => cx.emit(ContextEvent::Operation(ContextOperation::BufferOperation(
operation.clone(),
))),
} => cx.emit(TextThreadEvent::Operation(
TextThreadOperation::BufferOperation(operation.clone()),
)),
language::BufferEvent::Edited => {
self.count_remaining_tokens(cx);
self.reparse(cx);
cx.emit(ContextEvent::MessagesEdited);
cx.emit(TextThreadEvent::MessagesEdited);
}
_ => {}
}
@@ -1518,7 +1512,7 @@ impl AssistantContext {
if !updated_parsed_slash_commands.is_empty()
|| !removed_parsed_slash_command_ranges.is_empty()
{
cx.emit(ContextEvent::ParsedSlashCommandsUpdated {
cx.emit(TextThreadEvent::ParsedSlashCommandsUpdated {
removed: removed_parsed_slash_command_ranges,
updated: updated_parsed_slash_commands,
});
@@ -1592,7 +1586,7 @@ impl AssistantContext {
&& (!command.range.start.is_valid(buffer) || !command.range.end.is_valid(buffer))
{
command.status = InvokedSlashCommandStatus::Finished;
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
invalidated_command_ids.push(command_id);
}
}
@@ -1601,7 +1595,7 @@ impl AssistantContext {
let version = self.version.clone();
let timestamp = self.next_timestamp();
self.push_op(
ContextOperation::SlashCommandFinished {
TextThreadOperation::SlashCommandFinished {
id: command_id,
timestamp,
error_message: None,
@@ -1906,9 +1900,9 @@ impl AssistantContext {
}
}
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
this.push_op(
ContextOperation::SlashCommandFinished {
TextThreadOperation::SlashCommandFinished {
id: command_id,
timestamp,
error_message,
@@ -1931,9 +1925,9 @@ impl AssistantContext {
timestamp: command_id.0,
},
);
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
self.push_op(
ContextOperation::SlashCommandStarted {
TextThreadOperation::SlashCommandStarted {
id: command_id,
output_range: command_range,
name: name.to_string(),
@@ -1957,13 +1951,13 @@ impl AssistantContext {
};
self.slash_command_output_sections
.insert(insertion_ix, section.clone());
cx.emit(ContextEvent::SlashCommandOutputSectionAdded {
cx.emit(TextThreadEvent::SlashCommandOutputSectionAdded {
section: section.clone(),
});
let version = self.version.clone();
let timestamp = self.next_timestamp();
self.push_op(
ContextOperation::SlashCommandOutputSectionAdded {
TextThreadOperation::SlashCommandOutputSectionAdded {
timestamp,
section,
version,
@@ -1992,7 +1986,7 @@ impl AssistantContext {
let version = self.version.clone();
let timestamp = self.next_timestamp();
self.push_op(
ContextOperation::ThoughtProcessOutputSectionAdded {
TextThreadOperation::ThoughtProcessOutputSectionAdded {
timestamp,
section,
version,
@@ -2111,7 +2105,7 @@ impl AssistantContext {
let end = buffer
.anchor_before(message_old_end_offset + chunk_len);
context_event = Some(
ContextEvent::StartedThoughtProcess(start..end),
TextThreadEvent::StartedThoughtProcess(start..end),
);
} else {
// This ensures that all the thinking chunks are inserted inside the thinking tag
@@ -2129,7 +2123,7 @@ impl AssistantContext {
if let Some(start) = thought_process_stack.pop() {
let end = buffer.anchor_before(message_old_end_offset);
context_event =
Some(ContextEvent::EndedThoughtProcess(end));
Some(TextThreadEvent::EndedThoughtProcess(end));
thought_process_output_section =
Some(ThoughtProcessOutputSection {
range: start..end,
@@ -2159,7 +2153,7 @@ impl AssistantContext {
cx.emit(context_event);
}
cx.emit(ContextEvent::StreamedCompletion);
cx.emit(TextThreadEvent::StreamedCompletion);
Some(())
})?;
@@ -2180,7 +2174,7 @@ impl AssistantContext {
this.update(cx, |this, cx| {
let error_message = if let Some(error) = result.as_ref().err() {
if error.is::<PaymentRequiredError>() {
cx.emit(ContextEvent::ShowPaymentRequiredError);
cx.emit(TextThreadEvent::ShowPaymentRequiredError);
this.update_metadata(assistant_message_id, cx, |metadata| {
metadata.status = MessageStatus::Canceled;
});
@@ -2191,7 +2185,7 @@ impl AssistantContext {
.map(|err| err.to_string())
.collect::<Vec<_>>()
.join("\n");
cx.emit(ContextEvent::ShowAssistError(SharedString::from(
cx.emit(TextThreadEvent::ShowAssistError(SharedString::from(
error_message.clone(),
)));
this.update_metadata(assistant_message_id, cx, |metadata| {
@@ -2408,13 +2402,13 @@ impl AssistantContext {
if let Some(metadata) = self.messages_metadata.get_mut(&id) {
f(metadata);
metadata.timestamp = timestamp;
let operation = ContextOperation::UpdateMessage {
let operation = TextThreadOperation::UpdateMessage {
message_id: id,
metadata: metadata.clone(),
version,
};
self.push_op(operation, cx);
cx.emit(ContextEvent::MessagesEdited);
cx.emit(TextThreadEvent::MessagesEdited);
cx.notify();
}
}
@@ -2478,7 +2472,7 @@ impl AssistantContext {
};
self.insert_message(anchor.clone(), metadata.clone(), cx);
self.push_op(
ContextOperation::InsertMessage {
TextThreadOperation::InsertMessage {
anchor: anchor.clone(),
metadata,
version,
@@ -2501,7 +2495,7 @@ impl AssistantContext {
Err(ix) => ix,
};
self.contents.insert(insertion_ix, content);
cx.emit(ContextEvent::MessagesEdited);
cx.emit(TextThreadEvent::MessagesEdited);
}
pub fn contents<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = Content> {
@@ -2576,7 +2570,7 @@ impl AssistantContext {
};
self.insert_message(suffix.clone(), suffix_metadata.clone(), cx);
self.push_op(
ContextOperation::InsertMessage {
TextThreadOperation::InsertMessage {
anchor: suffix.clone(),
metadata: suffix_metadata,
version,
@@ -2626,7 +2620,7 @@ impl AssistantContext {
};
self.insert_message(selection.clone(), selection_metadata.clone(), cx);
self.push_op(
ContextOperation::InsertMessage {
TextThreadOperation::InsertMessage {
anchor: selection.clone(),
metadata: selection_metadata,
version,
@@ -2638,7 +2632,7 @@ impl AssistantContext {
};
if !edited_buffer {
cx.emit(ContextEvent::MessagesEdited);
cx.emit(TextThreadEvent::MessagesEdited);
}
new_messages
} else {
@@ -2652,7 +2646,7 @@ impl AssistantContext {
new_metadata: MessageMetadata,
cx: &mut Context<Self>,
) {
cx.emit(ContextEvent::MessagesEdited);
cx.emit(TextThreadEvent::MessagesEdited);
self.messages_metadata.insert(new_anchor.id, new_metadata);
@@ -2688,15 +2682,15 @@ impl AssistantContext {
// If there is no summary, it is set with `done: false` so that "Loading Summary…" can
// be displayed.
match self.summary {
ContextSummary::Pending | ContextSummary::Error => {
self.summary = ContextSummary::Content(ContextSummaryContent {
TextThreadSummary::Pending | TextThreadSummary::Error => {
self.summary = TextThreadSummary::Content(TextThreadSummaryContent {
text: "".to_string(),
done: false,
timestamp: clock::Lamport::default(),
timestamp: clock::Lamport::MIN,
});
replace_old = true;
}
ContextSummary::Content(_) => {}
TextThreadSummary::Content(_) => {}
}
self.summary_task = cx.spawn(async move |this, cx| {
@@ -2718,13 +2712,13 @@ impl AssistantContext {
}
summary.text.extend(lines.next());
summary.timestamp = timestamp;
let operation = ContextOperation::UpdateSummary {
let operation = TextThreadOperation::UpdateSummary {
summary: summary.clone(),
version,
};
this.push_op(operation, cx);
cx.emit(ContextEvent::SummaryChanged);
cx.emit(ContextEvent::SummaryGenerated);
cx.emit(TextThreadEvent::SummaryChanged);
cx.emit(TextThreadEvent::SummaryGenerated);
})?;
// Stop if the LLM generated multiple lines.
@@ -2748,13 +2742,13 @@ impl AssistantContext {
if let Some(summary) = this.summary.content_as_mut() {
summary.done = true;
summary.timestamp = timestamp;
let operation = ContextOperation::UpdateSummary {
let operation = TextThreadOperation::UpdateSummary {
summary: summary.clone(),
version,
};
this.push_op(operation, cx);
cx.emit(ContextEvent::SummaryChanged);
cx.emit(ContextEvent::SummaryGenerated);
cx.emit(TextThreadEvent::SummaryChanged);
cx.emit(TextThreadEvent::SummaryGenerated);
}
})?;
@@ -2764,8 +2758,8 @@ impl AssistantContext {
if let Err(err) = result {
this.update(cx, |this, cx| {
this.summary = ContextSummary::Error;
cx.emit(ContextEvent::SummaryChanged);
this.summary = TextThreadSummary::Error;
cx.emit(TextThreadEvent::SummaryChanged);
})
.log_err();
log::error!("Error generating context summary: {}", err);
@@ -2871,7 +2865,7 @@ impl AssistantContext {
&mut self,
debounce: Option<Duration>,
fs: Arc<dyn Fs>,
cx: &mut Context<AssistantContext>,
cx: &mut Context<TextThread>,
) {
if self.replica_id() != ReplicaId::default() {
// Prevent saving a remote context for now.
@@ -2902,7 +2896,7 @@ impl AssistantContext {
let mut discriminant = 1;
let mut new_path;
loop {
new_path = contexts_dir().join(&format!(
new_path = text_threads_dir().join(&format!(
"{} - {}.zed.json",
summary.trim(),
discriminant
@@ -2914,7 +2908,7 @@ impl AssistantContext {
}
}
fs.create_dir(contexts_dir().as_ref()).await?;
fs.create_dir(text_threads_dir().as_ref()).await?;
// rename before write ensures that only one file exists
if let Some(old_path) = old_path.as_ref()
@@ -2936,7 +2930,7 @@ impl AssistantContext {
let new_path: Arc<Path> = new_path.clone().into();
move |this, cx| {
this.path = Some(new_path.clone());
cx.emit(ContextEvent::PathChanged { old_path, new_path });
cx.emit(TextThreadEvent::PathChanged { old_path, new_path });
}
})
.ok();
@@ -2955,7 +2949,7 @@ impl AssistantContext {
summary.timestamp = timestamp;
summary.done = true;
summary.text = custom_summary;
cx.emit(ContextEvent::SummaryChanged);
cx.emit(TextThreadEvent::SummaryChanged);
}
fn update_model_request_usage(&self, amount: u32, limit: UsageLimit, cx: &mut App) {
@@ -2975,23 +2969,23 @@ impl AssistantContext {
}
#[derive(Debug, Default)]
pub struct ContextVersion {
context: clock::Global,
pub struct TextThreadVersion {
text_thread: clock::Global,
buffer: clock::Global,
}
impl ContextVersion {
impl TextThreadVersion {
pub fn from_proto(proto: &proto::ContextVersion) -> Self {
Self {
context: language::proto::deserialize_version(&proto.context_version),
text_thread: language::proto::deserialize_version(&proto.context_version),
buffer: language::proto::deserialize_version(&proto.buffer_version),
}
}
pub fn to_proto(&self, context_id: ContextId) -> proto::ContextVersion {
pub fn to_proto(&self, context_id: TextThreadId) -> proto::ContextVersion {
proto::ContextVersion {
context_id: context_id.to_proto(),
context_version: language::proto::serialize_version(&self.context),
context_version: language::proto::serialize_version(&self.text_thread),
buffer_version: language::proto::serialize_version(&self.buffer),
}
}
@@ -3059,8 +3053,8 @@ pub struct SavedMessage {
}
#[derive(Serialize, Deserialize)]
pub struct SavedContext {
pub id: Option<ContextId>,
pub struct SavedTextThread {
pub id: Option<TextThreadId>,
pub zed: String,
pub version: String,
pub text: String,
@@ -3072,7 +3066,7 @@ pub struct SavedContext {
pub thought_process_output_sections: Vec<ThoughtProcessOutputSection<usize>>,
}
impl SavedContext {
impl SavedTextThread {
pub const VERSION: &'static str = "0.4.0";
pub fn from_json(json: &str) -> Result<Self> {
@@ -3082,9 +3076,9 @@ impl SavedContext {
.context("version not found")?
{
serde_json::Value::String(version) => match version.as_str() {
SavedContext::VERSION => {
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
}
SavedTextThread::VERSION => Ok(serde_json::from_value::<SavedTextThread>(
saved_context_json,
)?),
SavedContextV0_3_0::VERSION => {
let saved_context =
serde_json::from_value::<SavedContextV0_3_0>(saved_context_json)?;
@@ -3109,18 +3103,18 @@ impl SavedContext {
fn into_ops(
self,
buffer: &Entity<Buffer>,
cx: &mut Context<AssistantContext>,
) -> Vec<ContextOperation> {
cx: &mut Context<TextThread>,
) -> Vec<TextThreadOperation> {
let mut operations = Vec::new();
let mut version = clock::Global::new();
let mut next_timestamp = clock::Lamport::new(ReplicaId::default());
let mut first_message_metadata = None;
for message in self.messages {
if message.id == MessageId(clock::Lamport::default()) {
if message.id == MessageId(clock::Lamport::MIN) {
first_message_metadata = Some(message.metadata);
} else {
operations.push(ContextOperation::InsertMessage {
operations.push(TextThreadOperation::InsertMessage {
anchor: MessageAnchor {
id: message.id,
start: buffer.read(cx).anchor_before(message.start),
@@ -3140,8 +3134,8 @@ impl SavedContext {
if let Some(metadata) = first_message_metadata {
let timestamp = next_timestamp.tick();
operations.push(ContextOperation::UpdateMessage {
message_id: MessageId(clock::Lamport::default()),
operations.push(TextThreadOperation::UpdateMessage {
message_id: MessageId(clock::Lamport::MIN),
metadata: MessageMetadata {
role: metadata.role,
status: metadata.status,
@@ -3156,7 +3150,7 @@ impl SavedContext {
let buffer = buffer.read(cx);
for section in self.slash_command_output_sections {
let timestamp = next_timestamp.tick();
operations.push(ContextOperation::SlashCommandOutputSectionAdded {
operations.push(TextThreadOperation::SlashCommandOutputSectionAdded {
timestamp,
section: SlashCommandOutputSection {
range: buffer.anchor_after(section.range.start)
@@ -3173,7 +3167,7 @@ impl SavedContext {
for section in self.thought_process_output_sections {
let timestamp = next_timestamp.tick();
operations.push(ContextOperation::ThoughtProcessOutputSectionAdded {
operations.push(TextThreadOperation::ThoughtProcessOutputSectionAdded {
timestamp,
section: ThoughtProcessOutputSection {
range: buffer.anchor_after(section.range.start)
@@ -3186,8 +3180,8 @@ impl SavedContext {
}
let timestamp = next_timestamp.tick();
operations.push(ContextOperation::UpdateSummary {
summary: ContextSummaryContent {
operations.push(TextThreadOperation::UpdateSummary {
summary: TextThreadSummaryContent {
text: self.summary,
done: true,
timestamp,
@@ -3217,7 +3211,7 @@ struct SavedMessageMetadataPreV0_4_0 {
#[derive(Serialize, Deserialize)]
struct SavedContextV0_3_0 {
id: Option<ContextId>,
id: Option<TextThreadId>,
zed: String,
version: String,
text: String,
@@ -3230,11 +3224,11 @@ struct SavedContextV0_3_0 {
impl SavedContextV0_3_0 {
const VERSION: &'static str = "0.3.0";
fn upgrade(self) -> SavedContext {
SavedContext {
fn upgrade(self) -> SavedTextThread {
SavedTextThread {
id: self.id,
zed: self.zed,
version: SavedContext::VERSION.into(),
version: SavedTextThread::VERSION.into(),
text: self.text,
messages: self
.messages
@@ -3266,7 +3260,7 @@ impl SavedContextV0_3_0 {
#[derive(Serialize, Deserialize)]
struct SavedContextV0_2_0 {
id: Option<ContextId>,
id: Option<TextThreadId>,
zed: String,
version: String,
text: String,
@@ -3278,7 +3272,7 @@ struct SavedContextV0_2_0 {
impl SavedContextV0_2_0 {
const VERSION: &'static str = "0.2.0";
fn upgrade(self) -> SavedContext {
fn upgrade(self) -> SavedTextThread {
SavedContextV0_3_0 {
id: self.id,
zed: self.zed,
@@ -3295,7 +3289,7 @@ impl SavedContextV0_2_0 {
#[derive(Serialize, Deserialize)]
struct SavedContextV0_1_0 {
id: Option<ContextId>,
id: Option<TextThreadId>,
zed: String,
version: String,
text: String,
@@ -3309,7 +3303,7 @@ struct SavedContextV0_1_0 {
impl SavedContextV0_1_0 {
const VERSION: &'static str = "0.1.0";
fn upgrade(self) -> SavedContext {
fn upgrade(self) -> SavedTextThread {
SavedContextV0_2_0 {
id: self.id,
zed: self.zed,
@@ -3324,7 +3318,7 @@ impl SavedContextV0_1_0 {
}
#[derive(Debug, Clone)]
pub struct SavedContextMetadata {
pub struct SavedTextThreadMetadata {
pub title: SharedString,
pub path: Arc<Path>,
pub mtime: chrono::DateTime<chrono::Local>,

View File

@@ -1,6 +1,6 @@
use crate::{
AssistantContext, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
SavedContextMetadata,
SavedTextThread, SavedTextThreadMetadata, TextThread, TextThreadEvent, TextThreadId,
TextThreadOperation, TextThreadVersion,
};
use anyhow::{Context as _, Result};
use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
@@ -11,9 +11,9 @@ use context_server::ContextServerId;
use fs::{Fs, RemoveOptions};
use futures::StreamExt;
use fuzzy::StringMatchCandidate;
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, Task, WeakEntity};
use language::LanguageRegistry;
use paths::contexts_dir;
use paths::text_threads_dir;
use project::{
Project,
context_server_store::{ContextServerStatus, ContextServerStore},
@@ -27,24 +27,24 @@ use util::{ResultExt, TryFutureExt};
use zed_env_vars::ZED_STATELESS;
pub(crate) fn init(client: &AnyProtoClient) {
client.add_entity_message_handler(ContextStore::handle_advertise_contexts);
client.add_entity_request_handler(ContextStore::handle_open_context);
client.add_entity_request_handler(ContextStore::handle_create_context);
client.add_entity_message_handler(ContextStore::handle_update_context);
client.add_entity_request_handler(ContextStore::handle_synchronize_contexts);
client.add_entity_message_handler(TextThreadStore::handle_advertise_contexts);
client.add_entity_request_handler(TextThreadStore::handle_open_context);
client.add_entity_request_handler(TextThreadStore::handle_create_context);
client.add_entity_message_handler(TextThreadStore::handle_update_context);
client.add_entity_request_handler(TextThreadStore::handle_synchronize_contexts);
}
#[derive(Clone)]
pub struct RemoteContextMetadata {
pub id: ContextId,
pub struct RemoteTextThreadMetadata {
pub id: TextThreadId,
pub summary: Option<String>,
}
pub struct ContextStore {
contexts: Vec<ContextHandle>,
contexts_metadata: Vec<SavedContextMetadata>,
pub struct TextThreadStore {
text_threads: Vec<TextThreadHandle>,
text_threads_metadata: Vec<SavedTextThreadMetadata>,
context_server_slash_command_ids: HashMap<ContextServerId, Vec<SlashCommandId>>,
host_contexts: Vec<RemoteContextMetadata>,
host_text_threads: Vec<RemoteTextThreadMetadata>,
fs: Arc<dyn Fs>,
languages: Arc<LanguageRegistry>,
slash_commands: Arc<SlashCommandWorkingSet>,
@@ -58,34 +58,28 @@ pub struct ContextStore {
prompt_builder: Arc<PromptBuilder>,
}
pub enum ContextStoreEvent {
ContextCreated(ContextId),
enum TextThreadHandle {
Weak(WeakEntity<TextThread>),
Strong(Entity<TextThread>),
}
impl EventEmitter<ContextStoreEvent> for ContextStore {}
enum ContextHandle {
Weak(WeakEntity<AssistantContext>),
Strong(Entity<AssistantContext>),
}
impl ContextHandle {
fn upgrade(&self) -> Option<Entity<AssistantContext>> {
impl TextThreadHandle {
fn upgrade(&self) -> Option<Entity<TextThread>> {
match self {
ContextHandle::Weak(weak) => weak.upgrade(),
ContextHandle::Strong(strong) => Some(strong.clone()),
TextThreadHandle::Weak(weak) => weak.upgrade(),
TextThreadHandle::Strong(strong) => Some(strong.clone()),
}
}
fn downgrade(&self) -> WeakEntity<AssistantContext> {
fn downgrade(&self) -> WeakEntity<TextThread> {
match self {
ContextHandle::Weak(weak) => weak.clone(),
ContextHandle::Strong(strong) => strong.downgrade(),
TextThreadHandle::Weak(weak) => weak.clone(),
TextThreadHandle::Strong(strong) => strong.downgrade(),
}
}
}
impl ContextStore {
impl TextThreadStore {
pub fn new(
project: Entity<Project>,
prompt_builder: Arc<PromptBuilder>,
@@ -97,14 +91,14 @@ impl ContextStore {
let telemetry = project.read(cx).client().telemetry().clone();
cx.spawn(async move |cx| {
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
let (mut events, _) = fs.watch(text_threads_dir(), CONTEXT_WATCH_DURATION).await;
let this = cx.new(|cx: &mut Context<Self>| {
let mut this = Self {
contexts: Vec::new(),
contexts_metadata: Vec::new(),
text_threads: Vec::new(),
text_threads_metadata: Vec::new(),
context_server_slash_command_ids: HashMap::default(),
host_contexts: Vec::new(),
host_text_threads: Vec::new(),
fs,
languages,
slash_commands,
@@ -142,10 +136,10 @@ impl ContextStore {
#[cfg(any(test, feature = "test-support"))]
pub fn fake(project: Entity<Project>, cx: &mut Context<Self>) -> Self {
Self {
contexts: Default::default(),
contexts_metadata: Default::default(),
text_threads: Default::default(),
text_threads_metadata: Default::default(),
context_server_slash_command_ids: Default::default(),
host_contexts: Default::default(),
host_text_threads: Default::default(),
fs: project.read(cx).fs().clone(),
languages: project.read(cx).languages().clone(),
slash_commands: Arc::default(),
@@ -166,13 +160,13 @@ impl ContextStore {
mut cx: AsyncApp,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
this.host_contexts = envelope
this.host_text_threads = envelope
.payload
.contexts
.into_iter()
.map(|context| RemoteContextMetadata {
id: ContextId::from_proto(context.context_id),
summary: context.summary,
.map(|text_thread| RemoteTextThreadMetadata {
id: TextThreadId::from_proto(text_thread.context_id),
summary: text_thread.summary,
})
.collect();
cx.notify();
@@ -184,25 +178,25 @@ impl ContextStore {
envelope: TypedEnvelope<proto::OpenContext>,
mut cx: AsyncApp,
) -> Result<proto::OpenContextResponse> {
let context_id = ContextId::from_proto(envelope.payload.context_id);
let context_id = TextThreadId::from_proto(envelope.payload.context_id);
let operations = this.update(&mut cx, |this, cx| {
anyhow::ensure!(
!this.project.read(cx).is_via_collab(),
"only the host contexts can be opened"
);
let context = this
.loaded_context_for_id(&context_id, cx)
let text_thread = this
.loaded_text_thread_for_id(&context_id, cx)
.context("context not found")?;
anyhow::ensure!(
context.read(cx).replica_id() == ReplicaId::default(),
text_thread.read(cx).replica_id() == ReplicaId::default(),
"context must be opened via the host"
);
anyhow::Ok(
context
text_thread
.read(cx)
.serialize_ops(&ContextVersion::default(), cx),
.serialize_ops(&TextThreadVersion::default(), cx),
)
})??;
let operations = operations.await;
@@ -222,15 +216,14 @@ impl ContextStore {
"can only create contexts as the host"
);
let context = this.create(cx);
let context_id = context.read(cx).id().clone();
cx.emit(ContextStoreEvent::ContextCreated(context_id.clone()));
let text_thread = this.create(cx);
let context_id = text_thread.read(cx).id().clone();
anyhow::Ok((
context_id,
context
text_thread
.read(cx)
.serialize_ops(&ContextVersion::default(), cx),
.serialize_ops(&TextThreadVersion::default(), cx),
))
})??;
let operations = operations.await;
@@ -246,11 +239,11 @@ impl ContextStore {
mut cx: AsyncApp,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
let context_id = ContextId::from_proto(envelope.payload.context_id);
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
let context_id = TextThreadId::from_proto(envelope.payload.context_id);
if let Some(text_thread) = this.loaded_text_thread_for_id(&context_id, cx) {
let operation_proto = envelope.payload.operation.context("invalid operation")?;
let operation = ContextOperation::from_proto(operation_proto)?;
context.update(cx, |context, cx| context.apply_ops([operation], cx));
let operation = TextThreadOperation::from_proto(operation_proto)?;
text_thread.update(cx, |text_thread, cx| text_thread.apply_ops([operation], cx));
}
Ok(())
})?
@@ -269,12 +262,12 @@ impl ContextStore {
let mut local_versions = Vec::new();
for remote_version_proto in envelope.payload.contexts {
let remote_version = ContextVersion::from_proto(&remote_version_proto);
let context_id = ContextId::from_proto(remote_version_proto.context_id);
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
let context = context.read(cx);
let operations = context.serialize_ops(&remote_version, cx);
local_versions.push(context.version(cx).to_proto(context_id.clone()));
let remote_version = TextThreadVersion::from_proto(&remote_version_proto);
let context_id = TextThreadId::from_proto(remote_version_proto.context_id);
if let Some(text_thread) = this.loaded_text_thread_for_id(&context_id, cx) {
let text_thread = text_thread.read(cx);
let operations = text_thread.serialize_ops(&remote_version, cx);
local_versions.push(text_thread.version(cx).to_proto(context_id.clone()));
let client = this.client.clone();
let project_id = envelope.payload.project_id;
cx.background_spawn(async move {
@@ -308,9 +301,9 @@ impl ContextStore {
}
if is_shared {
self.contexts.retain_mut(|context| {
if let Some(strong_context) = context.upgrade() {
*context = ContextHandle::Strong(strong_context);
self.text_threads.retain_mut(|text_thread| {
if let Some(strong_context) = text_thread.upgrade() {
*text_thread = TextThreadHandle::Strong(strong_context);
true
} else {
false
@@ -345,12 +338,12 @@ impl ContextStore {
self.synchronize_contexts(cx);
}
project::Event::DisconnectedFromHost => {
self.contexts.retain_mut(|context| {
if let Some(strong_context) = context.upgrade() {
*context = ContextHandle::Weak(context.downgrade());
strong_context.update(cx, |context, cx| {
if context.replica_id() != ReplicaId::default() {
context.set_capability(language::Capability::ReadOnly, cx);
self.text_threads.retain_mut(|text_thread| {
if let Some(strong_context) = text_thread.upgrade() {
*text_thread = TextThreadHandle::Weak(text_thread.downgrade());
strong_context.update(cx, |text_thread, cx| {
if text_thread.replica_id() != ReplicaId::default() {
text_thread.set_capability(language::Capability::ReadOnly, cx);
}
});
true
@@ -358,20 +351,24 @@ impl ContextStore {
false
}
});
self.host_contexts.clear();
self.host_text_threads.clear();
cx.notify();
}
_ => {}
}
}
pub fn unordered_contexts(&self) -> impl Iterator<Item = &SavedContextMetadata> {
self.contexts_metadata.iter()
pub fn unordered_text_threads(&self) -> impl Iterator<Item = &SavedTextThreadMetadata> {
self.text_threads_metadata.iter()
}
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
pub fn host_text_threads(&self) -> impl Iterator<Item = &RemoteTextThreadMetadata> {
self.host_text_threads.iter()
}
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<TextThread> {
let context = cx.new(|cx| {
AssistantContext::local(
TextThread::local(
self.languages.clone(),
Some(self.project.clone()),
Some(self.telemetry.clone()),
@@ -380,14 +377,11 @@ impl ContextStore {
cx,
)
});
self.register_context(&context, cx);
self.register_text_thread(&context, cx);
context
}
pub fn create_remote_context(
&mut self,
cx: &mut Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
pub fn create_remote(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<TextThread>>> {
let project = self.project.read(cx);
let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
@@ -403,10 +397,10 @@ impl ContextStore {
let request = self.client.request(proto::CreateContext { project_id });
cx.spawn(async move |this, cx| {
let response = request.await?;
let context_id = ContextId::from_proto(response.context_id);
let context_id = TextThreadId::from_proto(response.context_id);
let context_proto = response.context.context("invalid context")?;
let context = cx.new(|cx| {
AssistantContext::new(
let text_thread = cx.new(|cx| {
TextThread::new(
context_id.clone(),
replica_id,
capability,
@@ -423,29 +417,29 @@ impl ContextStore {
context_proto
.operations
.into_iter()
.map(ContextOperation::from_proto)
.map(TextThreadOperation::from_proto)
.collect::<Result<Vec<_>>>()
})
.await?;
context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
text_thread.update(cx, |context, cx| context.apply_ops(operations, cx))?;
this.update(cx, |this, cx| {
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
if let Some(existing_context) = this.loaded_text_thread_for_id(&context_id, cx) {
existing_context
} else {
this.register_context(&context, cx);
this.register_text_thread(&text_thread, cx);
this.synchronize_contexts(cx);
context
text_thread
}
})
})
}
pub fn open_local_context(
pub fn open_local(
&mut self,
path: Arc<Path>,
cx: &Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
if let Some(existing_context) = self.loaded_context_for_path(&path, cx) {
) -> Task<Result<Entity<TextThread>>> {
if let Some(existing_context) = self.loaded_text_thread_for_path(&path, cx) {
return Task::ready(Ok(existing_context));
}
@@ -457,7 +451,7 @@ impl ContextStore {
let path = path.clone();
async move {
let saved_context = fs.load(&path).await?;
SavedContext::from_json(&saved_context)
SavedTextThread::from_json(&saved_context)
}
});
let prompt_builder = self.prompt_builder.clone();
@@ -466,7 +460,7 @@ impl ContextStore {
cx.spawn(async move |this, cx| {
let saved_context = load.await?;
let context = cx.new(|cx| {
AssistantContext::deserialize(
TextThread::deserialize(
saved_context,
path.clone(),
languages,
@@ -478,21 +472,17 @@ impl ContextStore {
)
})?;
this.update(cx, |this, cx| {
if let Some(existing_context) = this.loaded_context_for_path(&path, cx) {
if let Some(existing_context) = this.loaded_text_thread_for_path(&path, cx) {
existing_context
} else {
this.register_context(&context, cx);
this.register_text_thread(&context, cx);
context
}
})
})
}
pub fn delete_local_context(
&mut self,
path: Arc<Path>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
pub fn delete_local(&mut self, path: Arc<Path>, cx: &mut Context<Self>) -> Task<Result<()>> {
let fs = self.fs.clone();
cx.spawn(async move |this, cx| {
@@ -506,57 +496,57 @@ impl ContextStore {
.await?;
this.update(cx, |this, cx| {
this.contexts.retain(|context| {
context
this.text_threads.retain(|text_thread| {
text_thread
.upgrade()
.and_then(|context| context.read(cx).path())
.and_then(|text_thread| text_thread.read(cx).path())
!= Some(&path)
});
this.contexts_metadata
.retain(|context| context.path.as_ref() != path.as_ref());
this.text_threads_metadata
.retain(|text_thread| text_thread.path.as_ref() != path.as_ref());
})?;
Ok(())
})
}
fn loaded_context_for_path(&self, path: &Path, cx: &App) -> Option<Entity<AssistantContext>> {
self.contexts.iter().find_map(|context| {
let context = context.upgrade()?;
if context.read(cx).path().map(Arc::as_ref) == Some(path) {
Some(context)
fn loaded_text_thread_for_path(&self, path: &Path, cx: &App) -> Option<Entity<TextThread>> {
self.text_threads.iter().find_map(|text_thread| {
let text_thread = text_thread.upgrade()?;
if text_thread.read(cx).path().map(Arc::as_ref) == Some(path) {
Some(text_thread)
} else {
None
}
})
}
pub fn loaded_context_for_id(
pub fn loaded_text_thread_for_id(
&self,
id: &ContextId,
id: &TextThreadId,
cx: &App,
) -> Option<Entity<AssistantContext>> {
self.contexts.iter().find_map(|context| {
let context = context.upgrade()?;
if context.read(cx).id() == id {
Some(context)
) -> Option<Entity<TextThread>> {
self.text_threads.iter().find_map(|text_thread| {
let text_thread = text_thread.upgrade()?;
if text_thread.read(cx).id() == id {
Some(text_thread)
} else {
None
}
})
}
pub fn open_remote_context(
pub fn open_remote(
&mut self,
context_id: ContextId,
text_thread_id: TextThreadId,
cx: &mut Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
) -> Task<Result<Entity<TextThread>>> {
let project = self.project.read(cx);
let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
};
if let Some(context) = self.loaded_context_for_id(&context_id, cx) {
if let Some(context) = self.loaded_text_thread_for_id(&text_thread_id, cx) {
return Task::ready(Ok(context));
}
@@ -567,16 +557,16 @@ impl ContextStore {
let telemetry = self.telemetry.clone();
let request = self.client.request(proto::OpenContext {
project_id,
context_id: context_id.to_proto(),
context_id: text_thread_id.to_proto(),
});
let prompt_builder = self.prompt_builder.clone();
let slash_commands = self.slash_commands.clone();
cx.spawn(async move |this, cx| {
let response = request.await?;
let context_proto = response.context.context("invalid context")?;
let context = cx.new(|cx| {
AssistantContext::new(
context_id.clone(),
let text_thread = cx.new(|cx| {
TextThread::new(
text_thread_id.clone(),
replica_id,
capability,
language_registry,
@@ -592,38 +582,40 @@ impl ContextStore {
context_proto
.operations
.into_iter()
.map(ContextOperation::from_proto)
.map(TextThreadOperation::from_proto)
.collect::<Result<Vec<_>>>()
})
.await?;
context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
text_thread.update(cx, |context, cx| context.apply_ops(operations, cx))?;
this.update(cx, |this, cx| {
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
if let Some(existing_context) = this.loaded_text_thread_for_id(&text_thread_id, cx)
{
existing_context
} else {
this.register_context(&context, cx);
this.register_text_thread(&text_thread, cx);
this.synchronize_contexts(cx);
context
text_thread
}
})
})
}
fn register_context(&mut self, context: &Entity<AssistantContext>, cx: &mut Context<Self>) {
fn register_text_thread(&mut self, text_thread: &Entity<TextThread>, cx: &mut Context<Self>) {
let handle = if self.project_is_shared {
ContextHandle::Strong(context.clone())
TextThreadHandle::Strong(text_thread.clone())
} else {
ContextHandle::Weak(context.downgrade())
TextThreadHandle::Weak(text_thread.downgrade())
};
self.contexts.push(handle);
self.text_threads.push(handle);
self.advertise_contexts(cx);
cx.subscribe(context, Self::handle_context_event).detach();
cx.subscribe(text_thread, Self::handle_context_event)
.detach();
}
fn handle_context_event(
&mut self,
context: Entity<AssistantContext>,
event: &ContextEvent,
text_thread: Entity<TextThread>,
event: &TextThreadEvent,
cx: &mut Context<Self>,
) {
let Some(project_id) = self.project.read(cx).remote_id() else {
@@ -631,12 +623,12 @@ impl ContextStore {
};
match event {
ContextEvent::SummaryChanged => {
TextThreadEvent::SummaryChanged => {
self.advertise_contexts(cx);
}
ContextEvent::PathChanged { old_path, new_path } => {
TextThreadEvent::PathChanged { old_path, new_path } => {
if let Some(old_path) = old_path.as_ref() {
for metadata in &mut self.contexts_metadata {
for metadata in &mut self.text_threads_metadata {
if &metadata.path == old_path {
metadata.path = new_path.clone();
break;
@@ -644,8 +636,8 @@ impl ContextStore {
}
}
}
ContextEvent::Operation(operation) => {
let context_id = context.read(cx).id().to_proto();
TextThreadEvent::Operation(operation) => {
let context_id = text_thread.read(cx).id().to_proto();
let operation = operation.to_proto();
self.client
.send(proto::UpdateContext {
@@ -670,15 +662,15 @@ impl ContextStore {
}
let contexts = self
.contexts
.text_threads
.iter()
.rev()
.filter_map(|context| {
let context = context.upgrade()?.read(cx);
if context.replica_id() == ReplicaId::default() {
.filter_map(|text_thread| {
let text_thread = text_thread.upgrade()?.read(cx);
if text_thread.replica_id() == ReplicaId::default() {
Some(proto::ContextMetadata {
context_id: context.id().to_proto(),
summary: context
context_id: text_thread.id().to_proto(),
summary: text_thread
.summary()
.content()
.map(|summary| summary.text.clone()),
@@ -701,13 +693,13 @@ impl ContextStore {
return;
};
let contexts = self
.contexts
let text_threads = self
.text_threads
.iter()
.filter_map(|context| {
let context = context.upgrade()?.read(cx);
if context.replica_id() != ReplicaId::default() {
Some(context.version(cx).to_proto(context.id().clone()))
.filter_map(|text_thread| {
let text_thread = text_thread.upgrade()?.read(cx);
if text_thread.replica_id() != ReplicaId::default() {
Some(text_thread.version(cx).to_proto(text_thread.id().clone()))
} else {
None
}
@@ -717,26 +709,27 @@ impl ContextStore {
let client = self.client.clone();
let request = self.client.request(proto::SynchronizeContexts {
project_id,
contexts,
contexts: text_threads,
});
cx.spawn(async move |this, cx| {
let response = request.await?;
let mut context_ids = Vec::new();
let mut text_thread_ids = Vec::new();
let mut operations = Vec::new();
this.read_with(cx, |this, cx| {
for context_version_proto in response.contexts {
let context_version = ContextVersion::from_proto(&context_version_proto);
let context_id = ContextId::from_proto(context_version_proto.context_id);
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
context_ids.push(context_id);
operations.push(context.read(cx).serialize_ops(&context_version, cx));
let text_thread_version = TextThreadVersion::from_proto(&context_version_proto);
let text_thread_id = TextThreadId::from_proto(context_version_proto.context_id);
if let Some(text_thread) = this.loaded_text_thread_for_id(&text_thread_id, cx) {
text_thread_ids.push(text_thread_id);
operations
.push(text_thread.read(cx).serialize_ops(&text_thread_version, cx));
}
}
})?;
let operations = futures::future::join_all(operations).await;
for (context_id, operations) in context_ids.into_iter().zip(operations) {
for (context_id, operations) in text_thread_ids.into_iter().zip(operations) {
for operation in operations {
client.send(proto::UpdateContext {
project_id,
@@ -751,8 +744,8 @@ impl ContextStore {
.detach_and_log_err(cx);
}
pub fn search(&self, query: String, cx: &App) -> Task<Vec<SavedContextMetadata>> {
let metadata = self.contexts_metadata.clone();
pub fn search(&self, query: String, cx: &App) -> Task<Vec<SavedTextThreadMetadata>> {
let metadata = self.text_threads_metadata.clone();
let executor = cx.background_executor().clone();
cx.background_spawn(async move {
if query.is_empty() {
@@ -782,20 +775,16 @@ impl ContextStore {
})
}
pub fn host_contexts(&self) -> &[RemoteContextMetadata] {
&self.host_contexts
}
fn reload(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let fs = self.fs.clone();
cx.spawn(async move |this, cx| {
if *ZED_STATELESS {
return Ok(());
}
fs.create_dir(contexts_dir()).await?;
fs.create_dir(text_threads_dir()).await?;
let mut paths = fs.read_dir(contexts_dir()).await?;
let mut contexts = Vec::<SavedContextMetadata>::new();
let mut paths = fs.read_dir(text_threads_dir()).await?;
let mut contexts = Vec::<SavedTextThreadMetadata>::new();
while let Some(path) = paths.next().await {
let path = path?;
if path.extension() != Some(OsStr::new("json")) {
@@ -821,7 +810,7 @@ impl ContextStore {
.lines()
.next()
{
contexts.push(SavedContextMetadata {
contexts.push(SavedTextThreadMetadata {
title: title.to_string().into(),
path: path.into(),
mtime: metadata.mtime.timestamp_for_user().into(),
@@ -829,10 +818,10 @@ impl ContextStore {
}
}
}
contexts.sort_unstable_by_key(|context| Reverse(context.mtime));
contexts.sort_unstable_by_key(|text_thread| Reverse(text_thread.mtime));
this.update(cx, |this, cx| {
this.contexts_metadata = contexts;
this.text_threads_metadata = contexts;
cx.notify();
})
})

View File

@@ -433,7 +433,7 @@ where
/// Stores already emitted samples, once its full we call the callback.
buffer: [Sample; N],
/// Next free element in buffer. If this is equal to the buffer length
/// we have no more free lements.
/// we have no more free elements.
free: usize,
}

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