Compare commits

...

291 Commits

Author SHA1 Message Date
Zed Bot
ac86469ebc Bump to 0.191.9 for @bennetbo 2025-06-24 16:31:33 +00:00
Bennet Bo Fenner
be041fe157 agent: Fix issue with Anthropic thinking models (#33317)
cc @osyvokon 

We were seeing a bunch of errors in our backend when people were using
Claude models with thinking enabled.

In the logs we would see
> an error occurred while interacting with the Anthropic API:
invalid_request_error: messages.x.content.0.type: Expected `thinking` or
`redacted_thinking`, but found `text`. When `thinking` is enabled, a
final `assistant` message must start with a thinking block (preceeding
the lastmost set of `tool_use` and `tool_result` blocks). We recommend
you include thinking blocks from previous turns. To avoid this
requirement, disable `thinking`. Please consult our documentation at
https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking

However, this issue did not occur frequently and was not easily
reproducible. Turns out it was triggered by us not correctly handling
[Redacted Thinking
Blocks](https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#thinking-redaction).

I could constantly reproduce this issue by including this magic string:
`ANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB
` in the request, which forces `claude-3-7-sonnet` to emit redacted
thinking blocks (confusingly the magic string does not seem to be
working for `claude-sonnet-4`). As soon as we hit a tool call Anthropic
would return an error.

Thanks to @osyvokon for pointing me in the right direction 😄!


Release Notes:

- agent: Fixed an issue where Anthropic models would sometimes return an
error when thinking was enabled
2025-06-24 18:25:56 +02:00
Zed Bot
e9ad95766b Bump to 0.191.8 for @bennetbo 2025-06-24 14:12:44 +00:00
Bennet Bo Fenner
406f5608a9 agent: Ensure tool names are unique (#33237)
Closes #31903

Release Notes:

- agent: Fix an issue where an error would occur when MCP servers
specified tools with the same name

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-06-24 16:02:00 +02:00
gcp-cherry-pick-bot[bot]
ca40b368c5 Implement save functionality for diff view (cherry-pick #33298) (#33301)
Cherry-picked Implement save functionality for diff view (#33298)

Add `can_save` and `save` methods to `DiffView`, enabling users to save
changes made within the diff view.

Release Notes:

- Allow saving changes in the `zed --diff` view

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
2025-06-24 13:28:33 +02:00
gcp-cherry-pick-bot[bot]
a649c3daf3 copilot: Fix config dir logic to support Flatpak environments (cherry-pick #32901) (#33159)
Cherry-picked copilot: Fix config dir logic to support Flatpak
environments (#32901)

Closes #30784

In github copilot we were not handling the config path correctly for
FLATPAK.

* Only tested on mac don't have access to other platform. But this
should work on other platform as well. It follows the similar pattern
seen in zed config path resolution.
- [x] Macos
- [ ] Linux
- [ ] Linux with Flatpak
- [ ] Windows

Release Notes:

- Fix copilot config detection for flatpack

Co-authored-by: Umesh Yadav <23421535+imumesh18@users.noreply.github.com>
2025-06-21 15:28:36 +02:00
Joseph T. Lyons
a3e535d720 zed 0.191.7 2025-06-20 10:58:27 -04:00
Piotr Osiewicz
487dc15331 docs: Update manifest keys in debugger extension docs (#33085)
This is silly and caused at least one of our users a lot of confusion.

Closes #33040

Release Notes:

- N/A
2025-06-20 10:57:07 -04:00
Joseph T. Lyons
43eb1596a9 Add docs for cloning extensions repository (#32897)
Release Notes:

- N/A

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-06-20 10:56:55 -04:00
Anthony Eid
004d0d6975 debugger: Refresh variable list on set variable value response (#33078)
Variable list wasn't notified when a set variable value request was
successfully. This caused the variable list and inline values to show
stale data in some cases, which this PR fixes.

Release Notes:

- debugger: Fix bug where setting a variable's value wouldn't update the
variable list or inline values
2025-06-20 10:56:04 -04:00
Anthony Eid
13f4a729be docs: Update development debugger guide to include Zed's debugger (#33080)
Closes #33069 

Release Notes:

- N/A
2025-06-20 10:54:39 -04:00
Piotr Osiewicz
d1c4d05410 debugger: Move breakpoint management to the pane strip (#33062)
Closes #ISSUE

Release Notes:

- debugger: Moved "remove breakpoint" button to the top of a breakpoint
list"
2025-06-20 10:52:52 -04:00
Max Brunsfeld
b6069420a6 Fix handling of --diff flag (#33094)
* Restore the ability to combine --diff with other path arguments
* Restore combining --diff with --wait

There is still one defect in the current handling of `--diff`: when Zed
is already open, we'll open the diff view in your current active zed
window. It would be better to search all of the open zed windows for any
window containing the diffed paths, but implementing that is a bit
complex. Currently, the logic for *picking* an existing zed window is
coupled to the logic for opening buffers in that window. I'd like to
decouple it, but I wanted to keep this change small, so that we hotfix
it to stable without too much risk.

Release Notes:

- Fixed a bug where the `--diff` CLI flag did not work with `--wait`

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-06-20 13:02:59 +01:00
gcp-cherry-pick-bot[bot]
4368a1c8bb debugger: Fix issues with debugging scripts from package.json (cherry-pick #32995) (#33043)
Cherry-picked debugger: Fix issues with debugging scripts from
package.json (#32995)

- [x] Pass in cwd
- [x] Use the appropriate package manager
- [x] Don't mix up package.json and composer.json

Release Notes:

- debugger: Fixed wrong arguments being passed to the DAP when debugging
scripts from package.json.

Co-authored-by: Cole Miller <cole@zed.dev>
2025-06-19 14:25:40 -04:00
Joseph T. Lyons
4f878cceba Remove missing field in ResponseStreamEvent 2025-06-19 14:11:57 -04:00
Joseph T. Lyons
ac2eb24d62 Revert "OpenAI cleanups (#32597)"
This reverts commit 15f044f0a1.
2025-06-19 14:03:52 -04:00
Joseph T. Lyons
f783f48b8d zed 0.191.6 2025-06-19 13:47:35 -04:00
Cole Miller
142d97b300 debugger: Fix issues with debugging scripts from package.json (#32995)
- [x] Pass in cwd
- [x] Use the appropriate package manager
- [x] Don't mix up package.json and composer.json

Release Notes:

- debugger: Fixed wrong arguments being passed to the DAP when debugging
scripts from package.json.
2025-06-19 13:46:22 -04:00
Danilo Leal
d272f3aff3 debugger: Refine session modal design (#33004)
This PR makes all footer elements in the debugger session modal more
consistent, as well as fixes some weird UI quirks with leaking borders
and whatnot. Took the opportunity to do some light style clean up and
use `prelude::*` for UI imports.

Release Notes:

- N/A
2025-06-19 13:46:04 -04:00
Jeff Bonhag
015497e063 docs: Ruby debug configuration should be an array (#32991)
Closes #ISSUE

Small correction for something I noticed while setting up the debugger
today.

Release Notes:

- N/A
2025-06-19 13:45:50 -04:00
Mikayla Maki
e3419b6098 Add a small script to make debugging the CLI easier (#32971)
Release Notes:

- N/A
2025-06-19 13:45:42 -04:00
Danilo Leal
d05d980f50 docs: Remove beta tag from Debugger (#32950)
Release Notes:

- N/A
2025-06-19 13:45:32 -04:00
gcp-cherry-pick-bot[bot]
fd4d61057d Revert "client: Fix an issue where non-IP proxy URLs didn’t resolve c… (cherry-pick #33013) (#33015)
Cherry-picked Revert "client: Fix an issue where non-IP proxy URLs
didn’t resolve c… (#33013)

This reverts commit bc68455320.

More bugs...

Closes #32838

Release Notes:

- N/A

Co-authored-by: 张小白 <364772080@qq.com>
2025-06-19 17:27:01 +08:00
Ben Brandt
15f044f0a1 OpenAI cleanups (#32597)
Release Notes:

- openai: Remove support for deprecated o1-preview and o1-mini models 
- openai: Support streaming for o1 model
2025-06-18 19:44:54 -04:00
Ben Brandt
dd4b0c4183 open_ai: Remove redundant serde aliases and add model limits (#32572)
Remove unnecessary alias attributes from Model enum variants and add
max_output_tokens limits for all OpenAI models. Also fix
supports_system_messages to explicitly handle all model variants.

Release Notes:

- N/A
2025-06-18 19:44:46 -04:00
Bennet Bo Fenner
0db3fec823 open_ai: Fix issues with OpenAI compatible APIs (#32982)
Ran into this while adding support for Vercel v0s models:
- The timestamp seems to be returned in Milliseconds instead of seconds
so it breaks the bounds of `created: u32`. We did not use this field
anywhere so just decided to remove it
- Sometimes the `choices` field can be empty when the last chunk comes
in because it only contains `usage`

Release Notes:

- N/A
2025-06-18 19:05:58 -04:00
Danilo Leal
e916e142a3 agent: Add ability to change the API base URL for OpenAI via the UI (#32979)
The `api_url` setting is one that most providers already support and can
be changed via the `settings.json`. We're adding the ability to change
it via the UI for OpenAI specifically so it can be more easily connected
to v0.

Release Notes:

- agent: Added ability to change the API base URL for OpenAI via the UI

---------

Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com>
2025-06-18 19:05:37 -04:00
Joseph T. Lyons
f8a213b487 zed 0.191.5 2025-06-18 15:13:38 -04:00
Piotr Osiewicz
8b55554a4c debugger: Run debug scenarios from package.json (#32958)
Release Notes:

- New session modal for a debugger will now show tasks from package.json
as debuggable scenarios

---------

Co-authored-by: Remco Smits <djsmits12@gmail.com>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-06-18 15:06:39 -04:00
Mikayla Maki
f3ccdf9820 Fix a bug where --diff wouldn't open the diff (#32962)
Release Notes:

- Fixed a bug where `zed --diff A B` wouldn't open a diff
2025-06-18 15:05:19 -04:00
Piotr Osiewicz
215c333440 debugger: Add onboarding modal (#32961)
- **debugger: Add debugger onboarding modal (wip)**
- **woops**

Release Notes:

- debugger: Added the onboarding modal.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Julia Ryan <p1n3appl3@users.noreply.github.com>
2025-06-18 14:53:58 -04:00
Conrad Irwin
fb06fb92f7 Wait for source maps when setting TypeScript breakpoints (#32954)
Closes #ISSUE

Release Notes:

- debugger: Fix setting breakpoints in typescript code when debugging
compiled javascript
2025-06-18 14:14:41 -04:00
Piotr Osiewicz
a99dadf314 copilot: Remove an unwrap in URI parsing code (#32698)
Closes #32630

Release Notes:

- Fixed a potential crash when opening active modules in a debugger
session (with Copilot enabled).
2025-06-18 11:34:49 -04:00
Joseph T. Lyons
83d7dd0050 v0.191.x stable 2025-06-18 10:51:49 -04:00
Conrad Irwin
9e98c4c17f Diff view (#32922)
Todo:

* [x] Open diffed files as regular buffers
* [x] Update diff when buffers change
* [x] Show diffed filenames in the tab title
* [x] Investigate why syntax highlighting isn't reliably handled for old
text
* [x] remove unstage/restore buttons

Release Notes:

- Adds `zed --diff A B` to show the diff between the two files

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Agus Zubiaga <agus@zed.dev>
2025-06-18 16:44:43 +02:00
Cole Miller
3584338f99 debugger: Fix a few issues with JS debugging (#32918)
- Don't assume all located tasks come from our test runnables
- Run tests from the right working directory
- Scope forking behavior customization for jest and vitest more tightly,
to just our test runnables
- Standardize on `$PACKAGE_MANAGER exec -- $TEST_LIBRARY ...` to fix
runnables not working with npm

Release Notes:

- Debugger Beta: Fixed issues with debugging tasks from package.json and
test runnables.
2025-06-18 10:38:06 -04:00
Conrad Irwin
99f6c0db59 debugger: Remove feature flag (#32877)
Release Notes:

- debugger: Now available for everyone!
2025-06-18 10:34:29 -04:00
Marshall Bowers
f4330c4ff6 zed_extension_api: Release v0.6.0 (#32945)
This PR releases v0.6.0 of the Zed extension API.

Support for this version of the extension API will land in Zed v0.192.x.

Release Notes:

- N/A
2025-06-18 10:30:44 -04:00
Piotr Osiewicz
d452611c68 debugger: Show child sessions as indented and ensure they're next to the parent session (#32939)
Closes #ISSUE

Release Notes:

- debugger: Tweaked how child sessions are shown in the session list.
2025-06-18 10:22:32 -04:00
Piotr Osiewicz
02fe50a4e4 debugger: Add breakpoint list to the empty state of debug panel (#32930)
![image](https://github.com/user-attachments/assets/3c80855a-3046-42b6-a1a7-409b03cd735d)

Release Notes:

- Debugger: Added breakpoint list to the empty debug panel
2025-06-18 10:22:20 -04:00
Danilo Leal
74a7ab5b91 docs: Add light formatting changes to the Debugger page (#32919)
Just some tiny little formatting improvement opportunities I stumbled
upon while working on the marketing stuff for the debugger.

Release Notes:

- N/A
2025-06-18 10:22:06 -04:00
Cole Miller
c6dcdd4f0a debugger: Parse and highlight text with ANSI escape sequences (#32915)
Relanding #32817 with an improved approach, bugs fixed, and a test.

Release Notes:

- N/A
2025-06-18 10:21:34 -04:00
gcp-cherry-pick-bot[bot]
a6811524a7 Fix bug where prior LSP completions can be displayed after trigger char (cherry-pick #32927) (#32931)
Cherry-picked Fix bug where prior LSP completions can be displayed after
trigger char (#32927)

Bug in #31872

Closes #32774

Release Notes:

- Fixed a bug in LSP completions caching where prior completions may be
used when they should not, after typing a trigger char like `.`

Co-authored-by: Michael Sloan <michael@zed.dev>
2025-06-18 03:04:28 -06:00
Julia Ryan
f4cdcf756a debugger: Add comment-preserving debug.json editing (#32896)
Release Notes:

- Re-added "Save to `debug.json`" for custom debug tasks

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-06-17 19:12:15 -04:00
Cole Miller
e6e6cfffe4 Revert "debugger: Process ANSI color escape codes in console" (#32906)
Reverts zed-industries/zed#32817

Release Notes:
- N/A
2025-06-17 19:12:04 -04:00
Nate Butler
8ba1ce7c8c debugger: Improve debugger panel empty state (#32889)
Before:

![CleanShot 2025-06-17 at 13 48
58@2x](https://github.com/user-attachments/assets/16ecebfa-871e-4a2d-b6a3-2178de70aaef)

After:

![CleanShot 2025-06-17 at 13 49
24@2x](https://github.com/user-attachments/assets/2d8a0444-6088-45f1-a880-0bdd0aef968e)


Release Notes:

- N/A (Beta: Improved the debugger panel when there are no currently
active sessions)
2025-06-17 19:11:51 -04:00
Ben Brandt
6d23fbcf77 v0.191.x: Google Model Updates for Gemini 2.5 (#32904)
Updates google_ai to use latest model information from the respective
model cards: https://ai.google.dev/gemini-api/docs/models

Release Notes:

- google: Update to latest Gemini 2.5 models
2025-06-17 22:55:19 +02:00
Joseph T. Lyons
02c8322bb8 zed 0.191.4 2025-06-17 12:00:05 -04:00
Conrad Irwin
0853c37d71 Revert "debugger: Remove feature flag"
This reverts commit 82dfa82ba7.
2025-06-17 11:59:09 -04:00
Conrad Irwin
633992fe1e debugger: Remove feature flag 2025-06-17 11:59:09 -04:00
Gilles De Mey
b2a1c023b1 docs: Fix typo in debugger.md (#32867)
A small silly typo :)
2025-06-17 11:58:33 -04:00
Piotr Osiewicz
c00bd4147b docs: Fix headings in debugger docs (#32641)
Reported by calebmeyer on Discord.
Closes #ISSUE

Release Notes:

- N/A
2025-06-17 11:58:22 -04:00
Cole Miller
ec49967339 Revert "Bail and signal error when the cwd of a resolved task doesn't exist" (#32866)
Reverts zed-industries/zed#32777
2025-06-17 11:57:50 -04:00
Conrad Irwin
7dca400955 debugger: Fix connections over SSH (#32834)
Before this change, we would see "connection reset" when sending the
initialize
request over SSH in the case that the debug adapter was slow to boot.

(Although we'd have successfully created a connection to the local SSH
port,
trying to read/write from it would not work until the remote end of the
connection had been established)

Fixes  #32575

Release Notes:

- debugger: Fix connecting to a Python debugger over SSH
2025-06-17 11:57:22 -04:00
Conrad Irwin
8ded7e58e3 debugger: Don't spawn unnecessary process (#32827)
Before this change, when spawning a child session we'd launch an extra
node process that would immediately die because it couldn't listen on
the debugger port

Release Notes:

- N/A
2025-06-17 11:57:05 -04:00
Piotr Osiewicz
11ce128dc8 debugger: Prevent port collision when attaching to existing node debugger (#32862)
We were translating port configuration incorrectly, using it for both
attach target and debugger port.
This however meant that we were spawning a 2nd process that'd listen on
the same port as the existing debugger.

Closes #32836

Release Notes:

- debugger: Fixed issues with auto-translated Visual Studio Code debug
configs for attaching to existing node debugger instances.
2025-06-17 11:45:52 -04:00
Piotr Osiewicz
d33470b93b debuggers: Mark processId as optional field in Delve Attach configurations (#32856)
Closes #32849

Release Notes:

- Fixed overly strict validation of Go debugging configurations.
2025-06-17 11:44:31 -04:00
Piotr Osiewicz
c92acca21a debugger: Do not query threads when session is still building (#32852)
This should silence a noisy log we see whenever a debug session is
started:
`2025-06-17T12:06:12+02:00 ERROR [project] no adapter running to send
request: ThreadsCommand`

Closes #ISSUE

Release Notes:

- Fixed debugger logs getting clobbered with internal logs about Threads
Command whenever a new debug session is created.
2025-06-17 11:44:22 -04:00
Piotr Osiewicz
da6f4033bd extensions: Add "Debug Adapters" category to the extension store (#32845)
Closes #ISSUE

Release Notes:

- N/A
2025-06-17 11:44:13 -04:00
Piotr Osiewicz
e485f8ac3b extensions: Yet another PR for debugger touchups (#32822)
We'll now clean up DAP locators for unloaded extensions and load schemas
proper

I can now load a custom Ruby extensions with all bells and whistles and
use it as my debugger.

Release Notes:

- N/A
2025-06-17 11:44:03 -04:00
Cole Miller
26fe8ba23f debugger: Process ANSI color escape codes in console (#32817)
- [x] foreground highlights
- [x] background highlights
- [x] advertise support in DAP capabilities

Closes #31372

Release Notes:

- Debugger Beta: added basic support for highlighting in the console
based on ANSI escape codes.
2025-06-17 11:42:59 -04:00
Cole Miller
201c2b5110 debugger: Make the remove button easier to click for breakpoint list entries (#32772)
Closes #31574 

Move this button a bit to the left so it doesn't get blocked by the
hitbox of the scrollbar.

Also makes the list entries a bit thicker vertically so that the button
can be `XSmall` instead of `Indicator`-sized again.

Release Notes:

- Debugger Beta: fixed a layout issue that made it hard to click the
remove (`X`) button for entries in the breakpoint list.
2025-06-17 11:42:49 -04:00
Cole Miller
3f28664964 Bail and signal error when the cwd of a resolved task doesn't exist (#32777)
Closes #32688

Release Notes:

- Fixed tasks (including build tasks for debug configurations) silently
using `/` as a working directory when the specified `cwd` didn't exist.
2025-06-17 11:42:38 -04:00
Piotr Osiewicz
4d478fb7ba extension: Another batch of updates for DAP extension API (#32809)
Closes #ISSUE

Release Notes:

- N/A
2025-06-17 11:42:00 -04:00
Piotr Osiewicz
fc26946e8d extension: Update DAP extension API (#32448)
- DAP schemas will be stored in `debug_adapters_schemas` subdirectory in
extension work dir.
- Added Debug Config integration and such.

Release Notes:

- N/A
2025-06-17 11:41:46 -04:00
Conrad Irwin
85dd7d0aea Pass project environment to runInTerminal requests (#32720)
Closes #ISSUE

Release Notes:

- debugger: Pass environment to run in terminal requests
2025-06-17 11:40:46 -04:00
gcp-cherry-pick-bot[bot]
628873c79e Attempt to log error instead of crash in bracket highlighting (cherry-pick #32837) (#32841)
Cherry-picked Attempt to log error instead of crash in bracket
highlighting (#32837)

Crashes look like:

```
Panic `offset 632 is greater than the snapshot.len() 631` on thread 0 (com.apple.main-thread)

<multi_buffer::MultiBufferSnapshot>::innermost_enclosing_bracket_ranges::<usize>
editor::highlight_matching_bracket::refresh_matching_bracket_highlights
<gpui::app::App>::update_window_id::<bool, <gpui::app::context::Context<editor::Editor>>::subscribe_in<multi_buffer::MultiBuffer, multi_buffer::Event, <editor::Editor>::on_buffer_event>::{closure#0}::{closure#0}>::{closure#0}
<gpui::app::context::Context<editor::Editor>>::subscribe_in::<multi_buffer::MultiBuffer, multi_buffer::Event, <editor::Editor>::on_buffer_event>::{closure#0}
<gpui::app::App>::flush_effects
<project::lsp_store::LocalLspStore>::format_buffer_locally::{closure#0}
<project::lsp_store::LspStore>::format::{closure#1}::{closure#0}::<i32>
```

Though `format_buffer_locally` is not always present. Both issue reports
mention usage of the agent. I suspect this is somehow a result of agent
format-on-save combined with the user's cursor being at the end of the
buffer as it's getting edited by the agent.

The offsets are always off-by-one in the error, so at first I thought
the issue was the condition `head < snapshot.buffer_snapshot.len()`
before setting `tail` to be `head + 1`, but an offset equal to len is
valid. Seems like to get a `to_offset` crash, `head` must be greater
than `len`. Which is quite weird, a selection's offset should never be
out of bounds.

Since this code is just about highlighting brackets, this PR logs an
error instead of crashing in the `head > len` case.

Closes #32732, #32171

Release Notes:

- N/A

Co-authored-by: Michael Sloan <michael@zed.dev>
2025-06-17 02:15:47 -06:00
Michael Sloan
f1ddd336f6 Cherry-pick "linux: Add mouse cursor icon name synonyms #32820" (#32833)
Most of the default icon sets on Ubuntu do not use the names that were
there. To fix, using the icon synonyms from the chromium source. This
will probably fix some of the linux mouse cursor issues tracked in
https://github.com/zed-industries/zed/issues/26141

Also adds a note in the load failure logs mentioning that misconfigured
XCURSOR_PATH may be the issue. I ran into this because
https://github.com/snapcrafters/alacritty/issues/21.

On X11 also adds:

Caching of load errors to log once for missing cursor icons.

Fallback on default cursor icon. This way if there was a transition from
a non-default icon to a missing icon it doesn't get stuck showing the
non-default icon.

Leaving release notes blank as I have other mouse cursor fixes and would
prefer to just have one entry in the release notes.

Release Notes:

Linux: Fixed a couple bugs that can cause the mouse cursor to not appear
or only appear as an arrow
2025-06-16 23:26:33 -06:00
gcp-cherry-pick-bot[bot]
44a31e2e5d Use a proper snapshot version when resolving for utf16 points (cherry-pick #32815) (#32826)
Cherry-picked Use a proper snapshot version when resolving for utf16
points (#32815)

Release Notes:

- Fixed a panic when merging pull and (newer) push diagnostics

Co-authored-by: Conrad Irwin <conrad@zed.dev>

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Conrad Irwin <conrad@zed.dev>
2025-06-17 02:12:25 +03:00
gcp-cherry-pick-bot[bot]
587da10d28 Deploy code runner menu from correct display row (cherry-pick #32594) (#32825)
Cherry-picked Deploy code runner menu from correct display row (#32594)

This fixes a bug introduced in #32579 where the code runner menu would
be deployed from the most recent cursor position instead of the row that
the runner icon was rendered on.

Release Notes:

- N/A

Co-authored-by: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com>
2025-06-17 02:12:03 +03:00
Bennet Bo Fenner
71e0df8afd inline assistant: Do not dismiss while generating when hitting enter (#32810)
Closes #32798

Release Notes:

- Fixed an issue where the inline assistant would be dismissed when
hitting enter while generating code
2025-06-16 17:36:55 -04:00
Joseph T. Lyons
80d56e2c31 zed 0.191.3 2025-06-16 11:12:42 -04:00
Piotr Osiewicz
657247e68f debugger: Fix module list getting queried when not shown (#32761)
Closes #ISSUE

Release Notes:

- N/A
2025-06-16 11:11:14 -04:00
Anthony Eid
82c1ae519e debugger: Select first stack frame with valid path (#32724)
This PR addresses an issue where we could get a stack frame list and
automatically select a stack frame that didn't have a valid path.
Causing a failure on Zed's end to select/update the active debug line.
The fix for this is selecting the first non-subtle stack frame that has
the optional path parameter.

We also made subtle stack frames move into their own collapsable list as
well.

Release Notes:

- debugger: Fix edge case where hitting a breakpoint wouldn't take you
to the active debug line

Co-authored-by: Remco Smits <djsmits12@gmail.com>
2025-06-16 11:11:05 -04:00
Cole Miller
d5fe212987 debugger: Use the right adapter for type: node-terminal (#32723)
Closes #32690 

Release Notes:

- Debugger Beta: fixed `node-terminal` JavaScript configurations from
launch.json not working.
2025-06-16 11:10:48 -04:00
Anthony Eid
b1b247d448 debugger: Add support for label presentation hints for stack frames (#32719)
Release Notes:

- debugger: Add support for `Label` stack frame kinds

Co-authored-by: Remco Smits <djsmits12@gmail.com>
2025-06-16 11:10:48 -04:00
Anthony Eid
484cc911db debugger: Improve logging of debug sessions (#32718)
This PR fixes a common issue where a debug session won't start up and
user's weren't able to get any logs from the debug session. We now do
these three things

1. We know store a history of debug sessions
2. We added a new option to only look at the initialization sequence 
3. We default to selecting a session in dap log view in stead of none

Release Notes:

- debugger: Add history to debug session logging

---------

Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Remco Smits <djsmits12@gmail.com>
2025-06-16 11:10:30 -04:00
Piotr Osiewicz
bb0c932022 debugger: Focus child sessions if parent has never stopped (#32693)
Closes #ISSUE

Release Notes:

- When debugging JavaScript, Zed will now preselect child sessions by
default.
2025-06-16 11:10:19 -04:00
Joseph T. Lyons
42608efecc zed 0.191.2 2025-06-13 13:45:37 -04:00
Piotr Osiewicz
b217daea38 debugger: Mark DapLocator::create_scenario as an async function (#32680)
Paves way for locators in extensions.

Release Notes:

- N/A
2025-06-13 13:26:25 -04:00
Conrad Irwin
8d40bc6c4e Fix code actions run confusion (#32579)
Now if you click the triangle you get runnables, if you click the
lightning bolt you get code actions, if you trigger the code actions
menu with the mouse/keyboard you still get both.

Release Notes:

- Fixed the run/code actions menu to not duplicate content when opened
from the respective icons.

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-06-13 13:26:12 -04:00
Piotr Osiewicz
5170bc38a6 debugger: Fix regression in rendering of stack frame list (#32682)
Closes #ISSUE

Release Notes:

- N/A
2025-06-13 11:42:32 -04:00
Conrad Irwin
1b3a5d92df debugger: Show errors loading stack (#32658)
- **TEMP**
- **Show errors loading stack frames**
- **Stop cloning every DAP response unnecessarily**

Closes #ISSUE

Release Notes:

- debugger: Show errors loading stack frames.

<img width="1840" alt="Screenshot 2025-06-12 at 23 53 42"
src="https://github.com/user-attachments/assets/310d3046-f34c-4964-acef-f9742441c9db"
/>
2025-06-13 11:42:23 -04:00
Cole Miller
a5113d8842 debugger: Fix running JS tests when worktree root and package root do not coincide (#32644)
- construct the correct path to the test library based on the location
of package.json
- run scripts from the package root where they were defined
- run tests in the directory of the defining file

Release Notes:

- Debugger Beta: fixed running JS tests when the worktree root is above
the location of package.json.

---------

Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-06-13 11:42:14 -04:00
Cole Miller
66427e52da debugger: Add an action to copy debuggee info and initialization args (#32647)
Release Notes:

- Debugger Beta: added the `dev: copy debug adapter arguments` action to
help troubleshoot debug configurations.
2025-06-13 11:42:06 -04:00
Anthony Eid
112a11d0a2 debugger: Pass --nocapture to cargo tests when building debug tasks with locator (#32633)
Release Notes:

- Add --nocapture as a default argument when debugging rust tests

Co-authored-by: Cole Miller <m@cole-miller.net>
2025-06-13 11:41:40 -04:00
Piotr Osiewicz
631b643d02 debugger: Do not swallow port property when converting launch.json (#32621)
with JavaScript scenarios.

Closes #32187

Release Notes:

- Fixed `port` property not being respected in debug scenarios converted
from VSC's launch.json

Co-authored-by: Ben Kunkle <ben.kunkle@gmail.com>
2025-06-13 11:41:31 -04:00
gcp-cherry-pick-bot[bot]
89f0c721f1 client: Fix an issue where non-IP proxy URLs didn’t resolve correctly (cherry-pick #32664) (#32679)
Cherry-picked client: Fix an issue where non-IP proxy URLs didn’t
resolve correctly (#32664)

If the proxy URL is in the form of `example.com` instead of a raw IP
address, and `example.com` isn't a well-known domain, then the default
URL resolution can fail.

The test setup:

A Linux machine runs a CoreDNS server with a custom entry: `10.254.7.38
example.com`. On a Windows machine, if the proxy URL is set to
`example.com`, the resolved address does **not** end up being
`10.254.7.38`.

Using `hickory_resolver` for more advanced DNS resolution fixes this
issue.


Release Notes:

- Fixed proxy URL resolution when using custom DNS entries.

Co-authored-by: 张小白 <364772080@qq.com>
2025-06-13 18:52:43 +08:00
Michael Sloan
85b6ca1d41 Use git config --global user.email for email address in automatic Co-authored-by (cherry-pick #32624) (#32639)
Release Notes:

- Automatic population of `Co-authored-by` now uses `git config --global
user.email`

Co-authored-by: Conrad <conrad@zed.dev>
2025-06-12 15:35:31 -06:00
Joseph T. Lyons
2f19e4262b zed 0.191.1 2025-06-12 11:58:40 -04:00
Piotr Osiewicz
8124758685 debugger: Allow use of externally-managed Delve for Go debugging (#32613)
Closes #ISSUE

Release Notes:

- Go debug scenarios can now use an externally-managed Delve instance.
Use `tcp_connection` in your debug scenario definition to provide
adapter's address.
2025-06-12 11:53:36 -04:00
Anthony Eid
441d738061 debugger: Handle session restart failures instead of hanging (#32595)
I also enabled the `Restart` action even for sessions that don't support
restarting because we have a restart fallback now.

Closes #31408

Release Notes:

- Fix bug where a debugger session would never be shutdown on a failed
restart attempt
2025-06-12 11:53:24 -04:00
Julia Ryan
fa003793ed debugger: Don't show VSCode worktree tasks when Zed ones exist (#32589)
Fixes #31699

Eventually we might want to merge the lists and deduplicate based on the
command and args that it's running. For now we'll just use the presence
of _any_ worktree local zed debug tasks to disable all VSCode ones.

Release Notes:

- N/A
2025-06-12 11:53:16 -04:00
Piotr Osiewicz
38e00a72a9 debugger: Fix DebugAdapterDelegate::worktree_root always using the first visible worktree (#32585)
Closes #32577

Release Notes:

- Fixed debugger malfunctioning when using ZED_WORKTREE_ROOT env
variable in multi-worktree workspaces.
2025-06-12 11:53:07 -04:00
Cole Miller
c14c370671 debugger: Special-case npm et al. as program field for JS debug definitions (#32549)
Send `runtimeExecutable` and `runtimeArgs` instead of `program` and
`args` to avoid the DAP implicitly wrapping the command in `node`.

This means that putting `pnpm vitest <file>` as the command in the
launch modal will work, as will this in debug.json:

```
[
  {
    "adapter": "JavaScript",
    "type": "pwa-node",
    "label": "Label",
    "request": "launch",
    "program": "pnpm",
    "args": ["vitest", "<file>"],
    "cwd": "/Users/name/project"
  }
]
```


Release Notes:

- Debugger Beta: made it possible to use commands like `pnpm
<subcommand> <args>` in the launch modal and debug.json
2025-06-12 11:51:48 -04:00
Cole Miller
bc6d75b4af debugger: Fix issues with launch.json handling (#32563)
After this PR we can run all the in-tree launch.json examples from [this
repo](https://github.com/microsoft/vscode-recipes).

Things done:

- Fill in default cwd at a lower level for all adapters
- Update launch.json parsing for DebugScenario changes
- Imitate how VS Code normalizes the `type` field for JS debug tasks
- Make version field optional
- Extend the variable replacer a bit

Release Notes:

- Debugger Beta: fixed issues preventing loading and running of debug
tasks from VS Code's launch.json.

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-06-12 11:51:37 -04:00
Conrad Irwin
55d751a1c9 Hush breakpoint deserialization logs (#32430)
Release Notes:

- debugger: Remove "Deserializing N breakpoints" from the Zed log
2025-06-12 11:51:20 -04:00
Conrad Irwin
2562daaa6d Rerun debug scenario now uses latest definition from JSON (#32569)
Co-authored-by: Piotr Osiewicz <piotr@zed.dev>

Closes #ISSUE

Release Notes:

- debugger: Re-running a debug scenario that has been edited on disk now
uses the latest version

Co-authored-by: Piotr Osiewicz <peterosiewicz@gmail.com>
2025-06-12 11:51:12 -04:00
Piotr Osiewicz
10d00e18c9 debugger: Fix preselection of debug adapters to not pick CodeLLDB by default (#32557)
Closes #ISSUE

Release Notes:

- debugger: Fix preselection of debug adapters to not pick CodeLLDB by
default
2025-06-12 11:51:01 -04:00
gcp-cherry-pick-bot[bot]
720fe2f437 agent: Don't stop following after edits (cherry-pick #32606) (#32608)
Cherry-picked agent: Don't stop following after edits (#32606)

This is reverting a change from #32071 which caused agent following to
stop after the file was edited.

This will reintroduce the behavior that the keyboard shortcuts don't
work until the model is done generating, but we will revisit that
afterwards.

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>

Release Notes:

- agent: Fix a regression in agent following behavior after file edits

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-06-12 15:49:02 +02:00
gcp-cherry-pick-bot[bot]
28f14093d1 gpui: Fix window cursor style flickering (cherry-pick #32596) (#32600)
Cherry-picked gpui: Fix window cursor style flickering (#32596)

Closes #32592
Follow-up to #31965 

This PR fixes the cursor style flickering on Linux systems. The issue
arose since the window cursor style was not reused anymore for
subsequent frames after the changes in #31965. This works on MacOS for
hiding cursors, since they are hidden until the next mouse movement
occurs, which is not the case for other systems.

This PR re-adds this whilst keeping the fixes applied in #31965. We now
determine the first cursor style that is hovered and continue searching
for a cursor style that should be applied globally. If one to apply for
the whole window is found, we return that cursor style early instead.

Alternatively, we could store window cursor style request in a vector
similar to normal cursor styles. That would require more memory in
exchange for fewer checks which cursor style to apply. I preferred the
approach here, though, but can change this should the other method be
preferred.

CC @smitbarmase since you assigned yourself that issue.

Release Notes:

- Fixed an issue where the cursor would flicker whilst typing.

Co-authored-by: Finn Evers <dev@bahn.sh>
2025-06-12 15:48:25 +05:30
gcp-cherry-pick-bot[bot]
ff6662052d Use buffer's main language when fetching language tasks (cherry-pick #32580) (#32582)
Cherry-picked Use buffer's main language when fetching language tasks
(#32580)

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

Release Notes:

- Fixed language tasks fetched incorrectly for certain selections

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-06-12 00:31:01 +03:00
Joseph T. Lyons
d8d6866191 v0.191.x preview 2025-06-11 11:18:53 -04:00
Cole Miller
06f7d791b7 debugger: Fix a couple of issues with vitest (#32543)
- Pass the right test name filter
- Limit the number of forks used by the testing pool in the spirit of
#32473

Release Notes:

- Debugger Beta: switched to running vitest tests serially when
debugging.
2025-06-11 10:36:23 -04:00
Ben Brandt
2ecc24eb26 eval: Add jitter to retry attempts (#32542)
Adds some jitter to avoid the issue that all requests will retry at
roughly the same time in eval where we have a lot of concurrent
requests.

Release Notes:

- N/A
2025-06-11 12:56:23 +00:00
Piotr Osiewicz
6c4728f00f debugger: Mark DebugAdapterBinary::program as optional (#32534)
This allows us to support debugging with a debug adapter not managed by
Zed. Note that this is not a user facing change, as DebugAdapterBinary
is used to determine how to spawn a debugger. Thus, this should not
break any configs or anything like that.

Closes #ISSUE

Release Notes:

- N/A
2025-06-11 12:38:12 +02:00
张小白
a3cc063107 windows: Show error messages when zed failed to lanuch (#32537)
Now, if either `WindowsPlatform` or `BladeRenderer` fails to initialize,
a window will pop up to notify the user.


![image](https://github.com/user-attachments/assets/40fe7f1d-5218-4ee2-b4ec-0945fed2b743)


Release Notes:

- N/A
2025-06-11 18:37:34 +08:00
Max Mynter
7d5a5d0984 Make minimum width for line numbers in gutter configurable (#31959)
Closes #7334

# Changes
This PR makes the minimum width allocated for line numbers in the side
gutter configurable in units of character width via the
`"line_number_base_width"` attribute in `gutter` settings. Set the
previously hard coded value of `4` as default.

Together with other settings (`"folds"`, `"breakpoints"`,...) this gives
the user control over the gutter width.

If the number of lines exceedes the base width, the number of digits in
the largest line number is chosen instead. This is consistent with
previous behaviour.

Screenshot for reference:
<img width="1104" alt="Screenshot 2025-06-03 at 12 15 29"
src="https://github.com/user-attachments/assets/77c869ad-164b-4b74-8e39-8be43d740ad4"
/>


P.S.: This is my first time contributing to zed (yay!🎉). Let me know if
i'm missing something.

Release Notes:

- Make minimum line number width in gutter configurable
2025-06-11 10:00:50 +00:00
张小白
4c3ada5753 windows: Add back hide_title_bar checks (#32427)
These `if` condition checks were removed in #30828, and this PR adds
them back. This is especially important in the handling of
`WM_NCHITTEST`, where all the calculations are based on the assumption
that `hide_title_bar = true`.


Release Notes:

- N/A
2025-06-11 09:46:16 +00:00
Ben Brandt
b3a8816c0e agent: Add completion cancellation when editing messages (#32533)
When editing a message, cancel any in-progress completion before
starting a new request to prevent overlapping model responses.

Release Notes:

- agent: Fixed previous completion not cancelling when editing a
previous message
2025-06-11 09:36:21 +00:00
Smit Barmase
6d9bcdb2af editor: Fix certain unwanted pre-emptive keys been shown in buffer (#32528)
Closes #32456

https://github.com/zed-industries/zed/pull/32007 added showing
pre-emptive keys for multi-key bindings. But for certain keys like
"control", "backspace", "escape", "shift", "f1", etc., shouldn't be
shown as these keys would not end up in buffer after pending input
delay. This PR changes it to use just `key_char`, as it represents
actual text that will end up in buffer and is `None` for all mentioned
keys.


fad4c17c97/crates/gpui/src/platform/keystroke.rs (L14-L21)

cc @ConradIrwin 

Release Notes:

- Fixed issue where triggering multi-key binding like "shift",
"control", etc. would write them to the buffer for a short time.
2025-06-11 14:16:21 +05:30
Umesh Yadav
0852912fd6 language_models: Add image support to OpenRouter models (#32012)
- [x] Manual Testing(Tested this with Qwen2.5 VL 32B Instruct (free) and
Llama 4 Scout (free), Llama 4 Maverick (free). Llama models have some
issues in write profile due to one of the in built tools schema, so I
tested it with minimal profile.

Closes #ISSUE

Release Notes:

- Add image support to OpenRouter models

---------

Signed-off-by: Umesh Yadav <umesh4257@gmail.com>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2025-06-11 08:01:29 +00:00
Julia Ryan
47ac01842b ci: Fix cachix secrets (#32259) 2025-06-10 23:38:44 -07:00
Michael Sloan
5b22994d9f Log error instead of panics in InlineAssistant::scroll_to_assist (#32519)
Leaving release notes blank as it's not very actionable to know that a
rare crash might be fixed.

Release Notes:

- N/A
2025-06-11 06:22:12 +00:00
Cole Miller
6c0ea88f5b debugger: Make sure debuggees are killed when quitting Zed (#32186)
Closes #31373 

We kill the DAP process in our `on_app_quit` handler, but the debuggee
might not be killed. Try to make this more reliable by making the DAP
process its own process group leader, and killing that entire process
group when quitting Zed.

I also considered going through the normal DAP shutdown sequence here,
but that seems dicey in a quit handler. There's also the DAP
`ProcessEvent` but it seems we can't rely on that as e.g. the JS DAP
doesn't send it.

Release Notes:

- Debugger Beta: Fixed debuggee processes not getting cleaned up when
quitting Zed.
2025-06-11 05:23:38 +00:00
Smit Barmase
fc4ca346be editor: Adjust scope for prefer label for snippet workaround (#32515)
Closes #32159

This PR refines the scope to match just the function name with **the
type argument** instead of the whole call expression.

Matching to whole call expression prevented methods from expanding
inside the function argument. For example, `const foo =
bar(someMethod(2)^);` instead of `const foo = bar(someMethod^)`;

Follow-up for https://github.com/zed-industries/zed/pull/30312,
https://github.com/zed-industries/zed/pull/30351. Mistakenly regressed
since https://github.com/zed-industries/zed/pull/31872 when we stopped
receiving `insert_range` for this particular case and fallback to
`replace_range`.

Release Notes:

- Fixed issue where code completion in TypeScript function arguments
sometimes omitted the dot separator, for example resulting in
`NumberparseInt` instead of `Number.parseInt(string)`.

---------

Co-authored-by: Michael Sloan <michael@zed.dev>
Co-authored-by: Michael Sloan <mgsloan@gmail.com>
2025-06-11 10:38:39 +05:30
Conrad Irwin
e9570eefbf Fix go stop on panic (#32512)
Release Notes:

- debugger: Fix stopping on a panic
2025-06-10 22:24:59 -06:00
Max Brunsfeld
72de3143c8 Add a test demonstrating ERB language loading bug (#32278)
Fixes https://github.com/zed-industries/zed/issues/12174

Release Notes:

- Fixed a bug where ERB files were not parsed correctly when the
languages were initially loaded.
2025-06-11 04:03:42 +00:00
Conrad Irwin
ad206a6a97 Recenter current stack frame on click (#32508)
Release Notes:

- debugger: Recenter current stack frame on click
2025-06-10 22:00:20 -06:00
Conrad Irwin
1e1bc7c373 Fix detach (#32506)
Release Notes:

- debugger: Fix detach to not terminate debuggee (and only be available
when detaching makes sense)
2025-06-10 20:20:28 -06:00
Stanislav Alekseev
84eca53319 Add ANSI C quoting to export env parsing (#32404)
Follow up to #31799 to support ansi-c quoting. This is used by
nix/direnv

Release Notes:

- N/A
2025-06-10 20:15:35 -06:00
fantacell
b4e558ce3d Add more keymaps from helix (#32453)
I added three additional keymaps to simulate helix behavior.

Release Notes:

- N/A
2025-06-11 02:10:43 +00:00
Conrad Irwin
00a8101016 Add a run menu (#32505)
As part of this I refactored the logic that enabled/disabled actions in
the debugger to happen at action registration time instead of using
command palette filters. This allows the menu to grey out actions correctly.

Release Notes:

- Add a "Run" menu to contain tasks and debugger
2025-06-10 19:57:46 -06:00
Anthony Eid
444f797827 debugger beta: Improve resolve debug scenario error message (#32504)
When no locator or valid config is found we expose the invalid config
error message to the user now.

Closes #32067 

Release Notes:

- debugger beta: Improve error message when starting a debugger session
with an invalid configuration
2025-06-11 01:13:27 +00:00
Anthony Eid
7a14987c02 debugger beta: Fix inline value provider panic (#32502)
Closes #32143

Release Notes:

- debugger beta: Fix panic that could occur when generating inline
values
2025-06-11 01:01:30 +00:00
Anthony Eid
5eb68f0ea4 debugger: Fix panic when handling invalid RunInTerminal request (#32500)
The new dap-types version has a default to cwd for the
RunInTerminalRequest

Closes #31695

Release Notes:

- debugger beta: Fix panic that occurred when a debug adapter sent an
invalid `RunInTerminal` request
2025-06-11 00:44:32 +00:00
Kirill Bulatov
9c513223c4 Add initial package.json scripts task autodetection (#32497)
Now, every JS/TS-related file will get their package.json script
contents added as tasks:

<img width="1020" alt="image"
src="https://github.com/user-attachments/assets/5bf80f80-fd72-4ba8-8ccf-418872895a25"
/>

To achieve that, `fn associated_tasks` from the `ContextProvider` was
made asynchronous and the related code adjusted.

Release Notes:

- Added initial `package.json` scripts task autodetection

---------

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
2025-06-10 22:16:27 +00:00
Cole Miller
0c0933d1c0 debugger: Ungate locator for JS tasks (#32495)
Closes #ISSUE

Release Notes:

- N/A

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-06-10 18:16:07 -04:00
Piotr Osiewicz
a4c5a2d4d3 debugger: Add 'open docs' button in the panel and mention onboarding in the docs (#32496)
Closes #ISSUE

Release Notes:

- N/A
2025-06-10 21:56:29 +00:00
Cole Miller
311e136e30 debugger: Reuse parent's debug terminal for child sessions (#32493)
Closes #ISSUE

Release Notes:

- Debugger Beta: fixed an issue where the terminal pane of the debug
panel would be empty when debugging JavaScript.

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-06-10 17:13:58 -04:00
Michael Sloan
4f5433a180 Filter language server completions even when is_incomplete: true (#32491)
In #31872 I changed the behavior of completions to not filter instead of
requerying completions when `is_incomplete: false`. Unfortunately this
also stopped filtering completions when `is_incomplete: true` - we still
want to filter the incomplete completions so that the menu updates
quickly even when completions are slow. This does mean that the
completions menu will display partial results, hopefully only briefly
while waiting for fresh completions.

Thanks to @mikayla-maki for noticing the regression. Thankfully just in
time to fix it before this makes it into a stable release. Leaving off
release notes since I will cherry-pick this to the current preview
version, 190.x, and there probably won't be a preview release before the
next stable.

Release Notes:

- N/A
2025-06-10 21:01:59 +00:00
Piotr Osiewicz
295db79c47 debugger: Fix phantom JavaScript frames (#32469)
JavaScript debugger is using a phantom stack frame to delineate await
points; that frame reuses a frame ID of 0, which collides with other
frames returned from that adapter.

934075df8c/src/adapter/stackTrace.ts (L287)

The bug has since been fixed in
https://github.com/microsoft/vscode-js-debug/issues/2234, but we'll need
to wait for a new release of node debugger for that to make a
difference. Until then..

Release Notes:

- Fixed a bug with JavaScript debugging which led to stack trace list
containing excessive amount of `await` entries.

---------

Co-authored-by: Conrad Irwin <conrad@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-06-10 14:48:07 -06:00
Cole Miller
71d5c57119 debugger: Specify runtimeExecutable in output of node locator (#32464)
This appears to fix some cases where we fail to launch JS tests under
the debugger.

Release Notes:

- N/A (node locator is still gated)

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-06-10 20:42:55 +00:00
Julia Ryan
dd17fd3d5a debug: Launch custom commands from start modal (#32484)
Release Notes:

- Add custom command launching from the `debug: start` modal

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-06-10 16:29:11 -04:00
Cole Miller
e4f8c4fb4c debugger: Don't spin forever when adapter disconnects unexpectedly (#32489)
Closes #ISSUE

Release Notes:

- Debugger Beta: made the debug panel UI more helpful when an invalid
configuration is sent to the debug adapter.

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-06-10 16:26:43 -04:00
Andy Waite
e62e9facf0 docs: Condense Ruby test framework docs (#32472)
Since `tldr` and `quickdraw` use the same kind of task syntax as RSpec,
I don't think it's necessary to have separate examples.

cc @joeldrapper @vitallium 

Release Notes:

- N/A
2025-06-10 22:58:57 +03:00
Andy Waite
3f419b32f8 docs: Update Ruby docs about args syntax in tasks (#32471)
Due to https://github.com/zed-industries/zed/pull/32345

cc @vitallium 

Release Notes:

- N/A
2025-06-10 22:58:38 +03:00
Joseph T. Lyons
5270844b42 Revert "Preserve selection direction when running editor: open selections in multibuffer" (#32483)
Reverts zed-industries/zed#31399

I found that in some cases, Zed will panic when using `editor: open
selections in multibuffer` if the selection is reversed. It doesn't
happen in most cases that I've tested, but in some strange edge cases
(that I dont fully understand ATM), it does. I'm reverting for now, as
the previous behavior is better than a panic, but will re-implement this
fix to preserving selection directions in a new PR with comprehensive
testing

Release Notes:

- N/A
2025-06-10 15:31:38 -04:00
Ben Kunkle
f567bb52ff gpui: Simplify uniform list API by removing entity param (#32480)
This PR also introduces `Context::processor`, a sibling of
`Context::listener` that takes a strong pointer to entity and allows for
a return result.

Release Notes:

- N/A

Co-authored-by: Mikayla <mikayla@zed.dev>
2025-06-10 18:50:57 +00:00
Cole Miller
c55630889a debugger: Run jest tests serially (#32473)
Pass `--runInBand` to jest when debugging. This prevents jest from
creating a bunch of child processes that clutter the session list.

It might be a bit more natural to add this argument in the test
templates themselves, but I don't think we want to give up parallelism
when running via `task: spawn`.

Release Notes:

- N/A (JS locator is still gated)
2025-06-10 14:25:07 -04:00
Cole Miller
e0ca4270b4 debugger: Use JS adapter's suggested names for child sessions (#32474)
Also introduces an extension point for other adapters to do this if it
turns out they also send this information.

Release Notes:

- N/A (JS locator is still gated)
2025-06-10 14:24:43 -04:00
Peter Tripp
02dfaf7799 ci: Suppress evals on forks (#32479)
Be kind to those with Zed forks.

Example [action run on
fork](https://github.com/G36maid/freebsd-ports-zed/actions/runs/15525942275)
where [this
job](https://github.com/G36maid/freebsd-ports-zed/actions/runs/15549650437/job/43777665341)
will wait forever. Sorry @G36maid

Release Notes:

- N/A
2025-06-10 18:20:03 +00:00
Ben Kunkle
c9972ca532 docs: Consolidate and improve organization of Linux GPU issue documentation (#32468)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-06-10 12:18:57 -04:00
Alexander
9334e152b4 Allow identifiers in TypeScript/JavaScript test names (#32467)
Current behavior (not detected as runnable):

<img width="1105" alt="image"
src="https://github.com/user-attachments/assets/7d3b7936-43d8-4645-bbbb-e81ed5f9b35a"
/>

New behavior:



https://github.com/user-attachments/assets/524e2a56-cb30-4dc0-98ec-b34b510015e0

Release Notes:

- Improved detection of runnable TypeScript/JavaScript test cases when
they contain identifier
2025-06-10 18:00:42 +02:00
Peter Tripp
9c47c52de5 ci: Restore lychee link check. Only validate internal links (#32463)
Follow-up to: https://github.com/zed-industries/zed/pull/32460
Follow-up to: https://github.com/zed-industries/zed/pull/30844

Release Notes:

- N/A
2025-06-10 11:20:07 -04:00
Umesh Yadav
286b97c0de agent: Fix agent panel model selector layout pushing send button off screen (#32251)
| Before | After |
|--------|-------|
| <video
src="https://github.com/user-attachments/assets/db4dcc91-9a32-4621-be78-87fe9d80b801"
controls width="400"></video> | <video
src="https://github.com/user-attachments/assets/8ee31d6d-5150-4239-a4af-eeca112d56d5"
controls width="400"></video> |

While working on something else I found this weird behaviour in message
editor of agent panel. When model names are too long, the model selector
would expand and push the send button outside the visible area. This
change fixes the flex layout to ensure the send button always remains
accessible while properly truncating long model names.

Closes #ISSUE

Release Notes:

- Fix agent panel model selector layout pushing send button off screen

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
2025-06-10 14:59:42 +00:00
Danilo Leal
415d482395 agent: Only show the MCP configuration modal in the active window (#32450)
We were previously displaying this modal in all open Zed windows if
triggered. That was a bit annoying because I had to go to each window
individually to close it, which meant doing it multiple times. 😅

Release Notes:

- agent: Fixed the MCP configuration modal to show only in the active
window.
2025-06-10 14:34:21 +00:00
Danilo Leal
a9d0eee2a9 docs: Add link to MCP extensions in the overview page (#32458)
Follow up to https://github.com/zed-industries/zed/pull/32422. Missed
this one in this latest round of MCP-related docs changes.

Release Notes:

- N/A

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-06-10 11:16:06 -03:00
Smit Barmase
e4e3409952 extension_host: Fix SSH reconnect breaks language server (#32457)
Closes #29032

This PR fixes an issue where reconnecting to SSH Remote would result in
a broken language server.

This was caused by SSH clients not registering because the `ssh_clients`
map would still contain an entry from a previously killed SSH server.
For fix, now we also check if its value has been dropped.

Release Notes:

- Fixed issue where reconnecting to SSH Remote would result in broken
code completions and diagnostics.
2025-06-10 19:45:57 +05:30
Peter Tripp
46f98b6001 ci: Move lychee link check to script/check-links (#32460)
Follow-up to: https://github.com/zed-industries/zed/pull/30844

Transient link failure was blocking PR tests passing. [Example
run](https://github.com/zed-industries/zed/actions/runs/15560960788/job/43812878693?pr=32458).

Release Notes:

- N/A
2025-06-10 10:11:24 -04:00
Kirill Bulatov
c1a4a24bce Ensure pull diagnostics do not happen for non-full mode editors (#32449)
Follow-up of https://github.com/zed-industries/zed/pull/19230

Release Notes:

- N/A
2025-06-10 12:05:45 +00:00
CharlesChen0823
eb5f59577d editor: Dismiss drag selection when dropped outside editor (#32382)
This PR fixes two issues:

1. On macOS, using Alt to copy the selection instead of cutting it.
2. Dropping the drag selection outside the editor dismisses it.  


https://github.com/user-attachments/assets/341e21c3-3eca-4e58-9bcc-8ec1de18e999


Release Notes:

- N/A

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-06-10 15:41:59 +05:30
张小白
2dad48d8d9 windows: Fix panic when deleting the last pre-edit char using Pinyin IME (#32442)
Release Notes:

- N/A
2025-06-10 09:51:12 +00:00
Burak Varlı
16853acbb1 Enable cross-region inference for Claude 4 family models on Amazon Bedrock provider (#32235)
These models require cross-region inference, and it currently fails if
you try to use them:
```
Invocation of model ID anthropic.claude-sonnet-4-20250514-v1:0 with on-demand throughput isn’t supported. 
```

Release Notes:

- Enable cross-region inference for Claude 4 family models on Amazon
Bedrock provider

Signed-off-by: Burak Varlı <burakvar@amazon.co.uk>
2025-06-09 23:38:39 -07:00
Michael Sloan
64d649245c Add missing #[track_caller] meant to be in #32433 (#32434)
Release Notes:

- N/A
2025-06-10 04:52:43 +00:00
Michael Sloan
08210b512d Don't push to selection history if selections are empty (#32433)
I got a panic during undo but haven't been able to repro it. Potentially
a consequence of my changes in #31731

> Thread "main" panicked with "There must be at least one selection" at
crates/editor/src/selections_collection.rs

Leaving release notes blank as I'm not sure this actually fixes the
panic

Release Notes:

- N/A
2025-06-10 04:29:45 +00:00
Michael Sloan
6070aea6c0 Skip adding initial 1:1 cursor position to selection history (#32432)
Also improves some minor corner cases in `undo_selection` and
`redo_selection` related to the use of `end_selection`. If the pending
selection was ended, this would separately get pushed to the redo or
undo stack and redundantly run all the other effects of selection
change.

Release Notes:

- N/A
2025-06-10 04:22:46 +00:00
Conrad Irwin
16b44d53f9 debugger: Use Delve to build Go binaries (#32221)
Release Notes:

- debugger: Use delve to build go debug executables, and pass arguments
through.

---------

Co-authored-by: sysradium <sysradium@users.noreply.github.com>
Co-authored-by: Zed AI <ai@zed.dev>
2025-06-09 21:49:04 -06:00
Haru Kim
3bed830a1f Use unix pipe to capture environment variables (#32136)
The use of `NamedTempFile` in #31799 was not secure and could
potentially cause write permission issues (see [this
comment](https://github.com/zed-industries/zed/issues/29528#issuecomment-2939672433)).
Therefore, it has been replaced with a Unix pipe.

Release Notes:
- N/A
2025-06-09 20:37:43 -06:00
Conrad Irwin
ee2a329981 Slow down reconnects on collab (#32418)
We believe that collab deploys currently cause outages because:

* All clients try to reconnect simultaneously
* This causes very high CPU usage on collab (and to some extent, the
database)
* This means that collab is slow to respond to clients
* So clients timeout and retry, over and over again.

We hope by letting clients in in buckets of 512, we can accept some
minor slowness to avoid
complete downtime, while we rewrite the system.

Release Notes:

- N/A
2025-06-09 19:59:04 -06:00
Joseph T. Lyons
6d64058fc6 Add pane: unpin all tabs (#32423)
After integrating pinned tabs into my workflow, I've come to the
conclusion that it is painfully slow to unpin all tabs by hand.


https://github.com/user-attachments/assets/ad087b8e-4595-4c4d-827f-188e36170c25

Release Notes:

- Added a `pane: unpin all tabs` action
2025-06-09 20:25:22 -04:00
Danilo Leal
7c2822a020 docs: Improve MCP-related pages (#32422)
While creating a new MCP extension this weekend, I visited these pages
and it felt like they could be improved a little bit. I'm renaming the
MCP-related page under the /extension directory to use the "MCP"
acronym, instead of "context servers".

Release Notes:

- N/A
2025-06-09 21:00:10 -03:00
Danilo Leal
3db00384f4 agent: Improve generating dots display (#32421)
Previously, upon hitting the "Continue" button to restart an interrupted
thread due to consecutive tool calls reaching its limit, you wouldn't
see the loading dots and the UI would be a weird state. This PR improves
when these loading dots actually show up, including in their conditional
a check for `message.is_hidden`.

Also took advantage of the opportunity to quickly organize some of these
variables. The `render_message` function could potentially be chopped up
in more smaller pieces. Lots of things going on here.

Release Notes:

- N/A
2025-06-09 20:52:50 -03:00
Conrad Irwin
3dfbd9e57c Fix ruby debugger (#32407)
Closes #ISSUE

Release Notes:

- debugger: Fix Ruby (was broken by #30833)

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Piotr Osiewicz <peterosiewicz@gmail.com>
Co-authored-by: Cole Miller <m@cole-miller.net>
2025-06-09 16:11:24 -06:00
Agus Zubiaga
b103d7621b Improve handling of large output in embedded terminals (#32416)
#31922 made embedded terminals automatically grow to fit the content. We
since found some issues with large output which this PR addresses by:

- Only shaping / laying out lines that are visible in the viewport
(based on `window.content_mask`)
- Falling back to embedded scrolling after 1K lines. The perf fix above
actually makes it possible to handle a lot of lines, but:
- Alacrity uses a `u16` for rows internally, so we needed a limit to
prevent overflow.
- Scrolling through thousands of lines to get to the other side of a
terminal tool call isn't great UX, so we might as well set the limit
low.
- We can consider raising the limit when we make card headers sticky.

Release Notes:

- Agent: Improve handling of large terminal output
2025-06-09 18:11:31 -03:00
Danilo Leal
ab70e524c8 agent: Improve MCP status indicator tooltip and loading state (#32414)
Mostly a small tweak making sure that the indicator tooltip hit area is
bigger and the loading state is clearer (not using an indicator
anymore). Way more little improvement opportunities in this component to
do, though.

Release Notes:

- N/A
2025-06-09 18:04:48 -03:00
JonasKaplan
f0ce62ead8 editor: Add trailing whitespace rendering (#32329)
Closes #5237

- Adds "trailing" option for "show_whitespaces" in settings.json
- Supports importing this setting from vscode

The option in question will render only whitespace characters that
appear after every non-whitespace character in a given line.

Release Notes:

- Added trailing whitespace rendering
2025-06-09 20:48:49 +00:00
Cole Miller
f0345df479 debugger: Undo conversion of stack frames list to uniform list (#32413)
Partially reverts #30682

A uniform list is desirable for the scrolling behavior, but this breaks
badly when there are collapsed entries or entries without paths, both of
which seem common with the JS adapter.

It would be nice to go back to a uniform list if we can come up with a
set of design tweaks that allow all entries to be the same height.

Release Notes:

- Debugger Beta: fixed an issue that caused entries in the stack frame
list to overlap in some situations.
2025-06-09 20:45:01 +00:00
Michael Sloan
bbd2262a93 Fix buffer rendering on every mouse move (#32408)
Closes #32210

This notify was added in #13433. Solution is to only notify when the
breakpoint indicator state has changed.

Also improves the logic for enqueuing a task to delay showing - now only
does this if it isn't already visible, and that delay task now only
notifies if still hovering.

Release Notes:

- Fixed a bug where buffers render on every mouse move.
2025-06-09 14:10:03 -06:00
Antonio Scandurra
c4fd9e1a6b Switch to using weak transactions for queries happening on connection (#32411)
Release Notes:

- N/A

Co-authored-by: Conrad <conrad@zed.dev>
2025-06-09 21:37:48 +02:00
Nate Butler
0b7583bae5 Refine styling of merge conflicts (#31012)
- Improved colors
- Blank out diff hunk gutter highlights in conflict regions
- Paint conflict marker highlights all the way to the gutter

Release Notes:

- Improved the highlighting of merge conflict markers in editors.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
Co-authored-by: Cole Miller <cole@zed.dev>
2025-06-09 19:03:19 +00:00
Ben Brandt
e4bd115a63 More resilient eval (#32257)
Bubbles up rate limit information so that we can retry after a certain
duration if needed higher up in the stack.

Also caps the number of concurrent evals running at once to also help.

Release Notes:

- N/A
2025-06-09 18:07:22 +00:00
Kirill Bulatov
fa54fa80d0 Store pulled diagnostics' result_ids more persistently (#32403)
Follow-up of https://github.com/zed-industries/zed/pull/19230

`BufferId` can change between file reopens: e.g. open the buffer, close
it, go back in history to reopen it — the 2nd one will have a different
`BufferId`, but the same `result_ids` semantically.

Release Notes:

- N/A
2025-06-09 17:05:33 +00:00
Antonio Scandurra
de16f2bbe6 Bypass account age check when feature flag is set (#32393)
Release Notes:

- N/A
2025-06-09 18:44:48 +02:00
Jason Lee
e3b13b54c9 title_bar: Merge Linux only code into platform_linux (#32401)
Release Notes:

- N/A
2025-06-09 19:19:24 +03:00
Tommy D. Rossi
2c5d2a58d8 Do not skip punctuation characters with alt-arrow if next character is \n (#32368)
Closes #32356

Release Notes:

- N/A
2025-06-09 09:25:32 -06:00
Peter Tripp
3485b7704b Update GitHub Issue Templates (June 2025) (#32399)
- Remove git/edit predictions templates
- Rename Agent to AI related (include edit predictions, copilot, etc)
- Other minor adjustments

Release Notes:

- N/A
2025-06-09 15:25:17 +00:00
Bennet Bo Fenner
6801b9137f context_server: Make notifications type safe (#32396)
Follow up to #32254 

Release Notes:

- N/A
2025-06-09 15:11:01 +00:00
Umesh Yadav
3853e83da7 git_ui: Improve error handling in commit message generation (#29005)
After and Before video of the issue and solution.


https://github.com/user-attachments/assets/40508f20-5549-4b3d-9331-85b8192a5b5a



Closes #27319

Release Notes:

- Provide Feedback on commit message generation error

---------

Signed-off-by: Umesh Yadav <umesh4257@gmail.com>
Co-authored-by: Cole Miller <cole@zed.dev>
2025-06-09 14:20:19 +00:00
Ben Hamment
047a7f5d29 Decrease the size of the branch picker icon (#32387)
<img width="323" alt="image"
src="https://github.com/user-attachments/assets/0060eaf3-35f9-4f0f-b9b6-e26ffad853c2"
/>

<img width="323" alt="image"
src="https://github.com/user-attachments/assets/57b66dae-2a74-401f-82c1-8fc730a98fb0
" />

Release Notes:

- Adjusted size of the icon inside the title bar's branch picker when
that's turned on.
2025-06-09 11:08:53 -03:00
Evan Simkowitz
8332e60ca9 language: Don't add final newline on format for an empty buffer (#32320)
Closes #32313

Release Notes:

- Fixed newline getting added on format to empty files
2025-06-09 09:00:17 -04:00
Bennet Bo Fenner
afab4b522e agent: Add tests for thread serialization code (#32383)
This adds some unit tests to ensure that the `update(...)`/migration
path to the latest versions works correctly

Release Notes:

- N/A
2025-06-09 12:20:19 +00:00
vipex
0cb7dd2972 git_panel: Persist dock size (#32111)
Closes #32054

The dock size for the git panel wasn't being persisted across Zed
restarts. This was because the git panel lacked the serialization
pattern used by other panels.

Please let me know if you have any sort of feedback or anything, as i'm
still trying to learn :]

Release Notes:

- Fixed Git Panel dock size not being remembered across Zed restarts

## TODO
- [x] Update/fix tests that may be broken by the GitPanel constructor
changes
2025-06-09 16:51:36 +05:30
Angelk90
387281fa5b project_panel: Add hide_root when only one folder in the project (#25289)
Closes #24188

Todo:
- [x] Hide root when only one worktree
- [x] Basic tests
- [x] Docs
- [x] Fix `select_first` + tests
- [x] Fix auto collapse dir + tests
- [x] Fix file / dir creation + tests
- [x] Fix root rename case

| Show root | Hide root |
|--------|--------|
| <img width="272" alt="Screenshot 2025-02-20 alle 22 35 55"
src="https://github.com/user-attachments/assets/361d93c7-e1ad-4419-a5f4-be62c9632807"
/> | <img width="269" alt="Screenshot 2025-02-20 alle 22 36 11"
src="https://github.com/user-attachments/assets/62011f76-a24b-4297-9734-f5c3b9f75760"
/> |
| <img width="275" alt="Screenshot 2025-02-20 alle 22 56 33"
src="https://github.com/user-attachments/assets/77e7e6e6-3dfe-4e88-b4b0-b620cb809d2b"
/> | <img width="267" alt="Screenshot 2025-02-20 alle 22 55 53"
src="https://github.com/user-attachments/assets/fa1099c8-7ed0-45ef-a7cf-aeb54b8283b1"
/> |


Release Notes:

- Added support to hide the root entry of the Project Panel when there’s
only one folder in the project. This can be enabled by setting
`hide_root` to `true` in the `project_panel` config.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-06-09 16:46:31 +05:30
Piotr Osiewicz
72bcb0beb7 chore: Fix warnings for Rust 1.89 (#32378)
Closes #ISSUE

Release Notes:

- N/A
2025-06-09 13:11:57 +02:00
Bennet Bo Fenner
4ff41ba62e context_server: Update types to reflect latest protocol version (2025-03-26) (#32377)
This updates the `types.rs` file to reflect the latest version of the
MCP spec. Next up is making use of some of these new capabilities. Would
also be great to add support for [Streamable HTTP
Transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http)

Release Notes:

- N/A
2025-06-09 13:03:47 +02:00
Bennet Bo Fenner
16e901fb8f docs: Remove reference to outdated Gemini models (#32379)
Release Notes:

- N/A
2025-06-09 10:50:30 +00:00
smaster
54b4587f9a Add bound checks for resizing right dock (#32246)
Closes #30293


[Before](https://github.com/user-attachments/assets/0b95e317-391a-4d90-ba78-ed3d4f10871d)
|
[After](https://github.com/user-attachments/assets/23002a73-103c-4a4f-a7a1-70950372c9d9)

Release Notes:

- Fixed right panel expanding in backwards, when dragged out of its
intended bounds, by adding a bounds check to ensure its size never gets
to high.
2025-06-09 10:39:53 +00:00
Clauses Kim
1fe10117b7 Add GitHub token environment variable support for Copilot (#31392)
Add support for environment variables as authentication alternatives to
OAuth flow for Copilot. Closes #31172

We can include the token in HTTPS request headers to hopefully resolve
the rate limiting issue in #9483. This change will be part of a separate
PR.

Release Notes:

- Added support for manually providing an OAuth token for GitHub Copilot
Chat by assigning the GH_COPILOT_TOKEN environment variable

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-06-09 12:39:44 +02:00
Bennet Bo Fenner
78fd2685d5 gemini: Fix edge case when transforming MCP tool schema (#32373)
Closes #31766

Release Notes:

- Fixed an issue where some MCP tools would not work when using Gemini
2025-06-09 10:27:21 +00:00
Vitaly Slobodin
da9e958b15 ruby: Update documentation (#32345)
Hi! This pull request updates the Ruby extension documentation for [the
upcoming v0.9.0
upgrade](https://github.com/zed-extensions/ruby/pull/106):

- Added documentation for two newly added language servers: `sorbet` and
`steep`.
- Updated documentation on using the `ZED_CUSTOM_RUBY_TEST_NAME` symbol
for tasks.

Thanks!

Release Notes:

- N/A
2025-06-09 13:19:43 +03:00
dannybunschoten
3908ca9744 docs: Add JavaScript configuration to the example setup of Deno (#32104)
When using Deno with the example configuration as described here,
duplicate lsp information is displayed in Javascript files.

This pull request solves that issue by adding Javascript to the
configuration.

Release Notes:

- Improve LSP support when using Deno with Javascript using the default
configuration.
2025-06-09 13:18:06 +03:00
Alexander
6fe58a2c4e Allow to run dynamic TypeScript and JavaScript tests (#31499)
First of all thank you for such a fast editor!

I realized that the existing support for detecting runnable test cases
for typescript/javascript is not full. Meanwhile I can run most of test
by pressing "run button":

<img width="713" alt="image"
src="https://github.com/user-attachments/assets/e8bb1cb1-f0a5-4eb1-b9a6-7188a9fa47ae"
/>

I can't run dynamic tests:

<img width="703" alt="image"
src="https://github.com/user-attachments/assets/d7eef1bc-e99a-4f05-9d62-ec49b8194959"
/>

I was curious whether I can improve it on my own and created this pr. I
edited schemas and added minor changes in `TaskTemplate` to allow to
replace '%s' with regexp pattern, so it can match test cases:

<img width="772" alt="image"
src="https://github.com/user-attachments/assets/db3a6fe0-ad90-4853-8e98-4215e41dfe88"
/>

Release Notes:

- Allow to run dynamic TypeScript/JavaScript tests

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-06-09 12:13:25 +02:00
Dino
79e7ccc1fe vim: Handle case sensitive search editor setting (#32276)
Update the `vim::normal::search::Vim.search` method in order to
correctly set the search bar's case sensitive search option if the
`search.case_sensitive` setting is enabled.

Closes #32172 

Release Notes:

- vim: Fixed a bug where the `search.case_sensitive` setting was not respected when activating search with <kbd>/</kbd> (`vim::Search`)
2025-06-09 06:12:23 -04:00
Umesh Yadav
0bc9478b46 language_models: Add support for images to Mistral models (#32154)
Tested with following models. Hallucinates with whites outline images
like white lined zed logo but works fine with zed black outlined logo:

Pixtral 12B (pixtral-12b-latest)
Pixtral Large (pixtral-large-latest)
Mistral Medium (mistral-medium-latest)
Mistral Small (mistral-small-latest)

After this PR, almost all of the zed's llm provider who support images
are now supported. Only remaining one is LMStudio. Hopefully we will get
that one as well soon.

Release Notes:

- Add support for images to mistral models

---------

Signed-off-by: Umesh Yadav <git@umesh.dev>
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
2025-06-09 10:00:02 +00:00
Umesh Yadav
4ac7935589 language_models: Add thinking support to LM Studio provider (#32337)
It works similar to how deepseek works where the thinking is returned as
reasoning_content and we don't have to send the reasoning_content back
in the request.

This is a experiment feature which can be enabled from settings like
this:
<img width="1381" alt="Screenshot 2025-06-08 at 4 26 06 PM"
src="https://github.com/user-attachments/assets/d2f60f3c-0f93-45fc-bae2-4ded42981820"
/>

Here is how it looks to use(tested with
`deepseek/deepseek-r1-0528-qwen3-8b`

<img width="528" alt="Screenshot 2025-06-08 at 5 12 33 PM"
src="https://github.com/user-attachments/assets/f7716f52-5417-4f14-82b8-e853de054f63"
/>


Release Notes:

- Add thinking support to LM Studio provider
2025-06-09 11:55:34 +02:00
Umesh Yadav
c75ad2fd11 language_models: Add thinking support to DeepSeek provider (#32338)
For DeepSeek provider thinking is returned as reasoning_content and we
don't have to send the reasoning_content back in the request.

Release Notes:

- Add thinking support to DeepSeek provider
2025-06-09 11:10:55 +02:00
andrewkolda
365997d79d docs: Remove duplicate Clang-Format link (#32359)
Release Notes:

- N/A
2025-06-09 06:42:31 +00:00
Smit Barmase
c57a6263aa editor: Fix select when click on existing selection (#32365)
Follow-up for https://github.com/zed-industries/zed/pull/30671

Now, when clicking on an existing selection, the cursor will change on
`mouse_up` when `drag_and_drop_selection` is `true`. When
`drag_and_drop_selection` is `false`, it will change on `mouse_down`
(previous default).

Release Notes:

- N/A
2025-06-09 11:58:06 +05:30
Joseph T. Lyons
ebea734515 Coalesce consecutive spaces in new buffer tab titles (#32363)
VS Code has a behavior where it coalesces consecutive spaces in new
buffer tab titles, which I quite like. This presents the content better
and allows more meaningful content to be displayed, as consecutive
spaces don't count towards the 40 character limit.

VS Code

<img width="1013" alt="SCR-20250608-uelt"
src="https://github.com/user-attachments/assets/71a1fd4b-a506-4eab-b6a4-66096a12f1ad"
/>

Zed

<img width="1136" alt="SCR-20250608-ueif"
src="https://github.com/user-attachments/assets/f40fc3c9-0f0f-471d-93ed-be9568fbe778"
/>


Release Notes:

- N/A
2025-06-09 02:01:32 -04:00
CharlesChen0823
4fe05530b0 editor: Add support for drag_and_drop_selection (#30671)
Closes #4958 

Release Notes:

- Added support for drag and drop text selection. It can be disabled by
setting `drag_and_drop_selection` to `false`.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-06-09 10:21:18 +05:30
Joseph T. Lyons
b15aef4310 Introduce dynamic tab titles for unsaved files based on buffer content (#32353)
https://github.com/user-attachments/assets/0bb08784-251c-4221-890a-2d6b3fb94e0f

For new, unsaved files:

- If a buffer has no content, or contains only whitespace, use
`untitled`
- If a buffer has content, take the first 40 chars of the first line

| Sublime | VS Code | Zed |
|---------|---------|-----|
| <img width="227" alt="SCR-20250608-ouux"
src="https://github.com/user-attachments/assets/d02b1e50-5775-4252-86e6-6c9d3f6c72fb"
/> | <img width="230" alt="SCR-20250608-ousn"
src="https://github.com/user-attachments/assets/7c9c016b-642f-4a80-9bc1-8c9bdc7bbd32"
/> | <img width="242" alt="SCR-20250608-ovbg"
src="https://github.com/user-attachments/assets/c7f4be5c-5bba-4a2a-b477-1392ca938cd5"
/> |

Note that this implementation also trims all leading whitespace, so that
if the buffer has any non-whitespace content, we use it. VS Code and
Sublime do not do this.

| Sublime | VS Code | Zed |
|---------|---------|-----|
| <img width="233" alt="SCR-20250608-oviq"
src="https://github.com/user-attachments/assets/ccffecc6-0f46-4d1b-8739-740240bc067b"
/> | <img width="198" alt="SCR-20250608-ovkq"
src="https://github.com/user-attachments/assets/35c20149-f898-417b-aff3-dda22b8cc1f3"
/> | <img width="233" alt="SCR-20250608-ovns"
src="https://github.com/user-attachments/assets/2509e8f6-254b-4fcb-a0ea-e18e95bb685b"
/> |

Release Notes:

- Introduced dynamic tab titles for unsaved files based on buffer
content
2025-06-08 17:30:33 -04:00
Michael Sloan
23adff6ff2 Add CI check that cmd- is not in linux keymaps + check other mods (#32334)
Motivation for the `cmd-` check is that there were a couple keybindings
using `cmd-` in the linux keymap and so these were bound to super /
windows

Release Notes:

- N/A
2025-06-08 09:34:07 +00:00
Michael Sloan
866fe427b3 Cleanup comments in linux keymaps (#32333)
Release Notes:

- N/A
2025-06-08 09:02:52 +00:00
Michael Sloan
f7b2faf64f Fix a few linux keybindings that use cmd- instead of ctrl- (#32332)
Release Notes:

- N/A
2025-06-08 08:50:35 +00:00
Kirill Bulatov
5187954711 Remove previous multi buffer hardcode from the outline panel (#32321)
Closes https://github.com/zed-industries/zed/issues/32316

Multi buffer design was changed so that control buttons are not
occupying extra lines, the hardcoded logic for that is obsolete thus
removed.

Release Notes:

- Fixed incorrect offsets during outline panel navigation in singleton
buffers
2025-06-07 23:54:47 +00:00
Michael Sloan
cabd22f36b No longer instantiate recently opened agent threads on startup (#32285)
This was causing a lot of work on startup, particularly due to
instantiating edit tool cards. The minor downside is that now these
threads don't open quite as fast.

Includes a few other improvements:

* On text thread rename, now immediately updates the metadata for
display in the UI instead of waiting for reload.

* On text thread rename, first renames the file before writing. Before
if the file removal failed you'd end up with a duplicate.

* Now only stores text thread file names instead of full paths. This is
more concise and allows for the app data dir changing location.

* Renames `ThreadStore::unordered_threads` to
`ThreadStore::reverse_chronological_threads` (and removes the old one
that sorted), since the recent change to use a SQL database queries them
in that order.

* Removes `ContextStore::reverse_chronological_contexts` since it was
only used in one location where it does sorting anyway - no need to sort
twice.

* `SavedContextMetadata::title` is now `SharedString` instead of
`String`.

Release Notes:

- Fixed regression in startup performance by not deserializing and
instantiating recently opened agent threads.
2025-06-07 14:53:36 -06:00
Tommy D. Rossi
1552198b55 Cursor keymap: Add cmd-enter to submit inline assistant (#32295)
Closes https://github.com/zed-industries/zed/discussions/29035

Release Notes:

- N/A
2025-06-07 16:37:45 -04:00
Richard Feldman
0da97b0c8b editor: Respect multi_cursor_modifier setting when making columnar selections using mouse (#32273)
Closes https://github.com/zed-industries/zed/issues/31181

Release Notes:

- Added the `multi_cursor_modifier` setting to be respected when making
columnar selections using the mouse drag.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-06-08 01:51:13 +05:30
Joseph T. Lyons
037df8cec5 Simplify logic updating pinned tab count (#32310)
Just a tiny improvement to clean up the logic

Release Notes:

- N/A
2025-06-07 19:15:30 +00:00
Peter Tripp
05ac9f1f84 docs: Missing . from .sql-formatter.json (#32312)
See:
https://github.com/zed-industries/zed/issues/9537#issuecomment-2952784074

Release Notes:

- N/A
2025-06-07 19:11:19 +00:00
morgankrey
9ffb3c5176 Add Opus to Model Docs (#32302)
Release Notes:

- N/A
2025-06-07 14:38:10 -03:00
Joseph T. Lyons
72d787b3ae Fix panic dragging tabs multiple positions to the right (#32305)
Closes https://github.com/zed-industries/zed/issues/32303

Release Notes:

- Fixed a panic that occurred when dragging tabs multiple positions to
the right (preview only)
2025-06-07 17:15:31 +00:00
Umesh Yadav
104f601413 language_models: Fix Copilot models not loading (#32288)
Recently in this PR: https://github.com/zed-industries/zed/pull/32248
github copilot settings was introduced. This had missing settings update
which was leading to github copilot models not getting fetched. This had
missing subscription to update the settings inside the copilot language
model provider. Which caused it not show models at all.

cc @osiewicz 

Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-06-07 09:32:01 +00:00
Peter Tripp
46773ebbd8 docs: Fix typo in SUMMARY.md (#32275)
- Follow-up to: https://github.com/zed-industries/zed/pull/30981
- See also: https://github.com/zed-industries/zed/pull/30844
- See also: https://github.com/zed-industries/zed/pull/31073

Changes made to docs tests meant that failure to build docs did not
prevent an automerge.
This resulted in breaking main in
65a93a0036 [action run
link](https://github.com/zed-industries/zed/actions/runs/15501437863).

CC: @probably-neb 

Release Notes:

- N/A
2025-06-06 19:26:59 -04:00
G36maid
65a93a0036 Add initial FreeBSD script & installation doc (#30981)
This PR adds initial FreeBSD support for building Zed:

*  Adds `script/freebsd` to install required dependencies on FreeBSD
*  Adds `docs/freebsd.md` with build instructions and notes
* ⚠️ Mentions that `webrtc` is still **work-in-progress** on FreeBSD.

Related to : #15309 
I’m currently working at discussions :
[Discussions](https://github.com/zed-industries/zed/discussions/29550)

Release Notes:

- N/A

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-06-06 23:14:25 +00:00
Kirill Bulatov
dc63138089 Use proper paths when determining file finder icons for external files (#32274)
Before:
<img width="576" alt="before"
src="https://github.com/user-attachments/assets/8469e27f-c878-4b89-a6f0-577a502fc625"
/>

After:
<img width="558" alt="after"
src="https://github.com/user-attachments/assets/b8361b88-c6c6-40a5-890d-047fff8e909e"
/>


Release Notes:

- N/A
2025-06-06 23:04:49 +00:00
Kirill Bulatov
fa02bd71c3 Select applicable positions for lsp_ext methods more leniently (#32272)
Closes https://github.com/zed-industries/zed/issues/27238

Release Notes:

- Fixed `editor::SwitchSourceHeader` and
`editor::ExpandMacroRecursively` not working with text selections
2025-06-06 22:47:20 +00:00
Peter Tripp
6d95fd9167 Make assistant::QuoteSelection shortcuts work in agent threads (#32270)
Closes: https://github.com/zed-industries/zed/discussions/30626

Release Notes:

- Fixed `assistant::QuoteSelection` default shortcuts (`cmd->` and
`ctrl->`) so they work in Agent threads too (in addition to text threads
and in the Editor pane).
2025-06-06 17:50:55 -04:00
Peter Tripp
899153d9a4 Stop formatting SQL by default with prettier (#32268)
- Closes: https://github.com/zed-industries/zed/discussions/32261
- Follow-up to: https://github.com/zed-extensions/sql/pull/19
- Follow-up to: https://github.com/zed-industries/zed/pull/32003

The underlying `sql-formatter` used by `prettier-plugin-sql` needs to
have the SQL dialect (mysql, postgresql) passed as the prettier language
name, while Zed passes `sql`, which default will corrupt sql files with
vendor specific extensions (postgresql jsonb operators, etc).

I improved the [Zed SQL Language
Docs](https://zed.dev/docs/languages/sql) in
https://github.com/zed-industries/zed/pull/32003 to show how to use
`sql-formatter` directly as an external formatter.

Release Notes:

- SQL: Disable `format_on_save` using `prettier-plugin-sql` by default.
Please see the [Zed SQL Language
Docs](https://zed.dev/docs/languages/sql) for settings to use
`sql-formatter` directly instead.
2025-06-06 17:21:46 -04:00
Kirill Bulatov
77ead25f8c Implement the rest of the worktree pulls (#32269)
Follow-up of https://github.com/zed-industries/zed/pull/19230

Implements the workspace diagnostics pulling, and replaces "pull
diagnostics every open editors' buffer" strategy with "pull changed
buffer's diagnostics" + "schedule workspace diagnostics pull" for the
rest of the diagnostics.

This means that if the server does not support the workspace diagnostics
and does not return more in linked files, only the currently edited
buffer has its diagnostics updated.

This is better than the existing implementation that causes a lot of
diagnostics pulls to be done instead, and we can add more heuristics on
top later for querying more diagnostics.

Release Notes:

- N/A
2025-06-06 21:19:46 +00:00
chbk
9e5f89dc26 Improve CSS syntax highlighting (#25326)
Release Notes:

  - Improved CSS syntax highlighting

| Zed 0.174.6 | With this PR |
| --- | --- |
| ![css_0 174
6](https://github.com/user-attachments/assets/d069f20e-5f1f-4d03-a010-81ba4b61b3a0)
|
![css_pr](https://github.com/user-attachments/assets/36463ef1-2ead-421d-9825-bd359e7677ab)
|

- `|`: `operator`
- `and`, `or`, `not`, `only`: `operator` -> `keyword.operator`, as
defined in other languages
- `id_name`, `class_name`: `property`/`attribute` -> `selector`, not a
property name. [CSS
reference](https://www.w3.org/TR/selectors-3/#class-html)
- `namespace_name`: `property` -> `namespace`, not a property name
- `property_name`: `constant` -> `property`, like `feature_name` already
defined
- `(keyword_query)`: `property`, similar to `feature_name`. [CSS
reference](https://www.w3.org/TR/mediaqueries-3/#media1)
- `keyword_query`: `constant.builtin`, [CSS
reference](https://www.w3.org/TR/mediaqueries-3/#media0)
- `plain_value`, `keyframes_name`: `constant.builtin`, [CSS
reference](https://www.w3.org/TR/css-values-3/#value-defs)
- `unit`: `type` -> `type.unit`,
[Atom](9e4afce058/grammars/tree-sitter-css.cson (L73))
and [VS
Code](336801752d/extensions/css/syntaxes/css.tmLanguage.json (L1393))
also have a `unit` scope for this. [CSS
reference](https://www.w3.org/TR/css3-values/#dimensions)


```css
@media (keyword_query) and keyword_query {}
@supports (feature_name: plain_value) {}
@namespace namespace_name url("string");
namespace_name|tag_name {}
@keyframes keyframes_name {
  to {
    top: 200unit;
    color: #c01045;
  }
}
tag_name::before,
#id_name:nth-child(even),
.class_name[attribute_name=plain_value] {
  property_name: 2em 1.2em;
  --variable: rgb(250, 0, 0);
  color: var(--variable);
  animation: keyframes_name 5s plain_value;
}
```
2025-06-06 17:14:32 -04:00
CharlesChen0823
cf5e76b1b9 git: Add PushTo to select which remote to push (#31482)
mostly, I using `git checkout -b branch_name upstream/main` to create
new branch which reference remote upstream not my fork.
When using `Push` will always failed with not permission. So we need
ability to select which remote to push.

Current branch is based on my previous pr #26897 

Release Notes:

- Add `PushTo` to select which remote to push.

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-06-06 21:07:40 +00:00
tidely
9775747ba9 gpui: Pre-allocate paths in open file dialog (#32106)
Pre-allocates the required memory for storing paths returned by open
file dialog on windows

Release Notes:

- N/A
2025-06-06 17:07:24 -04:00
not a cow
b7c2d4876c Fix syntax highlighting conflicts with certain glsl types (#32022)
added first line pattern for glsl because some extensions conflict with
others like fragment shaders (.fs) would be highlighted by f#

<details>

| no f# extension no fix | f# extension no fix | f# extension with fix |
|--------|--------|--------|
|
![zed_TW1mkwXSMS](https://github.com/user-attachments/assets/d3d64b44-ced5-41a8-86b1-36cafc92f7e4)
|
![zed_T5ewceKmHo](https://github.com/user-attachments/assets/08ced11d-c2c6-4b73-964d-768e8ba763da)
|
![zed_9Rhkc5flQZ](https://github.com/user-attachments/assets/9e949d00-4e69-4687-9863-e0ab38b8b3df)
|

</details>

Release Notes:

- glsl: Added a workaround for an issue where fragment shaders with the `.fs` file extension would be misidentified as F#. Now, adding `#version {version}` to the first line of your fragment shader will ensure it is always recognized as glsl
2025-06-06 20:58:18 +00:00
Finn Evers
2fe1293fba Improve cursor style behavior for some draggable elements (#31965)
Follow-up to #24797

This PR ensures some cursor styles do not change for draggable elements
during dragging. The linked PR covered this on the higher level for
draggable divs. However, e.g. the pane divider inbetween two editors is
not a draggable div and thus still has the issue that the cursor style
changes during dragging. This PR fixes this issue by setting the hitbox
to `None` in cases where the element is currently being dragged, which
ensures the cursor style is applied to the cursor no matter what during
dragging.

Namely, this change fixes this for
- non-div pane dividers
- minimap slider and the
- editor scrollbars

and implements it for the UI scrollbars (Notably, UI scrollbars do
already have `cursor_default` on their parent container but would not
keep this during dragging. I opted out on removing this from the parent
containers until #30194 or a similar PR is merged).


https://github.com/user-attachments/assets/f97859dd-5f1d-4449-ab92-c27f2d933c4a

Release Notes:

- N/A
2025-06-06 16:56:27 -04:00
Michael Sloan
e0057ccd0f Fix anchor biases for completion replacement ranges (esp slash commands) (#32262)
Closes #32205

The issue was that in some places the end of the replacement range used
anchors with `Bias::Left` instead of `Bias::Right`. Before #31872
completions were recomputed on every change and so the anchor bias
didn't matter. After that change, the end anchor didn't move as the
user's typing. Changing it to `Bias::Right` to "stick" to the character
to the right of the cursor fixes this.

Release Notes:

- Fixes incorrect auto-completion of `/files` in text threads (Preview
Only)
2025-06-06 20:54:00 +00:00
Michael Sloan
51585e770d Contextualize errors from extensions with extension name and version (#32202)
Before this I'd get log lines like

> ERROR [project] missing `database_url` setting

Now it's:

> ERROR [project] from extension "Postgres Context Server" version
0.0.3: missing `database_url` setting

Release Notes:

- N/A
2025-06-06 14:47:46 -06:00
Elijah McMorris
52fa7ababb lmstudio: Fill max_tokens using the response from /models (#25606)
The info for `max_tokens` for the model is included in
`{api_url}/models`
I don't think this needs to be `.clamp` like in
`crates/ollama/src/ollama.rs` `get_max_tokens`, but it might need to be

## Before:
Every model shows 2k

![image](https://github.com/user-attachments/assets/676075c8-0ceb-44b1-ae27-72ed6a6d783c)

## After:

![image](https://github.com/user-attachments/assets/8291535b-976e-4601-b617-1a508bf44e12)

### Json from `{api_url}/models` with model not loaded
```json
  {
      "id": "qwen2.5-coder-1.5b-instruct-mlx",
      "object": "model",
      "type": "llm",
      "publisher": "lmstudio-community",
      "arch": "qwen2",
      "compatibility_type": "mlx",
      "quantization": "4bit",
      "state": "not-loaded",
      "max_context_length": 32768
    },
```

## Notes
The response from `{api_url}/models` seems to return the `max_tokens`
for the model, not the currently configured context length, but I think
showing the `max_tokens` for the model is better than setting 2k for
everything

`loaded_context_length` exists, but only if the model is loaded at the
startup of zed, which usually isn't the case

maybe `fetch_models` should be rerun when swapping lmstudio models

### Currently configured context
this isn't shown in `{api_url}/models`

![image](https://github.com/user-attachments/assets/8511cb9d-914b-4065-9eba-c0b086ad253b)

### Json from `{api_url}/models` with model loaded
```json
  {
     "id": "qwen2.5-coder-1.5b-instruct-mlx",
      "object": "model",
      "type": "llm",
      "publisher": "lmstudio-community",
      "arch": "qwen2",
      "compatibility_type": "mlx",
      "quantization": "4bit",
      "state": "loaded",
      "max_context_length": 32768,
      "loaded_context_length": 4096
    },
```

Release Notes:

- lmstudio: Fixed showing `max_tokens` in the assistant panel

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-06-06 20:21:23 +00:00
Conrad Irwin
5ad51ca48e vim: Show 'j' from jk pre-emptively (#32007)
Fixes: #29812
Fixes: #22538

Co-Authored-By: <corentinhenry@gmail.com>

Release Notes:

- vim: Multi-key bindings in insert mode will now show the pending
keystroke in the buffer. For example if you have `jk` mapped to escape,
pressing `j` will immediately show a `j`.
2025-06-06 14:11:51 -06:00
Diógenes Castro
35a119d573 Add Go debugging example to debugger documentation (#31798)
This pull request updates the documentation for the debugger to include
Go-specific examples alongside existing Python examples.

Documentation update:

*
[`docs/src/debugger.md`](diffhunk://#diff-aa14715cca56f3ad6a32c669b0c317250dab212b8108136b7ca79217465f39b8R69-R80):
Added a new "Go examples" section with a JSON snippet demonstrating how
to configure the debugger for Go using Delve.

Release Notes:

- debugger: Add Go debugging example to debugger documentation

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-06-06 19:51:09 +00:00
Joseph T. Lyons
bbf3b20fc3 Fix panic when dragging pinned item left in pinned region (#32263)
This was a regression with my recent fixes to pinned tabs. Dragging a
pinned tab left in the pinned region would still update the pinned tab
count, which would later cause an out-of-bounds later when it used that
value to index into a vec.

https://zed-industries.slack.com/archives/C04S6T1T7TQ/p1749220447796559

Release Notes:

- Fixed a panic caused by dragging a pinned item to the left in the
pinned region
2025-06-06 15:35:18 -04:00
Peter Tripp
c1b997002a ci: Auto-release release prefix hotfixes again (#32265)
Undo:
- https://github.com/zed-industries/zed/pull/24894

CC: @JosephTLyons

Release Notes:

- N/A
2025-06-06 15:29:33 -04:00
Mikayla Maki
69e99b9f2f Remove unescessary unimplemented (#32264)
Release Notes:

- N/A
2025-06-06 11:25:21 -07:00
Peter Tripp
0fc85a020a Fix script/build-linux with newer GCC (#32029)
If you follow the [workaround advice in the
Docs](https://zed.dev/docs/development/linux#installing-a-development-build)
for building with a newer GCC, it will error:


79b1dd7db8/script/bundle-linux (L88-L91)

[Reported on
Discord](https://discord.com/channels/869392257814519848/1379093394105696288)

Release Notes:

- Fixed `script/build-linux` for non-musl builds.
2025-06-06 13:58:13 -04:00
Pavle Sokic
974f724151 vim: Enable window shortcuts in Agent panel (#31000)
Release Notes:

- Enabled vim window commands (ctrl-w X) when agent panel is focused

Co-authored-by: Cole Miller <cole@zed.dev>
2025-06-06 17:20:04 +00:00
Matin Aniss
ca3f46588a gpui: Implement dynamic window control elements (#30828)
Allows setting element as window control elements which consist of
`Drag`, `Close`, `Max`, or `Min`. This allows you to implement
dynamically sized elements that control the platform window, this is
used for areas such as the title bar. Currently only implemented for
Windows.

Release Notes:

- N/A
2025-06-06 10:11:24 -07:00
Jason Lee
d9efa2860f gpui: Fix scroll area to support two-layer scrolling in different directions (#31062)
Release Notes:

- N/A

---

This change is used to solve the problem of not being able to respond
correctly in two-layer scrolling (in different directions). This is a
common practical requirement.

As in the example, in actual use, there may be a scene with a horizontal
scroll in a vertical scroll. Before the modification, if we scroll up
and down in the area that can scroll horizontally, it will not respond
(because it is blocked by the horizontal scroll layer).

## Before


https://github.com/user-attachments/assets/e8ea0118-52a5-44d8-b419-639d4b6c0793

## After


https://github.com/user-attachments/assets/aa14ddd7-5596-4dc5-9c6e-278aabdfef8e

----

This change may cause many side effects, causing some scrolling details
to be different from before, and more testing and analysis are needed.

I have tested some existing scenarios of Zed (such as opening the Branch
panel on the Editor and scrolling) and it seems to be correct (but it is
possible that I don’t know some interaction details). Here, the person
who added this line of code before needs to evaluate the original
purpose.
2025-06-06 10:06:09 -07:00
Floyd Wang
ac806d982b gpui: Introduce dash array support for PathBuilder (#31678)
A simple way to draw dashed lines.


https://github.com/user-attachments/assets/2105d7b2-42d0-4d73-bb29-83a4a6bd7029

Release Notes:

- N/A
2025-06-06 09:54:21 -07:00
Piotr Osiewicz
73cd6ef92c Add UI for configuring the API Url directly (#32248)
Closes #22901 

Release Notes:

- Copilot Chat endpoint URLs can now be configured via `settings.json`
or Configuration View.
2025-06-06 18:05:40 +02:00
Antonio Scandurra
019a14bcde Replace async-watch with a custom watch (#32245)
The `async-watch` crate doesn't seem to be maintained and we noticed
several panics coming from it, such as:

```
[bug] failed to observe change after notificaton.
zed::reliability::init_panic_hook::{{closure}}::hea8cdcb6299fad6b+154543526
std::panicking::rust_panic_with_hook::h33b18b24045abff4+127578547
std::panicking::begin_panic_handler::{{closure}}::hf8313cc2fd0126bc+127577770
std::sys::backtrace::__rust_end_short_backtrace::h57fe07c8aea5c98a+127571385
__rustc[95feac21a9532783]::rust_begin_unwind+127576909
core::panicking::panic_fmt::hd54fb667be51beea+9433328
core::option::expect_failed::h8456634a3dada3e4+9433291
assistant_tools::edit_agent::EditAgent::apply_edit_chunks::{{closure}}::habe2e1a32b267fd4+26921553
gpui::app::async_context::AsyncApp::spawn::{{closure}}::h12f5f25757f572ea+25923441
async_task::raw::RawTask<F,T,S,M>::run::h3cca0d402690ccba+25186815
<gpui::platform::linux::x11::client::X11Client as gpui::platform::linux::platform::LinuxClient>::run::h26264aefbcfbc14b+73961666
gpui::platform::linux::platform::<impl gpui::platform::Platform for P>::run::hb12dcd4abad715b5+73562509
gpui::app::Application::run::h0f936a5f855a3f9f+150676820
zed::main::ha17f9a25fe257d35+154788471
std::sys::backtrace::__rust_begin_short_backtrace::h1edd02429370b2bd+154624579
std::rt::lang_start::{{closure}}::h3d2e300f10059b0a+154264777
std::rt::lang_start_internal::h418648f91f5be3a1+127502049
main+154806636
__libc_start_main+46051972301573
_start+12358494
```

I didn't find an executor-agnostic watch crate that was well maintained
(we already tried postage and async-watch), so decided to implement it
our own version.

Release Notes:

- Fixed a panic that could sometimes occur when the agent performed
edits.
2025-06-06 16:00:09 +00:00
Bennet Bo Fenner
95d78ff8d5 context server: Make requests type safe (#32254)
This changes the context server crate so that the input/output for a
request are encoded at the type level, similar to how it is done for LSP
requests.

This also makes it easier to write tests that mock context servers, e.g.
you can write something like this now when using the `test-support`
feature of the `context-server` crate:

```rust
create_fake_transport("mcp-1", cx.background_executor())
    .on_request::<context_server::types::request::PromptsList>(|_params| {
        PromptsListResponse {
            prompts: vec![/* some prompts */],
            ..
        }
    })
```

Release Notes:

- N/A
2025-06-06 17:47:21 +02:00
Peter Tripp
454adfacae freebsd: Improve nightly builds of zed-remote-server (#32255)
Follow-up to: https://github.com/zed-industries/zed/pull/29561

Don't create a release
Don't upload artifact to workflow.
Add freebsd support to upload-nightly

Release Notes:

- N/A
2025-06-06 15:30:03 +00:00
CharlesChen0823
edd40566b7 git: Pick which remote to fetch (#26897)
I don't want to fetch `--all` branch, we should can picker which remote
to fetch.

Release Notes:

- Added the `git::FetchFrom` action to fetch from a single remote.

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-06-06 11:28:07 -04:00
Ben Kunkle
a40ee74a1f Improve handling of injection.combined injections in SyntaxSnapshot::layers_for_range (#32145)
Closes #27596

The problem in this case was incorrect identification of which language
(layer) contains the selection.

Language layer selection incorrectly assumed that the deepest
`SyntaxLayer` containing a range was the most specific. This worked for
Markdown (base document + injected subtrees) but failed for PHP, where
`injection.combined` injections are used to make HTML logically function
as the base layer, despite being at a greater depth in the layer stack.
This caused HTML to be incorrectly identified as the most specific
language for PHP ranges.

The solution is to track included sub-ranges for syntax layers and
filter out layers that don't contain a sub-range covering the desired
range. The top-level layer is never filtered to ensure gaps between
sibling nodes always have a fallback language, as the top-level layer is
likely more correct than the default language settings.

Release Notes:

- Fixed an issue in PHP where PHP language settings would be
occasionally overridden by HTML language settings
2025-06-06 10:47:28 -04:00
Richard Feldman
2e883be4b5 Add regression test for #11671 (#32250)
I can reproduce #11671 on current Nightly but not on `main`; it looks
like https://github.com/zed-industries/zed/pull/32204 fixed it. So I'm
adding a regression test and closing that issue.

Closes #11671

Release Notes:

- N/A
2025-06-06 14:29:59 +00:00
Jonathan LEI
6ea4d2b30d agent: Fix MCP server handler subscription race condition (#32133)
Closes #32132

Release Notes:

- Fixed MCP server handler subscription race condition causing tools to
not load.

---------

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-06-06 15:32:06 +02:00
Kirill Bulatov
380d8c5662 Pull diagnostics fixes (#32242)
Follow-up of https://github.com/zed-industries/zed/pull/19230

* starts to send `result_id` in pull requests to allow servers to reply
with non-full results
* fixes a bug where disk-based diagnostics were offset after pulling the
diagnostics
* fixes a bug due to which pull diagnostics could not be disabled
* uses better names and comments for the workspace pull diagnostics part

Release Notes:

- N/A
2025-06-06 16:18:05 +03:00
Ben Kunkle
508b604b67 project: Try to make git tests less flaky (#32234)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-06-06 13:01:42 +00:00
chbk
3da1de2a48 Add JSDoc scope (#29476)
This is a small PR that adds a `.jsdoc` scope to JSDoc tokens, just like
[JSX](3fdbc3090d/crates/languages/src/javascript/highlights.scm (L239))
has a specific scope.
This effectively allows differentiating between JavaScript keywords and
JSDoc tags in comments.

Release Notes:

  - Add scope for JSDoc
2025-06-06 08:31:59 -04:00
Dave Waggoner
8837e5564d Add new terminal hyperlink tests (#28525)
Part of #28238

This PR refactors `FindHyperlink` handling and associated code in
`terminal.rs` into its own file for improved testability, and adds
tests.

Release Notes:

- N/A
2025-06-06 08:08:20 -04:00
Ben Brandt
709523bf36 Store profile per thread (#31907)
This allows storing the profile per thread, as well as moving the logic
of which tools are enabled or not to the profile itself.

This makes it much easier to switch between profiles, means there is
less global state being changed on every profile change.

Release Notes:

- agent panel: allow saving the profile per thread

---------

Co-authored-by: Ben Kunkle <ben.kunkle@gmail.com>
2025-06-06 12:05:27 +00:00
Piotr Osiewicz
7afee64119 multi_buffer: Refactor diff_transforms field into a separate struct (#32237)
A minor refactor ~needed to unblock #22546; it's pretty hard to add an
extra field to `diff_transforms` dimension, as it is a 2-tuple (which
uses a blanket impl)

Release Notes:

- N/A
2025-06-06 11:06:42 +00:00
shenjack
cd0ef4b982 docs: Add more troubleshooting steps for Windows (#31500)
Release Notes:

- N/A

---------

Co-authored-by: 张小白 <364772080@qq.com>
2025-06-06 18:57:40 +08:00
Roland Crosby
be6f29cc28 terminal: Use conventional XTerm indexed color values (#32200)
Fixes rendering of colors in the terminal to use XTerm's idiosyncratic standard steps instead of the range that was previously in use. Matches the behavior of Alacritty, Ghostty, iTerm2, and every other terminal emulator I've looked at.

Release Notes:

- Fixed rendering of terminal colors for the XTerm 256-color indexed color palette.
2025-06-06 13:02:07 +03:00
Thomas Zahner
38b8e6549f ci: Check for broken links (#30844)
This PR fixes some broken links that where found using
[lychee](https://github.com/lycheeverse/lychee/) as discussed today with
@JosephTLyons and @nathansobo at the RustNL hackathon. Using
[lychee-action](https://github.com/lycheeverse/lychee-action/) we can
scan for broken links daily to prevent issues in the future.
There are still 6 broken links that I didn't know how to fix myself.
See https://github.com/thomas-zahner/zed/actions/runs/15075808232 for
details.

## Missing images

```
Errors in ./docs/src/channels.md

    [ERROR] file:///home/runner/work/zed/zed/docs/.gitbook/assets/channels-3.png | Cannot find file
    [ERROR] file:///home/runner/work/zed/zed/docs/.gitbook/assets/channels-1.png | Cannot find file
    [ERROR] file:///home/runner/work/zed/zed/docs/.gitbook/assets/channels-2.png | Cannot find file
```

These errors are showing up as missing images on
https://zed.dev/docs/channels
I tried to search the git history to see when or why they were deleted
but didn't find anything.

## ./crates/assistant_tools/src/edit_agent/evals/fixtures/zode/prompt.md

There are three errors in that file. I don't fully understand how these
issues were caused historically. Technically it would be possible to
ignore the files but of course if possible we should address the issues.
 
Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-06-06 09:39:35 +00:00
Umesh Yadav
b8c1b54f9e language_models: Fix Mistral tool->user message sequence handling (#31736)
Closes #31491

### Problem
Mistral API enforces strict conversation flow requirements that other
providers don't. Specifically, after a `tool` message, the next message
**must** be from the `assistant` role, not `user`. This causes the
error:
```
"Unexpected role 'user' after role 'tool'"
```
This can also occur in normal conversation flow where mistral doesn't
return the assistant message but that is something which can't be
reproduce reliably.

### Root Cause
When users interrupt an ongoing tool call sequence by sending a new
message, we insert a `user` message directly after a `tool` message,
violating Mistral's protocol.

**Expected Mistral flow:**
```
user → assistant (with tool_calls) → tool (results) → assistant (processes results) → user (next input)
```

**What we were doing:**
```
user → assistant (with tool_calls) → tool (results) → user (interruption) 
```

### Solution
Insert an empty `assistant` message between any `tool` → `user` sequence
in the Mistral provider's request construction. This satisfies Mistral's
API requirements without affecting other providers or requiring UX
changes.

### Testing
To reproduce the original error:
1. Start agent chat with `codestral-latest`
2. Send: "Describe this project using tool call only"
3. Once tool calls begin, send: "stop this"
4. Main branch: API error
5. This fix: Works correctly

Release Notes:

- Fixed Mistral tool calling in some cases
2025-06-06 12:35:22 +03:00
Jakub Sygnowski
c304e964fe Display the first keystroke instead of an error for multi-keystroke binding (#31456)
Ideally we would show multi-keystroke binding, but I'd say this improves
over the status quo.

A partial solution to #27334

Release Notes:

- Fixed spurious warning for lack of edit prediction on multi-keystroke
binding

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-06-06 09:30:57 +00:00
Joseph T. Lyons
53abad5979 Fixed more bugs around moving pinned tabs (#32228)
Closes https://github.com/zed-industries/zed/issues/32199
https://github.com/zed-industries/zed/issues/32229
https://github.com/zed-industries/zed/issues/32230
https://github.com/zed-industries/zed/issues/32232

Release Notes:

- Fixed a bug where if the last tab was a pinned tab and it was dragged
to the right, resulting in a no-op, it would become unpinned
- Fixed a bug where a pinned tab dragged just to the right of the end of
the pinned tab region would become unpinned
- Fixed a bug where dragging a pinned tab from one pane to another
pane's pinned region could result in an existing pinned tab becoming
unpinned when `max_tabs` was reached
- Fixed a bug where moving an unpinned tab to the left, just to the end
of the pinned region, would cause the pinned tabs to become unpinned.
2025-06-06 05:09:48 -04:00
张小白
54e64b2407 windows: Refactor the current ime implementation (#32224)
Release Notes:

- N/A
2025-06-06 07:31:45 +00:00
Michael Sloan
ce8854007f Add crates/assistant_tools/src/evals/fixtures to file_scan_exclusions (#32211)
Particularly got tired of `disable_cursor_blinking/before.rs` (an old
copy of `editor.rs`) showing up in tons of searches

Release Notes:

- N/A
2025-06-06 06:56:41 +00:00
Michael Sloan
3e8565ac25 Initialize zlog default filters on init rather than waiting for settings load (#32209)
Now immediately initializes the zlog filter even when there isn't an env
config. Before this change the default filters were applied after
settings load - I was seeing some `zbus` logs on init.

Also defaults to allowing warnings and errors from the suppressed log
sources. If these turn out to be chatty (they don't seem to be so far),
can bring back more suppression.

Release Notes:

- N/A
2025-06-06 00:49:30 -06:00
Michael Sloan
d801b7b12e Fix bindings_for_action handling of shadowed key bindings (#32220)
Fixes two things:

* ~3 months ago [in PR
#26420](https://github.com/zed-industries/zed/pull/26420/files#diff-33b58aa2da03d791c2c4761af6012851b7400e348922d64babe5fd48ac2a8e60)
`bindings_for_action` was changed to return bindings even when they are
shadowed (when the keystrokes would actually do something else).

* For edit prediction keybindings there was some odd behavior where
bindings for `edit_prediction_conflict` were taking precedence over
bindings for `edit_prediction` even when the `edit_prediction_conflict`
predicate didn't match. The workaround for this was #24812. The way it
worked was:

    - List all bindings for the action

- For each binding, get the highest precedence binding with the same
input sequence

- If the highest precedence binding has the same action, include this
binding. This was the bug - this meant that if a binding in the keymap
has the same keystrokes and action it can incorrectly take display
precedence even if its context predicate does not pass.

- Fix is to check that the highest precedence binding is a full match.
To do this efficiently, it's based on an index within the keymap
bindings.

Also adds `highest_precedence_binding_*` variants which avoid the
inefficiency of building lists of bindings just to use the last.

Release Notes:

- Fixed display of keybindings to skip bindings that are shadowed by a
binding that uses the same keystrokes.
- Fixed display of `editor::AcceptEditPrediction` bindings to use the
normal precedence that prioritizes user bindings.
2025-06-06 06:24:59 +00:00
张小白
37fa42d5cc windows: Fix a typo in function name (#32223)
Release Notes:

- N/A
2025-06-06 06:24:05 +00:00
Michael Sloan
5c9b8e8321 Move workspace::toast_layer::RunAction to zed_actions::toast::RunAction (#32222)
Cleaner to have references to this be `toast::RunAction` matching how it
appears in the keymap, instead of `workspace::RunAction`.

Release Notes:

- N/A
2025-06-06 06:23:09 +00:00
Lucas
96609151c6 Fix typo in assistant_tool.rs (#32207)
Release Notes:

- N/A
2025-06-06 05:23:25 +00:00
Joseph T. Lyons
e37c78bde7 Refactor some logic in handle_tab_drop (#32213)
Tiny little clean up PR after #32184 

Release Notes:

- N/A
2025-06-06 04:49:36 +00:00
Michael Sloan
920ca688a7 Display subtle-mode prediction preview when partial accept modifiers held (#32212)
Closes #27567

Release notes covered by the notes for #32193

Release Notes:

- N/A
2025-06-06 02:52:16 +00:00
Julia Ryan
f62d76159b Fix matching braces in jsx/tsx tags (#32196)
Closes #27998

Also fixed an issue where jumping back from closing to opening tags
didn't work in javascript due to missing brackets in our tree-sitter
query.

Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-06-05 18:10:22 -07:00
Smit Barmase
6a8fdbfd62 editor: Add multi cursor support for AddSelectionAbove/AddSelectionBelow (#32204)
Closes #31648

This PR adds support for:
- Expanding multiple cursors above/below
- Expanding multiple selections above/below
- Adding new cursors/selections when expansion has already been done.
Existing expansions preserve their state and expand/shrink according to
the action, while new cursors/selections act like freshly created ones.

Tests for both cursor and selections:
- below/above cases
- undo/redo cases
- adding new cursors/selections with existing expansion

Before/After:


https://github.com/user-attachments/assets/d2fd556b-8972-4719-bd86-e633d42a1aa3


Release Notes:

- Improved `AddSelectionAbove` and `AddSelectionBelow` to extend
multiple cursors/selections.
2025-06-06 06:20:12 +05:30
Michael Sloan
711a9e5753 x11: Remove logs for mac-os specific set_edited and show_character_palette (#32203)
Release Notes:

- N/A
2025-06-06 00:14:20 +00:00
Michael Sloan
6de5d29bff Fix caching of Node.js runtime paths and improve error messages (#32198)
* `state.last_options` was never being updated, so the caching wasn't
working.

* If node on the PATH was too old it was logging errors on every
invocation even though managed node is being used.

Release Notes:

- Fixed caching of Node.js runtime paths and improved error messages.
2025-06-05 23:35:43 +00:00
Joseph T. Lyons
d7015e5b8f Fix bugs around tab state loss when moving pinned tabs across panes (#32184)
Closes https://github.com/zed-industries/zed/issues/32181,
https://github.com/zed-industries/zed/issues/32179

In the screenshots: left = nightly, right = dev

Fix 1: A pinned tab dragged into a new split should remain pinned


https://github.com/user-attachments/assets/608a7e10-4ccb-4219-ba81-624298c960b0

Fix 2: Moving a pinned tab from one pane to another should not cause
other pinned tabs to be unpinned


https://github.com/user-attachments/assets/ccc05913-591d-4a43-85bb-3a7164a4d6a8

I also added tests for moving both pinned tabs and unpinned tabs into
existing panes, both into the "pinned" region and the "unpinned" region.

Release Notes:

- Fixed a bug where dragging a pinned tab into a new split would lose
its pinned tab state.
- Fixed a bug where pinned tabs in one pane could be lost when moving
one of the pinned tabs to another pane.
2025-06-05 22:31:30 +00:00
Ben Brandt
ddf70b3bb8 Add mismatched tag threshold parameter to eval function (#32190)
Replace hardcoded 0.10 threshold with configurable parameter and set
0.05 default for most tests, with 0.2 for from_pixels_constructor
eval that produces more mismatched tags.

Release Notes:

- N/A
2025-06-05 21:30:05 +00:00
Michael Sloan
8bd8435887 Fix default keybindings for AcceptPartialEditPrediction to work in subtle mode (#32193)
Closes #27567

Release Notes:

- Fixed default keybindings for `editor::AcceptPartialEditPrediction` to
work with subtle mode.

Co-authored-by: Richard <richard@zed.dev>
2025-06-05 21:21:06 +00:00
Conrad Irwin
4b297a9967 Fix innermost brackets panic (#32120)
Release Notes:

- Fixed a few rare panics that could happen when a multibuffer excerpt
started with expanded deleted content.
2025-06-05 20:24:56 +00:00
Vitaly Slobodin
7aa70a4858 lsp: Implement support for the textDocument/diagnostic command (#19230)
Closes [#13107](https://github.com/zed-industries/zed/issues/13107)

Enabled pull diagnostics by default, for the language servers that
declare support in the corresponding capabilities.

```
"diagnostics": {
    "lsp_pull_diagnostics_debounce_ms": null
}
```
settings can be used to disable the pulling.

Release Notes:

- Added support for the LSP `textDocument/diagnostic` command.

# Brief

This is draft PR that implements the LSP `textDocument/diagnostic`
command. The goal is to receive your feedback and establish further
steps towards fully implementing this command. I tried to re-use
existing method and structures to ensure:

1. The existing functionality works as before
2. There is no interference between the diagnostics sent by a server and
the diagnostics requested by a client.

The current implementation is done via a new LSP command
`GetDocumentDiagnostics` that is sent when a buffer is saved and when a
buffer is edited. There is a new method called `pull_diagnostic` that is
called for such events. It has debounce to ensure we don't spam a server
with commands every time the buffer is edited. Probably, we don't need
the debounce when the buffer is saved.

All in all, the goal is basically to get your feedback and ensure I am
on the right track. Thanks!


## References

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

## In action

You can clone any Ruby repo since the `ruby-lsp` supports the pull
diagnostics only.

Steps to reproduce:

1. Clone this repo https://github.com/vitallium/stimulus-lsp-error-zed
2. Install Ruby (via `asdf` or `mise).
4. Install Ruby gems via `bundle install`
5. Install Ruby LSP with `gem install ruby-lsp`
6. Check out this PR and build Zed
7. Open any file and start editing to see diagnostics in realtime.



https://github.com/user-attachments/assets/0ef6ec41-e4fa-4539-8f2c-6be0d8be4129

---------

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-06-05 19:42:52 +00:00
Oleksiy Syvokon
04cd3fcd23 google: Add latest versions of Gemini 2.5 Pro and Flash Preview (#32183)
Release Notes:

- Added the latest versions of Gemini 2.5 Pro and Flash Preview
2025-06-05 19:30:34 +00:00
Michael Sloan
d15d85830a snippets: Fix tabstop completion choices (#31955)
I'm not sure when snippet tabstop choices broke, I checked the parent of
#31872 and they also don't work before that.

Release Notes:

- N/A
2025-06-05 19:17:41 +00:00
VladKopylets
ccc173ebb1 Fix "j" key latency in vim mode with "j k" keymap (#31163)
Problem:
Initial keymap has "j k" keymap, which if uncommented will add +-1s
delay to every "j" key press
This workaround was taken from
https://github.com/zed-industries/zed/discussions/6661

Release Notes:

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

---------

Co-authored-by: Michael Sloan <michael@zed.dev>
2025-06-05 13:16:59 -06:00
Michael Sloan
03a030fd00 Add default method for CompletionProvider::resolve_completions (#32045)
Release Notes:

- N/A
2025-06-05 19:15:06 +00:00
Richard Feldman
894f3b9d15 Make a test no longer pub (#32177)
I spotted this while working on something else. Very quick fix!

Release Notes:

- N/A
2025-06-05 17:27:45 +00:00
Cole Miller
f36143a461 debugger: Run locators on LSP tasks for the new process modal (#32097)
- [x] pass LSP tasks into list_debug_scenarios
- [x] load LSP tasks only once for both modals
- [x] align ordering
- [x] improve appearance of LSP debug task icons
- [ ] reconsider how `add_current_language_tasks` works
- [ ] add a test

Release Notes:

- Debugger Beta: Added debuggable LSP tasks to the "Debug" tab of the
new process modal.

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-06-05 13:25:51 -04:00
Oleksiy Syvokon
8730d317a8 themes: Swap ANSI white with ANSI black (#32175)
Here’s how it looks after the fix:
![colors](https://github.com/user-attachments/assets/11c78ad6-da50-4aad-b133-9be5e3844878).
White is white and black is black, as intended.

In some cases, dimmed colors were poorly defined, so I took
`text.dimmed` values.

Note that white is defined exactly as the background color for light
themes. Similarly, black is the exact background color for dark themes.
I didn’t change this, but many themes intentionally make white and black
slightly different from the background color. This prevents issues where
programs assume, say, a dark background and set the foreground to white,
making text invisible. I'm not sure if we want to adjust these themes to
address this; just noting it here.


Closes #29379 

Release Notes:

- Fixed ANSI black and ANSI white colors in built-in themes
2025-06-05 17:14:39 +00:00
Cole Miller
783b33b5c9 git: Rewrap commit messages just before committing instead of interactively (#32114)
Closes #27508 

Release Notes:

- Fixed unintuitive wrapping behavior when editing Git commit messages.
2025-06-05 17:12:17 +00:00
Bennet Bo Fenner
28da99cc06 anthropic: Fix error when attaching multiple images (#32092)
Closes #31438

Release Notes:

- agent: Fixed an edge case were the request would fail when using
Claude and multiple images were attached

---------

Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2025-06-05 16:29:49 +00:00
Cole Miller
d2c265c71f debugger: Change some text in the launch tab (#32170)
I think using `Debugger Program` and `~/bin/debugger` here makes it seem
like that field is for specifying the path to the debugger itself, and
not the program being debugged.

Release Notes:

- N/A
2025-06-05 12:29:18 -04:00
Peter Tripp
bbd431ae8c Build zed-remote-server on FreeBSD (#29561)
Builds freebsd-remote-server under qemu working on linux runners.

Release Notes:

- Initial support for ssh remotes running FreeBSD x86_64
2025-06-05 11:59:10 -04:00
张小白
5b9d3ea097 windows: Only call TranslateMessage when we can't handle the event (#32166)
This PR improves key handling on Windows by moving the
`TranslateMessage` call from the message loop to after we handled
`WM_KEYDOWN`. This brings Windows behavior more in line with macOS and
gives us finer control over key events. As a result, Vim keybindings now
work properly even when an IME is active. The trade-off is that it might
introduce a slight delay in text input.


Release Notes:

- N/A
2025-06-05 23:57:47 +08:00
tidely
738cfdff84 gpui: Simplify u8 to u32 conversion (#32099)
Removes an allocation when converting four u8 into a u32.
Makes some functions const compatible.

Release Notes:

- N/A
2025-06-05 23:57:27 +08:00
Cole Miller
32d5a2cca0 debugger: Fix wrong variant of new process modal deployed (#32168)
I think this was added back when the `Launch` variant meant what we now
call `Debug`

Release Notes:

- N/A
2025-06-05 15:51:13 +00:00
456 changed files with 22834 additions and 8302 deletions

View File

@@ -1,4 +1,4 @@
name: Bug Report (AI Related)
name: Bug Report (AI)
description: Zed Agent Panel Bugs
type: "Bug"
labels: ["ai"]
@@ -19,15 +19,14 @@ body:
2.
3.
Actual Behavior:
Expected Behavior:
**Expected Behavior**:
**Actual Behavior**:
### Model Provider Details
- Provider: (Anthropic via ZedPro, Anthropic via API key, Copilot Chat, Mistral, OpenAI, etc)
- Model Name:
- Mode: (Agent Panel, Inline Assistant, Terminal Assistant or Text Threads)
- MCP Servers in-use:
- Other Details:
- Other Details (MCPs, other settings, etc):
validations:
required: true

View File

@@ -1,36 +0,0 @@
name: Bug Report (Edit Predictions)
description: Zed Edit Predictions bugs
type: "Bug"
labels: ["ai", "inline completion", "zeta"]
title: "Edit Predictions: <a short description of the Edit Prediction bug>"
body:
- type: textarea
attributes:
label: Summary
description: Describe the bug with a one line summary, and provide detailed reproduction steps
value: |
<!-- Please insert a one line summary of the issue below -->
SUMMARY_SENTENCE_HERE
### Description
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
<!-- Please include the LLM provider and model name you are using -->
Steps to trigger the problem:
1.
2.
3.
Actual Behavior:
Expected Behavior:
validations:
required: true
- type: textarea
id: environment
attributes:
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: copy system specs into clipboard"'
placeholder: |
Output of "zed: copy system specs into clipboard"
validations:
required: true

View File

@@ -1,35 +0,0 @@
name: Bug Report (Git)
description: Zed Git-Related Bugs
type: "Bug"
labels: ["git"]
title: "Git: <a short description of the Git bug>"
body:
- type: textarea
attributes:
label: Summary
description: Describe the bug with a one line summary, and provide detailed reproduction steps
value: |
<!-- Please insert a one line summary of the issue below -->
SUMMARY_SENTENCE_HERE
### Description
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
Steps to trigger the problem:
1.
2.
3.
Actual Behavior:
Expected Behavior:
validations:
required: true
- type: textarea
id: environment
attributes:
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: copy system specs into clipboard"'
placeholder: |
Output of "zed: copy system specs into clipboard"
validations:
required: true

View File

@@ -19,8 +19,8 @@ body:
2.
3.
Actual Behavior:
Expected Behavior:
**Expected Behavior**:
**Actual Behavior**:
validations:
required: true

View File

@@ -18,14 +18,16 @@ body:
- Issues with insufficient detail may be summarily closed.
-->
DESCRIPTION_HERE
Steps to reproduce:
1.
2.
3.
4.
Expected Behavior:
Actual Behavior:
**Expected Behavior**:
**Actual Behavior**:
<!-- Before Submitting, did you:
1. Include settings.json, keymap.json, .editorconfig if relevant?

View File

@@ -19,6 +19,12 @@ runs:
shell: bash -euxo pipefail {0}
run: ./script/linux
- name: Check for broken links
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
with:
args: --no-progress --exclude '^http' './docs/src/**/*'
fail: true
- name: Build book
shell: bash -euxo pipefail {0}
run: |

View File

@@ -183,6 +183,9 @@ jobs:
- 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
@@ -736,10 +739,69 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
freebsd:
timeout-minutes: 60
runs-on: github-8vcpu-ubuntu-2404
if: |
startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
needs: [linux_tests]
name: Build Zed on FreeBSD
# env:
# MYTOKEN : ${{ secrets.MYTOKEN }}
# MYTOKEN2: "value2"
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 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
if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix')
secrets: inherit
with:
flake-output: debug
# excludes the final package to only cache dependencies
@@ -750,12 +812,12 @@ jobs:
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]
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, freebsd]
runs-on:
- self-hosted
- bundle
steps:
- name: gh release
run: gh release edit $GITHUB_REF_NAME --draft=true
run: gh release edit $GITHUB_REF_NAME --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -30,6 +30,7 @@ jobs:
noop:
name: No-op
runs-on: ubuntu-latest
if: github.repository_owner == 'zed-industries'
steps:
- name: No-op
run: echo "Nothing to do"

View File

@@ -167,9 +167,54 @@ jobs:
- name: Upload Zed Nightly
run: script/upload-nightly linux-targz
freebsd:
timeout-minutes: 60
if: github.repository_owner == 'zed-industries'
runs-on: github-8vcpu-ubuntu-2404
needs: tests
env:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
name: Build Zed on FreeBSD
# env:
# MYTOKEN : ${{ secrets.MYTOKEN }}
# MYTOKEN2: "value2"
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
update-nightly-tag:

View File

@@ -19,6 +19,7 @@ env:
jobs:
unit_evals:
if: github.repository_owner == 'zed-industries'
timeout-minutes: 60
name: Run unit evals
runs-on:
@@ -62,11 +63,11 @@ jobs:
- name: Run unit evals
shell: bash -euxo pipefail {0}
run: cargo nextest run --workspace --no-fail-fast --features eval --no-capture -E 'test(::eval_)' --test-threads 1
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 the pull request link into the Slack channel
- name: Send failure message to Slack channel if needed
if: ${{ failure() }}
uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52
with:

View File

@@ -47,6 +47,7 @@
"remove_trailing_whitespace_on_save": true,
"ensure_final_newline_on_save": true,
"file_scan_exclusions": [
"crates/assistant_tools/src/evals/fixtures",
"crates/eval/worktrees/",
"crates/eval/repos/",
"**/.git",

91
Cargo.lock generated
View File

@@ -59,7 +59,7 @@ dependencies = [
"assistant_slash_command",
"assistant_slash_commands",
"assistant_tool",
"async-watch",
"assistant_tools",
"audio",
"buffer_diff",
"chrono",
@@ -99,6 +99,7 @@ dependencies = [
"paths",
"picker",
"postage",
"pretty_assertions",
"project",
"prompt_store",
"proto",
@@ -130,6 +131,7 @@ dependencies = [
"urlencoding",
"util",
"uuid",
"watch",
"workspace",
"workspace-hack",
"zed_actions",
@@ -147,7 +149,6 @@ dependencies = [
"deepseek",
"fs",
"gpui",
"indexmap",
"language_model",
"lmstudio",
"log",
@@ -631,7 +632,6 @@ name = "assistant_tool"
version = "0.1.0"
dependencies = [
"anyhow",
"async-watch",
"buffer_diff",
"clock",
"collections",
@@ -653,6 +653,7 @@ dependencies = [
"settings",
"text",
"util",
"watch",
"workspace",
"workspace-hack",
"zlog",
@@ -665,7 +666,6 @@ dependencies = [
"agent_settings",
"anyhow",
"assistant_tool",
"async-watch",
"buffer_diff",
"chrono",
"client",
@@ -705,6 +705,7 @@ dependencies = [
"serde_json",
"settings",
"smallvec",
"smol",
"streaming_diff",
"strsim",
"task",
@@ -716,6 +717,7 @@ dependencies = [
"ui",
"unindent",
"util",
"watch",
"web_search",
"which 6.0.3",
"workspace",
@@ -1074,15 +1076,6 @@ dependencies = [
"tungstenite 0.26.2",
]
[[package]]
name = "async-watch"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a078faf4e27c0c6cc0efb20e5da59dcccc04968ebf2801d8e0b2195124cdcdb2"
dependencies = [
"event-listener 2.5.3",
]
[[package]]
name = "async_zip"
version = "0.0.17"
@@ -2987,7 +2980,6 @@ dependencies = [
"anyhow",
"assistant_context_editor",
"assistant_slash_command",
"assistant_tool",
"async-stripe",
"async-trait",
"async-tungstenite",
@@ -3169,6 +3161,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "command-fds"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ec1052629a80c28594777d1252efc8a6b005d13f9edfd8c3fc0f44d5b32489a"
dependencies = [
"nix 0.30.1",
"thiserror 2.0.12",
]
[[package]]
name = "command_palette"
version = "0.1.0"
@@ -3343,6 +3345,7 @@ dependencies = [
"collections",
"command_palette_hooks",
"ctor",
"dirs 4.0.0",
"editor",
"fs",
"futures 0.3.31",
@@ -4025,6 +4028,7 @@ dependencies = [
"gpui",
"http_client",
"language",
"libc",
"log",
"node_runtime",
"parking_lot",
@@ -4048,7 +4052,7 @@ dependencies = [
[[package]]
name = "dap-types"
version = "0.0.1"
source = "git+https://github.com/zed-industries/dap-types?rev=68516de327fa1be15214133a0a2e52a12982ce75#68516de327fa1be15214133a0a2e52a12982ce75"
source = "git+https://github.com/zed-industries/dap-types?rev=b40956a7f4d1939da67429d941389ee306a3a308#b40956a7f4d1939da67429d941389ee306a3a308"
dependencies = [
"schemars",
"serde",
@@ -4061,6 +4065,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"collections",
"dap",
"futures 0.3.31",
"gpui",
@@ -4195,6 +4200,7 @@ dependencies = [
"gpui",
"serde_json",
"task",
"util",
"workspace-hack",
]
@@ -4220,6 +4226,7 @@ dependencies = [
name = "debugger_ui"
version = "0.1.0"
dependencies = [
"alacritty_terminal",
"anyhow",
"client",
"collections",
@@ -4229,11 +4236,11 @@ dependencies = [
"db",
"debugger_tools",
"editor",
"feature_flags",
"file_icons",
"futures 0.3.31",
"fuzzy",
"gpui",
"itertools 0.14.0",
"language",
"log",
"menu",
@@ -4245,13 +4252,17 @@ dependencies = [
"rpc",
"serde",
"serde_json",
"serde_json_lenient",
"settings",
"shlex",
"sysinfo",
"task",
"tasks_ui",
"telemetry",
"terminal_view",
"theme",
"tree-sitter",
"tree-sitter-json",
"ui",
"unindent",
"util",
@@ -4691,13 +4702,11 @@ dependencies = [
"client",
"clock",
"collections",
"command_palette_hooks",
"convert_case 0.8.0",
"ctor",
"dap",
"db",
"emojis",
"feature_flags",
"file_icons",
"fs",
"futures 0.3.31",
@@ -5013,7 +5022,6 @@ dependencies = [
"assistant_tool",
"assistant_tools",
"async-trait",
"async-watch",
"buffer_diff",
"chrono",
"clap",
@@ -5055,6 +5063,7 @@ dependencies = [
"unindent",
"util",
"uuid",
"watch",
"workspace-hack",
"zed_llm_client",
]
@@ -6117,6 +6126,7 @@ dependencies = [
"anyhow",
"askpass",
"buffer_diff",
"call",
"chrono",
"collections",
"command_palette_hooks",
@@ -6155,6 +6165,7 @@ dependencies = [
"ui",
"unindent",
"util",
"watch",
"windows 0.61.1",
"workspace",
"workspace-hack",
@@ -8739,7 +8750,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"async-watch",
"clock",
"collections",
"ctor",
@@ -8789,6 +8799,7 @@ dependencies = [
"unicase",
"unindent",
"util",
"watch",
"workspace-hack",
"zlog",
]
@@ -8884,6 +8895,7 @@ dependencies = [
"tiktoken-rs",
"tokio",
"ui",
"ui_input",
"util",
"workspace-hack",
"zed_llm_client",
@@ -8999,7 +9011,6 @@ dependencies = [
"tree-sitter-yaml",
"unindent",
"util",
"which 6.0.3",
"workspace",
"workspace-hack",
]
@@ -10138,6 +10149,18 @@ dependencies = [
"memoffset",
]
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.9.0",
"cfg-if",
"cfg_aliases 0.2.1",
"libc",
]
[[package]]
name = "node_runtime"
version = "0.1.0"
@@ -10147,7 +10170,6 @@ dependencies = [
"async-std",
"async-tar",
"async-trait",
"async-watch",
"futures 0.3.31",
"http_client",
"log",
@@ -10157,6 +10179,7 @@ dependencies = [
"serde_json",
"smol",
"util",
"watch",
"which 6.0.3",
"workspace-hack",
]
@@ -10205,6 +10228,7 @@ dependencies = [
"util",
"workspace",
"workspace-hack",
"zed_actions",
]
[[package]]
@@ -12117,7 +12141,6 @@ dependencies = [
"unindent",
"url",
"util",
"uuid",
"which 6.0.3",
"workspace-hack",
"worktree",
@@ -13006,7 +13029,6 @@ dependencies = [
"askpass",
"assistant_tool",
"assistant_tools",
"async-watch",
"backtrace",
"cargo_toml",
"chrono",
@@ -13053,6 +13075,7 @@ dependencies = [
"toml 0.8.20",
"unindent",
"util",
"watch",
"worktree",
"zlog",
]
@@ -15732,6 +15755,7 @@ dependencies = [
"task",
"theme",
"thiserror 2.0.12",
"url",
"util",
"windows 0.61.1",
"workspace-hack",
@@ -17128,6 +17152,7 @@ dependencies = [
"async-fs",
"async_zip",
"collections",
"command-fds",
"dirs 4.0.0",
"dunce",
"futures 0.3.31",
@@ -17913,6 +17938,19 @@ dependencies = [
"leb128",
]
[[package]]
name = "watch"
version = "0.1.0"
dependencies = [
"ctor",
"futures 0.3.31",
"gpui",
"parking_lot",
"rand 0.8.5",
"workspace-hack",
"zlog",
]
[[package]]
name = "wayland-backend"
version = "0.3.8"
@@ -19712,7 +19750,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.191.0"
version = "0.191.9"
dependencies = [
"activity_indicator",
"agent",
@@ -19724,7 +19762,6 @@ dependencies = [
"assistant_context_editor",
"assistant_tool",
"assistant_tools",
"async-watch",
"audio",
"auto_update",
"auto_update_ui",
@@ -19753,7 +19790,6 @@ dependencies = [
"extension",
"extension_host",
"extensions_ui",
"feature_flags",
"feedback",
"file_finder",
"fs",
@@ -19841,6 +19877,7 @@ dependencies = [
"uuid",
"vim",
"vim_mode_setting",
"watch",
"web_search",
"web_search_providers",
"welcome",

View File

@@ -165,6 +165,7 @@ members = [
"crates/util_macros",
"crates/vim",
"crates/vim_mode_setting",
"crates/watch",
"crates/web_search",
"crates/web_search_providers",
"crates/welcome",
@@ -373,6 +374,7 @@ util = { path = "crates/util" }
util_macros = { path = "crates/util_macros" }
vim = { path = "crates/vim" }
vim_mode_setting = { path = "crates/vim_mode_setting" }
watch = { path = "crates/watch" }
web_search = { path = "crates/web_search" }
web_search_providers = { path = "crates/web_search_providers" }
welcome = { path = "crates/welcome" }
@@ -403,7 +405,6 @@ async-recursion = "1.0.0"
async-tar = "0.5.0"
async-trait = "0.1"
async-tungstenite = "0.29.1"
async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
aws-config = { version = "1.6.1", features = ["behavior-version-latest"] }
aws-credential-types = { version = "1.2.2", features = [
@@ -434,7 +435,7 @@ core-foundation-sys = "0.8.6"
core-video = { version = "0.4.3", features = ["metal"] }
criterion = { version = "0.5", features = ["html_reports"] }
ctor = "0.4.0"
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "68516de327fa1be15214133a0a2e52a12982ce75" }
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "b40956a7f4d1939da67429d941389ee306a3a308" }
dashmap = "6.0"
derive_more = "0.99.17"
dirs = "4.0"
@@ -697,6 +698,8 @@ codegen-units = 16
[profile.dev.package]
taffy = { opt-level = 3 }
cranelift-codegen = { opt-level = 3 }
cranelift-codegen-meta = { opt-level = 3 }
cranelift-codegen-shared = { opt-level = 3 }
resvg = { opt-level = 3 }
rustybuzz = { opt-level = 3 }
ttf-parser = { opt-level = 3 }

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-blocks"><rect width="7" height="7" x="14" y="3" rx="1"/><path d="M10 21V8a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H3"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-blocks-icon lucide-blocks"><rect width="7" height="7" x="14" y="3" rx="1"/><path d="M10 21V8a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H3"/></svg>

Before

Width:  |  Height:  |  Size: 368 B

After

Width:  |  Height:  |  Size: 386 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-help-icon lucide-circle-help"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>

After

Width:  |  Height:  |  Size: 348 B

View File

@@ -0,0 +1,890 @@
<svg width="400" height="92" viewBox="0 0 400 92" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_532_2318)">
<mask id="mask0_532_2318" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="400" height="92">
<path d="M400 0H0V92H400V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_532_2318)">
<g clip-path="url(#clip1_532_2318)">
<path d="M62.1878 1.24874C60.4044 1.12767 59.0444 -0.430533 59.1654 -2.21393L59.2755 -3.8352C59.3144 -4.40851 59.5795 -4.94289 60.0124 -5.32076C60.4453 -5.69863 61.0106 -5.88906 61.5839 -5.85013L63.7456 -5.70338C64.3189 -5.66446 64.8533 -5.39938 65.2312 -4.96647C65.6091 -4.53355 65.7995 -3.96825 65.7606 -3.39494L65.6505 -1.77367C65.5294 0.00972748 63.9712 1.36981 62.1878 1.24874Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M62.1879 1.24869L62.5181 -3.61511" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M57.2873 1.45893C57.3644 0.324045 58.3491 -0.586346 59.4877 -0.563343" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M65.108 -0.181763C66.2393 -0.0506748 67.0919 0.984456 67.0149 2.11934" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip2_532_2318)">
<path d="M87.1305 2.94204C85.3471 2.82097 83.987 1.26277 84.108 -0.52063L84.2181 -2.1419C84.257 -2.71521 84.5221 -3.24959 84.955 -3.62746C85.3879 -4.00534 85.9532 -4.19576 86.5266 -4.15684L88.6883 -4.01008C89.2616 -3.97116 89.7959 -3.70608 90.1738 -3.27317C90.5517 -2.84025 90.7421 -2.27495 90.7032 -1.70164L90.5931 -0.0803691C90.4721 1.70303 88.9139 3.06311 87.1305 2.94204Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M87.1305 2.94199L87.4607 -1.92181" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M82.23 3.15223C82.307 2.01734 83.2918 1.10695 84.4303 1.12996" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M92.7915 -0.474035L90.6298 -0.620789" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M90.0507 1.51154C91.1819 1.64262 92.0346 2.67775 91.9575 3.81264" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip3_532_2318)">
<path d="M112.073 4.63534C110.29 4.51426 108.929 2.95606 109.051 1.17267L109.161 -0.4486C109.2 -1.02192 109.465 -1.55629 109.898 -1.93416C110.33 -2.31204 110.896 -2.50246 111.469 -2.46354L113.631 -2.31678C114.204 -2.27786 114.738 -2.01279 115.116 -1.57987C115.494 -1.14695 115.685 -0.581655 115.646 -0.00833894L115.536 1.61293C115.415 3.39632 113.856 4.75641 112.073 4.63534Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M112.073 4.63529L112.403 -0.228516" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M109.087 0.632227L106.926 0.485474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M107.172 4.84553C107.25 3.71064 108.234 2.80025 109.373 2.82325" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M117.734 1.21926L115.572 1.07251" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M114.993 3.20483C116.124 3.33592 116.977 4.37105 116.9 5.50594" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip4_532_2318)">
<path d="M135.867 -0.736649L135.903 -1.27707C135.908 -1.49674 135.958 -1.71311 136.049 -1.91312C136.14 -2.11313 136.27 -2.29262 136.433 -2.44078C136.595 -2.58894 136.786 -2.70268 136.993 -2.77515C137.2 -2.84762 137.42 -2.8773 137.64 -2.86242C137.859 -2.84754 138.073 -2.78839 138.269 -2.68855C138.464 -2.58872 138.638 -2.45025 138.779 -2.28153C138.919 -2.1128 139.024 -1.9173 139.087 -1.70683C139.151 -1.49636 139.17 -1.27528 139.146 -1.05694L139.109 -0.516519" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M137.016 6.32869C135.232 6.20762 133.872 4.64942 133.993 2.86603L134.103 1.24476C134.142 0.671444 134.407 0.13707 134.84 -0.240804C135.273 -0.618678 135.838 -0.809099 136.412 -0.770178L138.573 -0.623424C139.147 -0.584503 139.681 -0.319426 140.059 0.113491C140.437 0.546408 140.627 1.1117 140.588 1.68502L140.478 3.30629C140.357 5.08968 138.799 6.44977 137.016 6.32869Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M137.016 6.32865L137.346 1.46484" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M134.463 0.183352C133.427 0.00445886 132.625 -0.972961 132.702 -2.10785" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M134.03 2.32559L131.868 2.17883" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M132.115 6.53889C132.192 5.404 133.177 4.49361 134.315 4.51661" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M142.413 -1.44856C142.336 -0.313668 141.409 0.546349 140.375 0.584726" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M142.677 2.91262L140.515 2.76587" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M139.936 4.89819C141.067 5.02928 141.92 6.06441 141.843 7.1993" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip5_532_2318)">
<path d="M160.457 -1.85242L161.404 -0.767448" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M163.695 -0.611874L164.78 -1.55889" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M160.809 0.956649L160.846 0.416226C160.851 0.196553 160.9 -0.0198148 160.991 -0.219821C161.082 -0.419827 161.213 -0.599325 161.375 -0.747482C161.538 -0.895638 161.728 -1.00938 161.936 -1.08185C162.143 -1.15432 162.363 -1.184 162.582 -1.16912C162.801 -1.15424 163.015 -1.09509 163.211 -0.995255C163.407 -0.895417 163.58 -0.756956 163.721 -0.588227C163.862 -0.419498 163.967 -0.224001 164.03 -0.0135316C164.093 0.196937 164.113 0.418014 164.088 0.636357L164.052 1.17678" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M161.958 8.02199C160.175 7.90092 158.815 6.34272 158.936 4.55933L159.046 2.93806C159.085 2.36474 159.35 1.83037 159.783 1.45249C160.216 1.07462 160.781 0.884199 161.354 0.923121L163.516 1.06987C164.089 1.1088 164.624 1.37387 165.002 1.80679C165.379 2.23971 165.57 2.805 165.531 3.37832L165.421 4.99959C165.3 6.78298 163.742 8.14306 161.958 8.02199Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M161.958 8.02195L162.288 3.15814" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M159.406 1.87665C158.37 1.69776 157.568 0.720337 157.645 -0.414551" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M158.972 4.01888L156.811 3.87213" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M157.058 8.23219C157.135 7.0973 158.12 6.18691 159.258 6.20991" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M167.356 0.244742C167.279 1.37963 166.352 2.23965 165.318 2.27802" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M167.619 4.60592L165.458 4.45917" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M164.878 6.59149C166.01 6.72258 166.862 7.75771 166.785 8.8926" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip6_532_2318)">
<path d="M185.399 -0.159119L186.346 0.92585" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M188.638 1.08142L189.723 0.134404" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M185.752 2.64995L185.788 2.10952C185.793 1.88985 185.843 1.67348 185.934 1.47348C186.025 1.27347 186.156 1.09397 186.318 0.945817C186.48 0.79766 186.671 0.683916 186.878 0.611449C187.086 0.538982 187.306 0.509294 187.525 0.524177C187.744 0.53906 187.958 0.598205 188.154 0.698043C188.349 0.797881 188.523 0.936343 188.664 1.10507C188.804 1.2738 188.91 1.4693 188.973 1.67977C189.036 1.89024 189.056 2.11131 189.031 2.32966L188.994 2.87008" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M186.901 9.71529C185.117 9.59422 183.757 8.03602 183.878 6.25262L183.988 4.63136C184.027 4.05804 184.292 3.52367 184.725 3.14579C185.158 2.76792 185.724 2.5775 186.297 2.61642L188.459 2.76317C189.032 2.80209 189.566 3.06717 189.944 3.50009C190.322 3.933 190.512 4.4983 190.474 5.07162L190.363 6.69289C190.242 8.47628 188.684 9.83636 186.901 9.71529Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M186.901 9.71525L187.231 4.85144" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M184.348 3.56995C183.313 3.39106 182.51 2.41364 182.587 1.27875" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M183.915 5.71218L181.753 5.56543" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M182 9.92549C182.077 8.7906 183.062 7.88021 184.201 7.90321" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M192.299 1.93804C192.222 3.07293 191.295 3.93295 190.26 3.97132" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M192.562 6.29922L190.4 6.15247" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M189.821 8.28479C190.952 8.41588 191.805 9.45101 191.728 10.5859" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip7_532_2318)">
<path d="M210.342 1.53418L211.289 2.61915" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M213.58 2.77472L214.665 1.8277" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M210.694 4.34325L210.731 3.80282C210.736 3.58315 210.786 3.36678 210.877 3.16678C210.968 2.96677 211.098 2.78727 211.26 2.63911C211.423 2.49096 211.613 2.37721 211.821 2.30475C212.028 2.23228 212.248 2.20259 212.467 2.21748C212.687 2.23236 212.9 2.2915 213.096 2.39134C213.292 2.49118 213.465 2.62964 213.606 2.79837C213.747 2.9671 213.852 3.1626 213.915 3.37307C213.978 3.58353 213.998 3.80461 213.973 4.02295L213.937 4.56338" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M211.843 11.4086C210.06 11.2875 208.7 9.72932 208.821 7.94592L208.931 6.32465C208.97 5.75134 209.235 5.21697 209.668 4.83909C210.101 4.46122 210.666 4.2708 211.239 4.30972L213.401 4.45647C213.974 4.49539 214.509 4.76047 214.887 5.19339C215.265 5.6263 215.455 6.1916 215.416 6.76492L215.306 8.38618C215.185 10.1696 213.627 11.5297 211.843 11.4086Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M211.843 11.4085L212.174 6.54474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M209.291 5.26325C208.255 5.08435 207.453 4.10693 207.53 2.97205" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M208.858 7.40548L206.696 7.25873" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M206.943 11.6188C207.02 10.4839 208.005 9.5735 209.143 9.59651" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M217.241 3.63134C217.164 4.76623 216.237 5.62624 215.203 5.66462" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M217.504 7.99252L215.343 7.84576" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M214.764 9.97809C215.895 10.1092 216.748 11.1443 216.67 12.2792" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip8_532_2318)">
<path d="M235.285 3.22748L236.232 4.31245" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M238.523 4.46802L239.608 3.521" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M235.637 6.03654L235.674 5.49612C235.679 5.27645 235.728 5.06008 235.819 4.86007C235.91 4.66007 236.041 4.48057 236.203 4.33241C236.365 4.18426 236.556 4.07051 236.763 3.99805C236.971 3.92558 237.191 3.89589 237.41 3.91077C237.629 3.92566 237.843 3.9848 238.039 4.08464C238.235 4.18448 238.408 4.32294 238.549 4.49167C238.69 4.6604 238.795 4.85589 238.858 5.06636C238.921 5.27683 238.941 5.49791 238.916 5.71625L238.879 6.25667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M236.786 13.1019C235.003 12.9808 233.642 11.4226 233.764 9.63922L233.874 8.01795C233.913 7.44464 234.178 6.91026 234.611 6.53239C235.043 6.15452 235.609 5.96409 236.182 6.00302L238.344 6.14977C238.917 6.18869 239.451 6.45377 239.829 6.88668C240.207 7.3196 240.398 7.8849 240.359 8.45821L240.249 10.0795C240.128 11.8629 238.569 13.223 236.786 13.1019Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M236.786 13.1018L237.116 8.23804" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M234.233 6.95655C233.198 6.77765 232.395 5.80023 232.472 4.66534" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M233.8 9.09878L231.639 8.95203" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M231.885 13.3121C231.963 12.1772 232.947 11.2668 234.086 11.2898" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M242.184 5.32464C242.107 6.45953 241.18 7.31954 240.146 7.35792" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M242.447 9.68582L240.285 9.53906" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M239.706 11.6714C240.837 11.8025 241.69 12.8376 241.613 13.9725" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip9_532_2318)">
<path d="M260.227 4.92084L261.174 6.00581" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M263.466 6.16138L264.551 5.21436" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M260.579 7.7299L260.616 7.18948C260.621 6.96981 260.671 6.75344 260.762 6.55343C260.853 6.35343 260.983 6.17393 261.146 6.02577C261.308 5.87762 261.498 5.76387 261.706 5.6914C261.913 5.61894 262.133 5.58925 262.353 5.60413C262.572 5.61902 262.786 5.67816 262.981 5.778C263.177 5.87784 263.351 6.0163 263.491 6.18503C263.632 6.35376 263.737 6.54925 263.8 6.75972C263.864 6.97019 263.883 7.19127 263.859 7.40961L263.822 7.95003" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M261.729 14.7952C259.945 14.6742 258.585 13.116 258.706 11.3326L258.816 9.71131C258.855 9.138 259.12 8.60362 259.553 8.22575C259.986 7.84788 260.551 7.65745 261.125 7.69638L263.286 7.84313C263.86 7.88205 264.394 8.14713 264.772 8.58004C265.15 9.01296 265.34 9.57826 265.301 10.1516L265.191 11.7728C265.07 13.5562 263.512 14.9163 261.729 14.7952Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M261.729 14.7952L262.059 9.9314" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M259.176 8.6499C258.14 8.47101 257.338 7.49359 257.415 6.3587" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M258.743 10.7921L256.581 10.6454" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M256.828 15.0054C256.905 13.8706 257.89 12.9602 259.028 12.9832" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M267.126 7.018C267.049 8.15288 266.122 9.0129 265.088 9.05128" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M267.39 11.3792L265.228 11.2324" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M264.649 13.3647C265.78 13.4958 266.633 14.531 266.556 15.6659" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip10_532_2318)">
<path d="M285.17 6.61414L286.117 7.6991" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M288.408 7.85468L289.493 6.90766" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M285.522 9.4232L285.559 8.88278C285.564 8.66311 285.613 8.44674 285.704 8.24673C285.795 8.04673 285.926 7.86723 286.088 7.71907C286.25 7.57091 286.441 7.45717 286.648 7.3847C286.856 7.31224 287.076 7.28255 287.295 7.29743C287.514 7.31231 287.728 7.37146 287.924 7.4713C288.12 7.57114 288.293 7.7096 288.434 7.87833C288.575 8.04705 288.68 8.24255 288.743 8.45302C288.806 8.66349 288.826 8.88457 288.801 9.10291L288.765 9.64333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M286.671 16.4885C284.888 16.3675 283.528 14.8093 283.649 13.0259L283.759 11.4046C283.798 10.8313 284.063 10.2969 284.496 9.91905C284.929 9.54117 285.494 9.35075 286.067 9.38967L288.229 9.53643C288.802 9.57535 289.337 9.84042 289.714 10.2733C290.092 10.7063 290.283 11.2716 290.244 11.8449L290.134 13.4661C290.013 15.2495 288.455 16.6096 286.671 16.4885Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M286.671 16.4885L287.001 11.6247" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M284.119 10.3432C283.083 10.1643 282.281 9.18689 282.358 8.052" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M283.685 12.4854L281.524 12.3387" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M281.771 16.6987C281.848 15.5639 282.832 14.6535 283.971 14.6765" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M292.069 8.7113C291.992 9.84618 291.065 10.7062 290.031 10.7446" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M292.332 13.0725L290.17 12.9257" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M289.591 15.058C290.723 15.1891 291.575 16.2243 291.498 17.3592" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip11_532_2318)">
<path d="M310.112 8.30743L311.059 9.3924" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M313.351 9.54798L314.436 8.60096" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M310.465 11.1165L310.501 10.5761C310.506 10.3564 310.556 10.14 310.647 9.94003C310.738 9.74002 310.868 9.56053 311.031 9.41237C311.193 9.26421 311.384 9.15047 311.591 9.078C311.799 9.00553 312.018 8.97585 312.238 8.99073C312.457 9.00561 312.671 9.06476 312.867 9.1646C313.062 9.26443 313.236 9.4029 313.377 9.57162C313.517 9.74035 313.622 9.93585 313.686 10.1463C313.749 10.3568 313.769 10.5779 313.744 10.7962L313.707 11.3366" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M311.614 18.1818C309.83 18.0608 308.47 16.5026 308.591 14.7192L308.701 13.0979C308.74 12.5246 309.005 11.9902 309.438 11.6123C309.871 11.2345 310.437 11.0441 311.01 11.083L313.172 11.2297C313.745 11.2686 314.279 11.5337 314.657 11.9666C315.035 12.3996 315.225 12.9649 315.186 13.5382L315.076 15.1594C314.955 16.9428 313.397 18.3029 311.614 18.1818Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M311.614 18.1818L311.944 13.318" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M309.061 12.0365C308.025 11.8576 307.223 10.8802 307.3 9.7453" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M308.628 14.1787L306.466 14.032" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M306.713 18.392C306.79 17.2572 307.775 16.3468 308.914 16.3698" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M317.012 10.4046C316.935 11.5395 316.008 12.3995 314.973 12.4379" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M317.275 14.7658L315.113 14.619" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M314.534 16.7513C315.665 16.8824 316.518 17.9176 316.441 19.0524" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip12_532_2318)">
<path d="M335.055 10.0007L336.002 11.0857" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M338.293 11.2413L339.378 10.2943" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M335.407 12.8098L335.444 12.2694C335.449 12.0497 335.498 11.8333 335.589 11.6333C335.68 11.4333 335.811 11.2538 335.973 11.1057C336.136 10.9575 336.326 10.8438 336.534 10.7713C336.741 10.6988 336.961 10.6691 337.18 10.684C337.399 10.6989 337.613 10.7581 337.809 10.8579C338.005 10.9577 338.178 11.0962 338.319 11.2649C338.46 11.4337 338.565 11.6291 338.628 11.8396C338.691 12.0501 338.711 12.2712 338.686 12.4895L338.65 13.0299" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M336.556 19.8751C334.773 19.7541 333.413 18.1959 333.534 16.4125L333.644 14.7912C333.683 14.2179 333.948 13.6835 334.381 13.3056C334.814 12.9278 335.379 12.7373 335.952 12.7763L338.114 12.923C338.687 12.9619 339.222 13.227 339.6 13.6599C339.978 14.0929 340.168 14.6582 340.129 15.2315L340.019 16.8527C339.898 18.6361 338.34 19.9962 336.556 19.8751Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M336.556 19.8751L336.886 15.0113" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M334.004 13.7298C332.968 13.5509 332.166 12.5735 332.243 11.4386" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M333.571 15.872L331.409 15.7253" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M331.656 20.0853C331.733 18.9504 332.718 18.0401 333.856 18.0631" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M341.954 12.0979C341.877 13.2328 340.95 14.0928 339.916 14.1312" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M342.217 16.4591L340.056 16.3123" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M339.477 18.4446C340.608 18.5757 341.46 19.6109 341.383 20.7457" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip13_532_2318)">
<path d="M359.998 11.694L360.945 12.779" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M363.236 12.9346L364.321 11.9876" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M360.35 14.5031L360.386 13.9627C360.392 13.743 360.441 13.5266 360.532 13.3266C360.623 13.1266 360.754 12.9471 360.916 12.799C361.078 12.6508 361.269 12.5371 361.476 12.4646C361.684 12.3921 361.904 12.3624 362.123 12.3773C362.342 12.3922 362.556 12.4514 362.752 12.5512C362.947 12.651 363.121 12.7895 363.262 12.9582C363.402 13.1269 363.508 13.3224 363.571 13.5329C363.634 13.7434 363.654 13.9645 363.629 14.1828L363.592 14.7232" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M361.499 21.5684C359.715 21.4474 358.355 19.8892 358.476 18.1058L358.587 16.4845C358.625 15.9112 358.891 15.3768 359.323 14.9989C359.756 14.6211 360.322 14.4306 360.895 14.4696L363.057 14.6163C363.63 14.6552 364.164 14.9203 364.542 15.3532C364.92 15.7862 365.111 16.3515 365.072 16.9248L364.962 18.546C364.84 20.3294 363.282 21.6895 361.499 21.5684Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M361.499 21.5684L361.829 16.7046" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M358.946 15.4231C357.911 15.2442 357.108 14.2668 357.185 13.1319" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M358.513 17.5653L356.351 17.4186" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M356.598 21.7786C356.675 20.6437 357.66 19.7334 358.799 19.7564" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M366.897 13.7912C366.82 14.9261 365.893 15.7861 364.859 15.8245" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M367.16 18.1524L364.998 18.0056" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M364.419 20.1379C365.55 20.269 366.403 21.3042 366.326 22.439" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip14_532_2318)">
<path d="M384.94 13.3874L385.887 14.4724" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M388.179 14.6279L389.264 13.6809" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M385.292 16.1965L385.329 15.656C385.334 15.4364 385.384 15.22 385.475 15.02C385.566 14.82 385.696 14.6405 385.859 14.4923C386.021 14.3442 386.211 14.2304 386.419 14.158C386.626 14.0855 386.846 14.0558 387.065 14.0707C387.285 14.0856 387.499 14.1447 387.694 14.2446C387.89 14.3444 388.064 14.4829 388.204 14.6516C388.345 14.8203 388.45 15.0158 388.513 15.2263C388.576 15.4367 388.596 15.6578 388.572 15.8762L388.535 16.4166" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M386.441 23.2618C384.658 23.1407 383.298 21.5825 383.419 19.7991L383.529 18.1779C383.568 17.6045 383.833 17.0702 384.266 16.6923C384.699 16.3144 385.264 16.124 385.838 16.1629L387.999 16.3097C388.573 16.3486 389.107 16.6137 389.485 17.0466C389.863 17.4795 390.053 18.0448 390.014 18.6181L389.904 20.2394C389.783 22.0228 388.225 23.3829 386.441 23.2618Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M386.441 23.2618L386.772 18.3979" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M383.889 17.1165C382.853 16.9376 382.051 15.9601 382.128 14.8253" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M383.456 19.2587L381.294 19.1119" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M381.541 23.472C381.618 22.3371 382.603 21.4267 383.741 21.4497" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M391.839 15.4845C391.762 16.6194 390.835 17.4795 389.801 17.5178" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M392.103 19.8457L389.941 19.699" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M389.362 21.8313C390.493 21.9624 391.346 22.9975 391.269 24.1324" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip15_532_2318)">
<path d="M59.2642 12.3261L60.2112 13.4111" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M62.5026 13.5667L63.5875 12.6196" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M59.6164 15.1352L59.6531 14.5948C59.6582 14.3751 59.7077 14.1587 59.7987 13.9587C59.8897 13.7587 60.0203 13.5792 60.1825 13.431C60.3448 13.2829 60.5354 13.1691 60.7429 13.0967C60.9503 13.0242 61.1703 12.9945 61.3895 13.0094C61.6087 13.0243 61.8227 13.0834 62.0184 13.1833C62.2141 13.2831 62.3876 13.4216 62.5284 13.5903C62.6691 13.759 62.7742 13.9545 62.8374 14.165C62.9005 14.3755 62.9203 14.5965 62.8957 14.8149L62.859 15.3553" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M60.7655 22.2005C58.9821 22.0794 57.622 20.5212 57.7431 18.7379L57.8531 17.1166C57.892 16.5433 58.1571 16.0089 58.59 15.631C59.023 15.2531 59.5883 15.0627 60.1616 15.1016L62.3233 15.2484C62.8966 15.2873 63.4309 15.5524 63.8088 15.9853C64.1867 16.4182 64.3771 16.9835 64.3382 17.5568L64.2281 19.1781C64.1071 20.9615 62.5489 22.3216 60.7655 22.2005Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M60.7655 22.2005L61.0957 17.3367" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M58.213 16.0552C57.1773 15.8763 56.375 14.8989 56.452 13.764" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M57.7797 18.1974L55.618 18.0507" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M55.865 22.4107C55.942 21.2758 56.9268 20.3654 58.0653 20.3884" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M66.1633 14.4233C66.0863 15.5582 65.1592 16.4182 64.1251 16.4566" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M66.4265 18.7844L64.2648 18.6377" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M63.6857 20.77C64.8169 20.9011 65.6696 21.9362 65.5925 23.0711" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip16_532_2318)">
<path d="M84.2068 14.0194L85.1538 15.1044" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M87.4452 15.26L88.5302 14.3129" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M84.5591 16.8285L84.5958 16.2881C84.6008 16.0684 84.6503 15.852 84.7413 15.652C84.8323 15.452 84.9629 15.2725 85.1252 15.1243C85.2874 14.9762 85.478 14.8624 85.6855 14.79C85.8929 14.7175 86.1129 14.6878 86.3321 14.7027C86.5513 14.7176 86.7653 14.7767 86.961 14.8766C87.1568 14.9764 87.3302 15.1149 87.471 15.2836C87.6118 15.4523 87.7169 15.6478 87.78 15.8583C87.8431 16.0688 87.863 16.2898 87.8383 16.5082L87.8016 17.0486" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M85.7081 23.8938C83.9247 23.7727 82.5646 22.2145 82.6857 20.4312L82.7958 18.8099C82.8347 18.2366 83.0997 17.7022 83.5327 17.3243C83.9656 16.9464 84.5309 16.756 85.1042 16.7949L87.2659 16.9417C87.8392 16.9806 88.3736 17.2457 88.7515 17.6786C89.1293 18.1115 89.3197 18.6768 89.2808 19.2501L89.1708 20.8714C89.0497 22.6548 87.4915 24.0149 85.7081 23.8938Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M85.7081 23.8938L86.0383 19.03" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M83.1556 17.7485C82.1199 17.5696 81.3176 16.5922 81.3947 15.4573" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M82.7224 19.8907L80.5607 19.744" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M80.8076 24.104C80.8846 22.9691 81.8694 22.0587 83.008 22.0817" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M91.106 16.1166C91.0289 17.2515 90.1019 18.1115 89.0677 18.1499" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M91.3691 20.4777L89.2074 20.331" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M88.6283 22.4633C89.7595 22.5944 90.6122 23.6295 90.5351 24.7644" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip17_532_2318)">
<path d="M109.149 15.7127L110.096 16.7977" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M112.388 16.9533L113.473 16.0062" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M109.502 18.5218L109.538 17.9814C109.543 17.7617 109.593 17.5453 109.684 17.3453C109.775 17.1453 109.905 16.9658 110.068 16.8176C110.23 16.6695 110.421 16.5557 110.628 16.4833C110.835 16.4108 111.055 16.3811 111.275 16.396C111.494 16.4109 111.708 16.47 111.904 16.5699C112.099 16.6697 112.273 16.8082 112.414 16.9769C112.554 17.1456 112.659 17.3411 112.723 17.5516C112.786 17.7621 112.805 17.9831 112.781 18.2015L112.744 18.7419" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M110.651 25.5871C108.867 25.466 107.507 23.9078 107.628 22.1245L107.738 20.5032C107.777 19.9299 108.042 19.3955 108.475 19.0176C108.908 18.6397 109.473 18.4493 110.047 18.4882L112.208 18.635C112.782 18.6739 113.316 18.939 113.694 19.3719C114.072 19.8048 114.262 20.3701 114.223 20.9434L114.113 22.5647C113.992 24.3481 112.434 25.7082 110.651 25.5871Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M110.651 25.5871L110.981 20.7233" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M108.098 19.4418C107.062 19.2629 106.26 18.2855 106.337 17.1506" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M107.665 21.584L105.503 21.4373" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M105.75 25.7973C105.827 24.6624 106.812 23.752 107.95 23.775" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M116.048 17.8099C115.971 18.9448 115.044 19.8048 114.01 19.8431" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M116.312 22.171L114.15 22.0243" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M113.571 24.1566C114.702 24.2877 115.555 25.3228 115.478 26.4577" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip18_532_2318)">
<path d="M134.092 17.4061L135.039 18.491" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M137.33 18.6466L138.415 17.6996" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M134.444 20.2151L134.481 19.6747C134.486 19.455 134.535 19.2387 134.626 19.0387C134.717 18.8387 134.848 18.6592 135.01 18.511C135.173 18.3628 135.363 18.2491 135.571 18.1766C135.778 18.1042 135.998 18.0745 136.217 18.0894C136.436 18.1042 136.65 18.1634 136.846 18.2632C137.042 18.3631 137.215 18.5015 137.356 18.6703C137.497 18.839 137.602 19.0345 137.665 19.245C137.728 19.4554 137.748 19.6765 137.723 19.8948L137.687 20.4353" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M135.593 27.2805C133.81 27.1594 132.45 25.6012 132.571 23.8178L132.681 22.1965C132.72 21.6232 132.985 21.0889 133.418 20.711C133.851 20.3331 134.416 20.1427 134.989 20.1816L137.151 20.3284C137.724 20.3673 138.259 20.6324 138.637 21.0653C139.014 21.4982 139.205 22.0635 139.166 22.6368L139.056 24.2581C138.935 26.0415 137.377 27.4015 135.593 27.2805Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M135.593 27.2804L135.923 22.4166" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M133.041 21.1351C132.005 20.9562 131.203 19.9788 131.28 18.8439" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M132.607 23.2774L130.446 23.1306" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M130.693 27.4907C130.77 26.3558 131.755 25.4454 132.893 25.4684" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M140.991 19.5032C140.914 20.6381 139.987 21.4981 138.953 21.5365" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M141.254 23.8644L139.093 23.7177" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M138.513 25.85C139.645 25.9811 140.497 27.0162 140.42 28.1511" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip19_532_2318)">
<path d="M159.035 19.0994L159.982 20.1843" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M162.273 20.3399L163.358 19.3929" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M159.387 21.9084L159.424 21.368C159.429 21.1483 159.478 20.932 159.569 20.732C159.66 20.532 159.791 20.3525 159.953 20.2043C160.115 20.0561 160.306 19.9424 160.513 19.8699C160.721 19.7975 160.941 19.7678 161.16 19.7827C161.379 19.7975 161.593 19.8567 161.789 19.9565C161.985 20.0564 162.158 20.1948 162.299 20.3636C162.44 20.5323 162.545 20.7278 162.608 20.9383C162.671 21.1487 162.691 21.3698 162.666 21.5881L162.629 22.1286" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M160.536 28.9738C158.752 28.8527 157.392 27.2945 157.513 25.5111L157.624 23.8898C157.662 23.3165 157.928 22.7822 158.36 22.4043C158.793 22.0264 159.359 21.836 159.932 21.8749L162.094 22.0217C162.667 22.0606 163.201 22.3257 163.579 22.7586C163.957 23.1915 164.148 23.7568 164.109 24.3301L163.999 25.9514C163.877 27.7348 162.319 29.0948 160.536 28.9738Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M160.536 28.9737L160.866 24.1099" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M157.983 22.8284C156.948 22.6495 156.145 21.6721 156.222 20.5372" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M157.55 24.9707L155.388 24.8239" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M155.635 29.184C155.712 28.0491 156.697 27.1387 157.836 27.1617" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M165.934 21.1965C165.857 22.3314 164.93 23.1914 163.896 23.2298" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M166.197 25.5577L164.035 25.4109" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M163.456 27.5433C164.587 27.6744 165.44 28.7095 165.363 29.8444" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip20_532_2318)">
<path d="M183.977 20.7927L184.924 21.8776" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M187.216 22.0332L188.3 21.0862" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M184.329 23.6017L184.366 23.0613C184.371 22.8416 184.421 22.6253 184.512 22.4253C184.603 22.2253 184.733 22.0458 184.895 21.8976C185.058 21.7494 185.248 21.6357 185.456 21.5632C185.663 21.4908 185.883 21.4611 186.102 21.476C186.322 21.4908 186.536 21.55 186.731 21.6498C186.927 21.7497 187.101 21.8881 187.241 22.0569C187.382 22.2256 187.487 22.4211 187.55 22.6315C187.613 22.842 187.633 23.0631 187.609 23.2814L187.572 23.8219" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M185.478 30.6671C183.695 30.546 182.335 28.9878 182.456 27.2044L182.566 25.5831C182.605 25.0098 182.87 24.4754 183.303 24.0976C183.736 23.7197 184.301 23.5293 184.875 23.5682L187.036 23.715C187.61 23.7539 188.144 24.019 188.522 24.4519C188.9 24.8848 189.09 25.4501 189.051 26.0234L188.941 27.6447C188.82 29.4281 187.262 30.7881 185.478 30.6671Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M185.478 30.667L185.809 25.8032" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M182.926 24.5217C181.89 24.3428 181.088 23.3654 181.165 22.2305" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M182.493 26.664L180.331 26.5172" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M180.578 30.8773C180.655 29.7424 181.64 28.832 182.778 28.855" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M190.876 22.8898C190.799 24.0247 189.872 24.8847 188.838 24.9231" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M191.139 27.251L188.978 27.1042" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M188.399 29.2366C189.53 29.3677 190.383 30.4028 190.306 31.5377" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip21_532_2318)">
<path d="M208.92 22.486L209.867 23.5709" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M212.158 23.7265L213.243 22.7795" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M209.272 25.295L209.309 24.7546C209.314 24.5349 209.363 24.3186 209.454 24.1186C209.545 23.9186 209.676 23.7391 209.838 23.5909C210 23.4427 210.191 23.329 210.398 23.2565C210.606 23.1841 210.826 23.1544 211.045 23.1693C211.264 23.1841 211.478 23.2433 211.674 23.3431C211.87 23.443 212.043 23.5814 212.184 23.7502C212.325 23.9189 212.43 24.1144 212.493 24.3248C212.556 24.5353 212.576 24.7564 212.551 24.9747L212.514 25.5152" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M210.421 32.3604C208.638 32.2393 207.278 30.6811 207.399 28.8977L207.509 27.2764C207.548 26.7031 207.813 26.1687 208.246 25.7909C208.678 25.413 209.244 25.2226 209.817 25.2615L211.979 25.4083C212.552 25.4472 213.086 25.7123 213.464 26.1452C213.842 26.5781 214.033 27.1434 213.994 27.7167L213.884 29.338C213.763 31.1214 212.204 32.4814 210.421 32.3604Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M210.421 32.3603L210.751 27.4965" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M207.868 26.215C206.833 26.0361 206.03 25.0587 206.107 23.9238" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M207.435 28.3573L205.274 28.2105" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M205.52 32.5706C205.598 31.4357 206.582 30.5253 207.721 30.5483" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M215.819 24.5831C215.742 25.718 214.815 26.578 213.781 26.6164" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M216.082 28.9443L213.92 28.7975" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M213.341 30.9299C214.472 31.061 215.325 32.0961 215.248 33.231" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip22_532_2318)">
<path d="M233.862 24.1793L234.809 25.2642" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M237.101 25.4198L238.186 24.4728" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M234.215 26.9883L234.251 26.4479C234.256 26.2282 234.306 26.0119 234.397 25.8119C234.488 25.6118 234.618 25.4324 234.781 25.2842C234.943 25.136 235.134 25.0223 235.341 24.9498C235.548 24.8774 235.768 24.8477 235.988 24.8626C236.207 24.8774 236.421 24.9366 236.616 25.0364C236.812 25.1363 236.986 25.2747 237.126 25.4435C237.267 25.6122 237.372 25.8077 237.435 26.0181C237.499 26.2286 237.518 26.4497 237.494 26.668L237.457 27.2085" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M235.364 34.0537C233.58 33.9326 232.22 32.3744 232.341 30.591L232.451 28.9697C232.49 28.3964 232.755 27.862 233.188 27.4842C233.621 27.1063 234.186 26.9159 234.76 26.9548L236.921 27.1016C237.495 27.1405 238.029 27.4055 238.407 27.8385C238.785 28.2714 238.975 28.8367 238.936 29.41L238.826 31.0313C238.705 32.8147 237.147 34.1747 235.364 34.0537Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M235.364 34.0536L235.694 29.1898" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M232.811 27.9083C231.775 27.7294 230.973 26.752 231.05 25.6171" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M232.378 30.0506L230.216 29.9038" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M230.463 34.2639C230.54 33.129 231.525 32.2186 232.663 32.2416" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M240.762 26.2764C240.684 27.4113 239.757 28.2713 238.723 28.3097" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M241.025 30.6376L238.863 30.4908" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M238.284 32.6232C239.415 32.7543 240.268 33.7894 240.191 34.9243" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip23_532_2318)">
<path d="M258.805 25.8726L259.752 26.9576" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M262.043 27.1132L263.128 26.1661" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M259.157 28.6817L259.194 28.1413C259.199 27.9216 259.248 27.7052 259.339 27.5052C259.43 27.3052 259.561 27.1257 259.723 26.9776C259.885 26.8294 260.076 26.7157 260.284 26.6432C260.491 26.5707 260.711 26.541 260.93 26.5559C261.149 26.5708 261.363 26.6299 261.559 26.7298C261.755 26.8296 261.928 26.9681 262.069 27.1368C262.21 27.3055 262.315 27.501 262.378 27.7115C262.441 27.922 262.461 28.1431 262.436 28.3614L262.4 28.9018" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M260.306 35.747C258.523 35.626 257.163 34.0678 257.284 32.2844L257.394 30.6631C257.433 30.0898 257.698 29.5554 258.131 29.1775C258.564 28.7997 259.129 28.6092 259.702 28.6482L261.864 28.7949C262.437 28.8338 262.972 29.0989 263.35 29.5318C263.727 29.9647 263.918 30.53 263.879 31.1034L263.769 32.7246C263.648 34.508 262.09 35.8681 260.306 35.747Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M260.306 35.747L260.636 30.8832" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M257.754 29.6017C256.718 29.4228 255.916 28.4454 255.993 27.3105" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M257.32 31.7439L255.159 31.5972" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M255.406 35.9572C255.483 34.8223 256.467 33.9119 257.606 33.9349" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M265.704 27.9698C265.627 29.1047 264.7 29.9647 263.666 30.0031" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M265.967 32.331L263.806 32.1842" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M263.226 34.3165C264.358 34.4476 265.21 35.4827 265.133 36.6176" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip24_532_2318)">
<path d="M283.747 27.5659L284.694 28.6509" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M286.986 28.8065L288.071 27.8594" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M284.1 30.375L284.136 29.8346C284.141 29.6149 284.191 29.3985 284.282 29.1985C284.373 28.9985 284.503 28.819 284.666 28.6709C284.828 28.5227 285.019 28.409 285.226 28.3365C285.434 28.264 285.653 28.2343 285.873 28.2492C286.092 28.2641 286.306 28.3232 286.502 28.4231C286.697 28.5229 286.871 28.6614 287.012 28.8301C287.152 28.9988 287.257 29.1943 287.321 29.4048C287.384 29.6153 287.404 29.8363 287.379 30.0547L287.342 30.5951" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M285.249 37.4403C283.465 37.3193 282.105 35.7611 282.226 33.9777L282.336 32.3564C282.375 31.7831 282.64 31.2487 283.073 30.8708C283.506 30.493 284.072 30.3025 284.645 30.3415L286.807 30.4882C287.38 30.5271 287.914 30.7922 288.292 31.2251C288.67 31.658 288.86 32.2233 288.821 32.7967L288.711 34.4179C288.59 36.2013 287.032 37.5614 285.249 37.4403Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M285.249 37.4403L285.579 32.5765" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M282.696 31.295C281.661 31.1161 280.858 30.1387 280.935 29.0038" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M282.263 33.4372L280.101 33.2905" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M280.348 37.6505C280.425 36.5156 281.41 35.6052 282.549 35.6282" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M290.647 29.6631C290.57 30.798 289.643 31.658 288.608 31.6964" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M290.91 34.0243L288.748 33.8775" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M288.169 36.0098C289.3 36.1409 290.153 37.176 290.076 38.3109" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip25_532_2318)">
<path d="M308.69 29.2592L309.637 30.3442" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M311.928 30.4998L313.013 29.5527" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M309.042 32.0683L309.079 31.5279C309.084 31.3082 309.134 31.0918 309.225 30.8918C309.316 30.6918 309.446 30.5123 309.608 30.3642C309.771 30.216 309.961 30.1023 310.169 30.0298C310.376 29.9573 310.596 29.9276 310.815 29.9425C311.035 29.9574 311.248 30.0165 311.444 30.1164C311.64 30.2162 311.813 30.3547 311.954 30.5234C312.095 30.6921 312.2 30.8876 312.263 31.0981C312.326 31.3086 312.346 31.5296 312.322 31.748L312.285 32.2884" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M310.191 39.1336C308.408 39.0126 307.048 37.4544 307.169 35.671L307.279 34.0497C307.318 33.4764 307.583 32.942 308.016 32.5641C308.449 32.1863 309.014 31.9958 309.587 32.0348L311.749 32.1815C312.322 32.2204 312.857 32.4855 313.235 32.9184C313.613 33.3513 313.803 33.9166 313.764 34.49L313.654 36.1112C313.533 37.8946 311.975 39.2547 310.191 39.1336Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M310.191 39.1336L310.522 34.2698" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M307.639 32.9883C306.603 32.8094 305.801 31.832 305.878 30.6971" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M307.206 35.1305L305.044 34.9838" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M305.291 39.3438C305.368 38.2089 306.353 37.2985 307.491 37.3215" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M315.589 31.3564C315.512 32.4913 314.585 33.3513 313.551 33.3897" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M315.852 35.7176L313.691 35.5708" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M313.112 37.7031C314.243 37.8342 315.096 38.8693 315.018 40.0042" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip26_532_2318)">
<path d="M333.633 30.9525L334.58 32.0375" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M336.871 32.1931L337.956 31.246" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M333.985 33.7616L334.022 33.2212C334.027 33.0015 334.076 32.7851 334.167 32.5851C334.258 32.3851 334.389 32.2056 334.551 32.0574C334.713 31.9093 334.904 31.7955 335.111 31.7231C335.319 31.6506 335.539 31.6209 335.758 31.6358C335.977 31.6507 336.191 31.7098 336.387 31.8097C336.583 31.9095 336.756 32.048 336.897 32.2167C337.038 32.3854 337.143 32.5809 337.206 32.7914C337.269 33.0019 337.289 33.2229 337.264 33.4413L337.227 33.9817" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M335.134 40.8269C333.351 40.7059 331.99 39.1477 332.112 37.3643L332.222 35.743C332.261 35.1697 332.526 34.6353 332.959 34.2574C333.391 33.8796 333.957 33.6891 334.53 33.7281L336.692 33.8748C337.265 33.9137 337.799 34.1788 338.177 34.6117C338.555 35.0446 338.746 35.6099 338.707 36.1833L338.597 37.8045C338.476 39.5879 336.917 40.948 335.134 40.8269Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M335.134 40.8269L335.464 35.9631" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M332.581 34.6816C331.546 34.5027 330.743 33.5253 330.82 32.3904" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M332.148 36.8238L329.987 36.6771" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M330.233 41.0371C330.31 39.9022 331.295 38.9918 332.434 39.0148" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M340.532 33.0497C340.455 34.1846 339.528 35.0446 338.494 35.083" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M340.795 37.4109L338.633 37.2641" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M338.054 39.3964C339.185 39.5275 340.038 40.5626 339.961 41.6975" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip27_532_2318)">
<path d="M358.575 32.6458L359.522 33.7308" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M361.814 33.8864L362.899 32.9393" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M358.927 35.4549L358.964 34.9145C358.969 34.6948 359.019 34.4784 359.11 34.2784C359.201 34.0784 359.331 33.8989 359.494 33.7507C359.656 33.6026 359.846 33.4888 360.054 33.4164C360.261 33.3439 360.481 33.3142 360.7 33.3291C360.92 33.344 361.134 33.4031 361.329 33.503C361.525 33.6028 361.699 33.7413 361.839 33.91C361.98 34.0787 362.085 34.2742 362.148 34.4847C362.211 34.6952 362.231 34.9162 362.207 35.1346L362.17 35.675" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M360.077 42.5202C358.293 42.3992 356.933 40.841 357.054 39.0576L357.164 37.4363C357.203 36.863 357.468 36.3286 357.901 35.9507C358.334 35.5729 358.899 35.3824 359.473 35.4214L361.634 35.5681C362.208 35.607 362.742 35.8721 363.12 36.305C363.498 36.7379 363.688 37.3032 363.649 37.8765L363.539 39.4978C363.418 41.2812 361.86 42.6413 360.077 42.5202Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M360.076 42.5202L360.407 37.6564" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M357.524 36.3749C356.488 36.196 355.686 35.2186 355.763 34.0837" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M357.091 38.5171L354.929 38.3704" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M355.176 42.7304C355.253 41.5955 356.238 40.6851 357.376 40.7081" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M365.474 34.743C365.397 35.8779 364.47 36.7379 363.436 36.7763" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M365.738 39.1042L363.576 38.9574" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M362.997 41.0897C364.128 41.2208 364.981 42.2559 364.904 43.3908" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip28_532_2318)">
<path d="M383.518 34.3392L384.465 35.4241" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M386.756 35.5797L387.841 34.6327" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M383.87 37.1482L383.907 36.6078C383.912 36.3881 383.961 36.1718 384.052 35.9718C384.143 35.7718 384.274 35.5923 384.436 35.4441C384.598 35.296 384.789 35.1822 384.996 35.1097C385.204 35.0373 385.424 35.0076 385.643 35.0225C385.862 35.0374 386.076 35.0965 386.272 35.1963C386.468 35.2962 386.641 35.4346 386.782 35.6034C386.923 35.7721 387.028 35.9676 387.091 36.1781C387.154 36.3885 387.174 36.6096 387.149 36.8279L387.113 37.3684" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M385.019 44.2136C383.236 44.0925 381.876 42.5343 381.997 40.7509L382.107 39.1296C382.146 38.5563 382.411 38.022 382.844 37.6441C383.277 37.2662 383.842 37.0758 384.415 37.1147L386.577 37.2615C387.15 37.3004 387.685 37.5655 388.062 37.9984C388.44 38.4313 388.631 38.9966 388.592 39.5699L388.482 41.1912C388.361 42.9746 386.803 44.3347 385.019 44.2136Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M385.019 44.2135L385.349 39.3497" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M382.467 38.0682C381.431 37.8893 380.629 36.9119 380.706 35.777" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M382.033 40.2105L379.872 40.0637" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M380.119 44.4238C380.196 43.2889 381.18 42.3785 382.319 42.4015" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M390.417 36.4363C390.34 37.5712 389.413 38.4312 388.379 38.4696" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M390.68 40.7975L388.518 40.6508" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M387.939 42.7831C389.071 42.9142 389.923 43.9493 389.846 45.0842" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip29_532_2318)">
<path d="M57.8418 33.2779L58.7888 34.3629" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M61.0802 34.5184L62.1652 33.5714" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M58.1941 36.087L58.2308 35.5465C58.2358 35.3269 58.2854 35.1105 58.3763 34.9105C58.4673 34.7105 58.5979 34.531 58.7602 34.3828C58.9225 34.2347 59.1131 34.1209 59.3205 34.0485C59.5279 33.976 59.7479 33.9463 59.9671 33.9612C60.1864 33.9761 60.4003 34.0352 60.596 34.1351C60.7918 34.2349 60.9653 34.3734 61.106 34.5421C61.2468 34.7108 61.3519 34.9063 61.415 35.1168C61.4781 35.3272 61.498 35.5483 61.4733 35.7667L61.4366 36.3071" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M59.3431 43.1523C57.5597 43.0312 56.1996 41.473 56.3207 39.6896L56.4308 38.0684C56.4697 37.4951 56.7348 36.9607 57.1677 36.5828C57.6006 36.2049 58.1659 36.0145 58.7392 36.0534L60.9009 36.2002C61.4742 36.2391 62.0086 36.5042 62.3865 36.9371C62.7643 37.37 62.9548 37.9353 62.9158 38.5086L62.8058 40.1299C62.6847 41.9133 61.1265 43.2734 59.3431 43.1523Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M59.3431 43.1523L59.6733 38.2885" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M56.7906 37.007C55.7549 36.8281 54.9526 35.8506 55.0297 34.7158" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M56.3574 39.1492L54.1957 39.0024" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M54.4426 43.3625C54.5196 42.2276 55.5044 41.3172 56.643 41.3402" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M64.741 35.3751C64.6639 36.5099 63.7369 37.37 62.7027 37.4083" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M65.0041 39.7362L62.8424 39.5895" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M62.2633 41.7218C63.3945 41.8529 64.2472 42.888 64.1702 44.0229" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip30_532_2318)">
<path d="M82.7844 34.9712L83.7314 36.0562" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M86.0228 36.2117L87.1078 35.2647" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M83.1367 37.7803L83.1734 37.2398C83.1785 37.0202 83.228 36.8038 83.319 36.6038C83.41 36.4038 83.5405 36.2243 83.7028 36.0761C83.8651 35.928 84.0557 35.8142 84.2631 35.7418C84.4706 35.6693 84.6905 35.6396 84.9098 35.6545C85.129 35.6694 85.3429 35.7285 85.5387 35.8284C85.7344 35.9282 85.9079 36.0667 86.0486 36.2354C86.1894 36.4041 86.2945 36.5996 86.3576 36.8101C86.4208 37.0205 86.4406 37.2416 86.4159 37.46L86.3792 38.0004" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M84.2857 44.8456C82.5023 44.7245 81.1423 43.1663 81.2633 41.3829L81.3734 39.7617C81.4123 39.1884 81.6774 38.654 82.1103 38.2761C82.5432 37.8982 83.1085 37.7078 83.6818 37.7467L85.8435 37.8935C86.4168 37.9324 86.9512 38.1975 87.3291 38.6304C87.707 39.0633 87.8974 39.6286 87.8585 40.2019L87.7484 41.8232C87.6273 43.6066 86.0691 44.9667 84.2857 44.8456Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M84.2858 44.8456L84.616 39.9818" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M81.7332 38.7003C80.6976 38.5214 79.8952 37.5439 79.9723 36.4091" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M81.3 40.8425L79.1383 40.6957" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M79.3852 45.0558C79.4623 43.9209 80.447 43.0105 81.5856 43.0335" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M89.6836 37.0684C89.6065 38.2032 88.6795 39.0633 87.6454 39.1016" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M89.9467 41.4295L87.785 41.2828" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M87.2059 43.4151C88.3372 43.5462 89.1898 44.5813 89.1128 45.7162" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip31_532_2318)">
<path d="M107.727 36.6645L108.674 37.7495" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M110.965 37.905L112.05 36.958" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M108.079 39.4736L108.116 38.9331C108.121 38.7135 108.17 38.4971 108.261 38.2971C108.352 38.0971 108.483 37.9176 108.645 37.7694C108.808 37.6213 108.998 37.5075 109.206 37.4351C109.413 37.3626 109.633 37.3329 109.852 37.3478C110.071 37.3627 110.285 37.4218 110.481 37.5217C110.677 37.6215 110.85 37.76 110.991 37.9287C111.132 38.0974 111.237 38.2929 111.3 38.5034C111.363 38.7138 111.383 38.9349 111.358 39.1533L111.322 39.6937" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M109.228 46.5389C107.445 46.4178 106.085 44.8596 106.206 43.0762L106.316 41.455C106.355 40.8816 106.62 40.3473 107.053 39.9694C107.486 39.5915 108.051 39.4011 108.624 39.44L110.786 39.5868C111.359 39.6257 111.894 39.8908 112.272 40.3237C112.649 40.7566 112.84 41.3219 112.801 41.8952L112.691 43.5165C112.57 45.2999 111.012 46.66 109.228 46.5389Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M109.228 46.5389L109.558 41.675" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M106.676 40.3936C105.64 40.2147 104.838 39.2372 104.915 38.1024" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M106.243 42.5358L104.081 42.389" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M104.328 46.7491C104.405 45.6142 105.39 44.7038 106.528 44.7268" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M114.626 38.7616C114.549 39.8965 113.622 40.7566 112.588 40.7949" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M114.889 43.1228L112.728 42.9761" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M112.148 45.1084C113.28 45.2395 114.132 46.2746 114.055 47.4095" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip32_532_2318)">
<path d="M132.67 38.3578L133.617 39.4428" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M135.908 39.5984L136.993 38.6514" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M133.022 41.1669L133.059 40.6265C133.064 40.4068 133.113 40.1905 133.204 39.9904C133.295 39.7904 133.426 39.6109 133.588 39.4628C133.75 39.3146 133.941 39.2009 134.148 39.1284C134.356 39.0559 134.576 39.0263 134.795 39.0411C135.014 39.056 135.228 39.1152 135.424 39.215C135.62 39.3148 135.793 39.4533 135.934 39.622C136.075 39.7908 136.18 39.9863 136.243 40.1967C136.306 40.4072 136.326 40.6283 136.301 40.8466L136.264 41.387" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M134.171 48.2323C132.387 48.1112 131.027 46.553 131.148 44.7696L131.259 43.1483C131.297 42.575 131.563 42.0406 131.995 41.6628C132.428 41.2849 132.994 41.0945 133.567 41.1334L135.729 41.2801C136.302 41.3191 136.836 41.5841 137.214 42.0171C137.592 42.45 137.783 43.0153 137.744 43.5886L137.634 45.2099C137.512 46.9932 135.954 48.3533 134.171 48.2323Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M134.171 48.2322L134.501 43.3684" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M131.618 42.0869C130.583 41.908 129.78 40.9306 129.857 39.7957" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M131.185 44.2292L129.023 44.0824" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M129.27 48.4425C129.347 47.3076 130.332 46.3972 131.471 46.4202" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M139.569 40.455C139.492 41.5899 138.565 42.4499 137.53 42.4883" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M139.832 44.8162L137.67 44.6694" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M137.091 46.8018C138.222 46.9328 139.075 47.968 138.998 49.1029" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip33_532_2318)">
<path d="M157.612 40.0511L158.559 41.1361" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M160.851 41.2917L161.936 40.3447" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M157.964 42.8602L158.001 42.3198C158.006 42.1001 158.056 41.8837 158.147 41.6837C158.238 41.4837 158.368 41.3042 158.531 41.1561C158.693 41.0079 158.883 40.8942 159.091 40.8217C159.298 40.7492 159.518 40.7196 159.738 40.7344C159.957 40.7493 160.171 40.8085 160.366 40.9083C160.562 41.0081 160.736 41.1466 160.876 41.3153C161.017 41.4841 161.122 41.6796 161.185 41.89C161.249 42.1005 161.268 42.3216 161.244 42.5399L161.207 43.0803" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M159.113 49.9256C157.33 49.8045 155.97 48.2463 156.091 46.4629L156.201 44.8416C156.24 44.2683 156.505 43.7339 156.938 43.3561C157.371 42.9782 157.936 42.7878 158.51 42.8267L160.671 42.9734C161.245 43.0124 161.779 43.2774 162.157 43.7104C162.535 44.1433 162.725 44.7086 162.686 45.2819L162.576 46.9032C162.455 48.6865 160.897 50.0466 159.113 49.9256Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M159.114 49.9255L159.444 45.0617" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M156.561 43.7802C155.525 43.6013 154.723 42.6239 154.8 41.489" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M156.128 45.9224L153.966 45.7757" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M154.213 50.1358C154.29 49.0009 155.275 48.0905 156.413 48.1135" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M164.511 42.1483C164.434 43.2832 163.507 44.1432 162.473 44.1816" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M164.775 46.5095L162.613 46.3627" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M162.034 48.4951C163.165 48.6261 164.018 49.6613 163.941 50.7962" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip34_532_2318)">
<path d="M182.555 41.7444L183.502 42.8294" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M185.793 42.985L186.878 42.038" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M182.907 44.5535L182.944 44.0131C182.949 43.7934 182.998 43.577 183.089 43.377C183.18 43.177 183.311 42.9975 183.473 42.8494C183.635 42.7012 183.826 42.5875 184.033 42.515C184.241 42.4425 184.461 42.4129 184.68 42.4277C184.899 42.4426 185.113 42.5018 185.309 42.6016C185.505 42.7014 185.678 42.8399 185.819 43.0086C185.96 43.1774 186.065 43.3729 186.128 43.5833C186.191 43.7938 186.211 44.0149 186.186 44.2332L186.149 44.7736" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M184.056 51.6189C182.273 51.4978 180.913 49.9396 181.034 48.1562L181.144 46.5349C181.183 45.9616 181.448 45.4272 181.881 45.0494C182.314 44.6715 182.879 44.4811 183.452 44.52L185.614 44.6667C186.187 44.7057 186.722 44.9707 187.099 45.4037C187.477 45.8366 187.668 46.4019 187.629 46.9752L187.519 48.5964C187.398 50.3798 185.839 51.7399 184.056 51.6189Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M184.056 51.6188L184.386 46.755" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M181.503 45.4735C180.468 45.2946 179.666 44.3172 179.743 43.1823" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M181.07 47.6157L178.909 47.469" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M179.156 51.829C179.233 50.6942 180.217 49.7838 181.356 49.8068" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M189.454 43.8416C189.377 44.9765 188.45 45.8365 187.416 45.8749" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M189.717 48.2028L187.555 48.056" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M186.976 50.1884C188.108 50.3194 188.96 51.3546 188.883 52.4895" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip35_532_2318)">
<path d="M207.497 43.4377L208.444 44.5227" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M210.736 44.6783L211.821 43.7313" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M207.85 46.2468L207.886 45.7064C207.891 45.4867 207.941 45.2703 208.032 45.0703C208.123 44.8703 208.253 44.6908 208.416 44.5427C208.578 44.3945 208.769 44.2808 208.976 44.2083C209.183 44.1358 209.403 44.1062 209.623 44.121C209.842 44.1359 210.056 44.1951 210.251 44.2949C210.447 44.3947 210.621 44.5332 210.761 44.7019C210.902 44.8707 211.007 45.0662 211.07 45.2766C211.134 45.4871 211.153 45.7082 211.129 45.9265L211.092 46.4669" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M208.999 53.3122C207.215 53.1911 205.855 51.6329 205.976 49.8495L206.086 48.2282C206.125 47.6549 206.39 47.1205 206.823 46.7427C207.256 46.3648 207.821 46.1744 208.395 46.2133L210.556 46.36C211.13 46.399 211.664 46.664 212.042 47.097C212.42 47.5299 212.61 48.0952 212.571 48.6685L212.461 50.2897C212.34 52.0731 210.782 53.4332 208.999 53.3122Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M208.999 53.3121L209.329 48.4483" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M206.446 47.1668C205.41 46.9879 204.608 46.0105 204.685 44.8756" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M206.013 49.309L203.851 49.1623" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M204.098 53.5223C204.175 52.3875 205.16 51.4771 206.298 51.5001" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M214.397 45.5349C214.32 46.6698 213.392 47.5298 212.358 47.5682" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M214.66 49.8961L212.498 49.7493" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M211.919 51.8817C213.05 52.0127 213.903 53.0479 213.826 54.1828" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip36_532_2318)">
<path d="M232.44 45.131L233.387 46.216" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M235.678 46.3716L236.763 45.4246" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M232.792 47.9401L232.829 47.3997C232.834 47.18 232.883 46.9636 232.974 46.7636C233.065 46.5636 233.196 46.3841 233.358 46.236C233.521 46.0878 233.711 45.9741 233.919 45.9016C234.126 45.8291 234.346 45.7995 234.565 45.8143C234.784 45.8292 234.998 45.8884 235.194 45.9882C235.39 46.088 235.563 46.2265 235.704 46.3952C235.845 46.564 235.95 46.7595 236.013 46.9699C236.076 47.1804 236.096 47.4015 236.071 47.6198L236.035 48.1602" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M233.941 55.0054C232.158 54.8844 230.798 53.3262 230.919 51.5428L231.029 49.9215C231.068 49.3482 231.333 48.8138 231.766 48.4359C232.199 48.058 232.764 47.8676 233.337 47.9065L235.499 48.0533C236.072 48.0922 236.607 48.3573 236.985 48.7902C237.362 49.2231 237.553 49.7884 237.514 50.3617L237.404 51.983C237.283 53.7664 235.725 55.1265 233.941 55.0054Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M233.941 55.0054L234.271 50.1416" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M231.389 48.8601C230.353 48.6812 229.551 47.7038 229.628 46.5689" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M230.956 51.0023L228.794 50.8556" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M229.041 55.2156C229.118 54.0808 230.103 53.1704 231.241 53.1934" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M239.339 47.2282C239.262 48.3631 238.335 49.2231 237.301 49.2615" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M239.602 51.5894L237.441 51.4426" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M236.862 53.575C237.993 53.706 238.845 54.7412 238.768 55.8761" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip37_532_2318)">
<path d="M257.383 46.8244L258.33 47.9094" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M260.621 48.0649L261.706 47.1179" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M257.735 49.6335L257.771 49.093C257.776 48.8734 257.826 48.657 257.917 48.457C258.008 48.257 258.139 48.0775 258.301 47.9293C258.463 47.7812 258.654 47.6674 258.861 47.595C259.069 47.5225 259.289 47.4928 259.508 47.5077C259.727 47.5226 259.941 47.5817 260.137 47.6816C260.332 47.7814 260.506 47.9199 260.647 48.0886C260.787 48.2573 260.893 48.4528 260.956 48.6633C261.019 48.8738 261.039 49.0948 261.014 49.3132L260.977 49.8536" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M258.884 56.6988C257.1 56.5777 255.74 55.0195 255.861 53.2361L255.971 51.6148C256.01 51.0415 256.275 50.5072 256.708 50.1293C257.141 49.7514 257.707 49.561 258.28 49.5999L260.442 49.7467C261.015 49.7856 261.549 50.0507 261.927 50.4836C262.305 50.9165 262.495 51.4818 262.457 52.0551L262.346 53.6764C262.225 55.4598 260.667 56.8199 258.884 56.6988Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M258.884 56.6988L259.214 51.835" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M256.331 50.5534C255.296 50.3745 254.493 49.3971 254.57 48.2622" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M255.898 52.6957L253.736 52.549" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M253.983 56.909C254.06 55.7741 255.045 54.8637 256.184 54.8867" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M264.282 48.9215C264.205 50.0564 263.278 50.9164 262.244 50.9548" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M264.545 53.2827L262.383 53.136" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M261.804 55.2683C262.935 55.3994 263.788 56.4345 263.711 57.5694" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip38_532_2318)">
<path d="M282.325 48.5177L283.272 49.6027" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M285.563 49.7582L286.648 48.8112" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M282.677 51.3268L282.714 50.7863C282.719 50.5667 282.769 50.3503 282.86 50.1503C282.951 49.9503 283.081 49.7708 283.243 49.6226C283.406 49.4745 283.596 49.3607 283.804 49.2883C284.011 49.2158 284.231 49.1861 284.45 49.201C284.67 49.2159 284.884 49.275 285.079 49.3749C285.275 49.4747 285.448 49.6132 285.589 49.7819C285.73 49.9506 285.835 50.1461 285.898 50.3566C285.961 50.5671 285.981 50.7881 285.957 51.0065L285.92 51.5469" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M283.826 58.3921C282.043 58.271 280.683 56.7128 280.804 54.9294L280.914 53.3081C280.953 52.7348 281.218 52.2005 281.651 51.8226C282.084 51.4447 282.649 51.2543 283.222 51.2932L285.384 51.44C285.957 51.4789 286.492 51.744 286.87 52.1769C287.248 52.6098 287.438 53.1751 287.399 53.7484L287.289 55.3697C287.168 57.1531 285.61 58.5132 283.826 58.3921Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M283.826 58.3921L284.157 53.5283" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M281.274 52.2467C280.238 52.0678 279.436 51.0904 279.513 49.9555" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M280.841 54.389L278.679 54.2422" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M278.926 58.6023C279.003 57.4674 279.988 56.557 281.126 56.58" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M289.224 50.6148C289.147 51.7497 288.22 52.6097 287.186 52.6481" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M289.487 54.976L287.326 54.8293" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M286.747 56.9616C287.878 57.0927 288.731 58.1278 288.653 59.2627" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip39_532_2318)">
<path d="M307.268 50.211L308.215 51.296" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M310.506 51.4515L311.591 50.5045" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M307.62 53.0201L307.657 52.4796C307.662 52.26 307.711 52.0436 307.802 51.8436C307.893 51.6436 308.024 51.4641 308.186 51.3159C308.348 51.1678 308.539 51.054 308.746 50.9816C308.954 50.9091 309.174 50.8794 309.393 50.8943C309.612 50.9092 309.826 50.9683 310.022 51.0682C310.218 51.168 310.391 51.3065 310.532 51.4752C310.673 51.6439 310.778 51.8394 310.841 52.0499C310.904 52.2604 310.924 52.4814 310.899 52.6998L310.862 53.2402" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M308.769 60.0854C306.986 59.9643 305.626 58.4061 305.747 56.6227L305.857 55.0014C305.896 54.4281 306.161 53.8938 306.594 53.5159C307.026 53.138 307.592 52.9476 308.165 52.9865L310.327 53.1333C310.9 53.1722 311.434 53.4373 311.812 53.8702C312.19 54.3031 312.381 54.8684 312.342 55.4417L312.232 57.063C312.111 58.8464 310.552 60.2064 308.769 60.0854Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M308.769 60.0854L309.099 55.2216" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M306.216 53.94C305.181 53.7611 304.378 52.7837 304.456 51.6488" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M305.783 56.0823L303.622 55.9355" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M303.869 60.2956C303.946 59.1607 304.93 58.2503 306.069 58.2733" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M314.167 52.3081C314.09 53.443 313.163 54.303 312.129 54.3414" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M314.43 56.6693L312.268 56.5226" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M311.689 58.6549C312.82 58.786 313.673 59.8211 313.596 60.956" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip40_532_2318)">
<path d="M332.21 51.9043L333.157 52.9893" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M335.449 53.1448L336.534 52.1978" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M332.562 54.7134L332.599 54.1729C332.604 53.9533 332.654 53.7369 332.745 53.5369C332.836 53.3369 332.966 53.1574 333.129 53.0092C333.291 52.8611 333.481 52.7473 333.689 52.6749C333.896 52.6024 334.116 52.5727 334.336 52.5876C334.555 52.6025 334.769 52.6616 334.964 52.7615C335.16 52.8613 335.334 52.9998 335.474 53.1685C335.615 53.3372 335.72 53.5327 335.783 53.7432C335.847 53.9537 335.866 54.1747 335.842 54.3931L335.805 54.9335" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M333.712 61.7787C331.928 61.6576 330.568 60.0994 330.689 58.316L330.799 56.6947C330.838 56.1214 331.103 55.5871 331.536 55.2092C331.969 54.8313 332.534 54.6409 333.108 54.6798L335.269 54.8266C335.843 54.8655 336.377 55.1306 336.755 55.5635C337.133 55.9964 337.323 56.5617 337.284 57.135L337.174 58.7563C337.053 60.5397 335.495 61.8997 333.712 61.7787Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M333.712 61.7787L334.042 56.9149" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M331.159 55.6333C330.123 55.4544 329.321 54.477 329.398 53.3421" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M330.726 57.7756L328.564 57.6288" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M328.811 61.9889C328.888 60.854 329.873 59.9436 331.011 59.9666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M339.109 54.0014C339.032 55.1363 338.105 55.9963 337.071 56.0347" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M339.373 58.3626L337.211 58.2159" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M336.632 60.3482C337.763 60.4793 338.616 61.5144 338.539 62.6493" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip41_532_2318)">
<path d="M357.153 53.5977L358.1 54.6826" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M360.391 54.8382L361.476 53.8911" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M357.505 56.4067L357.542 55.8663C357.547 55.6466 357.596 55.4302 357.687 55.2302C357.778 55.0302 357.909 54.8507 358.071 54.7026C358.233 54.5544 358.424 54.4407 358.631 54.3682C358.839 54.2957 359.059 54.266 359.278 54.2809C359.497 54.2958 359.711 54.3549 359.907 54.4548C360.103 54.5546 360.276 54.6931 360.417 54.8618C360.558 55.0305 360.663 55.226 360.726 55.4365C360.789 55.647 360.809 55.8681 360.784 56.0864L360.748 56.6268" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M358.654 63.472C356.871 63.3509 355.511 61.7927 355.632 60.0093L355.742 58.3881C355.781 57.8148 356.046 57.2804 356.479 56.9025C356.912 56.5246 357.477 56.3342 358.05 56.3731L360.212 56.5199C360.785 56.5588 361.32 56.8239 361.697 57.2568C362.075 57.6897 362.266 58.255 362.227 58.8283L362.117 60.4496C361.996 62.233 360.438 63.5931 358.654 63.472Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M358.654 63.472L358.984 58.6082" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M356.102 57.3267C355.066 57.1478 354.264 56.1703 354.341 55.0355" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M355.668 59.4689L353.507 59.3222" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M353.754 63.6822C353.831 62.5473 354.815 61.637 355.954 61.66" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M364.052 55.6948C363.975 56.8296 363.048 57.6897 362.014 57.728" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M364.315 60.0559L362.154 59.9092" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M361.574 62.0415C362.706 62.1726 363.558 63.2078 363.481 64.3426" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip42_532_2318)">
<path d="M382.095 55.291L383.042 56.3759" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M385.334 56.5315L386.419 55.5844" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M382.448 58.1L382.484 57.5596C382.489 57.3399 382.539 57.1235 382.63 56.9235C382.721 56.7235 382.852 56.544 383.014 56.3959C383.176 56.2477 383.367 56.134 383.574 56.0615C383.782 55.989 384.002 55.9593 384.221 55.9742C384.44 55.9891 384.654 56.0482 384.85 56.1481C385.045 56.2479 385.219 56.3864 385.36 56.5551C385.5 56.7238 385.605 56.9193 385.669 57.1298C385.732 57.3403 385.752 57.5614 385.727 57.7797L385.69 58.3201" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M383.597 65.1653C381.813 65.0442 380.453 63.486 380.574 61.7026L380.684 60.0814C380.723 59.5081 380.988 58.9737 381.421 58.5958C381.854 58.2179 382.42 58.0275 382.993 58.0664L385.155 58.2132C385.728 58.2521 386.262 58.5172 386.64 58.9501C387.018 59.383 387.208 59.9483 387.169 60.5216L387.059 62.1429C386.938 63.9263 385.38 65.2864 383.597 65.1653Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M383.597 65.1653L383.927 60.3015" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M381.044 59.02C380.009 58.8411 379.206 57.8636 379.283 56.7288" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M380.611 61.1622L378.449 61.0155" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M378.696 65.3755C378.773 64.2406 379.758 63.3302 380.897 63.3533" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M388.995 57.3881C388.918 58.5229 387.991 59.383 386.956 59.4213" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M389.258 61.7492L387.096 61.6025" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M386.517 63.7348C387.648 63.8659 388.501 64.9011 388.424 66.0359" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip43_532_2318)">
<path d="M56.4194 54.2297L57.3665 55.3146" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M59.6578 55.4702L60.7428 54.5232" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M56.7717 57.0387L56.8084 56.4983C56.8135 56.2786 56.863 56.0622 56.954 55.8622C57.045 55.6622 57.1755 55.4827 57.3378 55.3346C57.5001 55.1864 57.6907 55.0727 57.8981 55.0002C58.1056 54.9277 58.3255 54.8981 58.5448 54.9129C58.764 54.9278 58.9779 54.987 59.1737 55.0868C59.3694 55.1866 59.5429 55.3251 59.6837 55.4938C59.8244 55.6626 59.9295 55.8581 59.9926 56.0685C60.0558 56.279 60.0756 56.5001 60.0509 56.7184L60.0142 57.2588" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M57.9207 64.104C56.1373 63.983 54.7773 62.4248 54.8983 60.6414L55.0084 59.0201C55.0473 58.4468 55.3124 57.9124 55.7453 57.5345C56.1782 57.1567 56.7435 56.9662 57.3168 57.0052L59.4785 57.1519C60.0518 57.1908 60.5862 57.4559 60.9641 57.8888C61.342 58.3217 61.5324 58.887 61.4935 59.4604L61.3834 61.0816C61.2623 62.865 59.7041 64.2251 57.9207 64.104Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M57.9208 64.104L58.251 59.2402" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M55.3682 57.9587C54.3326 57.7798 53.5303 56.8024 53.6073 55.6675" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M54.935 60.1009L52.7733 59.9542" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M53.0202 64.3142C53.0973 63.1794 54.082 62.269 55.2206 62.292" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M63.3186 56.3268C63.2416 57.4617 62.3145 58.3217 61.2804 58.3601" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M63.5817 60.688L61.42 60.5412" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M60.8409 62.6736C61.9722 62.8046 62.8248 63.8398 62.7478 64.9747" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip44_532_2318)">
<path d="M81.3621 55.923L82.3091 57.0079" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M84.6005 57.1635L85.6854 56.2165" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M81.7143 58.732L81.751 58.1916C81.7561 57.9719 81.8056 57.7555 81.8966 57.5555C81.9876 57.3555 82.1182 57.176 82.2804 57.0279C82.4427 56.8797 82.6333 56.766 82.8408 56.6935C83.0482 56.621 83.2682 56.5914 83.4874 56.6062C83.7066 56.6211 83.9206 56.6803 84.1163 56.7801C84.312 56.8799 84.4855 57.0184 84.6263 57.1871C84.767 57.3559 84.8721 57.5514 84.9353 57.7618C84.9984 57.9723 85.0182 58.1934 84.9936 58.4117L84.9569 58.9521" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M82.8634 65.7973C81.08 65.6763 79.7199 64.1181 79.841 62.3347L79.951 60.7134C79.9899 60.1401 80.255 59.6057 80.6879 59.2278C81.1209 58.85 81.6862 58.6595 82.2595 58.6985L84.4212 58.8452C84.9945 58.8841 85.5288 59.1492 85.9067 59.5821C86.2846 60.015 86.475 60.5803 86.4361 61.1536L86.326 62.7749C86.205 64.5583 84.6468 65.9184 82.8634 65.7973Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M82.8634 65.7973L83.1936 60.9335" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M80.3109 59.652C79.2752 59.4731 78.4729 58.4957 78.5499 57.3608" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M79.8776 61.7942L77.7159 61.6475" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M77.9629 66.0075C78.0399 64.8727 79.0247 63.9623 80.1632 63.9853" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M88.2612 58.0201C88.1842 59.155 87.2571 60.015 86.223 60.0534" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M88.5244 62.3813L86.3627 62.2345" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M85.7836 64.3669C86.9148 64.4979 87.7675 65.5331 87.6904 66.668" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip45_532_2318)">
<path d="M106.305 57.6163L107.252 58.7013" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M109.543 58.8568L110.628 57.9098" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M106.657 60.4253L106.694 59.8849C106.699 59.6652 106.748 59.4489 106.839 59.2489C106.93 59.0489 107.061 58.8694 107.223 58.7212C107.385 58.5731 107.576 58.4593 107.783 58.3868C107.991 58.3144 108.211 58.2847 108.43 58.2996C108.649 58.3145 108.863 58.3736 109.059 58.4734C109.255 58.5733 109.428 58.7117 109.569 58.8805C109.71 59.0492 109.815 59.2447 109.878 59.4552C109.941 59.6656 109.961 59.8867 109.936 60.105L109.899 60.6455" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M107.806 67.4907C106.022 67.3696 104.662 65.8114 104.783 64.028L104.894 62.4067C104.932 61.8334 105.198 61.299 105.63 60.9212C106.063 60.5433 106.629 60.3529 107.202 60.3918L109.364 60.5385C109.937 60.5775 110.471 60.8425 110.849 61.2754C111.227 61.7084 111.418 62.2737 111.379 62.847L111.269 64.4682C111.147 66.2516 109.589 67.6117 107.806 67.4907Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M107.806 67.4907L108.136 62.6269" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M105.253 61.3453C104.218 61.1664 103.415 60.189 103.492 59.0541" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M104.82 63.4876L102.658 63.3408" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M102.905 67.7009C102.982 66.566 103.967 65.6556 105.106 65.6786" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M113.204 59.7134C113.127 60.8483 112.2 61.7083 111.166 61.7467" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M113.467 64.0746L111.305 63.9278" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M110.726 66.0602C111.857 66.1913 112.71 67.2264 112.633 68.3613" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip46_532_2318)">
<path d="M131.247 59.3096L132.194 60.3946" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M134.486 60.5501L135.571 59.6031" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M131.599 62.1186L131.636 61.5782C131.641 61.3585 131.691 61.1422 131.782 60.9422C131.873 60.7422 132.003 60.5627 132.166 60.4145C132.328 60.2663 132.518 60.1526 132.726 60.0801C132.933 60.0077 133.153 59.978 133.373 59.9929C133.592 60.0077 133.806 60.0669 134.001 60.1667C134.197 60.2666 134.371 60.405 134.511 60.5738C134.652 60.7425 134.757 60.938 134.82 61.1485C134.884 61.3589 134.903 61.58 134.879 61.7983L134.842 62.3388" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M132.748 69.1839C130.965 69.0629 129.605 67.5047 129.726 65.7213L129.836 64.1C129.875 63.5267 130.14 62.9923 130.573 62.6145C131.006 62.2366 131.571 62.0462 132.145 62.0851L134.306 62.2318C134.88 62.2708 135.414 62.5358 135.792 62.9687C136.17 63.4017 136.36 63.967 136.321 64.5403L136.211 66.1615C136.09 67.9449 134.532 69.305 132.748 69.1839Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M132.749 69.184L133.079 64.3202" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M130.196 63.0386C129.16 62.8597 128.358 61.8823 128.435 60.7474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M129.763 65.1809L127.601 65.0341" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M127.848 69.3942C127.925 68.2593 128.91 67.3489 130.048 67.3719" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M138.146 61.4067C138.069 62.5416 137.142 63.4016 136.108 63.44" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M138.409 65.7679L136.248 65.6211" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M135.669 67.7535C136.8 67.8846 137.653 68.9197 137.576 70.0546" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip47_532_2318)">
<path d="M156.19 61.0029L157.137 62.0879" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M159.428 62.2434L160.513 61.2964" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M156.542 63.812L156.579 63.2715C156.584 63.0519 156.633 62.8355 156.724 62.6355C156.815 62.4355 156.946 62.256 157.108 62.1078C157.27 61.9597 157.461 61.8459 157.669 61.7735C157.876 61.701 158.096 61.6713 158.315 61.6862C158.534 61.7011 158.748 61.7602 158.944 61.8601C159.14 61.9599 159.313 62.0984 159.454 62.2671C159.595 62.4358 159.7 62.6313 159.763 62.8418C159.826 63.0523 159.846 63.2733 159.821 63.4917L159.785 64.0321" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M157.691 70.8773C155.908 70.7562 154.548 69.198 154.669 67.4146L154.779 65.7933C154.818 65.22 155.083 64.6857 155.516 64.3078C155.949 63.9299 156.514 63.7395 157.087 63.7784L159.249 63.9252C159.822 63.9641 160.357 64.2292 160.734 64.6621C161.112 65.095 161.303 65.6603 161.264 66.2336L161.154 67.8549C161.033 69.6383 159.475 70.9984 157.691 70.8773Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M157.691 70.8773L158.021 66.0135" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M155.139 64.7319C154.103 64.553 153.301 63.5756 153.378 62.4407" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M154.705 66.8742L152.544 66.7274" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M152.791 71.0875C152.868 69.9526 153.852 69.0422 154.991 69.0652" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M163.089 63.1C163.012 64.2349 162.085 65.0949 161.051 65.1333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M163.352 67.4612L161.19 67.3145" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M160.611 69.4468C161.743 69.5779 162.595 70.613 162.518 71.7479" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip48_532_2318)">
<path d="M181.132 62.6962L182.079 63.7812" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M184.371 63.9367L185.456 62.9897" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M181.485 65.5053L181.521 64.9648C181.526 64.7452 181.576 64.5288 181.667 64.3288C181.758 64.1288 181.888 63.9493 182.051 63.8011C182.213 63.653 182.404 63.5392 182.611 63.4668C182.818 63.3943 183.038 63.3646 183.258 63.3795C183.477 63.3944 183.691 63.4535 183.887 63.5534C184.082 63.6532 184.256 63.7917 184.397 63.9604C184.537 64.1291 184.642 64.3246 184.706 64.5351C184.769 64.7456 184.788 64.9666 184.764 65.185L184.727 65.7254" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M182.634 72.5706C180.85 72.4495 179.49 70.8913 179.611 69.1079L179.721 67.4866C179.76 66.9133 180.025 66.379 180.458 66.0011C180.891 65.6232 181.456 65.4328 182.03 65.4717L184.191 65.6185C184.765 65.6574 185.299 65.9225 185.677 66.3554C186.055 66.7883 186.245 67.3536 186.206 67.9269L186.096 69.5482C185.975 71.3316 184.417 72.6916 182.634 72.5706Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M182.634 72.5706L182.964 67.7068" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M180.081 66.4252C179.045 66.2463 178.243 65.2689 178.32 64.134" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M179.648 68.5675L177.486 68.4207" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M177.733 72.7808C177.81 71.6459 178.795 70.7355 179.934 70.7585" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M188.032 64.7933C187.955 65.9282 187.028 66.7882 185.993 66.8266" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M188.295 69.1545L186.133 69.0078" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M185.554 71.1401C186.685 71.2712 187.538 72.3063 187.461 73.4412" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip49_532_2318)">
<path d="M206.075 64.3895L207.022 65.4745" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M209.313 65.63L210.398 64.683" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M206.427 67.1986L206.464 66.6581C206.469 66.4385 206.518 66.2221 206.609 66.0221C206.7 65.8221 206.831 65.6426 206.993 65.4944C207.156 65.3463 207.346 65.2325 207.554 65.1601C207.761 65.0876 207.981 65.0579 208.2 65.0728C208.419 65.0877 208.633 65.1468 208.829 65.2467C209.025 65.3465 209.198 65.485 209.339 65.6537C209.48 65.8224 209.585 66.0179 209.648 66.2284C209.711 66.4389 209.731 66.6599 209.706 66.8783L209.67 67.4187" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M207.576 74.2639C205.793 74.1428 204.433 72.5846 204.554 70.8012L204.664 69.1799C204.703 68.6066 204.968 68.0723 205.401 67.6944C205.834 67.3165 206.399 67.1261 206.972 67.165L209.134 67.3118C209.707 67.3507 210.242 67.6158 210.62 68.0487C210.997 68.4816 211.188 69.0469 211.149 69.6202L211.039 71.2415C210.918 73.0249 209.36 74.3849 207.576 74.2639Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M207.576 74.2639L207.906 69.4001" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M205.024 68.1185C203.988 67.9396 203.186 66.9622 203.263 65.8273" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M204.591 70.2608L202.429 70.114" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M202.676 74.4741C202.753 73.3392 203.738 72.4288 204.876 72.4518" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M212.974 66.4866C212.897 67.6215 211.97 68.4815 210.936 68.5199" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M213.237 70.8478L211.076 70.701" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M210.497 72.8334C211.628 72.9645 212.48 73.9996 212.403 75.1345" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip50_532_2318)">
<path d="M231.018 66.0829L231.965 67.1678" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M234.256 67.3234L235.341 66.3763" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M231.37 68.8919L231.406 68.3515C231.412 68.1318 231.461 67.9154 231.552 67.7154C231.643 67.5154 231.774 67.3359 231.936 67.1878C232.098 67.0396 232.289 66.9259 232.496 66.8534C232.704 66.7809 232.924 66.7512 233.143 66.7661C233.362 66.781 233.576 66.8401 233.772 66.94C233.967 67.0398 234.141 67.1783 234.282 67.347C234.422 67.5157 234.528 67.7112 234.591 67.9217C234.654 68.1322 234.674 68.3533 234.649 68.5716L234.612 69.112" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M232.519 75.9572C230.735 75.8361 229.375 74.2779 229.496 72.4945L229.607 70.8733C229.645 70.3 229.911 69.7656 230.343 69.3877C230.776 69.0098 231.342 68.8194 231.915 68.8583L234.077 69.0051C234.65 69.044 235.184 69.3091 235.562 69.742C235.94 70.1749 236.131 70.7402 236.092 71.3135L235.982 72.9348C235.86 74.7182 234.302 76.0783 232.519 75.9572Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M232.519 75.9572L232.849 71.0934" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M229.966 69.8119C228.931 69.633 228.128 68.6555 228.205 67.5207" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M229.533 71.9541L227.371 71.8074" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M227.618 76.1674C227.695 75.0325 228.68 74.1221 229.819 74.1452" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M237.917 68.18C237.84 69.3148 236.913 70.1749 235.879 70.2132" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M238.18 72.5411L236.018 72.3944" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M235.439 74.5267C236.57 74.6578 237.423 75.693 237.346 76.8278" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip51_532_2318)">
<path d="M255.96 67.7762L256.907 68.8611" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M259.199 69.0167L260.284 68.0696" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M256.312 70.5852L256.349 70.0448C256.354 69.8251 256.404 69.6087 256.495 69.4087C256.586 69.2087 256.716 69.0292 256.878 68.8811C257.041 68.7329 257.231 68.6192 257.439 68.5467C257.646 68.4742 257.866 68.4445 258.085 68.4594C258.305 68.4743 258.519 68.5334 258.714 68.6333C258.91 68.7331 259.084 68.8716 259.224 69.0403C259.365 69.209 259.47 69.4045 259.533 69.615C259.596 69.8255 259.616 70.0466 259.592 70.2649L259.555 70.8053" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M257.461 77.6505C255.678 77.5294 254.318 75.9712 254.439 74.1878L254.549 72.5666C254.588 71.9933 254.853 71.4589 255.286 71.081C255.719 70.7031 256.284 70.5127 256.858 70.5516L259.019 70.6984C259.593 70.7373 260.127 71.0024 260.505 71.4353C260.883 71.8682 261.073 72.4335 261.034 73.0068L260.924 74.6281C260.803 76.4115 259.245 77.7716 257.461 77.6505Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M257.461 77.6505L257.792 72.7867" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M254.909 71.5052C253.873 71.3263 253.071 70.3488 253.148 69.214" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M254.476 73.6474L252.314 73.5007" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M252.561 77.8607C252.638 76.7258 253.623 75.8154 254.761 75.8385" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M262.859 69.8733C262.782 71.0081 261.855 71.8682 260.821 71.9065" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M263.123 74.2344L260.961 74.0877" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M260.382 76.22C261.513 76.3511 262.366 77.3863 262.289 78.5211" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip52_532_2318)">
<path d="M280.903 69.4695L281.85 70.5545" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M284.141 70.71L285.226 69.763" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M281.255 72.2785L281.292 71.7381C281.297 71.5184 281.346 71.3021 281.437 71.102C281.528 70.902 281.659 70.7225 281.821 70.5744C281.983 70.4262 282.174 70.3125 282.381 70.24C282.589 70.1676 282.809 70.1379 283.028 70.1527C283.247 70.1676 283.461 70.2268 283.657 70.3266C283.853 70.4265 284.026 70.5649 284.167 70.7336C284.308 70.9024 284.413 71.0979 284.476 71.3083C284.539 71.5188 284.559 71.7399 284.534 71.9582L284.497 72.4986" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M282.404 79.3438C280.621 79.2228 279.261 77.6646 279.382 75.8812L279.492 74.2599C279.531 73.6866 279.796 73.1522 280.229 72.7743C280.662 72.3965 281.227 72.206 281.8 72.245L283.962 72.3917C284.535 72.4306 285.069 72.6957 285.447 73.1286C285.825 73.5615 286.016 74.1268 285.977 74.7002L285.867 76.3214C285.746 78.1048 284.187 79.4649 282.404 79.3438Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M282.404 79.3438L282.734 74.48" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M279.851 73.1985C278.816 73.0196 278.013 72.0422 278.091 70.9073" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M279.418 75.3408L277.257 75.194" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M277.504 79.5541C277.581 78.4192 278.565 77.5088 279.704 77.5318" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M287.802 71.5666C287.725 72.7015 286.798 73.5615 285.764 73.5999" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M288.065 75.9278L285.903 75.781" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M285.324 77.9134C286.455 78.0444 287.308 79.0796 287.231 80.2145" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip53_532_2318)">
<path d="M305.845 71.1628L306.792 72.2477" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M309.084 72.4033L310.169 71.4563" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M306.198 73.9718L306.234 73.4314C306.239 73.2117 306.289 72.9954 306.38 72.7953C306.471 72.5953 306.601 72.4158 306.764 72.2677C306.926 72.1195 307.117 72.0058 307.324 71.9333C307.531 71.8609 307.751 71.8312 307.971 71.846C308.19 71.8609 308.404 71.9201 308.6 72.0199C308.795 72.1198 308.969 72.2582 309.109 72.4269C309.25 72.5957 309.355 72.7912 309.418 73.0016C309.482 73.2121 309.501 73.4332 309.477 73.6515L309.44 74.1919" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M307.347 81.0371C305.563 80.9161 304.203 79.3579 304.324 77.5745L304.434 75.9532C304.473 75.3799 304.738 74.8455 305.171 74.4676C305.604 74.0898 306.169 73.8993 306.743 73.9383L308.904 74.085C309.478 74.1239 310.012 74.389 310.39 74.8219C310.768 75.2548 310.958 75.8201 310.919 76.3935L310.809 78.0147C310.688 79.7981 309.13 81.1582 307.347 81.0371Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M307.347 81.0371L307.677 76.1733" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M304.794 74.8918C303.758 74.7129 302.956 73.7355 303.033 72.6006" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M304.361 77.0341L302.199 76.8873" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M302.446 81.2474C302.523 80.1125 303.508 79.2021 304.647 79.2251" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M312.745 73.2599C312.668 74.3948 311.74 75.2548 310.706 75.2932" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M313.008 77.6211L310.846 77.4743" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M310.267 79.6067C311.398 79.7377 312.251 80.7729 312.174 81.9078" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip54_532_2318)">
<path d="M330.788 72.8561L331.735 73.941" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M334.026 74.0966L335.111 73.1496" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M331.14 75.6651L331.177 75.1247C331.182 74.905 331.231 74.6887 331.322 74.4886C331.413 74.2886 331.544 74.1091 331.706 73.961C331.868 73.8128 332.059 73.6991 332.267 73.6266C332.474 73.5541 332.694 73.5245 332.913 73.5393C333.132 73.5542 333.346 73.6134 333.542 73.7132C333.738 73.813 333.911 73.9515 334.052 74.1202C334.193 74.289 334.298 74.4845 334.361 74.6949C334.424 74.9054 334.444 75.1265 334.419 75.3448L334.383 75.8852" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M332.289 82.7304C330.506 82.6094 329.146 81.0512 329.267 79.2678L329.377 77.6465C329.416 77.0732 329.681 76.5388 330.114 76.1609C330.547 75.7831 331.112 75.5926 331.685 75.6316L333.847 75.7783C334.42 75.8172 334.955 76.0823 335.333 76.5152C335.71 76.9481 335.901 77.5134 335.862 78.0868L335.752 79.708C335.631 81.4914 334.073 82.8515 332.289 82.7304Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M332.289 82.7304L332.619 77.8666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M329.737 76.5851C328.701 76.4062 327.899 75.4288 327.976 74.2939" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M329.303 78.7274L327.142 78.5806" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M327.389 82.9407C327.466 81.8058 328.451 80.8954 329.589 80.9184" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M337.687 74.9532C337.61 76.0881 336.683 76.9481 335.649 76.9865" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M337.95 79.3144L335.789 79.1676" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M335.209 81.3C336.341 81.431 337.193 82.4662 337.116 83.6011" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g clip-path="url(#clip55_532_2318)">
<path d="M355.73 74.5494L356.677 75.6344" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M358.969 75.7899L360.054 74.8429" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M356.083 77.3584L356.119 76.818C356.124 76.5983 356.174 76.382 356.265 76.182C356.356 75.982 356.487 75.8025 356.649 75.6543C356.811 75.5062 357.002 75.3924 357.209 75.3199C357.417 75.2475 357.637 75.2178 357.856 75.2327C358.075 75.2476 358.289 75.3067 358.485 75.4065C358.68 75.5064 358.854 75.6448 358.995 75.8136C359.135 75.9823 359.24 76.1778 359.304 76.3883C359.367 76.5987 359.387 76.8198 359.362 77.0382L359.325 77.5786" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M357.232 84.4238C355.448 84.3027 354.088 82.7445 354.209 80.9611L354.319 79.3398C354.358 78.7665 354.623 78.2321 355.056 77.8543C355.489 77.4764 356.055 77.286 356.628 77.3249L358.79 77.4716C359.363 77.5106 359.897 77.7756 360.275 78.2086C360.653 78.6415 360.843 79.2068 360.805 79.7801L360.694 81.4014C360.573 83.1847 359.015 84.5448 357.232 84.4238Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M357.232 84.4238L357.562 79.56" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M354.679 78.2784C353.644 78.0995 352.841 77.1221 352.918 75.9872" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M354.246 80.4207L352.084 80.2739" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M352.331 84.634C352.408 83.4991 353.393 82.5887 354.532 82.6117" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M362.63 76.6465C362.553 77.7814 361.626 78.6414 360.591 78.6798" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M362.893 81.0077L360.731 80.8609" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M360.152 82.9933C361.283 83.1244 362.136 84.1595 362.059 85.2944" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<g opacity="0.6" clip-path="url(#clip56_532_2318)">
<path d="M380.673 76.2427L381.62 77.3277" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M383.911 77.4832L384.996 76.5362" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M381.025 79.0517L381.062 78.5113C381.067 78.2916 381.117 78.0753 381.208 77.8753C381.299 77.6753 381.429 77.4958 381.591 77.3476C381.754 77.1995 381.944 77.0857 382.152 77.0132C382.359 76.9408 382.579 76.9111 382.798 76.926C383.018 76.9409 383.232 77 383.427 77.0998C383.623 77.1997 383.796 77.3381 383.937 77.5069C384.078 77.6756 384.183 77.8711 384.246 78.0816C384.309 78.292 384.329 78.5131 384.305 78.7314L384.268 79.2719" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M382.174 86.1171C380.391 85.996 379.031 84.4378 379.152 82.6544L379.262 81.0331C379.301 80.4598 379.566 79.9254 379.999 79.5476C380.432 79.1697 380.997 78.9793 381.571 79.0182L383.732 79.1649C384.306 79.2039 384.84 79.4689 385.218 79.9019C385.596 80.3348 385.786 80.9001 385.747 81.4734L385.637 83.0946C385.516 84.878 383.958 86.2381 382.174 86.1171Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M382.174 86.1171L382.505 81.2533" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M379.622 79.9717C378.586 79.7928 377.784 78.8154 377.861 77.6805" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M379.189 82.114L377.027 81.9672" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M377.274 86.3273C377.351 85.1924 378.336 84.282 379.474 84.305" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M387.572 78.3398C387.495 79.4747 386.568 80.3347 385.534 80.3731" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M387.835 82.701L385.674 82.5542" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M385.095 84.6866C386.226 84.8177 387.079 85.8528 387.002 86.9877" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</g>
</g>
<defs>
<clipPath id="clip0_532_2318">
<rect width="400" height="92" fill="white"/>
</clipPath>
<clipPath id="clip1_532_2318">
<rect width="13" height="13" fill="white" transform="translate(56.4365 -10) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip2_532_2318">
<rect width="13" height="13" fill="white" transform="translate(81.3792 -8.3067) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip3_532_2318">
<rect width="13" height="13" fill="white" transform="translate(106.322 -6.6134) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip4_532_2318">
<rect width="13" height="13" fill="white" transform="translate(131.264 -4.92004) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip5_532_2318">
<rect width="13" height="13" fill="white" transform="translate(156.207 -3.22675) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip6_532_2318">
<rect width="13" height="13" fill="white" transform="translate(181.149 -1.53345) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip7_532_2318">
<rect width="13" height="13" fill="white" transform="translate(206.092 0.159851) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip8_532_2318">
<rect width="13" height="13" fill="white" transform="translate(231.035 1.85315) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip9_532_2318">
<rect width="13" height="13" fill="white" transform="translate(255.977 3.54651) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip10_532_2318">
<rect width="13" height="13" fill="white" transform="translate(280.92 5.23981) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip11_532_2318">
<rect width="13" height="13" fill="white" transform="translate(305.862 6.93311) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip12_532_2318">
<rect width="13" height="13" fill="white" transform="translate(330.805 8.6264) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip13_532_2318">
<rect width="13" height="13" fill="white" transform="translate(355.748 10.3197) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip14_532_2318">
<rect width="13" height="13" fill="white" transform="translate(380.69 12.0131) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip15_532_2318">
<rect width="13" height="13" fill="white" transform="translate(55.0142 10.9518) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip16_532_2318">
<rect width="13" height="13" fill="white" transform="translate(79.9568 12.6451) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip17_532_2318">
<rect width="13" height="13" fill="white" transform="translate(104.899 14.3384) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip18_532_2318">
<rect width="13" height="13" fill="white" transform="translate(129.842 16.0317) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip19_532_2318">
<rect width="13" height="13" fill="white" transform="translate(154.785 17.725) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip20_532_2318">
<rect width="13" height="13" fill="white" transform="translate(179.727 19.4183) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip21_532_2318">
<rect width="13" height="13" fill="white" transform="translate(204.67 21.1116) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip22_532_2318">
<rect width="13" height="13" fill="white" transform="translate(229.612 22.8049) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip23_532_2318">
<rect width="13" height="13" fill="white" transform="translate(254.555 24.4983) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip24_532_2318">
<rect width="13" height="13" fill="white" transform="translate(279.497 26.1916) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip25_532_2318">
<rect width="13" height="13" fill="white" transform="translate(304.44 27.8849) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip26_532_2318">
<rect width="13" height="13" fill="white" transform="translate(329.383 29.5782) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip27_532_2318">
<rect width="13" height="13" fill="white" transform="translate(354.325 31.2715) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip28_532_2318">
<rect width="13" height="13" fill="white" transform="translate(379.268 32.9648) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip29_532_2318">
<rect width="13" height="13" fill="white" transform="translate(53.5918 31.9036) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip30_532_2318">
<rect width="13" height="13" fill="white" transform="translate(78.5344 33.5969) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip31_532_2318">
<rect width="13" height="13" fill="white" transform="translate(103.477 35.2902) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip32_532_2318">
<rect width="13" height="13" fill="white" transform="translate(128.42 36.9835) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip33_532_2318">
<rect width="13" height="13" fill="white" transform="translate(153.362 38.6768) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip34_532_2318">
<rect width="13" height="13" fill="white" transform="translate(178.305 40.3701) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip35_532_2318">
<rect width="13" height="13" fill="white" transform="translate(203.247 42.0634) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip36_532_2318">
<rect width="13" height="13" fill="white" transform="translate(228.19 43.7567) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip37_532_2318">
<rect width="13" height="13" fill="white" transform="translate(253.133 45.4501) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip38_532_2318">
<rect width="13" height="13" fill="white" transform="translate(278.075 47.1434) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip39_532_2318">
<rect width="13" height="13" fill="white" transform="translate(303.018 48.8367) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip40_532_2318">
<rect width="13" height="13" fill="white" transform="translate(327.96 50.53) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip41_532_2318">
<rect width="13" height="13" fill="white" transform="translate(352.903 52.2233) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip42_532_2318">
<rect width="13" height="13" fill="white" transform="translate(377.845 53.9166) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip43_532_2318">
<rect width="13" height="13" fill="white" transform="translate(52.1694 52.8553) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip44_532_2318">
<rect width="13" height="13" fill="white" transform="translate(77.1121 54.5486) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip45_532_2318">
<rect width="13" height="13" fill="white" transform="translate(102.055 56.2419) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip46_532_2318">
<rect width="13" height="13" fill="white" transform="translate(126.997 57.9352) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip47_532_2318">
<rect width="13" height="13" fill="white" transform="translate(151.94 59.6286) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip48_532_2318">
<rect width="13" height="13" fill="white" transform="translate(176.882 61.3219) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip49_532_2318">
<rect width="13" height="13" fill="white" transform="translate(201.825 63.0152) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip50_532_2318">
<rect width="13" height="13" fill="white" transform="translate(226.768 64.7085) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip51_532_2318">
<rect width="13" height="13" fill="white" transform="translate(251.71 66.4018) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip52_532_2318">
<rect width="13" height="13" fill="white" transform="translate(276.653 68.0951) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip53_532_2318">
<rect width="13" height="13" fill="white" transform="translate(301.595 69.7884) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip54_532_2318">
<rect width="13" height="13" fill="white" transform="translate(326.538 71.4817) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip55_532_2318">
<rect width="13" height="13" fill="white" transform="translate(351.48 73.175) rotate(3.88375)"/>
</clipPath>
<clipPath id="clip56_532_2318">
<rect width="13" height="13" fill="white" transform="translate(376.423 74.8683) rotate(3.88375)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 129 KiB

View File

@@ -35,7 +35,7 @@
"ctrl-shift-f5": "debugger::Restart",
"f6": "debugger::Pause",
"f7": "debugger::StepOver",
"cmd-f11": "debugger::StepInto",
"ctrl-f11": "debugger::StepInto",
"shift-f11": "debugger::StepOut",
"f11": "zed::ToggleFullScreen",
"ctrl-alt-z": "edit_prediction::RateCompletions",
@@ -59,7 +59,6 @@
"tab": "editor::Tab",
"shift-tab": "editor::Backtab",
"ctrl-k": "editor::CutToEndOfLine",
// "ctrl-t": "editor::Transpose",
"ctrl-k ctrl-q": "editor::Rewrap",
"ctrl-k q": "editor::Rewrap",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
@@ -100,21 +99,16 @@
"shift-down": "editor::SelectDown",
"shift-left": "editor::SelectLeft",
"shift-right": "editor::SelectRight",
"ctrl-shift-left": "editor::SelectToPreviousWordStart", // cursorWordLeftSelect
"ctrl-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
"ctrl-shift-left": "editor::SelectToPreviousWordStart",
"ctrl-shift-right": "editor::SelectToNextWordEnd",
"ctrl-shift-home": "editor::SelectToBeginning",
"ctrl-shift-end": "editor::SelectToEnd",
"ctrl-a": "editor::SelectAll",
"ctrl-l": "editor::SelectLine",
"ctrl-shift-i": "editor::Format",
"alt-shift-o": "editor::OrganizeImports",
// "cmd-shift-left": ["editor::SelectToBeginningOfLine", {"stop_at_soft_wraps": true, "stop_at_indent": true }],
// "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
// "cmd-shift-right": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
// "ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
// "alt-v": ["editor::MovePageUp", { "center_cursor": true }],
"ctrl-alt-space": "editor::ShowCharacterPalette",
"ctrl-;": "editor::ToggleLineNumbers",
"ctrl-'": "editor::ToggleSelectedDiffHunks",
@@ -140,7 +134,6 @@
"find": "buffer_search::Deploy",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
// "cmd-e": ["buffer_search::Deploy", { "focus": false }],
"ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
@@ -153,8 +146,7 @@
"context": "Editor && mode == full && edit_prediction",
"bindings": {
"alt-]": "editor::NextEditPrediction",
"alt-[": "editor::PreviousEditPrediction",
"alt-right": "editor::AcceptPartialEditPrediction"
"alt-[": "editor::PreviousEditPrediction"
}
},
{
@@ -219,7 +211,6 @@
"ctrl-enter": "assistant::Assist",
"ctrl-s": "workspace::Save",
"save": "workspace::Save",
"ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",
"shift-enter": "assistant::Split",
"ctrl-r": "assistant::CycleMessageRole",
@@ -245,6 +236,7 @@
"ctrl-shift-j": "agent::ToggleNavigationMenu",
"ctrl-shift-i": "agent::ToggleOptionsMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl->": "assistant::QuoteSelection",
"ctrl-alt-e": "agent::RemoveAllContext",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
@@ -268,8 +260,8 @@
{
"context": "AgentPanel && prompt_editor",
"bindings": {
"cmd-n": "agent::NewTextThread",
"cmd-alt-t": "agent::NewThread"
"ctrl-n": "agent::NewTextThread",
"ctrl-alt-t": "agent::NewThread"
}
},
{
@@ -662,14 +654,16 @@
"bindings": {
"alt-tab": "editor::AcceptEditPrediction",
"alt-l": "editor::AcceptEditPrediction",
"tab": "editor::AcceptEditPrediction"
"tab": "editor::AcceptEditPrediction",
"alt-right": "editor::AcceptPartialEditPrediction"
}
},
{
"context": "Editor && edit_prediction_conflict",
"bindings": {
"alt-tab": "editor::AcceptEditPrediction",
"alt-l": "editor::AcceptEditPrediction"
"alt-l": "editor::AcceptEditPrediction",
"alt-right": "editor::AcceptPartialEditPrediction"
}
},
{

View File

@@ -181,8 +181,7 @@
"use_key_equivalents": true,
"bindings": {
"alt-tab": "editor::NextEditPrediction",
"alt-shift-tab": "editor::PreviousEditPrediction",
"ctrl-cmd-right": "editor::AcceptPartialEditPrediction"
"alt-shift-tab": "editor::PreviousEditPrediction"
}
},
{
@@ -253,7 +252,6 @@
"bindings": {
"cmd-enter": "assistant::Assist",
"cmd-s": "workspace::Save",
"cmd->": "assistant::QuoteSelection",
"cmd-<": "assistant::InsertIntoEditor",
"shift-enter": "assistant::Split",
"ctrl-r": "assistant::CycleMessageRole",
@@ -280,6 +278,7 @@
"cmd-shift-j": "agent::ToggleNavigationMenu",
"cmd-shift-i": "agent::ToggleOptionsMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"cmd->": "assistant::QuoteSelection",
"cmd-alt-e": "agent::RemoveAllContext",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-shift-enter": "agent::ContinueThread",
@@ -719,14 +718,16 @@
"context": "Editor && edit_prediction",
"bindings": {
"alt-tab": "editor::AcceptEditPrediction",
"tab": "editor::AcceptEditPrediction"
"tab": "editor::AcceptEditPrediction",
"ctrl-cmd-right": "editor::AcceptPartialEditPrediction"
}
},
{
"context": "Editor && edit_prediction_conflict",
"use_key_equivalents": true,
"bindings": {
"alt-tab": "editor::AcceptEditPrediction"
"alt-tab": "editor::AcceptEditPrediction",
"ctrl-cmd-right": "editor::AcceptPartialEditPrediction"
}
},
{

View File

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

View File

@@ -38,7 +38,7 @@
"ctrl-shift-d": "editor::DuplicateSelection",
"alt-f3": "editor::SelectAllMatches", // find_all_under
// "ctrl-f3": "", // find_under (cancels any selections)
// "cmd-alt-shift-g": "" // find_under_prev (cancels any selections)
// "ctrl-alt-shift-g": "" // find_under_prev (cancels any selections)
"f9": "editor::SortLinesCaseSensitive",
"ctrl-f9": "editor::SortLinesCaseInsensitive",
"f12": "editor::GoToDefinition",

View File

@@ -28,7 +28,8 @@
"context": "InlineAssistEditor",
"use_key_equivalents": true,
"bindings": {
"cmd-shift-backspace": "editor::Cancel"
"cmd-shift-backspace": "editor::Cancel",
"cmd-enter": "menu::Confirm"
// "alt-enter": // Quick Question
// "cmd-shift-enter": // Full File Context
// "cmd-shift-k": // Toggle input focus (editor <> inline assist)

View File

@@ -395,6 +395,8 @@
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePreviousItem",
"insert": "vim::InsertBefore",
".": "vim::Repeat",
"alt-.": "vim::RepeatFind",
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
"] x": "editor::SelectSmallerSyntaxNode",
@@ -421,6 +423,7 @@
"x": "editor::SelectLine",
"shift-x": "editor::SelectLine",
"%": "editor::SelectAll",
// Window mode
"space w h": "workspace::ActivatePaneLeft",
"space w l": "workspace::ActivatePaneRight",
@@ -450,7 +453,8 @@
"ctrl-c": "editor::ToggleComments",
"d": "vim::HelixDelete",
"c": "vim::Substitute",
"shift-c": "editor::AddSelectionBelow"
"shift-c": "editor::AddSelectionBelow",
"alt-shift-c": "editor::AddSelectionAbove"
}
},
{
@@ -711,7 +715,7 @@
}
},
{
"context": "GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || DebugPanel",
"context": "AgentPanel || GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || DebugPanel",
"bindings": {
// window related commands (ctrl-w X)
"ctrl-w": null,

View File

@@ -101,9 +101,12 @@
// The second option is decimal.
"unit": "binary"
},
// The key to use for adding multiple cursors
// Currently "alt" or "cmd_or_ctrl" (also aliased as
// "cmd" and "ctrl") are supported.
// Determines the modifier to be used to add multiple cursors with the mouse. The open hover link mouse gestures will adapt such that it do not conflict with the multicursor modifier.
//
// 1. Maps to `Alt` on Linux and Windows and to `Option` on MacOS:
// "alt"
// 2. Maps `Control` on Linux and Windows and to `Command` on MacOS:
// "cmd_or_ctrl" (alias: "cmd", "ctrl")
"multi_cursor_modifier": "alt",
// Whether to enable vim modes and key bindings.
"vim_mode": false,
@@ -214,6 +217,8 @@
"show_signature_help_after_edits": false,
// Whether to show code action button at start of buffer line.
"inline_code_actions": true,
// Whether to allow drag and drop text selection in buffer.
"drag_and_drop_selection": true,
// What to do when go to definition yields no results.
//
// 1. Do nothing: `none`
@@ -440,7 +445,9 @@
// Whether to show breakpoints in the gutter.
"breakpoints": true,
// Whether to show fold buttons in the gutter.
"folds": true
"folds": true,
// Minimum number of characters to reserve space for in the gutter.
"min_line_number_digits": 4
},
"indent_guides": {
// Whether to show indent guides in the editor.
@@ -599,7 +606,9 @@
// 2. Never show indent guides:
// "never"
"show": "always"
}
},
// Whether to hide the root entry when only one folder is open in the window.
"hide_root": false
},
"outline_panel": {
// Whether to show the outline panel button in the status bar
@@ -771,7 +780,6 @@
"tools": {
"copy_path": true,
"create_directory": true,
"create_file": true,
"delete_path": true,
"diagnostics": true,
"edit_file": true,
@@ -1034,6 +1042,14 @@
"button": true,
// Whether to show warnings or not by default.
"include_warnings": true,
// Settings for using LSP pull diagnostics mechanism in Zed.
"lsp_pull_diagnostics": {
// Whether to pull for diagnostics or not.
"enabled": true,
// Minimum time to wait before pulling diagnostics from the language server(s).
// 0 turns the debounce off.
"debounce_ms": 50
},
// Settings for inline diagnostics
"inline": {
// Whether to show diagnostics inline or not
@@ -1457,12 +1473,15 @@
"language_servers": ["erlang-ls", "!elp", "..."]
},
"Git Commit": {
"allow_rewrap": "anywhere"
"allow_rewrap": "anywhere",
"soft_wrap": "editor_width",
"preferred_line_length": 72
},
"Go": {
"code_actions_on_format": {
"source.organizeImports": true
}
},
"debuggers": ["Delve"]
},
"GraphQL": {
"prettier": {
@@ -1527,20 +1546,20 @@
"Plain Text": {
"allow_rewrap": "anywhere"
},
"Python": {
"debuggers": ["Debugpy"]
},
"Ruby": {
"language_servers": ["solargraph", "!ruby-lsp", "!rubocop", "!sorbet", "!steep", "..."]
},
"Rust": {
"debuggers": ["CodeLLDB"]
},
"SCSS": {
"prettier": {
"allowed": true
}
},
"SQL": {
"prettier": {
"allowed": true,
"plugins": ["prettier-plugin-sql"]
}
},
"Starlark": {
"language_servers": ["starpls", "!buck2-lsp", "..."]
},

View File

@@ -1,4 +1,4 @@
// Static tasks configuration.
// Project tasks configuration. See https://zed.dev/docs/tasks for documentation.
//
// Example:
[

View File

@@ -261,6 +261,11 @@
"font_style": null,
"font_weight": null
},
"namespace": {
"color": "#bfbdb6ff",
"font_style": null,
"font_weight": null
},
"number": {
"color": "#d2a6ffff",
"font_style": null,
@@ -316,6 +321,16 @@
"font_style": null,
"font_weight": null
},
"selector": {
"color": "#d2a6ffff",
"font_style": null,
"font_weight": null
},
"selector.pseudo": {
"color": "#5ac1feff",
"font_style": null,
"font_weight": null
},
"string": {
"color": "#a9d94bff",
"font_style": null,
@@ -442,9 +457,9 @@
"terminal.foreground": "#5c6166ff",
"terminal.bright_foreground": "#5c6166ff",
"terminal.dim_foreground": "#fcfcfcff",
"terminal.ansi.black": "#fcfcfcff",
"terminal.ansi.bright_black": "#bcbec0ff",
"terminal.ansi.dim_black": "#5c6166ff",
"terminal.ansi.black": "#5c6166ff",
"terminal.ansi.bright_black": "#3b9ee5ff",
"terminal.ansi.dim_black": "#9c9fa2ff",
"terminal.ansi.red": "#ef7271ff",
"terminal.ansi.bright_red": "#febab6ff",
"terminal.ansi.dim_red": "#833538ff",
@@ -463,9 +478,9 @@
"terminal.ansi.cyan": "#4dbf99ff",
"terminal.ansi.bright_cyan": "#ace0cbff",
"terminal.ansi.dim_cyan": "#2a5f4aff",
"terminal.ansi.white": "#5c6166ff",
"terminal.ansi.bright_white": "#5c6166ff",
"terminal.ansi.dim_white": "#9c9fa2ff",
"terminal.ansi.white": "#fcfcfcff",
"terminal.ansi.bright_white": "#fcfcfcff",
"terminal.ansi.dim_white": "#bcbec0ff",
"link_text.hover": "#3b9ee5ff",
"conflict": "#f1ad49ff",
"conflict.background": "#ffeedaff",
@@ -632,6 +647,11 @@
"font_style": null,
"font_weight": null
},
"namespace": {
"color": "#5c6166ff",
"font_style": null,
"font_weight": null
},
"number": {
"color": "#a37accff",
"font_style": null,
@@ -687,6 +707,16 @@
"font_style": null,
"font_weight": null
},
"selector": {
"color": "#a37accff",
"font_style": null,
"font_weight": null
},
"selector.pseudo": {
"color": "#3b9ee5ff",
"font_style": null,
"font_weight": null
},
"string": {
"color": "#86b300ff",
"font_style": null,
@@ -1003,6 +1033,11 @@
"font_style": null,
"font_weight": null
},
"namespace": {
"color": "#cccac2ff",
"font_style": null,
"font_weight": null
},
"number": {
"color": "#dfbfffff",
"font_style": null,
@@ -1058,6 +1093,16 @@
"font_style": null,
"font_weight": null
},
"selector": {
"color": "#dfbfffff",
"font_style": null,
"font_weight": null
},
"selector.pseudo": {
"color": "#72cffeff",
"font_style": null,
"font_weight": null
},
"string": {
"color": "#d4fe7fff",
"font_style": null,

View File

@@ -270,6 +270,11 @@
"font_style": null,
"font_weight": null
},
"namespace": {
"color": "#83a598ff",
"font_style": null,
"font_weight": null
},
"number": {
"color": "#d3869bff",
"font_style": null,
@@ -325,6 +330,16 @@
"font_style": null,
"font_weight": null
},
"selector": {
"color": "#fabd2eff",
"font_style": null,
"font_weight": null
},
"selector.pseudo": {
"color": "#83a598ff",
"font_style": null,
"font_weight": null
},
"string": {
"color": "#b8bb25ff",
"font_style": null,
@@ -655,6 +670,11 @@
"font_style": null,
"font_weight": null
},
"namespace": {
"color": "#83a598ff",
"font_style": null,
"font_weight": null
},
"number": {
"color": "#d3869bff",
"font_style": null,
@@ -710,6 +730,16 @@
"font_style": null,
"font_weight": null
},
"selector": {
"color": "#fabd2eff",
"font_style": null,
"font_weight": null
},
"selector.pseudo": {
"color": "#83a598ff",
"font_style": null,
"font_weight": null
},
"string": {
"color": "#b8bb25ff",
"font_style": null,
@@ -1040,6 +1070,11 @@
"font_style": null,
"font_weight": null
},
"namespace": {
"color": "#83a598ff",
"font_style": null,
"font_weight": null
},
"number": {
"color": "#d3869bff",
"font_style": null,
@@ -1095,6 +1130,16 @@
"font_style": null,
"font_weight": null
},
"selector": {
"color": "#fabd2eff",
"font_style": null,
"font_weight": null
},
"selector.pseudo": {
"color": "#83a598ff",
"font_style": null,
"font_weight": null
},
"string": {
"color": "#b8bb25ff",
"font_style": null,
@@ -1227,9 +1272,9 @@
"terminal.foreground": "#282828ff",
"terminal.bright_foreground": "#282828ff",
"terminal.dim_foreground": "#fbf1c7ff",
"terminal.ansi.black": "#fbf1c7ff",
"terminal.ansi.bright_black": "#b0a189ff",
"terminal.ansi.dim_black": "#282828ff",
"terminal.ansi.black": "#282828ff",
"terminal.ansi.bright_black": "#0b6678ff",
"terminal.ansi.dim_black": "#5f5650ff",
"terminal.ansi.red": "#9d0308ff",
"terminal.ansi.bright_red": "#db8b7aff",
"terminal.ansi.dim_red": "#4e1207ff",
@@ -1248,9 +1293,9 @@
"terminal.ansi.cyan": "#437b59ff",
"terminal.ansi.bright_cyan": "#9fbca8ff",
"terminal.ansi.dim_cyan": "#253e2eff",
"terminal.ansi.white": "#282828ff",
"terminal.ansi.bright_white": "#282828ff",
"terminal.ansi.dim_white": "#73675eff",
"terminal.ansi.white": "#fbf1c7ff",
"terminal.ansi.bright_white": "#fbf1c7ff",
"terminal.ansi.dim_white": "#b0a189ff",
"link_text.hover": "#0b6678ff",
"version_control.added": "#797410ff",
"version_control.modified": "#b57615ff",
@@ -1425,6 +1470,11 @@
"font_style": null,
"font_weight": null
},
"namespace": {
"color": "#066578ff",
"font_style": null,
"font_weight": null
},
"number": {
"color": "#8f3e71ff",
"font_style": null,
@@ -1480,6 +1530,16 @@
"font_style": null,
"font_weight": null
},
"selector": {
"color": "#b57613ff",
"font_style": null,
"font_weight": null
},
"selector.pseudo": {
"color": "#0b6678ff",
"font_style": null,
"font_weight": null
},
"string": {
"color": "#79740eff",
"font_style": null,
@@ -1612,9 +1672,9 @@
"terminal.foreground": "#282828ff",
"terminal.bright_foreground": "#282828ff",
"terminal.dim_foreground": "#f9f5d7ff",
"terminal.ansi.black": "#f9f5d7ff",
"terminal.ansi.bright_black": "#b0a189ff",
"terminal.ansi.dim_black": "#282828ff",
"terminal.ansi.black": "#282828ff",
"terminal.ansi.bright_black": "#73675eff",
"terminal.ansi.dim_black": "#f9f5d7ff",
"terminal.ansi.red": "#9d0308ff",
"terminal.ansi.bright_red": "#db8b7aff",
"terminal.ansi.dim_red": "#4e1207ff",
@@ -1633,9 +1693,9 @@
"terminal.ansi.cyan": "#437b59ff",
"terminal.ansi.bright_cyan": "#9fbca8ff",
"terminal.ansi.dim_cyan": "#253e2eff",
"terminal.ansi.white": "#282828ff",
"terminal.ansi.bright_white": "#282828ff",
"terminal.ansi.dim_white": "#73675eff",
"terminal.ansi.white": "#f9f5d7ff",
"terminal.ansi.bright_white": "#f9f5d7ff",
"terminal.ansi.dim_white": "#b0a189ff",
"link_text.hover": "#0b6678ff",
"version_control.added": "#797410ff",
"version_control.modified": "#b57615ff",
@@ -1810,6 +1870,11 @@
"font_style": null,
"font_weight": null
},
"namespace": {
"color": "#066578ff",
"font_style": null,
"font_weight": null
},
"number": {
"color": "#8f3e71ff",
"font_style": null,
@@ -1865,6 +1930,16 @@
"font_style": null,
"font_weight": null
},
"selector": {
"color": "#b57613ff",
"font_style": null,
"font_weight": null
},
"selector.pseudo": {
"color": "#0b6678ff",
"font_style": null,
"font_weight": null
},
"string": {
"color": "#79740eff",
"font_style": null,
@@ -1997,9 +2072,9 @@
"terminal.foreground": "#282828ff",
"terminal.bright_foreground": "#282828ff",
"terminal.dim_foreground": "#f2e5bcff",
"terminal.ansi.black": "#f2e5bcff",
"terminal.ansi.bright_black": "#b0a189ff",
"terminal.ansi.dim_black": "#282828ff",
"terminal.ansi.black": "#282828ff",
"terminal.ansi.bright_black": "#73675eff",
"terminal.ansi.dim_black": "#f2e5bcff",
"terminal.ansi.red": "#9d0308ff",
"terminal.ansi.bright_red": "#db8b7aff",
"terminal.ansi.dim_red": "#4e1207ff",
@@ -2018,9 +2093,9 @@
"terminal.ansi.cyan": "#437b59ff",
"terminal.ansi.bright_cyan": "#9fbca8ff",
"terminal.ansi.dim_cyan": "#253e2eff",
"terminal.ansi.white": "#282828ff",
"terminal.ansi.bright_white": "#282828ff",
"terminal.ansi.dim_white": "#73675eff",
"terminal.ansi.white": "#f2e5bcff",
"terminal.ansi.bright_white": "#f2e5bcff",
"terminal.ansi.dim_white": "#b0a189ff",
"link_text.hover": "#0b6678ff",
"version_control.added": "#797410ff",
"version_control.modified": "#b57615ff",
@@ -2195,6 +2270,11 @@
"font_style": null,
"font_weight": null
},
"namespace": {
"color": "#066578ff",
"font_style": null,
"font_weight": null
},
"number": {
"color": "#8f3e71ff",
"font_style": null,
@@ -2250,6 +2330,16 @@
"font_style": null,
"font_weight": null
},
"selector": {
"color": "#b57613ff",
"font_style": null,
"font_weight": null
},
"selector.pseudo": {
"color": "#0b6678ff",
"font_style": null,
"font_weight": null
},
"string": {
"color": "#79740eff",
"font_style": null,

View File

@@ -99,6 +99,8 @@
"version_control.added": "#27a657ff",
"version_control.modified": "#d3b020ff",
"version_control.deleted": "#e06c76ff",
"version_control.conflict_marker.ours": "#a1c1811a",
"version_control.conflict_marker.theirs": "#74ade81a",
"conflict": "#dec184ff",
"conflict.background": "#dec1841a",
"conflict.border": "#5d4c2fff",
@@ -264,6 +266,11 @@
"font_style": null,
"font_weight": null
},
"namespace": {
"color": "#dce0e5ff",
"font_style": null,
"font_weight": null
},
"number": {
"color": "#bf956aff",
"font_style": null,
@@ -319,6 +326,16 @@
"font_style": null,
"font_weight": null
},
"selector": {
"color": "#dfc184ff",
"font_style": null,
"font_weight": null
},
"selector.pseudo": {
"color": "#74ade8ff",
"font_style": null,
"font_weight": null
},
"string": {
"color": "#a1c181ff",
"font_style": null,
@@ -450,9 +467,9 @@
"terminal.foreground": "#242529ff",
"terminal.bright_foreground": "#242529ff",
"terminal.dim_foreground": "#fafafaff",
"terminal.ansi.black": "#fafafaff",
"terminal.ansi.bright_black": "#aaaaaaff",
"terminal.ansi.dim_black": "#242529ff",
"terminal.ansi.black": "#242529ff",
"terminal.ansi.bright_black": "#242529ff",
"terminal.ansi.dim_black": "#97979aff",
"terminal.ansi.red": "#d36151ff",
"terminal.ansi.bright_red": "#f0b0a4ff",
"terminal.ansi.dim_red": "#6f312aff",
@@ -471,9 +488,9 @@
"terminal.ansi.cyan": "#3a82b7ff",
"terminal.ansi.bright_cyan": "#a3bedaff",
"terminal.ansi.dim_cyan": "#254058ff",
"terminal.ansi.white": "#242529ff",
"terminal.ansi.bright_white": "#242529ff",
"terminal.ansi.dim_white": "#97979aff",
"terminal.ansi.white": "#fafafaff",
"terminal.ansi.bright_white": "#fafafaff",
"terminal.ansi.dim_white": "#aaaaaaff",
"link_text.hover": "#5c78e2ff",
"version_control.added": "#27a657ff",
"version_control.modified": "#d3b020ff",
@@ -643,6 +660,11 @@
"font_style": null,
"font_weight": null
},
"namespace": {
"color": "#242529ff",
"font_style": null,
"font_weight": null
},
"number": {
"color": "#ad6e25ff",
"font_style": null,
@@ -698,6 +720,16 @@
"font_style": null,
"font_weight": null
},
"selector": {
"color": "#669f59ff",
"font_style": null,
"font_weight": null
},
"selector.pseudo": {
"color": "#5c78e2ff",
"font_style": null,
"font_weight": null
},
"string": {
"color": "#649f57ff",
"font_style": null,

View File

@@ -25,7 +25,6 @@ assistant_context_editor.workspace = true
assistant_slash_command.workspace = true
assistant_slash_commands.workspace = true
assistant_tool.workspace = true
async-watch.workspace = true
audio.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
@@ -95,6 +94,7 @@ ui_input.workspace = true
urlencoding.workspace = true
util.workspace = true
uuid.workspace = true
watch.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true
@@ -102,11 +102,13 @@ zed_llm_client.workspace = true
zstd.workspace = true
[dev-dependencies]
assistant_tools.workspace = true
buffer_diff = { workspace = true, features = ["test-support"] }
editor = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, "features" = ["test-support"] }
indoc.workspace = true
language = { workspace = true, "features" = ["test-support"] }
language_model = { workspace = true, "features" = ["test-support"] }
pretty_assertions.workspace = true
project = { workspace = true, features = ["test-support"] }
rand.workspace = true

View File

@@ -1144,6 +1144,10 @@ impl ActiveThread {
cx,
);
}
ThreadEvent::ProfileChanged => {
self.save_thread(cx);
cx.notify();
}
}
}
@@ -1601,6 +1605,7 @@ impl ActiveThread {
this.thread.update(cx, |thread, cx| {
thread.advance_prompt_id();
thread.cancel_last_completion(Some(window.window_handle()), cx);
thread.send_to_model(
model.model,
CompletionIntent::UserPrompt,
@@ -1784,12 +1789,31 @@ impl ActiveThread {
fn render_message(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
let message_id = self.messages[ix];
let Some(message) = self.thread.read(cx).message(message_id) else {
let workspace = self.workspace.clone();
let thread = self.thread.read(cx);
let is_first_message = ix == 0;
let is_last_message = ix == self.messages.len() - 1;
let Some(message) = thread.message(message_id) else {
return Empty.into_any();
};
let is_generating = thread.is_generating();
let is_generating_stale = thread.is_generation_stale().unwrap_or(false);
let loading_dots = (is_generating && is_last_message).then(|| {
h_flex()
.h_8()
.my_3()
.mx_5()
.when(is_generating_stale || message.is_hidden, |this| {
this.child(AnimatedLabel::new("").size(LabelSize::Small))
})
});
if message.is_hidden {
return Empty.into_any();
return div().children(loading_dots).into_any();
}
let message_creases = message.creases.clone();
@@ -1798,9 +1822,6 @@ impl ActiveThread {
return Empty.into_any();
};
let workspace = self.workspace.clone();
let thread = self.thread.read(cx);
// Get all the data we need from thread before we start using it in closures
let checkpoint = thread.checkpoint_for_message(message_id);
let configured_model = thread.configured_model().map(|m| m.model);
@@ -1811,14 +1832,6 @@ impl ActiveThread {
let tool_uses = thread.tool_uses_for_message(message_id, cx);
let has_tool_uses = !tool_uses.is_empty();
let is_generating = thread.is_generating();
let is_generating_stale = thread.is_generation_stale().unwrap_or(false);
let is_first_message = ix == 0;
let is_last_message = ix == self.messages.len() - 1;
let loading_dots = (is_generating_stale && is_last_message)
.then(|| AnimatedLabel::new("").size(LabelSize::Small));
let editing_message_state = self
.editing_message
@@ -2234,17 +2247,7 @@ impl ActiveThread {
parent.child(self.render_rules_item(cx))
})
.child(styled_message)
.when(is_generating && is_last_message, |this| {
this.child(
h_flex()
.h_8()
.mt_2()
.mb_4()
.ml_4()
.py_1p5()
.when_some(loading_dots, |this, loading_dots| this.child(loading_dots)),
)
})
.children(loading_dots)
.when(show_feedback, move |parent| {
parent.child(feedback_items).when_some(
self.open_feedback_editors.get(&message_id),
@@ -3704,7 +3707,7 @@ mod tests {
use util::path;
use workspace::CollaboratorId;
use crate::{ContextLoadResult, thread_store};
use crate::{ContextLoadResult, thread::MessageSegment, thread_store};
use super::*;
@@ -3838,6 +3841,114 @@ mod tests {
});
}
#[gpui::test]
async fn test_editing_message_cancels_previous_completion(cx: &mut TestAppContext) {
init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
let (cx, active_thread, _, thread, model) =
setup_test_environment(cx, project.clone()).await;
cx.update(|_, cx| {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.set_default_model(
Some(ConfiguredModel {
provider: Arc::new(FakeLanguageModelProvider),
model: model.clone(),
}),
cx,
);
});
});
// Track thread events to verify cancellation
let cancellation_events = Arc::new(std::sync::Mutex::new(Vec::new()));
let new_request_events = Arc::new(std::sync::Mutex::new(Vec::new()));
let _subscription = cx.update(|_, cx| {
let cancellation_events = cancellation_events.clone();
let new_request_events = new_request_events.clone();
cx.subscribe(
&thread,
move |_thread, event: &ThreadEvent, _cx| match event {
ThreadEvent::CompletionCanceled => {
cancellation_events.lock().unwrap().push(());
}
ThreadEvent::NewRequest => {
new_request_events.lock().unwrap().push(());
}
_ => {}
},
)
});
// Insert a user message and start streaming a response
let message = thread.update(cx, |thread, cx| {
let message_id = thread.insert_user_message(
"Hello, how are you?",
ContextLoadResult::default(),
None,
vec![],
cx,
);
thread.advance_prompt_id();
thread.send_to_model(
model.clone(),
CompletionIntent::UserPrompt,
cx.active_window(),
cx,
);
thread.message(message_id).cloned().unwrap()
});
cx.run_until_parked();
// Verify that a completion is in progress
assert!(cx.read(|cx| thread.read(cx).is_generating()));
assert_eq!(new_request_events.lock().unwrap().len(), 1);
// Edit the message while the completion is still running
active_thread.update_in(cx, |active_thread, window, cx| {
active_thread.start_editing_message(
message.id,
message.segments.as_slice(),
message.creases.as_slice(),
window,
cx,
);
let editor = active_thread
.editing_message
.as_ref()
.unwrap()
.1
.editor
.clone();
editor.update(cx, |editor, cx| {
editor.set_text("What is the weather like?", window, cx);
});
active_thread.confirm_editing_message(&Default::default(), window, cx);
});
cx.run_until_parked();
// Verify that the previous completion was cancelled
assert_eq!(cancellation_events.lock().unwrap().len(), 1);
// Verify that a new request was started after cancellation
assert_eq!(new_request_events.lock().unwrap().len(), 2);
// Verify that the edited message contains the new text
let edited_message =
thread.update(cx, |thread, _| thread.message(message.id).cloned().unwrap());
match &edited_message.segments[0] {
MessageSegment::Text(text) => {
assert_eq!(text, "What is the weather like?");
}
_ => panic!("Expected text segment"),
}
}
fn init_test_settings(cx: &mut TestAppContext) {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);

View File

@@ -3,6 +3,7 @@ mod agent_configuration;
mod agent_diff;
mod agent_model_selector;
mod agent_panel;
mod agent_profile;
mod buffer_codegen;
mod context;
mod context_picker;

View File

@@ -12,7 +12,7 @@ use context_server::ContextServerId;
use fs::Fs;
use gpui::{
Action, Animation, AnimationExt as _, AnyView, App, Entity, EventEmitter, FocusHandle,
Focusable, ScrollHandle, Subscription, pulsating_between,
Focusable, ScrollHandle, Subscription, Transformation, percentage,
};
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
use project::context_server_store::{ContextServerStatus, ContextServerStore};
@@ -475,7 +475,6 @@ impl AgentConfiguration {
.get(&context_server_id)
.copied()
.unwrap_or_default();
let tools = tools_by_source
.get(&ToolSource::ContextServer {
id: context_server_id.0.clone().into(),
@@ -484,25 +483,23 @@ impl AgentConfiguration {
let tool_count = tools.len();
let border_color = cx.theme().colors().border.opacity(0.6);
let success_color = Color::Success.color(cx);
let (status_indicator, tooltip_text) = match server_status {
ContextServerStatus::Starting => (
Indicator::dot()
.color(Color::Success)
Icon::new(IconName::LoadCircle)
.size(IconSize::XSmall)
.color(Color::Accent)
.with_animation(
SharedString::from(format!("{}-starting", context_server_id.0.clone(),)),
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(pulsating_between(0.4, 1.)),
move |this, delta| this.color(success_color.alpha(delta).into()),
Animation::new(Duration::from_secs(3)).repeat(),
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
)
.into_any_element(),
"Server is starting.",
),
ContextServerStatus::Running => (
Indicator::dot().color(Color::Success).into_any_element(),
"Server is running.",
"Server is active.",
),
ContextServerStatus::Error(_) => (
Indicator::dot().color(Color::Error).into_any_element(),
@@ -526,12 +523,11 @@ impl AgentConfiguration {
.p_1()
.justify_between()
.when(
error.is_some() || are_tools_expanded && tool_count > 1,
error.is_some() || are_tools_expanded && tool_count >= 1,
|element| element.border_b_1().border_color(border_color),
)
.child(
h_flex()
.gap_1p5()
.child(
Disclosure::new(
"tool-list-disclosure",
@@ -551,12 +547,16 @@ impl AgentConfiguration {
})),
)
.child(
div()
.id(item_id.clone())
h_flex()
.id(SharedString::from(format!("tooltip-{}", item_id)))
.h_full()
.w_3()
.mx_1()
.justify_center()
.tooltip(Tooltip::text(tooltip_text))
.child(status_indicator),
)
.child(Label::new(context_server_id.0.clone()).ml_0p5())
.child(Label::new(item_id).ml_0p5().mr_1p5())
.when(is_running, |this| {
this.child(
Label::new(if tool_count == 1 {

View File

@@ -2,25 +2,21 @@ mod profile_modal_header;
use std::sync::Arc;
use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profiles};
use agent_settings::{AgentProfileId, AgentSettings, builtin_profiles};
use assistant_tool::ToolWorkingSet;
use convert_case::{Case, Casing as _};
use editor::Editor;
use fs::Fs;
use gpui::{
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, WeakEntity,
prelude::*,
};
use settings::{Settings as _, update_settings_file};
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, prelude::*};
use settings::Settings as _;
use ui::{
KeyBinding, ListItem, ListItemSpacing, ListSeparator, Navigable, NavigableEntry, prelude::*,
};
use util::ResultExt as _;
use workspace::{ModalView, Workspace};
use crate::agent_configuration::manage_profiles_modal::profile_modal_header::ProfileModalHeader;
use crate::agent_configuration::tool_picker::{ToolPicker, ToolPickerDelegate};
use crate::{AgentPanel, ManageProfiles, ThreadStore};
use crate::agent_profile::AgentProfile;
use crate::{AgentPanel, ManageProfiles};
use super::tool_picker::ToolPickerMode;
@@ -103,7 +99,6 @@ pub struct NewProfileMode {
pub struct ManageProfilesModal {
fs: Arc<dyn Fs>,
tools: Entity<ToolWorkingSet>,
thread_store: WeakEntity<ThreadStore>,
focus_handle: FocusHandle,
mode: Mode,
}
@@ -119,9 +114,8 @@ impl ManageProfilesModal {
let fs = workspace.app_state().fs.clone();
let thread_store = panel.read(cx).thread_store();
let tools = thread_store.read(cx).tools();
let thread_store = thread_store.downgrade();
workspace.toggle_modal(window, cx, |window, cx| {
let mut this = Self::new(fs, tools, thread_store, window, cx);
let mut this = Self::new(fs, tools, window, cx);
if let Some(profile_id) = action.customize_tools.clone() {
this.configure_builtin_tools(profile_id, window, cx);
@@ -136,7 +130,6 @@ impl ManageProfilesModal {
pub fn new(
fs: Arc<dyn Fs>,
tools: Entity<ToolWorkingSet>,
thread_store: WeakEntity<ThreadStore>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
@@ -145,7 +138,6 @@ impl ManageProfilesModal {
Self {
fs,
tools,
thread_store,
focus_handle,
mode: Mode::choose_profile(window, cx),
}
@@ -206,7 +198,6 @@ impl ManageProfilesModal {
ToolPickerMode::McpTools,
self.fs.clone(),
self.tools.clone(),
self.thread_store.clone(),
profile_id.clone(),
profile,
cx,
@@ -244,7 +235,6 @@ impl ManageProfilesModal {
ToolPickerMode::BuiltinTools,
self.fs.clone(),
self.tools.clone(),
self.thread_store.clone(),
profile_id.clone(),
profile,
cx,
@@ -270,32 +260,10 @@ impl ManageProfilesModal {
match &self.mode {
Mode::ChooseProfile { .. } => {}
Mode::NewProfile(mode) => {
let settings = AgentSettings::get_global(cx);
let base_profile = mode
.base_profile_id
.as_ref()
.and_then(|profile_id| settings.profiles.get(profile_id).cloned());
let name = mode.name_editor.read(cx).text(cx);
let profile_id = AgentProfileId(name.to_case(Case::Kebab).into());
let profile = AgentProfile {
name: name.into(),
tools: base_profile
.as_ref()
.map(|profile| profile.tools.clone())
.unwrap_or_default(),
enable_all_context_servers: base_profile
.as_ref()
.map(|profile| profile.enable_all_context_servers)
.unwrap_or_default(),
context_servers: base_profile
.map(|profile| profile.context_servers)
.unwrap_or_default(),
};
self.create_profile(profile_id.clone(), profile, cx);
let profile_id =
AgentProfile::create(name, mode.base_profile_id.clone(), self.fs.clone(), cx);
self.view_profile(profile_id, window, cx);
}
Mode::ViewProfile(_) => {}
@@ -325,19 +293,6 @@ impl ManageProfilesModal {
}
}
}
fn create_profile(
&self,
profile_id: AgentProfileId,
profile: AgentProfile,
cx: &mut Context<Self>,
) {
update_settings_file::<AgentSettings>(self.fs.clone(), cx, {
move |settings, _cx| {
settings.create_profile(profile_id, profile).log_err();
}
});
}
}
impl ModalView for ManageProfilesModal {}
@@ -520,14 +475,13 @@ impl ManageProfilesModal {
) -> impl IntoElement {
let settings = AgentSettings::get_global(cx);
let profile_id = &settings.default_profile;
let profile_name = settings
.profiles
.get(&mode.profile_id)
.map(|profile| profile.name.clone())
.unwrap_or_else(|| "Unknown".into());
let icon = match profile_id.as_str() {
let icon = match mode.profile_id.as_str() {
"write" => IconName::Pencil,
"ask" => IconName::MessageBubbles,
_ => IconName::UserRoundPen,

View File

@@ -1,19 +1,17 @@
use std::{collections::BTreeMap, sync::Arc};
use agent_settings::{
AgentProfile, AgentProfileContent, AgentProfileId, AgentSettings, AgentSettingsContent,
AgentProfileContent, AgentProfileId, AgentProfileSettings, AgentSettings, AgentSettingsContent,
ContextServerPresetContent,
};
use assistant_tool::{ToolSource, ToolWorkingSet};
use fs::Fs;
use gpui::{App, Context, DismissEvent, Entity, EventEmitter, Focusable, Task, WeakEntity, Window};
use picker::{Picker, PickerDelegate};
use settings::{Settings as _, update_settings_file};
use settings::update_settings_file;
use ui::{ListItem, ListItemSpacing, prelude::*};
use util::ResultExt as _;
use crate::ThreadStore;
pub struct ToolPicker {
picker: Entity<Picker<ToolPickerDelegate>>,
}
@@ -71,11 +69,10 @@ pub enum PickerItem {
pub struct ToolPickerDelegate {
tool_picker: WeakEntity<ToolPicker>,
thread_store: WeakEntity<ThreadStore>,
fs: Arc<dyn Fs>,
items: Arc<Vec<PickerItem>>,
profile_id: AgentProfileId,
profile: AgentProfile,
profile_settings: AgentProfileSettings,
filtered_items: Vec<PickerItem>,
selected_index: usize,
mode: ToolPickerMode,
@@ -86,20 +83,18 @@ impl ToolPickerDelegate {
mode: ToolPickerMode,
fs: Arc<dyn Fs>,
tool_set: Entity<ToolWorkingSet>,
thread_store: WeakEntity<ThreadStore>,
profile_id: AgentProfileId,
profile: AgentProfile,
profile_settings: AgentProfileSettings,
cx: &mut Context<ToolPicker>,
) -> Self {
let items = Arc::new(Self::resolve_items(mode, &tool_set, cx));
Self {
tool_picker: cx.entity().downgrade(),
thread_store,
fs,
items,
profile_id,
profile,
profile_settings,
filtered_items: Vec::new(),
selected_index: 0,
mode,
@@ -249,28 +244,31 @@ impl PickerDelegate for ToolPickerDelegate {
};
let is_currently_enabled = if let Some(server_id) = server_id.clone() {
let preset = self.profile.context_servers.entry(server_id).or_default();
let preset = self
.profile_settings
.context_servers
.entry(server_id)
.or_default();
let is_enabled = *preset.tools.entry(tool_name.clone()).or_default();
*preset.tools.entry(tool_name.clone()).or_default() = !is_enabled;
is_enabled
} else {
let is_enabled = *self.profile.tools.entry(tool_name.clone()).or_default();
*self.profile.tools.entry(tool_name.clone()).or_default() = !is_enabled;
let is_enabled = *self
.profile_settings
.tools
.entry(tool_name.clone())
.or_default();
*self
.profile_settings
.tools
.entry(tool_name.clone())
.or_default() = !is_enabled;
is_enabled
};
let active_profile_id = &AgentSettings::get_global(cx).default_profile;
if active_profile_id == &self.profile_id {
self.thread_store
.update(cx, |this, cx| {
this.load_profile(self.profile.clone(), cx);
})
.log_err();
}
update_settings_file::<AgentSettings>(self.fs.clone(), cx, {
let profile_id = self.profile_id.clone();
let default_profile = self.profile.clone();
let default_profile = self.profile_settings.clone();
let server_id = server_id.clone();
let tool_name = tool_name.clone();
move |settings: &mut AgentSettingsContent, _cx| {
@@ -348,14 +346,18 @@ impl PickerDelegate for ToolPickerDelegate {
),
PickerItem::Tool { name, server_id } => {
let is_enabled = if let Some(server_id) = server_id {
self.profile
self.profile_settings
.context_servers
.get(server_id.as_ref())
.and_then(|preset| preset.tools.get(name))
.copied()
.unwrap_or(self.profile.enable_all_context_servers)
.unwrap_or(self.profile_settings.enable_all_context_servers)
} else {
self.profile.tools.get(name).copied().unwrap_or(false)
self.profile_settings
.tools
.get(name)
.copied()
.unwrap_or(false)
};
Some(

View File

@@ -1378,7 +1378,8 @@ impl AgentDiff {
| ThreadEvent::CheckpointChanged
| ThreadEvent::ToolConfirmationNeeded
| ThreadEvent::ToolUseLimitReached
| ThreadEvent::CancelEditing => {}
| ThreadEvent::CancelEditing
| ThreadEvent::ProfileChanged => {}
}
}
@@ -1512,7 +1513,7 @@ impl AgentDiff {
multibuffer.add_diff(diff_handle.clone(), cx);
});
let new_state = if thread.read(cx).has_pending_edit_tool_uses() {
let new_state = if thread.read(cx).is_generating() {
EditorState::Generating
} else {
EditorState::Reviewing

View File

@@ -91,12 +91,13 @@ impl AgentModelSelector {
impl Render for AgentModelSelector {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle.clone();
let model = self.selector.read(cx).delegate.active_model(cx);
let model_name = model
.map(|model| model.model.name().0)
.unwrap_or_else(|| SharedString::from("No model selected"));
let focus_handle = self.focus_handle.clone();
PickerPopoverMenu::new(
self.selector.clone(),
Button::new("active-model", model_name)

View File

@@ -57,7 +57,7 @@ use zed_llm_client::{CompletionIntent, UsageLimit};
use crate::active_thread::{self, ActiveThread, ActiveThreadEvent};
use crate::agent_configuration::{AgentConfiguration, AssistantConfigurationEvent};
use crate::agent_diff::AgentDiff;
use crate::history_store::{HistoryStore, RecentEntry};
use crate::history_store::{HistoryEntryId, HistoryStore};
use crate::message_editor::{MessageEditor, MessageEditorEvent};
use crate::thread::{Thread, ThreadError, ThreadId, ThreadSummary, TokenUsageRatio};
use crate::thread_history::{HistoryEntryElement, ThreadHistory};
@@ -257,6 +257,7 @@ impl ActiveView {
pub fn prompt_editor(
context_editor: Entity<ContextEditor>,
history_store: Entity<HistoryStore>,
language_registry: Arc<LanguageRegistry>,
window: &mut Window,
cx: &mut App,
@@ -322,6 +323,19 @@ impl ActiveView {
editor.set_text(summary, window, cx);
})
}
ContextEvent::PathChanged { old_path, new_path } => {
history_store.update(cx, |history_store, cx| {
if let Some(old_path) = old_path {
history_store
.replace_recently_opened_text_thread(old_path, new_path, cx);
} else {
history_store.push_recently_opened_entry(
HistoryEntryId::Context(new_path.clone()),
cx,
);
}
});
}
_ => {}
}
}),
@@ -516,8 +530,7 @@ impl AgentPanel {
HistoryStore::new(
thread_store.clone(),
context_store.clone(),
[RecentEntry::Thread(thread_id, thread.clone())],
window,
[HistoryEntryId::Thread(thread_id)],
cx,
)
});
@@ -544,7 +557,13 @@ impl AgentPanel {
editor.insert_default_prompt(window, cx);
editor
});
ActiveView::prompt_editor(context_editor, language_registry.clone(), window, cx)
ActiveView::prompt_editor(
context_editor,
history_store.clone(),
language_registry.clone(),
window,
cx,
)
}
};
@@ -581,86 +600,9 @@ impl AgentPanel {
let panel = weak_panel.clone();
let assistant_navigation_menu =
ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
let recently_opened = panel
.update(cx, |this, cx| {
this.history_store.update(cx, |history_store, cx| {
history_store.recently_opened_entries(cx)
})
})
.unwrap_or_default();
if !recently_opened.is_empty() {
menu = menu.header("Recently Opened");
for entry in recently_opened.iter() {
if let RecentEntry::Context(context) = entry {
if context.read(cx).path().is_none() {
log::error!(
"bug: text thread in recent history list was never saved"
);
continue;
}
}
let summary = entry.summary(cx);
menu = menu.entry_with_end_slot_on_hover(
summary,
None,
{
let panel = panel.clone();
let entry = entry.clone();
move |window, cx| {
panel
.update(cx, {
let entry = entry.clone();
move |this, cx| match entry {
RecentEntry::Thread(_, thread) => {
this.open_thread(thread, window, cx)
}
RecentEntry::Context(context) => {
let Some(path) = context.read(cx).path()
else {
return;
};
this.open_saved_prompt_editor(
path.clone(),
window,
cx,
)
.detach_and_log_err(cx)
}
}
})
.ok();
}
},
IconName::Close,
"Close Entry".into(),
{
let panel = panel.clone();
let entry = entry.clone();
move |_window, cx| {
panel
.update(cx, |this, cx| {
this.history_store.update(
cx,
|history_store, cx| {
history_store.remove_recently_opened_entry(
&entry, cx,
);
},
);
})
.ok();
}
},
);
}
menu = menu.separator();
if let Some(panel) = panel.upgrade() {
menu = Self::populate_recently_opened_menu_section(menu, panel, cx);
}
menu.action("View All", Box::new(OpenHistory))
.end_slot_action(DeleteRecentlyOpenThread.boxed_clone())
.fixed_width(px(320.).into())
@@ -898,6 +840,7 @@ impl AgentPanel {
self.set_active_view(
ActiveView::prompt_editor(
context_editor.clone(),
self.history_store.clone(),
self.language_registry.clone(),
window,
cx,
@@ -984,7 +927,13 @@ impl AgentPanel {
)
});
self.set_active_view(
ActiveView::prompt_editor(editor.clone(), self.language_registry.clone(), window, cx),
ActiveView::prompt_editor(
editor.clone(),
self.history_store.clone(),
self.language_registry.clone(),
window,
cx,
),
window,
cx,
);
@@ -1383,16 +1332,6 @@ impl AgentPanel {
}
}
}
ActiveView::TextThread { context_editor, .. } => {
let context = context_editor.read(cx).context();
// When switching away from an unsaved text thread, delete its entry.
if context.read(cx).path().is_none() {
let context = context.clone();
self.history_store.update(cx, |store, cx| {
store.remove_recently_opened_entry(&RecentEntry::Context(context), cx);
});
}
}
_ => {}
}
@@ -1400,13 +1339,14 @@ impl AgentPanel {
ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
if let Some(thread) = thread.upgrade() {
let id = thread.read(cx).id().clone();
store.push_recently_opened_entry(RecentEntry::Thread(id, thread), cx);
store.push_recently_opened_entry(HistoryEntryId::Thread(id), cx);
}
}),
ActiveView::TextThread { context_editor, .. } => {
self.history_store.update(cx, |store, cx| {
let context = context_editor.read(cx).context().clone();
store.push_recently_opened_entry(RecentEntry::Context(context), cx)
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
store.push_recently_opened_entry(HistoryEntryId::Context(path.clone()), cx)
}
})
}
_ => {}
@@ -1425,6 +1365,70 @@ impl AgentPanel {
self.focus_handle(cx).focus(window);
}
fn populate_recently_opened_menu_section(
mut menu: ContextMenu,
panel: Entity<Self>,
cx: &mut Context<ContextMenu>,
) -> ContextMenu {
let entries = panel
.read(cx)
.history_store
.read(cx)
.recently_opened_entries(cx);
if entries.is_empty() {
return menu;
}
menu = menu.header("Recently Opened");
for entry in entries {
let title = entry.title().clone();
let id = entry.id();
menu = menu.entry_with_end_slot_on_hover(
title,
None,
{
let panel = panel.downgrade();
let id = id.clone();
move |window, cx| {
let id = id.clone();
panel
.update(cx, move |this, cx| match id {
HistoryEntryId::Thread(id) => this
.open_thread_by_id(&id, window, cx)
.detach_and_log_err(cx),
HistoryEntryId::Context(path) => this
.open_saved_prompt_editor(path.clone(), window, cx)
.detach_and_log_err(cx),
})
.ok();
}
},
IconName::Close,
"Close Entry".into(),
{
let panel = panel.downgrade();
let id = id.clone();
move |_window, cx| {
panel
.update(cx, |this, cx| {
this.history_store.update(cx, |history_store, cx| {
history_store.remove_recently_opened_entry(&id, cx);
});
})
.ok();
}
},
);
}
menu = menu.separator();
menu
}
}
impl Focusable for AgentPanel {

View File

@@ -0,0 +1,334 @@
use std::sync::Arc;
use agent_settings::{AgentProfileId, AgentProfileSettings, AgentSettings};
use assistant_tool::{Tool, ToolSource, ToolWorkingSet};
use collections::IndexMap;
use convert_case::{Case, Casing};
use fs::Fs;
use gpui::{App, Entity};
use settings::{Settings, update_settings_file};
use ui::SharedString;
use util::ResultExt;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AgentProfile {
id: AgentProfileId,
tool_set: Entity<ToolWorkingSet>,
}
pub type AvailableProfiles = IndexMap<AgentProfileId, SharedString>;
impl AgentProfile {
pub fn new(id: AgentProfileId, tool_set: Entity<ToolWorkingSet>) -> Self {
Self { id, tool_set }
}
/// Saves a new profile to the settings.
pub fn create(
name: String,
base_profile_id: Option<AgentProfileId>,
fs: Arc<dyn Fs>,
cx: &App,
) -> AgentProfileId {
let id = AgentProfileId(name.to_case(Case::Kebab).into());
let base_profile =
base_profile_id.and_then(|id| AgentSettings::get_global(cx).profiles.get(&id).cloned());
let profile_settings = AgentProfileSettings {
name: name.into(),
tools: base_profile
.as_ref()
.map(|profile| profile.tools.clone())
.unwrap_or_default(),
enable_all_context_servers: base_profile
.as_ref()
.map(|profile| profile.enable_all_context_servers)
.unwrap_or_default(),
context_servers: base_profile
.map(|profile| profile.context_servers)
.unwrap_or_default(),
};
update_settings_file::<AgentSettings>(fs, cx, {
let id = id.clone();
move |settings, _cx| {
settings.create_profile(id, profile_settings).log_err();
}
});
id
}
/// Returns a map of AgentProfileIds to their names
pub fn available_profiles(cx: &App) -> AvailableProfiles {
let mut profiles = AvailableProfiles::default();
for (id, profile) in AgentSettings::get_global(cx).profiles.iter() {
profiles.insert(id.clone(), profile.name.clone());
}
profiles
}
pub fn id(&self) -> &AgentProfileId {
&self.id
}
pub fn enabled_tools(&self, cx: &App) -> Vec<Arc<dyn Tool>> {
let Some(settings) = AgentSettings::get_global(cx).profiles.get(&self.id) else {
return Vec::new();
};
self.tool_set
.read(cx)
.tools(cx)
.into_iter()
.filter(|tool| Self::is_enabled(settings, tool.source(), tool.name()))
.collect()
}
fn is_enabled(settings: &AgentProfileSettings, source: ToolSource, name: String) -> bool {
match source {
ToolSource::Native => *settings.tools.get(name.as_str()).unwrap_or(&false),
ToolSource::ContextServer { id } => {
if settings.enable_all_context_servers {
return true;
}
let Some(preset) = settings.context_servers.get(id.as_ref()) else {
return false;
};
*preset.tools.get(name.as_str()).unwrap_or(&false)
}
}
}
}
#[cfg(test)]
mod tests {
use agent_settings::ContextServerPreset;
use assistant_tool::ToolRegistry;
use collections::IndexMap;
use gpui::{AppContext, TestAppContext};
use http_client::FakeHttpClient;
use project::Project;
use settings::{Settings, SettingsStore};
use ui::SharedString;
use super::*;
#[gpui::test]
async fn test_enabled_built_in_tools_for_profile(cx: &mut TestAppContext) {
init_test_settings(cx);
let id = AgentProfileId::default();
let profile_settings = cx.read(|cx| {
AgentSettings::get_global(cx)
.profiles
.get(&id)
.unwrap()
.clone()
});
let tool_set = default_tool_set(cx);
let profile = AgentProfile::new(id.clone(), tool_set);
let mut enabled_tools = cx
.read(|cx| profile.enabled_tools(cx))
.into_iter()
.map(|tool| tool.name())
.collect::<Vec<_>>();
enabled_tools.sort();
let mut expected_tools = profile_settings
.tools
.into_iter()
.filter_map(|(tool, enabled)| enabled.then_some(tool.to_string()))
// Provider dependent
.filter(|tool| tool != "web_search")
.collect::<Vec<_>>();
// Plus all registered MCP tools
expected_tools.extend(["enabled_mcp_tool".into(), "disabled_mcp_tool".into()]);
expected_tools.sort();
assert_eq!(enabled_tools, expected_tools);
}
#[gpui::test]
async fn test_custom_mcp_settings(cx: &mut TestAppContext) {
init_test_settings(cx);
let id = AgentProfileId("custom_mcp".into());
let profile_settings = cx.read(|cx| {
AgentSettings::get_global(cx)
.profiles
.get(&id)
.unwrap()
.clone()
});
let tool_set = default_tool_set(cx);
let profile = AgentProfile::new(id.clone(), tool_set);
let mut enabled_tools = cx
.read(|cx| profile.enabled_tools(cx))
.into_iter()
.map(|tool| tool.name())
.collect::<Vec<_>>();
enabled_tools.sort();
let mut expected_tools = profile_settings.context_servers["mcp"]
.tools
.iter()
.filter_map(|(key, enabled)| enabled.then(|| key.to_string()))
.collect::<Vec<_>>();
expected_tools.sort();
assert_eq!(enabled_tools, expected_tools);
}
#[gpui::test]
async fn test_only_built_in(cx: &mut TestAppContext) {
init_test_settings(cx);
let id = AgentProfileId("write_minus_mcp".into());
let profile_settings = cx.read(|cx| {
AgentSettings::get_global(cx)
.profiles
.get(&id)
.unwrap()
.clone()
});
let tool_set = default_tool_set(cx);
let profile = AgentProfile::new(id.clone(), tool_set);
let mut enabled_tools = cx
.read(|cx| profile.enabled_tools(cx))
.into_iter()
.map(|tool| tool.name())
.collect::<Vec<_>>();
enabled_tools.sort();
let mut expected_tools = profile_settings
.tools
.into_iter()
.filter_map(|(tool, enabled)| enabled.then_some(tool.to_string()))
// Provider dependent
.filter(|tool| tool != "web_search")
.collect::<Vec<_>>();
expected_tools.sort();
assert_eq!(enabled_tools, expected_tools);
}
fn init_test_settings(cx: &mut TestAppContext) {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
Project::init_settings(cx);
AgentSettings::register(cx);
language_model::init_settings(cx);
ToolRegistry::default_global(cx);
assistant_tools::init(FakeHttpClient::with_404_response(), cx);
});
cx.update(|cx| {
let mut agent_settings = AgentSettings::get_global(cx).clone();
agent_settings.profiles.insert(
AgentProfileId("write_minus_mcp".into()),
AgentProfileSettings {
name: "write_minus_mcp".into(),
enable_all_context_servers: false,
..agent_settings.profiles[&AgentProfileId::default()].clone()
},
);
agent_settings.profiles.insert(
AgentProfileId("custom_mcp".into()),
AgentProfileSettings {
name: "mcp".into(),
tools: IndexMap::default(),
enable_all_context_servers: false,
context_servers: IndexMap::from_iter([("mcp".into(), context_server_preset())]),
},
);
AgentSettings::override_global(agent_settings, cx);
})
}
fn context_server_preset() -> ContextServerPreset {
ContextServerPreset {
tools: IndexMap::from_iter([
("enabled_mcp_tool".into(), true),
("disabled_mcp_tool".into(), false),
]),
}
}
fn default_tool_set(cx: &mut TestAppContext) -> Entity<ToolWorkingSet> {
cx.new(|_| {
let mut tool_set = ToolWorkingSet::default();
tool_set.insert(Arc::new(FakeTool::new("enabled_mcp_tool", "mcp")));
tool_set.insert(Arc::new(FakeTool::new("disabled_mcp_tool", "mcp")));
tool_set
})
}
struct FakeTool {
name: String,
source: SharedString,
}
impl FakeTool {
fn new(name: impl Into<String>, source: impl Into<SharedString>) -> Self {
Self {
name: name.into(),
source: source.into(),
}
}
}
impl Tool for FakeTool {
fn name(&self) -> String {
self.name.clone()
}
fn source(&self) -> ToolSource {
ToolSource::ContextServer {
id: self.source.clone(),
}
}
fn description(&self) -> String {
unimplemented!()
}
fn icon(&self) -> ui::IconName {
unimplemented!()
}
fn needs_confirmation(&self, _input: &serde_json::Value, _cx: &App) -> bool {
unimplemented!()
}
fn ui_text(&self, _input: &serde_json::Value) -> String {
unimplemented!()
}
fn run(
self: Arc<Self>,
_input: serde_json::Value,
_request: Arc<language_model::LanguageModelRequest>,
_project: Entity<Project>,
_action_log: Entity<assistant_tool::ActionLog>,
_model: Arc<dyn language_model::LanguageModel>,
_window: Option<gpui::AnyWindowHandle>,
_cx: &mut App,
) -> assistant_tool::ToolResult {
unimplemented!()
}
fn may_perform_edits(&self) -> bool {
unimplemented!()
}
}
}

View File

@@ -386,8 +386,10 @@ impl CodegenAlternative {
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
} else {
let request = self.build_request(&model, user_prompt, cx)?;
cx.spawn(async move |_, cx| model.stream_completion_text(request.await, &cx).await)
.boxed_local()
cx.spawn(async move |_, cx| {
Ok(model.stream_completion_text(request.await, &cx).await?)
})
.boxed_local()
};
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
Ok(())

View File

@@ -1,7 +1,5 @@
use std::cell::RefCell;
use std::ops::Range;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
@@ -767,7 +765,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
let snapshot = buffer.read(cx).snapshot();
let source_range = snapshot.anchor_before(state.source_range.start)
..snapshot.anchor_before(state.source_range.end);
..snapshot.anchor_after(state.source_range.end);
let thread_store = self.thread_store.clone();
let text_thread_store = self.text_thread_store.clone();
@@ -912,16 +910,6 @@ impl CompletionProvider for ContextPickerCompletionProvider {
})
}
fn resolve_completions(
&self,
_buffer: Entity<Buffer>,
_completion_indices: Vec<usize>,
_completions: Rc<RefCell<Box<[Completion]>>>,
_cx: &mut Context<Editor>,
) -> Task<Result<bool>> {
Task::ready(Ok(true))
}
fn is_completion_trigger(
&self,
buffer: &Entity<language::Buffer>,
@@ -1077,7 +1065,7 @@ mod tests {
use project::{Project, ProjectPath};
use serde_json::json;
use settings::SettingsStore;
use std::ops::Deref;
use std::{ops::Deref, rc::Rc};
use util::{path, separator};
use workspace::{AppState, Item};

View File

@@ -282,15 +282,18 @@ pub fn unordered_thread_entries(
text_thread_store: Entity<TextThreadStore>,
cx: &App,
) -> impl Iterator<Item = (DateTime<Utc>, ThreadContextEntry)> {
let threads = thread_store.read(cx).unordered_threads().map(|thread| {
(
thread.updated_at,
ThreadContextEntry::Thread {
id: thread.id.clone(),
title: thread.summary.clone(),
},
)
});
let threads = thread_store
.read(cx)
.reverse_chronological_threads()
.map(|thread| {
(
thread.updated_at,
ThreadContextEntry::Thread {
id: thread.id.clone(),
title: thread.summary.clone(),
},
)
});
let text_threads = text_thread_store
.read(cx)
@@ -300,7 +303,7 @@ pub fn unordered_thread_entries(
context.mtime.to_utc(),
ThreadContextEntry::Context {
path: context.path.clone(),
title: context.title.clone().into(),
title: context.title.clone(),
},
)
});

View File

@@ -71,6 +71,10 @@ fn show_configure_mcp_modal(
window: &mut Window,
cx: &mut Context<'_, Workspace>,
) {
if !window.is_window_active() {
return;
}
let context_server_store = workspace.project().read(cx).context_server_store();
let repository: Option<SharedString> = manifest.repository.as_ref().map(|s| s.clone().into());

View File

@@ -104,7 +104,15 @@ impl Tool for ContextServerTool {
tool_name,
arguments
);
let response = protocol.run_tool(tool_name, arguments).await?;
let response = protocol
.request::<context_server::types::requests::CallTool>(
context_server::types::CallToolParams {
name: tool_name,
arguments,
meta: None,
},
)
.await?;
let mut result = String::new();
for content in response.content {
@@ -115,6 +123,9 @@ impl Tool for ContextServerTool {
types::ToolResponseContent::Image { .. } => {
log::warn!("Ignoring image content from tool response");
}
types::ToolResponseContent::Audio { .. } => {
log::warn!("Ignoring audio content from tool response");
}
types::ToolResponseContent::Resource { .. } => {
log::warn!("Ignoring resource content from tool response");
}

View File

@@ -1,18 +1,17 @@
use std::{collections::VecDeque, path::Path, sync::Arc};
use anyhow::Context as _;
use assistant_context_editor::{AssistantContext, SavedContextMetadata};
use anyhow::{Context as _, Result};
use assistant_context_editor::SavedContextMetadata;
use chrono::{DateTime, Utc};
use futures::future::{TryFutureExt as _, join_all};
use gpui::{Entity, Task, prelude::*};
use gpui::{AsyncApp, Entity, SharedString, Task, prelude::*};
use itertools::Itertools;
use paths::contexts_dir;
use serde::{Deserialize, Serialize};
use smol::future::FutureExt;
use std::time::Duration;
use ui::{App, SharedString, Window};
use ui::App;
use util::ResultExt as _;
use crate::{
Thread,
thread::ThreadId,
thread_store::{SerializedThreadMetadata, ThreadStore},
};
@@ -41,52 +40,34 @@ impl HistoryEntry {
HistoryEntry::Context(context) => HistoryEntryId::Context(context.path.clone()),
}
}
pub fn title(&self) -> &SharedString {
match self {
HistoryEntry::Thread(thread) => &thread.summary,
HistoryEntry::Context(context) => &context.title,
}
}
}
/// Generic identifier for a history entry.
#[derive(Clone, PartialEq, Eq)]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum HistoryEntryId {
Thread(ThreadId),
Context(Arc<Path>),
}
#[derive(Clone, Debug)]
pub(crate) enum RecentEntry {
Thread(ThreadId, Entity<Thread>),
Context(Entity<AssistantContext>),
}
impl PartialEq for RecentEntry {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Thread(l0, _), Self::Thread(r0, _)) => l0 == r0,
(Self::Context(l0), Self::Context(r0)) => l0 == r0,
_ => false,
}
}
}
impl Eq for RecentEntry {}
impl RecentEntry {
pub(crate) fn summary(&self, cx: &App) -> SharedString {
match self {
RecentEntry::Thread(_, thread) => thread.read(cx).summary().or_default(),
RecentEntry::Context(context) => context.read(cx).summary().or_default(),
}
}
}
#[derive(Serialize, Deserialize)]
enum SerializedRecentEntry {
enum SerializedRecentOpen {
Thread(String),
ContextName(String),
/// Old format which stores the full path
Context(String),
}
pub struct HistoryStore {
thread_store: Entity<ThreadStore>,
context_store: Entity<assistant_context_editor::ContextStore>,
recently_opened_entries: VecDeque<RecentEntry>,
recently_opened_entries: VecDeque<HistoryEntryId>,
_subscriptions: Vec<gpui::Subscription>,
_save_recently_opened_entries_task: Task<()>,
}
@@ -95,8 +76,7 @@ impl HistoryStore {
pub fn new(
thread_store: Entity<ThreadStore>,
context_store: Entity<assistant_context_editor::ContextStore>,
initial_recent_entries: impl IntoIterator<Item = RecentEntry>,
window: &mut Window,
initial_recent_entries: impl IntoIterator<Item = HistoryEntryId>,
cx: &mut Context<Self>,
) -> Self {
let subscriptions = vec![
@@ -104,68 +84,20 @@ impl HistoryStore {
cx.observe(&context_store, |_, _, cx| cx.notify()),
];
window
.spawn(cx, {
let thread_store = thread_store.downgrade();
let context_store = context_store.downgrade();
let this = cx.weak_entity();
async move |cx| {
let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
let contents = cx
.background_spawn(async move { std::fs::read_to_string(path) })
.await
.ok()?;
let entries = serde_json::from_str::<Vec<SerializedRecentEntry>>(&contents)
.context("deserializing persisted agent panel navigation history")
.log_err()?
.into_iter()
.take(MAX_RECENTLY_OPENED_ENTRIES)
.map(|serialized| match serialized {
SerializedRecentEntry::Thread(id) => thread_store
.update_in(cx, |thread_store, window, cx| {
let thread_id = ThreadId::from(id.as_str());
thread_store
.open_thread(&thread_id, window, cx)
.map_ok(|thread| RecentEntry::Thread(thread_id, thread))
.boxed()
})
.unwrap_or_else(|_| {
async {
anyhow::bail!("no thread store");
}
.boxed()
}),
SerializedRecentEntry::Context(id) => context_store
.update(cx, |context_store, cx| {
context_store
.open_local_context(Path::new(&id).into(), cx)
.map_ok(RecentEntry::Context)
.boxed()
})
.unwrap_or_else(|_| {
async {
anyhow::bail!("no context store");
}
.boxed()
}),
});
let entries = join_all(entries)
.await
.into_iter()
.filter_map(|result| result.log_with_level(log::Level::Debug))
.collect::<VecDeque<_>>();
this.update(cx, |this, _| {
this.recently_opened_entries.extend(entries);
this.recently_opened_entries
.truncate(MAX_RECENTLY_OPENED_ENTRIES);
})
.ok();
Some(())
}
cx.spawn(async move |this, cx| {
let entries = Self::load_recently_opened_entries(cx).await.log_err()?;
this.update(cx, |this, _| {
this.recently_opened_entries
.extend(
entries.into_iter().take(
MAX_RECENTLY_OPENED_ENTRIES
.saturating_sub(this.recently_opened_entries.len()),
),
);
})
.detach();
.ok()
})
.detach();
Self {
thread_store,
@@ -184,19 +116,20 @@ impl HistoryStore {
return history_entries;
}
for thread in self
.thread_store
.update(cx, |this, _cx| this.reverse_chronological_threads())
{
history_entries.push(HistoryEntry::Thread(thread));
}
for context in self
.context_store
.update(cx, |this, _cx| this.reverse_chronological_contexts())
{
history_entries.push(HistoryEntry::Context(context));
}
history_entries.extend(
self.thread_store
.read(cx)
.reverse_chronological_threads()
.cloned()
.map(HistoryEntry::Thread),
);
history_entries.extend(
self.context_store
.read(cx)
.unordered_contexts()
.cloned()
.map(HistoryEntry::Context),
);
history_entries.sort_unstable_by_key(|entry| std::cmp::Reverse(entry.updated_at()));
history_entries
@@ -206,15 +139,62 @@ impl HistoryStore {
self.entries(cx).into_iter().take(limit).collect()
}
pub fn recently_opened_entries(&self, cx: &App) -> Vec<HistoryEntry> {
#[cfg(debug_assertions)]
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
return Vec::new();
}
let thread_entries = self
.thread_store
.read(cx)
.reverse_chronological_threads()
.flat_map(|thread| {
self.recently_opened_entries
.iter()
.enumerate()
.flat_map(|(index, entry)| match entry {
HistoryEntryId::Thread(id) if &thread.id == id => {
Some((index, HistoryEntry::Thread(thread.clone())))
}
_ => None,
})
});
let context_entries =
self.context_store
.read(cx)
.unordered_contexts()
.flat_map(|context| {
self.recently_opened_entries
.iter()
.enumerate()
.flat_map(|(index, entry)| match entry {
HistoryEntryId::Context(path) if &context.path == path => {
Some((index, HistoryEntry::Context(context.clone())))
}
_ => None,
})
});
thread_entries
.chain(context_entries)
// optimization to halt iteration early
.take(self.recently_opened_entries.len())
.sorted_unstable_by_key(|(index, _)| *index)
.map(|(_, entry)| entry)
.collect()
}
fn save_recently_opened_entries(&mut self, cx: &mut Context<Self>) {
let serialized_entries = self
.recently_opened_entries
.iter()
.filter_map(|entry| match entry {
RecentEntry::Context(context) => Some(SerializedRecentEntry::Context(
context.read(cx).path()?.to_str()?.to_owned(),
)),
RecentEntry::Thread(id, _) => Some(SerializedRecentEntry::Thread(id.to_string())),
HistoryEntryId::Context(path) => path.file_name().map(|file| {
SerializedRecentOpen::ContextName(file.to_string_lossy().to_string())
}),
HistoryEntryId::Thread(id) => Some(SerializedRecentOpen::Thread(id.to_string())),
})
.collect::<Vec<_>>();
@@ -233,7 +213,33 @@ impl HistoryStore {
});
}
pub fn push_recently_opened_entry(&mut self, entry: RecentEntry, cx: &mut Context<Self>) {
fn load_recently_opened_entries(cx: &AsyncApp) -> Task<Result<Vec<HistoryEntryId>>> {
cx.background_spawn(async move {
let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
let contents = smol::fs::read_to_string(path).await?;
let entries = serde_json::from_str::<Vec<SerializedRecentOpen>>(&contents)
.context("deserializing persisted agent panel navigation history")?
.into_iter()
.take(MAX_RECENTLY_OPENED_ENTRIES)
.flat_map(|entry| match entry {
SerializedRecentOpen::Thread(id) => {
Some(HistoryEntryId::Thread(id.as_str().into()))
}
SerializedRecentOpen::ContextName(file_name) => Some(HistoryEntryId::Context(
contexts_dir().join(file_name).into(),
)),
SerializedRecentOpen::Context(path) => {
Path::new(&path).file_name().map(|file_name| {
HistoryEntryId::Context(contexts_dir().join(file_name).into())
})
}
})
.collect::<Vec<_>>();
Ok(entries)
})
}
pub fn push_recently_opened_entry(&mut self, entry: HistoryEntryId, cx: &mut Context<Self>) {
self.recently_opened_entries
.retain(|old_entry| old_entry != &entry);
self.recently_opened_entries.push_front(entry);
@@ -244,24 +250,33 @@ impl HistoryStore {
pub fn remove_recently_opened_thread(&mut self, id: ThreadId, cx: &mut Context<Self>) {
self.recently_opened_entries.retain(|entry| match entry {
RecentEntry::Thread(thread_id, _) if thread_id == &id => false,
HistoryEntryId::Thread(thread_id) if thread_id == &id => false,
_ => true,
});
self.save_recently_opened_entries(cx);
}
pub fn remove_recently_opened_entry(&mut self, entry: &RecentEntry, cx: &mut Context<Self>) {
pub fn replace_recently_opened_text_thread(
&mut self,
old_path: &Path,
new_path: &Arc<Path>,
cx: &mut Context<Self>,
) {
for entry in &mut self.recently_opened_entries {
match entry {
HistoryEntryId::Context(path) if path.as_ref() == old_path => {
*entry = HistoryEntryId::Context(new_path.clone());
break;
}
_ => {}
}
}
self.save_recently_opened_entries(cx);
}
pub fn remove_recently_opened_entry(&mut self, entry: &HistoryEntryId, cx: &mut Context<Self>) {
self.recently_opened_entries
.retain(|old_entry| old_entry != entry);
self.save_recently_opened_entries(cx);
}
pub fn recently_opened_entries(&self, _cx: &mut Context<Self>) -> VecDeque<RecentEntry> {
#[cfg(debug_assertions)]
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
return VecDeque::new();
}
self.recently_opened_entries.clone()
}
}

View File

@@ -38,8 +38,7 @@ use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
use text::{OffsetRangeExt, ToPoint as _};
use ui::prelude::*;
use util::RangeExt;
use util::ResultExt;
use util::{RangeExt, ResultExt, maybe};
use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId};
use zed_actions::agent::OpenConfiguration;
@@ -768,9 +767,6 @@ impl InlineAssistant {
PromptEditorEvent::CancelRequested => {
self.finish_assist(assist_id, true, window, cx);
}
PromptEditorEvent::DismissRequested => {
self.dismiss_assist(assist_id, window, cx);
}
PromptEditorEvent::Resized { .. } => {
// This only matters for the terminal inline assistant
}
@@ -1011,7 +1007,7 @@ impl InlineAssistant {
self.update_editor_highlights(&editor, cx);
}
} else {
entry.get().highlight_updates.send(()).ok();
entry.get_mut().highlight_updates.send(()).ok();
}
}
@@ -1171,27 +1167,31 @@ impl InlineAssistant {
selections.select_anchor_ranges([position..position])
});
let mut scroll_target_top;
let mut scroll_target_bottom;
let mut scroll_target_range = None;
if let Some(decorations) = assist.decorations.as_ref() {
scroll_target_top = editor
.row_for_block(decorations.prompt_block_id, cx)
.unwrap()
.0 as f32;
scroll_target_bottom = editor
.row_for_block(decorations.end_block_id, cx)
.unwrap()
.0 as f32;
} else {
scroll_target_range = maybe!({
let top = editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f32;
let bottom = editor.row_for_block(decorations.end_block_id, cx)?.0 as f32;
Some((top, bottom))
});
if scroll_target_range.is_none() {
log::error!("bug: failed to find blocks for scrolling to inline assist");
}
}
let scroll_target_range = scroll_target_range.unwrap_or_else(|| {
let snapshot = editor.snapshot(window, cx);
let start_row = assist
.range
.start
.to_display_point(&snapshot.display_snapshot)
.row();
scroll_target_top = start_row.0 as f32;
scroll_target_bottom = scroll_target_top + 1.;
}
let top = start_row.0 as f32;
let bottom = top + 1.0;
(top, bottom)
});
let mut scroll_target_top = scroll_target_range.0;
let mut scroll_target_bottom = scroll_target_range.1;
scroll_target_top -= editor.vertical_scroll_margin() as f32;
scroll_target_bottom += editor.vertical_scroll_margin() as f32;
@@ -1331,7 +1331,7 @@ impl InlineAssistant {
editor.clear_gutter_highlights::<GutterPendingRange>(cx);
} else {
editor.highlight_gutter::<GutterPendingRange>(
&gutter_pending_ranges,
gutter_pending_ranges,
|cx| cx.theme().status().info_background,
cx,
)
@@ -1342,7 +1342,7 @@ impl InlineAssistant {
editor.clear_gutter_highlights::<GutterTransformedRange>(cx);
} else {
editor.highlight_gutter::<GutterTransformedRange>(
&gutter_transformed_ranges,
gutter_transformed_ranges,
|cx| cx.theme().status().info,
cx,
)
@@ -1519,7 +1519,7 @@ impl InlineAssistant {
struct EditorInlineAssists {
assist_ids: Vec<InlineAssistId>,
scroll_lock: Option<InlineAssistScrollLock>,
highlight_updates: async_watch::Sender<()>,
highlight_updates: watch::Sender<()>,
_update_highlights: Task<Result<()>>,
_subscriptions: Vec<gpui::Subscription>,
}
@@ -1531,7 +1531,7 @@ struct InlineAssistScrollLock {
impl EditorInlineAssists {
fn new(editor: &Entity<Editor>, window: &mut Window, cx: &mut App) -> Self {
let (highlight_updates_tx, mut highlight_updates_rx) = async_watch::channel(());
let (highlight_updates_tx, mut highlight_updates_rx) = watch::channel(());
Self {
assist_ids: Vec::new(),
scroll_lock: None,
@@ -1689,7 +1689,7 @@ impl InlineAssist {
if let Some(editor) = editor.upgrade() {
InlineAssistant::update_global(cx, |this, cx| {
if let Some(editor_assists) =
this.assists_by_editor.get(&editor.downgrade())
this.assists_by_editor.get_mut(&editor.downgrade())
{
editor_assists.highlight_updates.send(()).ok();
}

View File

@@ -403,9 +403,7 @@ impl<T: 'static> PromptEditor<T> {
CodegenStatus::Idle => {
cx.emit(PromptEditorEvent::StartRequested);
}
CodegenStatus::Pending => {
cx.emit(PromptEditorEvent::DismissRequested);
}
CodegenStatus::Pending => {}
CodegenStatus::Done => {
if self.edited_since_done {
cx.emit(PromptEditorEvent::StartRequested);
@@ -831,7 +829,6 @@ pub enum PromptEditorEvent {
StopRequested,
ConfirmRequested { execute: bool },
CancelRequested,
DismissRequested,
Resized { height_in_lines: u8 },
}

View File

@@ -175,8 +175,7 @@ impl MessageEditor {
)
});
let incompatible_tools =
cx.new(|cx| IncompatibleToolsState::new(thread.read(cx).tools().clone(), cx));
let incompatible_tools = cx.new(|cx| IncompatibleToolsState::new(thread.clone(), cx));
let subscriptions = vec![
cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event),
@@ -204,15 +203,8 @@ impl MessageEditor {
)
});
let profile_selector = cx.new(|cx| {
ProfileSelector::new(
fs,
thread.clone(),
thread_store,
editor.focus_handle(cx),
cx,
)
});
let profile_selector =
cx.new(|cx| ProfileSelector::new(fs, thread.clone(), editor.focus_handle(cx), cx));
Self {
editor: editor.clone(),
@@ -730,6 +722,7 @@ impl MessageEditor {
.child(
h_flex()
.flex_none()
.flex_wrap()
.justify_between()
.child(
h_flex()
@@ -739,6 +732,7 @@ impl MessageEditor {
.child(
h_flex()
.gap_1()
.flex_wrap()
.when(!incompatible_tools.is_empty(), |this| {
this.child(
IconButton::new(

View File

@@ -1,26 +1,24 @@
use std::sync::Arc;
use agent_settings::{
AgentDockPosition, AgentProfile, AgentProfileId, AgentSettings, GroupedAgentProfiles,
builtin_profiles,
};
use agent_settings::{AgentDockPosition, AgentProfileId, AgentSettings, builtin_profiles};
use fs::Fs;
use gpui::{Action, Empty, Entity, FocusHandle, Subscription, WeakEntity, prelude::*};
use gpui::{Action, Empty, Entity, FocusHandle, Subscription, prelude::*};
use language_model::LanguageModelRegistry;
use settings::{Settings as _, SettingsStore, update_settings_file};
use ui::{
ContextMenu, ContextMenuEntry, DocumentationSide, PopoverMenu, PopoverMenuHandle, Tooltip,
prelude::*,
};
use util::ResultExt as _;
use crate::{ManageProfiles, Thread, ThreadStore, ToggleProfileSelector};
use crate::{
ManageProfiles, Thread, ToggleProfileSelector,
agent_profile::{AgentProfile, AvailableProfiles},
};
pub struct ProfileSelector {
profiles: GroupedAgentProfiles,
profiles: AvailableProfiles,
fs: Arc<dyn Fs>,
thread: Entity<Thread>,
thread_store: WeakEntity<ThreadStore>,
menu_handle: PopoverMenuHandle<ContextMenu>,
focus_handle: FocusHandle,
_subscriptions: Vec<Subscription>,
@@ -30,7 +28,6 @@ impl ProfileSelector {
pub fn new(
fs: Arc<dyn Fs>,
thread: Entity<Thread>,
thread_store: WeakEntity<ThreadStore>,
focus_handle: FocusHandle,
cx: &mut Context<Self>,
) -> Self {
@@ -39,10 +36,9 @@ impl ProfileSelector {
});
Self {
profiles: GroupedAgentProfiles::from_settings(AgentSettings::get_global(cx)),
profiles: AgentProfile::available_profiles(cx),
fs,
thread,
thread_store,
menu_handle: PopoverMenuHandle::default(),
focus_handle,
_subscriptions: vec![settings_subscription],
@@ -54,7 +50,7 @@ impl ProfileSelector {
}
fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
self.profiles = GroupedAgentProfiles::from_settings(AgentSettings::get_global(cx));
self.profiles = AgentProfile::available_profiles(cx);
}
fn build_context_menu(
@@ -64,21 +60,30 @@ impl ProfileSelector {
) -> Entity<ContextMenu> {
ContextMenu::build(window, cx, |mut menu, _window, cx| {
let settings = AgentSettings::get_global(cx);
for (profile_id, profile) in self.profiles.builtin.iter() {
let mut found_non_builtin = false;
for (profile_id, profile_name) in self.profiles.iter() {
if !builtin_profiles::is_builtin(profile_id) {
found_non_builtin = true;
continue;
}
menu = menu.item(self.menu_entry_for_profile(
profile_id.clone(),
profile,
profile_name,
settings,
cx,
));
}
if !self.profiles.custom.is_empty() {
if found_non_builtin {
menu = menu.separator().header("Custom Profiles");
for (profile_id, profile) in self.profiles.custom.iter() {
for (profile_id, profile_name) in self.profiles.iter() {
if builtin_profiles::is_builtin(profile_id) {
continue;
}
menu = menu.item(self.menu_entry_for_profile(
profile_id.clone(),
profile,
profile_name,
settings,
cx,
));
@@ -99,19 +104,20 @@ impl ProfileSelector {
fn menu_entry_for_profile(
&self,
profile_id: AgentProfileId,
profile: &AgentProfile,
profile_name: &SharedString,
settings: &AgentSettings,
_cx: &App,
cx: &App,
) -> ContextMenuEntry {
let documentation = match profile.name.to_lowercase().as_str() {
let documentation = match profile_name.to_lowercase().as_str() {
builtin_profiles::WRITE => Some("Get help to write anything."),
builtin_profiles::ASK => Some("Chat about your codebase."),
builtin_profiles::MINIMAL => Some("Chat about anything with no tools."),
_ => None,
};
let thread_profile_id = self.thread.read(cx).profile().id();
let entry = ContextMenuEntry::new(profile.name.clone())
.toggleable(IconPosition::End, profile_id == settings.default_profile);
let entry = ContextMenuEntry::new(profile_name.clone())
.toggleable(IconPosition::End, &profile_id == thread_profile_id);
let entry = if let Some(doc_text) = documentation {
entry.documentation_aside(documentation_side(settings.dock), move |_| {
@@ -123,7 +129,7 @@ impl ProfileSelector {
entry.handler({
let fs = self.fs.clone();
let thread_store = self.thread_store.clone();
let thread = self.thread.clone();
let profile_id = profile_id.clone();
move |_window, cx| {
update_settings_file::<AgentSettings>(fs.clone(), cx, {
@@ -133,11 +139,9 @@ impl ProfileSelector {
}
});
thread_store
.update(cx, |this, cx| {
this.load_profile_by_id(profile_id.clone(), cx);
})
.log_err();
thread.update(cx, |this, cx| {
this.set_profile(profile_id.clone(), cx);
});
}
})
}
@@ -146,7 +150,7 @@ impl ProfileSelector {
impl Render for ProfileSelector {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let settings = AgentSettings::get_global(cx);
let profile_id = &settings.default_profile;
let profile_id = self.thread.read(cx).profile().id();
let profile = settings.profiles.get(profile_id);
let selected_profile = profile

View File

@@ -167,9 +167,6 @@ impl TerminalInlineAssistant {
PromptEditorEvent::CancelRequested => {
self.finish_assist(assist_id, true, false, window, cx);
}
PromptEditorEvent::DismissRequested => {
self.dismiss_assist(assist_id, window, cx);
}
PromptEditorEvent::Resized { height_in_lines } => {
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, window, cx);
}

View File

@@ -4,11 +4,11 @@ use std::ops::Range;
use std::sync::Arc;
use std::time::Instant;
use agent_settings::{AgentSettings, CompletionMode};
use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet};
use chrono::{DateTime, Utc};
use collections::HashMap;
use collections::{HashMap, HashSet};
use editor::display_map::CreaseMetadata;
use feature_flags::{self, FeatureFlagAppExt};
use futures::future::Shared;
@@ -41,6 +41,7 @@ use uuid::Uuid;
use zed_llm_client::{CompletionIntent, CompletionRequestStatus};
use crate::ThreadStore;
use crate::agent_profile::AgentProfile;
use crate::context::{AgentContext, AgentContextHandle, ContextLoadResult, LoadedContext};
use crate::thread_store::{
SerializedCrease, SerializedLanguageModel, SerializedMessage, SerializedMessageSegment,
@@ -143,6 +144,10 @@ impl Message {
}
}
pub fn push_redacted_thinking(&mut self, data: String) {
self.segments.push(MessageSegment::RedactedThinking(data));
}
pub fn push_text(&mut self, text: &str) {
if let Some(MessageSegment::Text(segment)) = self.segments.last_mut() {
segment.push_str(text);
@@ -181,7 +186,7 @@ pub enum MessageSegment {
text: String,
signature: Option<String>,
},
RedactedThinking(Vec<u8>),
RedactedThinking(String),
}
impl MessageSegment {
@@ -194,20 +199,20 @@ impl MessageSegment {
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectSnapshot {
pub worktree_snapshots: Vec<WorktreeSnapshot>,
pub unsaved_buffer_paths: Vec<String>,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WorktreeSnapshot {
pub worktree_path: String,
pub git_state: Option<GitState>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GitState {
pub remote_url: Option<String>,
pub head_sha: Option<String>,
@@ -246,7 +251,7 @@ impl LastRestoreCheckpoint {
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub enum DetailedSummaryState {
#[default]
NotGenerated,
@@ -360,6 +365,7 @@ pub struct Thread {
>,
remaining_turns: u32,
configured_model: Option<ConfiguredModel>,
profile: AgentProfile,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -389,7 +395,7 @@ impl ThreadSummary {
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ExceededWindowError {
/// Model used when last message exceeded context window
model_id: LanguageModelId,
@@ -407,6 +413,7 @@ impl Thread {
) -> Self {
let (detailed_summary_tx, detailed_summary_rx) = postage::watch::channel();
let configured_model = LanguageModelRegistry::read_global(cx).default_model();
let profile_id = AgentSettings::get_global(cx).default_profile.clone();
Self {
id: ThreadId::new(),
@@ -449,6 +456,7 @@ impl Thread {
request_callback: None,
remaining_turns: u32::MAX,
configured_model,
profile: AgentProfile::new(profile_id, tools),
}
}
@@ -495,6 +503,9 @@ impl Thread {
let completion_mode = serialized
.completion_mode
.unwrap_or_else(|| AgentSettings::get_global(cx).preferred_completion_mode);
let profile_id = serialized
.profile
.unwrap_or_else(|| AgentSettings::get_global(cx).default_profile.clone());
Self {
id,
@@ -554,7 +565,7 @@ impl Thread {
pending_checkpoint: None,
project: project.clone(),
prompt_builder,
tools,
tools: tools.clone(),
tool_use,
action_log: cx.new(|_| ActionLog::new(project)),
initial_project_snapshot: Task::ready(serialized.initial_project_snapshot).shared(),
@@ -570,6 +581,7 @@ impl Thread {
request_callback: None,
remaining_turns: u32::MAX,
configured_model,
profile: AgentProfile::new(profile_id, tools),
}
}
@@ -585,6 +597,17 @@ impl Thread {
&self.id
}
pub fn profile(&self) -> &AgentProfile {
&self.profile
}
pub fn set_profile(&mut self, id: AgentProfileId, cx: &mut Context<Self>) {
if &id != self.profile.id() {
self.profile = AgentProfile::new(id, self.tools.clone());
cx.emit(ThreadEvent::ProfileChanged);
}
}
pub fn is_empty(&self) -> bool {
self.messages.is_empty()
}
@@ -919,15 +942,13 @@ impl Thread {
model: Arc<dyn LanguageModel>,
) -> Vec<LanguageModelRequestTool> {
if model.supports_tools() {
self.tools()
.read(cx)
.enabled_tools(cx)
resolve_tool_name_conflicts(self.profile.enabled_tools(cx).as_slice())
.into_iter()
.filter_map(|tool| {
.filter_map(|(name, tool)| {
// Skip tools that cannot be supported
let input_schema = tool.input_schema(model.tool_input_format()).ok()?;
Some(LanguageModelRequestTool {
name: tool.name(),
name,
description: tool.description(),
input_schema,
})
@@ -1180,6 +1201,7 @@ impl Thread {
}),
completion_mode: Some(this.completion_mode),
tool_use_limit_reached: this.tool_use_limit_reached,
profile: Some(this.profile.id().clone()),
})
})
}
@@ -1544,6 +1566,9 @@ impl Thread {
Err(LanguageModelCompletionError::Other(error)) => {
return Err(error);
}
Err(err @ LanguageModelCompletionError::RateLimit(..)) => {
return Err(err.into());
}
};
match event {
@@ -1623,6 +1648,25 @@ impl Thread {
};
}
}
LanguageModelCompletionEvent::RedactedThinking {
data
} => {
thread.received_chunk();
if let Some(last_message) = thread.messages.last_mut() {
if last_message.role == Role::Assistant
&& !thread.tool_use.has_tool_results(last_message.id)
{
last_message.push_redacted_thinking(data);
} else {
request_assistant_message_id =
Some(thread.insert_assistant_message(
vec![MessageSegment::RedactedThinking(data)],
cx,
));
};
}
}
LanguageModelCompletionEvent::ToolUse(tool_use) => {
let last_assistant_message_id = request_assistant_message_id
.unwrap_or_else(|| {
@@ -2121,7 +2165,7 @@ impl Thread {
window: Option<AnyWindowHandle>,
cx: &mut Context<Thread>,
) {
let available_tools = self.tools.read(cx).enabled_tools(cx);
let available_tools = self.profile.enabled_tools(cx);
let tool_list = available_tools
.iter()
@@ -2213,19 +2257,15 @@ impl Thread {
) -> Task<()> {
let tool_name: Arc<str> = tool.name().into();
let tool_result = if self.tools.read(cx).is_disabled(&tool.source(), &tool_name) {
Task::ready(Err(anyhow!("tool is disabled: {tool_name}"))).into()
} else {
tool.run(
input,
request,
self.project.clone(),
self.action_log.clone(),
model,
window,
cx,
)
};
let tool_result = tool.run(
input,
request,
self.project.clone(),
self.action_log.clone(),
model,
window,
cx,
);
// Store the card separately if it exists
if let Some(card) = tool_result.card.clone() {
@@ -2344,8 +2384,7 @@ impl Thread {
let client = self.project.read(cx).client();
let enabled_tool_names: Vec<String> = self
.tools()
.read(cx)
.profile
.enabled_tools(cx)
.iter()
.map(|tool| tool.name())
@@ -2858,6 +2897,7 @@ pub enum ThreadEvent {
ToolUseLimitReached,
CancelEditing,
CompletionCanceled,
ProfileChanged,
}
impl EventEmitter<ThreadEvent> for Thread {}
@@ -2868,11 +2908,90 @@ struct PendingCompletion {
_task: Task<()>,
}
/// Resolves tool name conflicts by ensuring all tool names are unique.
///
/// When multiple tools have the same name, this function applies the following rules:
/// 1. Native tools always keep their original name
/// 2. Context server tools get prefixed with their server ID and an underscore
/// 3. All tool names are truncated to MAX_TOOL_NAME_LENGTH (64 characters)
/// 4. If conflicts still exist after prefixing, the conflicting tools are filtered out
///
/// Note: This function assumes that built-in tools occur before MCP tools in the tools list.
fn resolve_tool_name_conflicts(tools: &[Arc<dyn Tool>]) -> Vec<(String, Arc<dyn Tool>)> {
fn resolve_tool_name(tool: &Arc<dyn Tool>) -> String {
let mut tool_name = tool.name();
tool_name.truncate(MAX_TOOL_NAME_LENGTH);
tool_name
}
const MAX_TOOL_NAME_LENGTH: usize = 64;
let mut duplicated_tool_names = HashSet::default();
let mut seen_tool_names = HashSet::default();
for tool in tools {
let tool_name = resolve_tool_name(tool);
if seen_tool_names.contains(&tool_name) {
debug_assert!(
tool.source() != assistant_tool::ToolSource::Native,
"There are two built-in tools with the same name: {}",
tool_name
);
duplicated_tool_names.insert(tool_name);
} else {
seen_tool_names.insert(tool_name);
}
}
if duplicated_tool_names.is_empty() {
return tools
.into_iter()
.map(|tool| (resolve_tool_name(tool), tool.clone()))
.collect();
}
tools
.into_iter()
.filter_map(|tool| {
let mut tool_name = resolve_tool_name(tool);
if !duplicated_tool_names.contains(&tool_name) {
return Some((tool_name, tool.clone()));
}
match tool.source() {
assistant_tool::ToolSource::Native => {
// Built-in tools always keep their original name
Some((tool_name, tool.clone()))
}
assistant_tool::ToolSource::ContextServer { id } => {
// Context server tools are prefixed with the context server ID, and truncated if necessary
tool_name.insert(0, '_');
if tool_name.len() + id.len() > MAX_TOOL_NAME_LENGTH {
let len = MAX_TOOL_NAME_LENGTH - tool_name.len();
let mut id = id.to_string();
id.truncate(len);
tool_name.insert_str(0, &id);
} else {
tool_name.insert_str(0, &id);
}
tool_name.truncate(MAX_TOOL_NAME_LENGTH);
if seen_tool_names.contains(&tool_name) {
log::error!("Cannot resolve tool name conflict for tool {}", tool.name());
None
} else {
Some((tool_name, tool.clone()))
}
}
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ThreadStore, context::load_context, context_store::ContextStore, thread_store};
use agent_settings::{AgentSettings, LanguageModelParameters};
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelParameters};
use assistant_tool::ToolRegistry;
use editor::EditorSettings;
use gpui::TestAppContext;
@@ -2883,6 +3002,7 @@ mod tests {
use settings::{Settings, SettingsStore};
use std::sync::Arc;
use theme::ThemeSettings;
use ui::IconName;
use util::path;
use workspace::Workspace;
@@ -3285,6 +3405,71 @@ fn main() {{
);
}
#[gpui::test]
async fn test_storing_profile_setting_per_thread(cx: &mut TestAppContext) {
init_test_settings(cx);
let project = create_test_project(
cx,
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
)
.await;
let (_workspace, thread_store, thread, _context_store, _model) =
setup_test_environment(cx, project.clone()).await;
// Check that we are starting with the default profile
let profile = cx.read(|cx| thread.read(cx).profile.clone());
let tool_set = cx.read(|cx| thread_store.read(cx).tools());
assert_eq!(
profile,
AgentProfile::new(AgentProfileId::default(), tool_set)
);
}
#[gpui::test]
async fn test_serializing_thread_profile(cx: &mut TestAppContext) {
init_test_settings(cx);
let project = create_test_project(
cx,
json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
)
.await;
let (_workspace, thread_store, thread, _context_store, _model) =
setup_test_environment(cx, project.clone()).await;
// Profile gets serialized with default values
let serialized = thread
.update(cx, |thread, cx| thread.serialize(cx))
.await
.unwrap();
assert_eq!(serialized.profile, Some(AgentProfileId::default()));
let deserialized = cx.update(|cx| {
thread.update(cx, |thread, cx| {
Thread::deserialize(
thread.id.clone(),
serialized,
thread.project.clone(),
thread.tools.clone(),
thread.prompt_builder.clone(),
thread.project_context.clone(),
None,
cx,
)
})
});
let tool_set = cx.read(|cx| thread_store.read(cx).tools());
assert_eq!(
deserialized.profile,
AgentProfile::new(AgentProfileId::default(), tool_set)
);
}
#[gpui::test]
async fn test_temperature_setting(cx: &mut TestAppContext) {
init_test_settings(cx);
@@ -3537,6 +3722,148 @@ fn main() {{
});
}
#[gpui::test]
fn test_resolve_tool_name_conflicts() {
use assistant_tool::{Tool, ToolSource};
assert_resolve_tool_name_conflicts(
vec![
TestTool::new("tool1", ToolSource::Native),
TestTool::new("tool2", ToolSource::Native),
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-1".into() }),
],
vec!["tool1", "tool2", "tool3"],
);
assert_resolve_tool_name_conflicts(
vec![
TestTool::new("tool1", ToolSource::Native),
TestTool::new("tool2", ToolSource::Native),
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-1".into() }),
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-2".into() }),
],
vec!["tool1", "tool2", "mcp-1_tool3", "mcp-2_tool3"],
);
assert_resolve_tool_name_conflicts(
vec![
TestTool::new("tool1", ToolSource::Native),
TestTool::new("tool2", ToolSource::Native),
TestTool::new("tool3", ToolSource::Native),
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-1".into() }),
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-2".into() }),
],
vec!["tool1", "tool2", "tool3", "mcp-1_tool3", "mcp-2_tool3"],
);
// Test that tool with very long name is always truncated
assert_resolve_tool_name_conflicts(
vec![TestTool::new(
"tool-with-more-then-64-characters-blah-blah-blah-blah-blah-blah-blah-blah",
ToolSource::Native,
)],
vec!["tool-with-more-then-64-characters-blah-blah-blah-blah-blah-blah-"],
);
// Test deduplication of tools with very long names, in this case the mcp server name should be truncated
assert_resolve_tool_name_conflicts(
vec![
TestTool::new("tool-with-very-very-very-long-name", ToolSource::Native),
TestTool::new(
"tool-with-very-very-very-long-name",
ToolSource::ContextServer {
id: "mcp-with-very-very-very-long-name".into(),
},
),
],
vec![
"tool-with-very-very-very-long-name",
"mcp-with-very-very-very-long-_tool-with-very-very-very-long-name",
],
);
fn assert_resolve_tool_name_conflicts(
tools: Vec<TestTool>,
expected: Vec<impl Into<String>>,
) {
let tools: Vec<Arc<dyn Tool>> = tools
.into_iter()
.map(|t| Arc::new(t) as Arc<dyn Tool>)
.collect();
let tools = resolve_tool_name_conflicts(&tools);
assert_eq!(tools.len(), expected.len());
for (i, expected_name) in expected.into_iter().enumerate() {
let expected_name = expected_name.into();
let actual_name = &tools[i].0;
assert_eq!(
actual_name, &expected_name,
"Expected '{}' got '{}' at index {}",
expected_name, actual_name, i
);
}
}
struct TestTool {
name: String,
source: ToolSource,
}
impl TestTool {
fn new(name: impl Into<String>, source: ToolSource) -> Self {
Self {
name: name.into(),
source,
}
}
}
impl Tool for TestTool {
fn name(&self) -> String {
self.name.clone()
}
fn icon(&self) -> IconName {
IconName::Ai
}
fn may_perform_edits(&self) -> bool {
false
}
fn needs_confirmation(&self, _input: &serde_json::Value, _cx: &App) -> bool {
true
}
fn source(&self) -> ToolSource {
self.source.clone()
}
fn description(&self) -> String {
"Test tool".to_string()
}
fn ui_text(&self, _input: &serde_json::Value) -> String {
"Test tool".to_string()
}
fn run(
self: Arc<Self>,
_input: serde_json::Value,
_request: Arc<LanguageModelRequest>,
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_model: Arc<dyn LanguageModel>,
_window: Option<AnyWindowHandle>,
_cx: &mut App,
) -> assistant_tool::ToolResult {
assistant_tool::ToolResult {
output: Task::ready(Err(anyhow::anyhow!("No content"))),
card: None,
}
}
}
}
fn test_summarize_error(
model: &Arc<dyn LanguageModel>,
thread: &Entity<Thread>,

View File

@@ -594,10 +594,11 @@ impl Render for ThreadHistory {
view.pr_5()
.child(
uniform_list(
cx.entity().clone(),
"thread-history",
self.list_item_count(),
Self::list_items,
cx.processor(|this, range: Range<usize>, window, cx| {
this.list_items(range, window, cx)
}),
)
.p_1()
.track_scroll(self.scroll_handle.clone())
@@ -671,7 +672,7 @@ impl RenderOnce for HistoryEntryElement {
),
HistoryEntry::Context(context) => (
context.path.to_string_lossy().to_string(),
context.title.clone().into(),
context.title.clone(),
context.mtime.timestamp(),
),
};

View File

@@ -3,9 +3,9 @@ use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, CompletionMode};
use agent_settings::{AgentProfileId, CompletionMode};
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ToolId, ToolSource, ToolWorkingSet};
use assistant_tool::{ToolId, ToolWorkingSet};
use chrono::{DateTime, Utc};
use collections::HashMap;
use context_server::ContextServerId;
@@ -25,7 +25,6 @@ use prompt_store::{
UserRulesContext, WorktreeContext,
};
use serde::{Deserialize, Serialize};
use settings::{Settings as _, SettingsStore};
use ui::Window;
use util::ResultExt as _;
@@ -90,7 +89,7 @@ pub fn init(cx: &mut App) {
pub struct SharedProjectContext(Rc<RefCell<Option<ProjectContext>>>);
impl SharedProjectContext {
pub fn borrow(&self) -> Ref<Option<ProjectContext>> {
pub fn borrow(&self) -> Ref<'_, Option<ProjectContext>> {
self.0.borrow()
}
}
@@ -147,12 +146,7 @@ impl ThreadStore {
prompt_store: Option<Entity<PromptStore>>,
cx: &mut Context<Self>,
) -> (Self, oneshot::Receiver<()>) {
let mut subscriptions = vec![
cx.observe_global::<SettingsStore>(move |this: &mut Self, cx| {
this.load_default_profile(cx);
}),
cx.subscribe(&project, Self::handle_project_event),
];
let mut subscriptions = vec![cx.subscribe(&project, Self::handle_project_event)];
if let Some(prompt_store) = prompt_store.as_ref() {
subscriptions.push(cx.subscribe(
@@ -200,7 +194,6 @@ impl ThreadStore {
_reload_system_prompt_task: reload_system_prompt_task,
_subscriptions: subscriptions,
};
this.load_default_profile(cx);
this.register_context_server_handlers(cx);
this.reload(cx).detach_and_log_err(cx);
(this, ready_rx)
@@ -400,16 +393,11 @@ impl ThreadStore {
self.threads.len()
}
pub fn unordered_threads(&self) -> impl Iterator<Item = &SerializedThreadMetadata> {
pub fn reverse_chronological_threads(&self) -> impl Iterator<Item = &SerializedThreadMetadata> {
// ordering is from "ORDER BY" in `list_threads`
self.threads.iter()
}
pub fn reverse_chronological_threads(&self) -> Vec<SerializedThreadMetadata> {
let mut threads = self.threads.iter().cloned().collect::<Vec<_>>();
threads.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.updated_at));
threads
}
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {
cx.new(|cx| {
Thread::new(
@@ -520,92 +508,15 @@ impl ThreadStore {
})
}
fn load_default_profile(&self, cx: &mut Context<Self>) {
let assistant_settings = AgentSettings::get_global(cx);
self.load_profile_by_id(assistant_settings.default_profile.clone(), cx);
}
pub fn load_profile_by_id(&self, profile_id: AgentProfileId, cx: &mut Context<Self>) {
let assistant_settings = AgentSettings::get_global(cx);
if let Some(profile) = assistant_settings.profiles.get(&profile_id) {
self.load_profile(profile.clone(), cx);
}
}
pub fn load_profile(&self, profile: AgentProfile, cx: &mut Context<Self>) {
self.tools.update(cx, |tools, cx| {
tools.disable_all_tools(cx);
tools.enable(
ToolSource::Native,
&profile
.tools
.into_iter()
.filter_map(|(tool, enabled)| enabled.then(|| tool))
.collect::<Vec<_>>(),
cx,
);
});
if profile.enable_all_context_servers {
for context_server_id in self
.project
.read(cx)
.context_server_store()
.read(cx)
.all_server_ids()
{
self.tools.update(cx, |tools, cx| {
tools.enable_source(
ToolSource::ContextServer {
id: context_server_id.0.into(),
},
cx,
);
});
}
// Enable all the tools from all context servers, but disable the ones that are explicitly disabled
for (context_server_id, preset) in profile.context_servers {
self.tools.update(cx, |tools, cx| {
tools.disable(
ToolSource::ContextServer {
id: context_server_id.into(),
},
&preset
.tools
.into_iter()
.filter_map(|(tool, enabled)| (!enabled).then(|| tool))
.collect::<Vec<_>>(),
cx,
)
})
}
} else {
for (context_server_id, preset) in profile.context_servers {
self.tools.update(cx, |tools, cx| {
tools.enable(
ToolSource::ContextServer {
id: context_server_id.into(),
},
&preset
.tools
.into_iter()
.filter_map(|(tool, enabled)| enabled.then(|| tool))
.collect::<Vec<_>>(),
cx,
)
})
}
}
}
fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
cx.subscribe(
&self.project.read(cx).context_server_store(),
Self::handle_context_server_event,
)
.detach();
let context_server_store = self.project.read(cx).context_server_store();
cx.subscribe(&context_server_store, Self::handle_context_server_event)
.detach();
// Check for any servers that were already running before the handler was registered
for server in context_server_store.read(cx).running_servers() {
self.load_context_server_tools(server.id(), context_server_store.clone(), cx);
}
}
fn handle_context_server_event(
@@ -618,71 +529,71 @@ impl ThreadStore {
match event {
project::context_server_store::Event::ServerStatusChanged { server_id, status } => {
match status {
ContextServerStatus::Starting => {}
ContextServerStatus::Running => {
if let Some(server) =
context_server_store.read(cx).get_running_server(server_id)
{
let context_server_manager = context_server_store.clone();
cx.spawn({
let server = server.clone();
let server_id = server_id.clone();
async move |this, cx| {
let Some(protocol) = server.client() else {
return;
};
if protocol.capable(context_server::protocol::ServerCapability::Tools) {
if let Some(tools) = protocol.list_tools().await.log_err() {
let tool_ids = tool_working_set
.update(cx, |tool_working_set, _| {
tools
.tools
.into_iter()
.map(|tool| {
log::info!(
"registering context server tool: {:?}",
tool.name
);
tool_working_set.insert(Arc::new(
ContextServerTool::new(
context_server_manager.clone(),
server.id(),
tool,
),
))
})
.collect::<Vec<_>>()
})
.log_err();
if let Some(tool_ids) = tool_ids {
this.update(cx, |this, cx| {
this.context_server_tool_ids
.insert(server_id, tool_ids);
this.load_default_profile(cx);
})
.log_err();
}
}
}
}
})
.detach();
}
self.load_context_server_tools(server_id.clone(), context_server_store, cx);
}
ContextServerStatus::Stopped | ContextServerStatus::Error(_) => {
if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
tool_working_set.update(cx, |tool_working_set, _| {
tool_working_set.remove(&tool_ids);
});
self.load_default_profile(cx);
}
}
_ => {}
}
}
}
}
fn load_context_server_tools(
&self,
server_id: ContextServerId,
context_server_store: Entity<ContextServerStore>,
cx: &mut Context<Self>,
) {
let Some(server) = context_server_store.read(cx).get_running_server(&server_id) else {
return;
};
let tool_working_set = self.tools.clone();
cx.spawn(async move |this, cx| {
let Some(protocol) = server.client() else {
return;
};
if protocol.capable(context_server::protocol::ServerCapability::Tools) {
if let Some(response) = protocol
.request::<context_server::types::requests::ListTools>(())
.await
.log_err()
{
let tool_ids = tool_working_set
.update(cx, |tool_working_set, _| {
response
.tools
.into_iter()
.map(|tool| {
log::info!("registering context server tool: {:?}", tool.name);
tool_working_set.insert(Arc::new(ContextServerTool::new(
context_server_store.clone(),
server.id(),
tool,
)))
})
.collect::<Vec<_>>()
})
.log_err();
if let Some(tool_ids) = tool_ids {
this.update(cx, |this, _| {
this.context_server_tool_ids.insert(server_id, tool_ids);
})
.log_err();
}
}
}
})
.detach();
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -692,7 +603,7 @@ pub struct SerializedThreadMetadata {
pub updated_at: DateTime<Utc>,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct SerializedThread {
pub version: String,
pub summary: SharedString,
@@ -714,9 +625,11 @@ pub struct SerializedThread {
pub completion_mode: Option<CompletionMode>,
#[serde(default)]
pub tool_use_limit_reached: bool,
#[serde(default)]
pub profile: Option<AgentProfileId>,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct SerializedLanguageModel {
pub provider: String,
pub model: String,
@@ -777,11 +690,15 @@ impl SerializedThreadV0_1_0 {
messages.push(message);
}
SerializedThread { messages, ..self.0 }
SerializedThread {
messages,
version: SerializedThread::VERSION.to_string(),
..self.0
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct SerializedMessage {
pub id: MessageId,
pub role: Role,
@@ -799,7 +716,7 @@ pub struct SerializedMessage {
pub is_hidden: bool,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type")]
pub enum SerializedMessageSegment {
#[serde(rename = "text")]
@@ -813,18 +730,18 @@ pub enum SerializedMessageSegment {
signature: Option<String>,
},
RedactedThinking {
data: Vec<u8>,
data: String,
},
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct SerializedToolUse {
pub id: LanguageModelToolUseId,
pub name: SharedString,
pub input: serde_json::Value,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct SerializedToolResult {
pub tool_use_id: LanguageModelToolUseId,
pub is_error: bool,
@@ -856,6 +773,7 @@ impl LegacySerializedThread {
model: None,
completion_mode: None,
tool_use_limit_reached: false,
profile: None,
}
}
}
@@ -886,7 +804,7 @@ impl LegacySerializedMessage {
}
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct SerializedCrease {
pub start: usize,
pub end: usize,
@@ -1005,7 +923,7 @@ impl ThreadsDatabase {
fn bytes_encode(
item: &Self::EItem,
) -> Result<std::borrow::Cow<[u8]>, heed::BoxedError> {
) -> Result<std::borrow::Cow<'_, [u8]>, heed::BoxedError> {
serde_json::to_vec(&item.0)
.map(std::borrow::Cow::Owned)
.map_err(Into::into)
@@ -1143,3 +1061,181 @@ impl ThreadsDatabase {
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::thread::{DetailedSummaryState, MessageId};
use chrono::Utc;
use language_model::{Role, TokenUsage};
use pretty_assertions::assert_eq;
#[test]
fn test_legacy_serialized_thread_upgrade() {
let updated_at = Utc::now();
let legacy_thread = LegacySerializedThread {
summary: "Test conversation".into(),
updated_at,
messages: vec![LegacySerializedMessage {
id: MessageId(1),
role: Role::User,
text: "Hello, world!".to_string(),
tool_uses: vec![],
tool_results: vec![],
}],
initial_project_snapshot: None,
};
let upgraded = legacy_thread.upgrade();
assert_eq!(
upgraded,
SerializedThread {
summary: "Test conversation".into(),
updated_at,
messages: vec![SerializedMessage {
id: MessageId(1),
role: Role::User,
segments: vec![SerializedMessageSegment::Text {
text: "Hello, world!".to_string()
}],
tool_uses: vec![],
tool_results: vec![],
context: "".to_string(),
creases: vec![],
is_hidden: false
}],
version: SerializedThread::VERSION.to_string(),
initial_project_snapshot: None,
cumulative_token_usage: TokenUsage::default(),
request_token_usage: vec![],
detailed_summary_state: DetailedSummaryState::default(),
exceeded_window_error: None,
model: None,
completion_mode: None,
tool_use_limit_reached: false,
profile: None
}
)
}
#[test]
fn test_serialized_threadv0_1_0_upgrade() {
let updated_at = Utc::now();
let thread_v0_1_0 = SerializedThreadV0_1_0(SerializedThread {
summary: "Test conversation".into(),
updated_at,
messages: vec![
SerializedMessage {
id: MessageId(1),
role: Role::User,
segments: vec![SerializedMessageSegment::Text {
text: "Use tool_1".to_string(),
}],
tool_uses: vec![],
tool_results: vec![],
context: "".to_string(),
creases: vec![],
is_hidden: false,
},
SerializedMessage {
id: MessageId(2),
role: Role::Assistant,
segments: vec![SerializedMessageSegment::Text {
text: "I want to use a tool".to_string(),
}],
tool_uses: vec![SerializedToolUse {
id: "abc".into(),
name: "tool_1".into(),
input: serde_json::Value::Null,
}],
tool_results: vec![],
context: "".to_string(),
creases: vec![],
is_hidden: false,
},
SerializedMessage {
id: MessageId(1),
role: Role::User,
segments: vec![SerializedMessageSegment::Text {
text: "Here is the tool result".to_string(),
}],
tool_uses: vec![],
tool_results: vec![SerializedToolResult {
tool_use_id: "abc".into(),
is_error: false,
content: LanguageModelToolResultContent::Text("abcdef".into()),
output: Some(serde_json::Value::Null),
}],
context: "".to_string(),
creases: vec![],
is_hidden: false,
},
],
version: SerializedThreadV0_1_0::VERSION.to_string(),
initial_project_snapshot: None,
cumulative_token_usage: TokenUsage::default(),
request_token_usage: vec![],
detailed_summary_state: DetailedSummaryState::default(),
exceeded_window_error: None,
model: None,
completion_mode: None,
tool_use_limit_reached: false,
profile: None,
});
let upgraded = thread_v0_1_0.upgrade();
assert_eq!(
upgraded,
SerializedThread {
summary: "Test conversation".into(),
updated_at,
messages: vec![
SerializedMessage {
id: MessageId(1),
role: Role::User,
segments: vec![SerializedMessageSegment::Text {
text: "Use tool_1".to_string()
}],
tool_uses: vec![],
tool_results: vec![],
context: "".to_string(),
creases: vec![],
is_hidden: false
},
SerializedMessage {
id: MessageId(2),
role: Role::Assistant,
segments: vec![SerializedMessageSegment::Text {
text: "I want to use a tool".to_string(),
}],
tool_uses: vec![SerializedToolUse {
id: "abc".into(),
name: "tool_1".into(),
input: serde_json::Value::Null,
}],
tool_results: vec![SerializedToolResult {
tool_use_id: "abc".into(),
is_error: false,
content: LanguageModelToolResultContent::Text("abcdef".into()),
output: Some(serde_json::Value::Null),
}],
context: "".to_string(),
creases: vec![],
is_hidden: false,
},
],
version: SerializedThread::VERSION.to_string(),
initial_project_snapshot: None,
cumulative_token_usage: TokenUsage::default(),
request_token_usage: vec![],
detailed_summary_state: DetailedSummaryState::default(),
exceeded_window_error: None,
model: None,
completion_mode: None,
tool_use_limit_reached: false,
profile: None
}
)
}
}

View File

@@ -1,30 +1,33 @@
use std::sync::Arc;
use assistant_tool::{Tool, ToolSource, ToolWorkingSet, ToolWorkingSetEvent};
use assistant_tool::{Tool, ToolSource};
use collections::HashMap;
use gpui::{App, Context, Entity, IntoElement, Render, Subscription, Window};
use language_model::{LanguageModel, LanguageModelToolSchemaFormat};
use ui::prelude::*;
use crate::{Thread, ThreadEvent};
pub struct IncompatibleToolsState {
cache: HashMap<LanguageModelToolSchemaFormat, Vec<Arc<dyn Tool>>>,
tool_working_set: Entity<ToolWorkingSet>,
_tool_working_set_subscription: Subscription,
thread: Entity<Thread>,
_thread_subscription: Subscription,
}
impl IncompatibleToolsState {
pub fn new(tool_working_set: Entity<ToolWorkingSet>, cx: &mut Context<Self>) -> Self {
pub fn new(thread: Entity<Thread>, cx: &mut Context<Self>) -> Self {
let _tool_working_set_subscription =
cx.subscribe(&tool_working_set, |this, _, event, _| match event {
ToolWorkingSetEvent::EnabledToolsChanged => {
cx.subscribe(&thread, |this, _, event, _| match event {
ThreadEvent::ProfileChanged => {
this.cache.clear();
}
_ => {}
});
Self {
cache: HashMap::default(),
tool_working_set,
_tool_working_set_subscription,
thread,
_thread_subscription: _tool_working_set_subscription,
}
}
@@ -36,8 +39,9 @@ impl IncompatibleToolsState {
self.cache
.entry(model.tool_input_format())
.or_insert_with(|| {
self.tool_working_set
self.thread
.read(cx)
.profile()
.enabled_tools(cx)
.iter()
.filter(|tool| tool.input_schema(model.tool_input_format()).is_err())

View File

@@ -16,7 +16,6 @@ anthropic = { workspace = true, features = ["schemars"] }
anyhow.workspace = true
collections.workspace = true
gpui.workspace = true
indexmap.workspace = true
language_model.workspace = true
lmstudio = { workspace = true, features = ["schemars"] }
log.workspace = true

View File

@@ -17,29 +17,6 @@ pub mod builtin_profiles {
}
}
#[derive(Default)]
pub struct GroupedAgentProfiles {
pub builtin: IndexMap<AgentProfileId, AgentProfile>,
pub custom: IndexMap<AgentProfileId, AgentProfile>,
}
impl GroupedAgentProfiles {
pub fn from_settings(settings: &crate::AgentSettings) -> Self {
let mut builtin = IndexMap::default();
let mut custom = IndexMap::default();
for (profile_id, profile) in settings.profiles.clone() {
if builtin_profiles::is_builtin(&profile_id) {
builtin.insert(profile_id, profile);
} else {
custom.insert(profile_id, profile);
}
}
Self { builtin, custom }
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
pub struct AgentProfileId(pub Arc<str>);
@@ -63,7 +40,7 @@ impl Default for AgentProfileId {
/// A profile for the Zed Agent that controls its behavior.
#[derive(Debug, Clone)]
pub struct AgentProfile {
pub struct AgentProfileSettings {
/// The name of the profile.
pub name: SharedString,
pub tools: IndexMap<Arc<str>, bool>,

View File

@@ -102,7 +102,7 @@ pub struct AgentSettings {
pub using_outdated_settings_version: bool,
pub default_profile: AgentProfileId,
pub default_view: DefaultView,
pub profiles: IndexMap<AgentProfileId, AgentProfile>,
pub profiles: IndexMap<AgentProfileId, AgentProfileSettings>,
pub always_allow_tool_actions: bool,
pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
pub play_sound_when_agent_done: bool,
@@ -531,7 +531,7 @@ impl AgentSettingsContent {
pub fn create_profile(
&mut self,
profile_id: AgentProfileId,
profile: AgentProfile,
profile_settings: AgentProfileSettings,
) -> Result<()> {
self.v2_setting(|settings| {
let profiles = settings.profiles.get_or_insert_default();
@@ -542,10 +542,10 @@ impl AgentSettingsContent {
profiles.insert(
profile_id,
AgentProfileContent {
name: profile.name.into(),
tools: profile.tools,
enable_all_context_servers: Some(profile.enable_all_context_servers),
context_servers: profile
name: profile_settings.name.into(),
tools: profile_settings.tools,
enable_all_context_servers: Some(profile_settings.enable_all_context_servers),
context_servers: profile_settings
.context_servers
.into_iter()
.map(|(server_id, preset)| {
@@ -910,7 +910,7 @@ impl Settings for AgentSettings {
.extend(profiles.into_iter().map(|(id, profile)| {
(
id,
AgentProfile {
AgentProfileSettings {
name: profile.name.into(),
tools: profile.tools,
enable_all_context_servers: profile

View File

@@ -1,4 +1,5 @@
use std::str::FromStr;
use std::time::Duration;
use anyhow::{Context as _, Result, anyhow};
use chrono::{DateTime, Utc};
@@ -406,6 +407,7 @@ impl RateLimit {
/// <https://docs.anthropic.com/en/api/rate-limits#response-headers>
#[derive(Debug)]
pub struct RateLimitInfo {
pub retry_after: Option<Duration>,
pub requests: Option<RateLimit>,
pub tokens: Option<RateLimit>,
pub input_tokens: Option<RateLimit>,
@@ -417,10 +419,11 @@ impl RateLimitInfo {
// Check if any rate limit headers exist
let has_rate_limit_headers = headers
.keys()
.any(|k| k.as_str().starts_with("anthropic-ratelimit-"));
.any(|k| k == "retry-after" || k.as_str().starts_with("anthropic-ratelimit-"));
if !has_rate_limit_headers {
return Self {
retry_after: None,
requests: None,
tokens: None,
input_tokens: None,
@@ -429,6 +432,11 @@ impl RateLimitInfo {
}
Self {
retry_after: headers
.get("retry-after")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse::<u64>().ok())
.map(Duration::from_secs),
requests: RateLimit::from_headers("requests", headers).ok(),
tokens: RateLimit::from_headers("tokens", headers).ok(),
input_tokens: RateLimit::from_headers("input-tokens", headers).ok(),
@@ -481,8 +489,8 @@ pub async fn stream_completion_with_rate_limit_info(
.send(request)
.await
.context("failed to send request to Anthropic")?;
let rate_limits = RateLimitInfo::from_headers(response.headers());
if response.status().is_success() {
let rate_limits = RateLimitInfo::from_headers(response.headers());
let reader = BufReader::new(response.into_body());
let stream = reader
.lines()
@@ -500,6 +508,8 @@ pub async fn stream_completion_with_rate_limit_info(
})
.boxed();
Ok((stream, Some(rate_limits)))
} else if let Some(retry_after) = rate_limits.retry_after {
Err(AnthropicError::RateLimit(retry_after))
} else {
let mut body = Vec::new();
response
@@ -769,6 +779,8 @@ pub struct MessageDelta {
#[derive(Error, Debug)]
pub enum AnthropicError {
#[error("rate limit exceeded, retry after {0:?}")]
RateLimit(Duration),
#[error("an error occurred while interacting with the Anthropic API: {error_type}: {message}", error_type = .0.error_type, message = .0.message)]
ApiError(ApiError),
#[error("{0}")]

View File

@@ -11,7 +11,7 @@ use assistant_slash_commands::FileCommandMetadata;
use client::{self, proto, telemetry::Telemetry};
use clock::ReplicaId;
use collections::{HashMap, HashSet};
use fs::{Fs, RemoveOptions};
use fs::{Fs, RenameOptions};
use futures::{FutureExt, StreamExt, future::Shared};
use gpui::{
App, AppContext as _, Context, Entity, EventEmitter, RenderImage, SharedString, Subscription,
@@ -452,6 +452,10 @@ pub enum ContextEvent {
MessagesEdited,
SummaryChanged,
SummaryGenerated,
PathChanged {
old_path: Option<Arc<Path>>,
new_path: Arc<Path>,
},
StreamedCompletion,
StartedThoughtProcess(Range<language::Anchor>),
EndedThoughtProcess(language::Anchor),
@@ -2106,6 +2110,7 @@ impl AssistantContext {
);
}
}
LanguageModelCompletionEvent::RedactedThinking { .. } => {},
LanguageModelCompletionEvent::Text(mut chunk) => {
if let Some(start) = thought_process_stack.pop() {
let end = buffer.anchor_before(message_old_end_offset);
@@ -2894,22 +2899,34 @@ impl AssistantContext {
}
fs.create_dir(contexts_dir().as_ref()).await?;
fs.atomic_write(new_path.clone(), serde_json::to_string(&context).unwrap())
.await?;
if let Some(old_path) = old_path {
// rename before write ensures that only one file exists
if let Some(old_path) = old_path.as_ref() {
if new_path.as_path() != old_path.as_ref() {
fs.remove_file(
fs.rename(
&old_path,
RemoveOptions {
recursive: false,
ignore_if_not_exists: true,
&new_path,
RenameOptions {
overwrite: true,
ignore_if_exists: true,
},
)
.await?;
}
}
this.update(cx, |this, _| this.path = Some(new_path.into()))?;
// update path before write in case it fails
this.update(cx, {
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 });
}
})
.ok();
fs.atomic_write(new_path, serde_json::to_string(&context).unwrap())
.await?;
}
Ok(())
@@ -3277,7 +3294,7 @@ impl SavedContextV0_1_0 {
#[derive(Debug, Clone)]
pub struct SavedContextMetadata {
pub title: String,
pub title: SharedString,
pub path: Arc<Path>,
pub mtime: chrono::DateTime<chrono::Local>,
}

View File

@@ -580,6 +580,7 @@ impl ContextEditor {
});
}
ContextEvent::SummaryGenerated => {}
ContextEvent::PathChanged { .. } => {}
ContextEvent::StartedThoughtProcess(range) => {
let creases = self.insert_thought_process_output_sections(
[(

View File

@@ -347,12 +347,6 @@ impl ContextStore {
self.contexts_metadata.iter()
}
pub fn reverse_chronological_contexts(&self) -> Vec<SavedContextMetadata> {
let mut contexts = self.contexts_metadata.iter().cloned().collect::<Vec<_>>();
contexts.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.mtime));
contexts
}
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
let context = cx.new(|cx| {
AssistantContext::local(
@@ -618,6 +612,16 @@ impl ContextStore {
ContextEvent::SummaryChanged => {
self.advertise_contexts(cx);
}
ContextEvent::PathChanged { old_path, new_path } => {
if let Some(old_path) = old_path.as_ref() {
for metadata in &mut self.contexts_metadata {
if &metadata.path == old_path {
metadata.path = new_path.clone();
break;
}
}
}
}
ContextEvent::Operation(operation) => {
let context_id = context.read(cx).id().to_proto();
let operation = operation.to_proto();
@@ -792,7 +796,7 @@ impl ContextStore {
.next()
{
contexts.push(SavedContextMetadata {
title: title.to_string(),
title: title.to_string().into(),
path: path.into(),
mtime: metadata.mtime.timestamp_for_user().into(),
});
@@ -809,74 +813,37 @@ impl ContextStore {
}
fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
cx.subscribe(
&self.project.read(cx).context_server_store(),
Self::handle_context_server_event,
)
.detach();
let context_server_store = self.project.read(cx).context_server_store();
cx.subscribe(&context_server_store, Self::handle_context_server_event)
.detach();
// Check for any servers that were already running before the handler was registered
for server in context_server_store.read(cx).running_servers() {
self.load_context_server_slash_commands(server.id(), context_server_store.clone(), cx);
}
}
fn handle_context_server_event(
&mut self,
context_server_manager: Entity<ContextServerStore>,
context_server_store: Entity<ContextServerStore>,
event: &project::context_server_store::Event,
cx: &mut Context<Self>,
) {
let slash_command_working_set = self.slash_commands.clone();
match event {
project::context_server_store::Event::ServerStatusChanged { server_id, status } => {
match status {
ContextServerStatus::Running => {
if let Some(server) = context_server_manager
.read(cx)
.get_running_server(server_id)
{
let context_server_manager = context_server_manager.clone();
cx.spawn({
let server = server.clone();
let server_id = server_id.clone();
async move |this, cx| {
let Some(protocol) = server.client() else {
return;
};
if protocol.capable(context_server::protocol::ServerCapability::Prompts) {
if let Some(prompts) = protocol.list_prompts().await.log_err() {
let slash_command_ids = prompts
.into_iter()
.filter(assistant_slash_commands::acceptable_prompt)
.map(|prompt| {
log::info!(
"registering context server command: {:?}",
prompt.name
);
slash_command_working_set.insert(Arc::new(
assistant_slash_commands::ContextServerSlashCommand::new(
context_server_manager.clone(),
server.id(),
prompt,
),
))
})
.collect::<Vec<_>>();
this.update( cx, |this, _cx| {
this.context_server_slash_command_ids
.insert(server_id.clone(), slash_command_ids);
})
.log_err();
}
}
}
})
.detach();
}
self.load_context_server_slash_commands(
server_id.clone(),
context_server_store.clone(),
cx,
);
}
ContextServerStatus::Stopped | ContextServerStatus::Error(_) => {
if let Some(slash_command_ids) =
self.context_server_slash_command_ids.remove(server_id)
{
slash_command_working_set.remove(&slash_command_ids);
self.slash_commands.remove(&slash_command_ids);
}
}
_ => {}
@@ -884,4 +851,52 @@ impl ContextStore {
}
}
}
fn load_context_server_slash_commands(
&self,
server_id: ContextServerId,
context_server_store: Entity<ContextServerStore>,
cx: &mut Context<Self>,
) {
let Some(server) = context_server_store.read(cx).get_running_server(&server_id) else {
return;
};
let slash_command_working_set = self.slash_commands.clone();
cx.spawn(async move |this, cx| {
let Some(protocol) = server.client() else {
return;
};
if protocol.capable(context_server::protocol::ServerCapability::Prompts) {
if let Some(response) = protocol
.request::<context_server::types::requests::PromptsList>(())
.await
.log_err()
{
let slash_command_ids = response
.prompts
.into_iter()
.filter(assistant_slash_commands::acceptable_prompt)
.map(|prompt| {
log::info!("registering context server command: {:?}", prompt.name);
slash_command_working_set.insert(Arc::new(
assistant_slash_commands::ContextServerSlashCommand::new(
context_server_store.clone(),
server.id(),
prompt,
),
))
})
.collect::<Vec<_>>();
this.update(cx, |this, _cx| {
this.context_server_slash_command_ids
.insert(server_id.clone(), slash_command_ids);
})
.log_err();
}
}
})
.detach();
}
}

View File

@@ -682,11 +682,12 @@ mod tests {
_: &AsyncApp,
) -> BoxFuture<
'static,
http_client::Result<
Result<
BoxStream<
'static,
http_client::Result<LanguageModelCompletionEvent, LanguageModelCompletionError>,
Result<LanguageModelCompletionEvent, LanguageModelCompletionError>,
>,
LanguageModelCompletionError,
>,
> {
unimplemented!()

View File

@@ -10,9 +10,7 @@ use parking_lot::Mutex;
use project::{CompletionIntent, CompletionSource, lsp_store::CompletionDocumentation};
use rope::Point;
use std::{
cell::RefCell,
ops::Range,
rc::Rc,
sync::{
Arc,
atomic::{AtomicBool, Ordering::SeqCst},
@@ -240,13 +238,14 @@ impl SlashCommandCompletionProvider {
Ok(vec![project::CompletionResponse {
completions,
is_incomplete: false,
// TODO: Could have slash commands indicate whether their completions are incomplete.
is_incomplete: true,
}])
})
} else {
Task::ready(Ok(vec![project::CompletionResponse {
completions: Vec::new(),
is_incomplete: false,
is_incomplete: true,
}]))
}
}
@@ -275,17 +274,17 @@ impl CompletionProvider for SlashCommandCompletionProvider {
position.row,
call.arguments.last().map_or(call.name.end, |arg| arg.end) as u32,
);
let command_range = buffer.anchor_after(command_range_start)
let command_range = buffer.anchor_before(command_range_start)
..buffer.anchor_after(command_range_end);
let name = line[call.name.clone()].to_string();
let (arguments, last_argument_range) = if let Some(argument) = call.arguments.last()
{
let last_arg_start =
buffer.anchor_after(Point::new(position.row, argument.start as u32));
buffer.anchor_before(Point::new(position.row, argument.start as u32));
let first_arg_start = call.arguments.first().expect("we have the last element");
let first_arg_start =
buffer.anchor_after(Point::new(position.row, first_arg_start.start as u32));
let first_arg_start = buffer
.anchor_before(Point::new(position.row, first_arg_start.start as u32));
let arguments = call
.arguments
.into_iter()
@@ -298,7 +297,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
)
} else {
let start =
buffer.anchor_after(Point::new(position.row, call.name.start as u32));
buffer.anchor_before(Point::new(position.row, call.name.start as u32));
(None, start..buffer_position)
};
@@ -326,16 +325,6 @@ impl CompletionProvider for SlashCommandCompletionProvider {
}
}
fn resolve_completions(
&self,
_: Entity<Buffer>,
_: Vec<usize>,
_: Rc<RefCell<Box<[project::Completion]>>>,
_: &mut Context<Editor>,
) -> Task<Result<bool>> {
Task::ready(Ok(true))
}
fn is_completion_trigger(
&self,
buffer: &Entity<Buffer>,

View File

@@ -86,20 +86,26 @@ impl SlashCommand for ContextServerSlashCommand {
cx.foreground_executor().spawn(async move {
let protocol = server.client().context("Context server not initialized")?;
let completion_result = protocol
.completion(
context_server::types::CompletionReference::Prompt(
context_server::types::PromptReference {
r#type: context_server::types::PromptReferenceType::Prompt,
name: prompt_name,
let response = protocol
.request::<context_server::types::requests::CompletionComplete>(
context_server::types::CompletionCompleteParams {
reference: context_server::types::CompletionReference::Prompt(
context_server::types::PromptReference {
ty: context_server::types::PromptReferenceType::Prompt,
name: prompt_name,
},
),
argument: context_server::types::CompletionArgument {
name: arg_name,
value: arg_value,
},
),
arg_name,
arg_value,
meta: None,
},
)
.await?;
let completions = completion_result
let completions = response
.completion
.values
.into_iter()
.map(|value| ArgumentCompletion {
@@ -138,10 +144,18 @@ impl SlashCommand for ContextServerSlashCommand {
if let Some(server) = store.get_running_server(&server_id) {
cx.foreground_executor().spawn(async move {
let protocol = server.client().context("Context server not initialized")?;
let result = protocol.run_prompt(&prompt_name, prompt_args).await?;
let response = protocol
.request::<context_server::types::requests::PromptsGet>(
context_server::types::PromptsGetParams {
name: prompt_name.clone(),
arguments: Some(prompt_args),
meta: None,
},
)
.await?;
anyhow::ensure!(
result
response
.messages
.iter()
.all(|msg| matches!(msg.role, context_server::types::Role::User)),
@@ -149,7 +163,7 @@ impl SlashCommand for ContextServerSlashCommand {
);
// Extract text from user messages into a single prompt string
let mut prompt = result
let mut prompt = response
.messages
.into_iter()
.filter_map(|msg| match msg.content {
@@ -167,7 +181,7 @@ impl SlashCommand for ContextServerSlashCommand {
range: 0..(prompt.len()),
icon: IconName::ZedAssistant,
label: SharedString::from(
result
response
.description
.unwrap_or(format!("Result from {}", prompt_name)),
),

View File

@@ -13,7 +13,6 @@ path = "src/assistant_tool.rs"
[dependencies]
anyhow.workspace = true
async-watch.workspace = true
buffer_diff.workspace = true
clock.workspace = true
collections.workspace = true
@@ -30,6 +29,7 @@ serde.workspace = true
serde_json.workspace = true
text.workspace = true
util.workspace = true
watch.workspace = true
workspace.workspace = true
workspace-hack.workspace = true

View File

@@ -204,7 +204,7 @@ impl ActionLog {
git_store.repository_and_path_for_buffer_id(buffer.read(cx).remote_id(), cx)
})?;
let (git_diff_updates_tx, mut git_diff_updates_rx) = async_watch::channel(());
let (mut git_diff_updates_tx, mut git_diff_updates_rx) = watch::channel(());
let _repo_subscription =
if let Some((git_diff, (buffer_repo, _))) = git_diff.as_ref().zip(buffer_repo) {
cx.update(|cx| {

View File

@@ -214,7 +214,7 @@ pub trait Tool: 'static + Send + Sync {
ToolSource::Native
}
/// Returns true iff the tool needs the users's confirmation
/// Returns true if the tool needs the users's confirmation
/// before having permission to run.
fn needs_confirmation(&self, input: &serde_json::Value, cx: &App) -> bool;

View File

@@ -46,15 +46,19 @@ fn adapt_to_json_schema_subset(json: &mut Value) -> Result<()> {
);
}
const KEYS_TO_REMOVE: [&str; 5] = [
"format",
"additionalProperties",
"exclusiveMinimum",
"exclusiveMaximum",
"optional",
const KEYS_TO_REMOVE: [(&str, fn(&Value) -> bool); 5] = [
("format", |value| value.is_string()),
("additionalProperties", |value| value.is_boolean()),
("exclusiveMinimum", |value| value.is_number()),
("exclusiveMaximum", |value| value.is_number()),
("optional", |value| value.is_boolean()),
];
for key in KEYS_TO_REMOVE {
obj.remove(key);
for (key, predicate) in KEYS_TO_REMOVE {
if let Some(value) = obj.get(key) {
if predicate(value) {
obj.remove(key);
}
}
}
// If a type is not specified for an input parameter, add a default type
@@ -153,6 +157,24 @@ mod tests {
"type": "integer"
})
);
// Ensure that we do not remove keys that are actually supported (e.g. "format" can just be used as another property)
let mut json = json!({
"description": "A test field",
"type": "integer",
"format": {},
});
adapt_to_json_schema_subset(&mut json).unwrap();
assert_eq!(
json,
json!({
"description": "A test field",
"type": "integer",
"format": {},
})
);
}
#[test]

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use collections::{HashMap, HashSet, IndexMap};
use gpui::{App, Context, EventEmitter};
use collections::{HashMap, IndexMap};
use gpui::App;
use crate::{Tool, ToolRegistry, ToolSource};
@@ -13,17 +13,9 @@ pub struct ToolId(usize);
pub struct ToolWorkingSet {
context_server_tools_by_id: HashMap<ToolId, Arc<dyn Tool>>,
context_server_tools_by_name: HashMap<String, Arc<dyn Tool>>,
enabled_sources: HashSet<ToolSource>,
enabled_tools_by_source: HashMap<ToolSource, HashSet<Arc<str>>>,
next_tool_id: ToolId,
}
pub enum ToolWorkingSetEvent {
EnabledToolsChanged,
}
impl EventEmitter<ToolWorkingSetEvent> for ToolWorkingSet {}
impl ToolWorkingSet {
pub fn tool(&self, name: &str, cx: &App) -> Option<Arc<dyn Tool>> {
self.context_server_tools_by_name
@@ -57,42 +49,6 @@ impl ToolWorkingSet {
tools_by_source
}
pub fn enabled_tools(&self, cx: &App) -> Vec<Arc<dyn Tool>> {
let all_tools = self.tools(cx);
all_tools
.into_iter()
.filter(|tool| self.is_enabled(&tool.source(), &tool.name().into()))
.collect()
}
pub fn disable_all_tools(&mut self, cx: &mut Context<Self>) {
self.enabled_tools_by_source.clear();
cx.emit(ToolWorkingSetEvent::EnabledToolsChanged);
}
pub fn enable_source(&mut self, source: ToolSource, cx: &mut Context<Self>) {
self.enabled_sources.insert(source.clone());
let tools_by_source = self.tools_by_source(cx);
if let Some(tools) = tools_by_source.get(&source) {
self.enabled_tools_by_source.insert(
source,
tools
.into_iter()
.map(|tool| tool.name().into())
.collect::<HashSet<_>>(),
);
}
cx.emit(ToolWorkingSetEvent::EnabledToolsChanged);
}
pub fn disable_source(&mut self, source: &ToolSource, cx: &mut Context<Self>) {
self.enabled_sources.remove(source);
self.enabled_tools_by_source.remove(source);
cx.emit(ToolWorkingSetEvent::EnabledToolsChanged);
}
pub fn insert(&mut self, tool: Arc<dyn Tool>) -> ToolId {
let tool_id = self.next_tool_id;
self.next_tool_id.0 += 1;
@@ -102,42 +58,6 @@ impl ToolWorkingSet {
tool_id
}
pub fn is_enabled(&self, source: &ToolSource, name: &Arc<str>) -> bool {
self.enabled_tools_by_source
.get(source)
.map_or(false, |enabled_tools| enabled_tools.contains(name))
}
pub fn is_disabled(&self, source: &ToolSource, name: &Arc<str>) -> bool {
!self.is_enabled(source, name)
}
pub fn enable(
&mut self,
source: ToolSource,
tools_to_enable: &[Arc<str>],
cx: &mut Context<Self>,
) {
self.enabled_tools_by_source
.entry(source)
.or_default()
.extend(tools_to_enable.into_iter().cloned());
cx.emit(ToolWorkingSetEvent::EnabledToolsChanged);
}
pub fn disable(
&mut self,
source: ToolSource,
tools_to_disable: &[Arc<str>],
cx: &mut Context<Self>,
) {
self.enabled_tools_by_source
.entry(source)
.or_default()
.retain(|name| !tools_to_disable.contains(name));
cx.emit(ToolWorkingSetEvent::EnabledToolsChanged);
}
pub fn remove(&mut self, tool_ids_to_remove: &[ToolId]) {
self.context_server_tools_by_id
.retain(|id, _| !tool_ids_to_remove.contains(id));

View File

@@ -18,7 +18,6 @@ eval = []
agent_settings.workspace = true
anyhow.workspace = true
assistant_tool.workspace = true
async-watch.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
collections.workspace = true
@@ -58,6 +57,7 @@ terminal_view.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true
watch.workspace = true
web_search.workspace = true
which.workspace = true
workspace-hack.workspace = true
@@ -80,6 +80,7 @@ rand.workspace = true
pretty_assertions.workspace = true
reqwest_client.workspace = true
settings = { workspace = true, features = ["test-support"] }
smol.workspace = true
task = { workspace = true, features = ["test-support"]}
tempfile.workspace = true
theme.workspace = true

View File

@@ -420,12 +420,12 @@ impl EditAgent {
cx: &mut AsyncApp,
) -> (
Task<Result<(T, Vec<ResolvedOldText>)>>,
async_watch::Receiver<Option<Range<usize>>>,
watch::Receiver<Option<Range<usize>>>,
)
where
T: 'static + Send + Unpin + Stream<Item = Result<EditParserEvent>>,
{
let (old_range_tx, old_range_rx) = async_watch::channel(None);
let (mut old_range_tx, old_range_rx) = watch::channel(None);
let task = cx.background_spawn(async move {
let mut matcher = StreamingFuzzyMatcher::new(snapshot);
while let Some(edit_event) = edit_events.next().await {

View File

@@ -11,7 +11,7 @@ use client::{Client, UserStore};
use collections::HashMap;
use fs::FakeFs;
use futures::{FutureExt, future::LocalBoxFuture};
use gpui::{AppContext, TestAppContext};
use gpui::{AppContext, TestAppContext, Timer};
use indoc::{formatdoc, indoc};
use language_model::{
LanguageModelRegistry, LanguageModelRequestTool, LanguageModelToolResult,
@@ -39,7 +39,7 @@ fn eval_extract_handle_command_output() {
// Model | Pass rate
// ----------------------------|----------
// claude-3.7-sonnet | 0.98
// gemini-2.5-pro | 0.86
// gemini-2.5-pro-06-05 | 0.77
// gemini-2.5-flash | 0.11
// gpt-4.1 | 1.00
@@ -58,6 +58,7 @@ fn eval_extract_handle_command_output() {
eval(
100,
0.7, // Taking the lower bar for Gemini
0.05,
EvalInput::from_conversation(
vec![
message(
@@ -116,6 +117,7 @@ fn eval_delete_run_git_blame() {
eval(
100,
0.95,
0.05,
EvalInput::from_conversation(
vec![
message(
@@ -178,6 +180,7 @@ fn eval_translate_doc_comments() {
eval(
200,
1.,
0.05,
EvalInput::from_conversation(
vec![
message(
@@ -241,6 +244,7 @@ fn eval_use_wasi_sdk_in_compile_parser_to_wasm() {
eval(
100,
0.95,
0.05,
EvalInput::from_conversation(
vec![
message(
@@ -365,6 +369,7 @@ fn eval_disable_cursor_blinking() {
eval(
100,
0.95,
0.05,
EvalInput::from_conversation(
vec![
message(User, [text("Let's research how to cursor blinking works.")]),
@@ -448,6 +453,9 @@ fn eval_from_pixels_constructor() {
eval(
100,
0.95,
// For whatever reason, this eval produces more mismatched tags.
// Increasing for now, let's see if we can bring this down.
0.2,
EvalInput::from_conversation(
vec![
message(
@@ -648,6 +656,7 @@ fn eval_zode() {
eval(
50,
1.,
0.05,
EvalInput::from_conversation(
vec![
message(User, [text(include_str!("evals/fixtures/zode/prompt.md"))]),
@@ -754,6 +763,7 @@ fn eval_add_overwrite_test() {
eval(
200,
0.5, // TODO: make this eval better
0.05,
EvalInput::from_conversation(
vec![
message(
@@ -993,6 +1003,7 @@ fn eval_create_empty_file() {
eval(
100,
0.99,
0.05,
EvalInput::from_conversation(
vec![
message(User, [text("Create a second empty todo file ")]),
@@ -1244,9 +1255,12 @@ impl EvalAssertion {
}],
..Default::default()
};
let mut response = judge
.stream_completion_text(request, &cx.to_async())
.await?;
let mut response = retry_on_rate_limit(async || {
Ok(judge
.stream_completion_text(request.clone(), &cx.to_async())
.await?)
})
.await?;
let mut output = String::new();
while let Some(chunk) = response.stream.next().await {
let chunk = chunk?;
@@ -1279,7 +1293,12 @@ impl EvalAssertion {
}
}
fn eval(iterations: usize, expected_pass_ratio: f32, mut eval: EvalInput) {
fn eval(
iterations: usize,
expected_pass_ratio: f32,
mismatched_tag_threshold: f32,
mut eval: EvalInput,
) {
let mut evaluated_count = 0;
let mut failed_count = 0;
report_progress(evaluated_count, failed_count, iterations);
@@ -1292,10 +1311,17 @@ fn eval(iterations: usize, expected_pass_ratio: f32, mut eval: EvalInput) {
run_eval(eval.clone(), tx.clone());
let executor = gpui::background_executor();
let semaphore = Arc::new(smol::lock::Semaphore::new(32));
for _ in 1..iterations {
let eval = eval.clone();
let tx = tx.clone();
executor.spawn(async move { run_eval(eval, tx) }).detach();
let semaphore = semaphore.clone();
executor
.spawn(async move {
let _guard = semaphore.acquire().await;
run_eval(eval, tx)
})
.detach();
}
drop(tx);
@@ -1351,7 +1377,7 @@ fn eval(iterations: usize, expected_pass_ratio: f32, mut eval: EvalInput) {
let mismatched_tag_ratio =
cumulative_parser_metrics.mismatched_tags as f32 / cumulative_parser_metrics.tags as f32;
if mismatched_tag_ratio > 0.10 {
if mismatched_tag_ratio > mismatched_tag_threshold {
for eval_output in eval_outputs {
println!("{}", eval_output);
}
@@ -1561,21 +1587,31 @@ impl EditAgentTest {
if let Some(input_content) = eval.input_content.as_deref() {
buffer.update(cx, |buffer, cx| buffer.set_text(input_content, cx));
}
let (edit_output, _) = self.agent.edit(
buffer.clone(),
eval.edit_file_input.display_description,
&conversation,
&mut cx.to_async(),
);
edit_output.await?
retry_on_rate_limit(async || {
self.agent
.edit(
buffer.clone(),
eval.edit_file_input.display_description.clone(),
&conversation,
&mut cx.to_async(),
)
.0
.await
})
.await?
} else {
let (edit_output, _) = self.agent.overwrite(
buffer.clone(),
eval.edit_file_input.display_description,
&conversation,
&mut cx.to_async(),
);
edit_output.await?
retry_on_rate_limit(async || {
self.agent
.overwrite(
buffer.clone(),
eval.edit_file_input.display_description.clone(),
&conversation,
&mut cx.to_async(),
)
.0
.await
})
.await?
};
let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text());
@@ -1597,6 +1633,31 @@ impl EditAgentTest {
}
}
async fn retry_on_rate_limit<R>(mut request: impl AsyncFnMut() -> Result<R>) -> Result<R> {
let mut attempt = 0;
loop {
attempt += 1;
match request().await {
Ok(result) => return Ok(result),
Err(err) => match err.downcast::<LanguageModelCompletionError>() {
Ok(err) => match err {
LanguageModelCompletionError::RateLimit(duration) => {
// Wait for the duration supplied, with some jitter to avoid all requests being made at the same time.
let jitter = duration.mul_f64(rand::thread_rng().gen_range(0.0..0.5));
eprintln!(
"Attempt #{attempt}: Rate limit exceeded. Retry after {duration:?} + jitter of {jitter:?}"
);
Timer::after(duration + jitter).await;
continue;
}
_ => return Err(err.into()),
},
Err(err) => return Err(err),
},
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
struct EvalAssertionOutcome {
score: usize,

View File

@@ -498,7 +498,7 @@ client.with_options(max_retries=5).messages.create(
### Timeouts
By default requests time out after 10 minutes. You can configure this with a `timeout` option,
which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:
```python
from anthropic import Anthropic

View File

@@ -638,29 +638,36 @@ impl ToolCard for TerminalToolCard {
.bg(cx.theme().colors().editor_background)
.rounded_b_md()
.text_ui_sm(cx)
.child(
ToolOutputPreview::new(
terminal.clone().into_any_element(),
terminal.entity_id(),
)
.with_total_lines(self.content_line_count)
.toggle_state(!terminal.read(cx).is_content_limited(window))
.on_toggle({
let terminal = terminal.clone();
move |is_expanded, _, cx| {
terminal.update(cx, |terminal, cx| {
terminal.set_embedded_mode(
if is_expanded {
None
} else {
Some(COLLAPSED_LINES)
},
cx,
);
});
}
}),
),
.child({
let content_mode = terminal.read(cx).content_mode(window, cx);
if content_mode.is_scrollable() {
div().h_72().child(terminal.clone()).into_any_element()
} else {
ToolOutputPreview::new(
terminal.clone().into_any_element(),
terminal.entity_id(),
)
.with_total_lines(self.content_line_count)
.toggle_state(!content_mode.is_limited())
.on_toggle({
let terminal = terminal.clone();
move |is_expanded, _, cx| {
terminal.update(cx, |terminal, cx| {
terminal.set_embedded_mode(
if is_expanded {
None
} else {
Some(COLLAPSED_LINES)
},
cx,
);
});
}
})
.into_any_element()
}
}),
)
},
)

View File

@@ -452,6 +452,10 @@ impl Model {
| Model::Claude3_5SonnetV2
| Model::Claude3_7Sonnet
| Model::Claude3_7SonnetThinking
| Model::ClaudeSonnet4
| Model::ClaudeSonnet4Thinking
| Model::ClaudeOpus4
| Model::ClaudeOpus4Thinking
| Model::Claude3Haiku
| Model::Claude3Opus
| Model::Claude3Sonnet

View File

@@ -1028,7 +1028,11 @@ impl BufferDiff {
let (base_text_changed, mut changed_range) =
match (state.base_text_exists, new_state.base_text_exists) {
(false, false) => (true, None),
(true, true) if state.base_text.remote_id() == new_state.base_text.remote_id() => {
(true, true)
if state.base_text.remote_id() == new_state.base_text.remote_id()
&& state.base_text.syntax_update_count()
== new_state.base_text.syntax_update_count() =>
{
(false, new_state.compare(&state, buffer))
}
_ => (true, Some(text::Anchor::MIN..text::Anchor::MAX)),

View File

@@ -111,7 +111,7 @@ pub struct ChannelMembership {
pub role: proto::ChannelRole,
}
impl ChannelMembership {
pub fn sort_key(&self) -> MembershipSortKey {
pub fn sort_key(&self) -> MembershipSortKey<'_> {
MembershipSortKey {
role_order: match self.role {
proto::ChannelRole::Admin => 0,

View File

@@ -32,7 +32,7 @@ impl ChannelIndex {
.retain(|channel_id| !channels.contains(channel_id));
}
pub fn bulk_insert(&mut self) -> ChannelPathsInsertGuard {
pub fn bulk_insert(&mut self) -> ChannelPathsInsertGuard<'_> {
ChannelPathsInsertGuard {
channels_ordered: &mut self.channels_ordered,
channels_by_id: &mut self.channels_by_id,

View File

@@ -269,7 +269,6 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
github_login: "nathansobo".into(),
avatar_url: "http://avatar.com/nathansobo".into(),
name: None,
email: None,
}],
},
);
@@ -323,7 +322,6 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
github_login: "maxbrunsfeld".into(),
avatar_url: "http://avatar.com/maxbrunsfeld".into(),
name: None,
email: None,
}],
},
);
@@ -368,7 +366,6 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
github_login: "as-cii".into(),
avatar_url: "http://avatar.com/as-cii".into(),
name: None,
email: None,
}],
},
);

View File

@@ -13,6 +13,7 @@ pub enum CliRequest {
Open {
paths: Vec<String>,
urls: Vec<String>,
diff_paths: Vec<[String; 2]>,
wait: bool,
open_new_workspace: Option<bool>,
env: Option<HashMap<String, String>>,

View File

@@ -89,6 +89,9 @@ struct Args {
/// Will attempt to give the correct command to run
#[arg(long)]
system_specs: bool,
/// Pairs of file paths to diff. Can be specified multiple times.
#[arg(long, action = clap::ArgAction::Append, num_args = 2, value_names = ["OLD_PATH", "NEW_PATH"])]
diff: Vec<String>,
/// Uninstall Zed from user system
#[cfg(all(
any(target_os = "linux", target_os = "macos"),
@@ -229,9 +232,17 @@ fn main() -> Result<()> {
let exit_status = Arc::new(Mutex::new(None));
let mut paths = vec![];
let mut urls = vec![];
let mut diff_paths = vec![];
let mut stdin_tmp_file: Option<fs::File> = None;
let mut anonymous_fd_tmp_files = vec![];
for path in args.diff.chunks(2) {
diff_paths.push([
parse_path_with_position(&path[0])?,
parse_path_with_position(&path[1])?,
]);
}
for path in args.paths_with_position.iter() {
if path.starts_with("zed://")
|| path.starts_with("http://")
@@ -270,6 +281,7 @@ fn main() -> Result<()> {
tx.send(CliRequest::Open {
paths,
urls,
diff_paths,
wait: args.wait,
open_new_workspace,
env,

View File

@@ -1887,8 +1887,16 @@ mod tests {
.set_entity(&entity3, &mut cx.to_async());
drop(subscription3);
server.send(proto::JoinProject { project_id: 1 });
server.send(proto::JoinProject { project_id: 2 });
server.send(proto::JoinProject {
project_id: 1,
committer_name: None,
committer_email: None,
});
server.send(proto::JoinProject {
project_id: 2,
committer_name: None,
committer_email: None,
});
done_rx1.recv().await.unwrap();
done_rx2.recv().await.unwrap();
}

View File

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

View File

@@ -49,7 +49,6 @@ pub struct User {
pub github_login: String,
pub avatar_uri: SharedUri,
pub name: Option<String>,
pub email: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -58,6 +57,8 @@ pub struct Collaborator {
pub replica_id: ReplicaId,
pub user_id: UserId,
pub is_host: bool,
pub committer_name: Option<String>,
pub committer_email: Option<String>,
}
impl PartialOrd for User {
@@ -881,7 +882,6 @@ impl User {
github_login: message.github_login,
avatar_uri: message.avatar_url.into(),
name: message.name,
email: message.email,
})
}
}
@@ -912,6 +912,8 @@ impl Collaborator {
replica_id: message.replica_id as ReplicaId,
user_id: message.user_id as UserId,
is_host: message.is_host,
committer_name: message.committer_name,
committer_email: message.committer_email,
})
}
}

View File

@@ -80,7 +80,6 @@ zed_llm_client.workspace = true
agent_settings.workspace = true
assistant_context_editor.workspace = true
assistant_slash_command.workspace = true
assistant_tool.workspace = true
async-trait.workspace = true
audio.workspace = true
buffer_diff.workspace = true

View File

@@ -185,7 +185,9 @@ CREATE TABLE "project_collaborators" (
"connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
"user_id" INTEGER NOT NULL,
"replica_id" INTEGER NOT NULL,
"is_host" BOOLEAN NOT NULL
"is_host" BOOLEAN NOT NULL,
"committer_name" VARCHAR,
"committer_email" VARCHAR
);
CREATE INDEX "index_project_collaborators_on_project_id" ON "project_collaborators" ("project_id");
@@ -463,6 +465,7 @@ CREATE TABLE extension_versions (
provides_slash_commands BOOLEAN NOT NULL DEFAULT FALSE,
provides_indexed_docs_providers BOOLEAN NOT NULL DEFAULT FALSE,
provides_snippets BOOLEAN NOT NULL DEFAULT FALSE,
provides_debug_adapters BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (extension_id, version)
);

View File

@@ -0,0 +1,4 @@
alter table project_collaborators
add column committer_name varchar;
alter table project_collaborators
add column committer_email varchar;

View File

@@ -0,0 +1,2 @@
alter table extension_versions
add column provides_debug_adapters bool not null default false

View File

@@ -751,6 +751,8 @@ pub struct ProjectCollaborator {
pub user_id: UserId,
pub replica_id: ReplicaId,
pub is_host: bool,
pub committer_name: Option<String>,
pub committer_email: Option<String>,
}
impl ProjectCollaborator {
@@ -760,6 +762,8 @@ impl ProjectCollaborator {
replica_id: self.replica_id.0 as u32,
user_id: self.user_id.to_proto(),
is_host: self.is_host,
committer_name: self.committer_name.clone(),
committer_email: self.committer_email.clone(),
}
}
}

View File

@@ -118,6 +118,8 @@ impl Database {
user_id: collaborator.user_id.to_proto(),
replica_id: collaborator.replica_id.0 as u32,
is_host: false,
committer_name: None,
committer_email: None,
})
.collect(),
})
@@ -225,6 +227,8 @@ impl Database {
user_id: collaborator.user_id.to_proto(),
replica_id: collaborator.replica_id.0 as u32,
is_host: false,
committer_name: None,
committer_email: None,
})
.collect(),
},
@@ -261,6 +265,8 @@ impl Database {
replica_id: db_collaborator.replica_id.0 as u32,
user_id: db_collaborator.user_id.to_proto(),
is_host: false,
committer_name: None,
committer_email: None,
})
} else {
collaborator_ids_to_remove.push(db_collaborator.id);
@@ -390,6 +396,8 @@ impl Database {
replica_id: row.replica_id.0 as u32,
user_id: row.user_id.to_proto(),
is_host: false,
committer_name: None,
committer_email: None,
});
}

View File

@@ -501,8 +501,10 @@ impl Database {
/// Returns all channels for the user with the given ID.
pub async fn get_channels_for_user(&self, user_id: UserId) -> Result<ChannelsForUser> {
self.transaction(|tx| async move { self.get_user_channels(user_id, None, true, &tx).await })
.await
self.weak_transaction(
|tx| async move { self.get_user_channels(user_id, None, true, &tx).await },
)
.await
}
/// Returns all channels for the user with the given ID that are descendants
@@ -737,7 +739,6 @@ impl Database {
),
github_login: user.github_login,
name: user.name,
email: user.email_address,
})
}
proto::ChannelMember {

View File

@@ -15,7 +15,7 @@ impl Database {
user_b_busy: bool,
}
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let user_a_participant = Alias::new("user_a_participant");
let user_b_participant = Alias::new("user_b_participant");
let mut db_contacts = contact::Entity::find()
@@ -91,7 +91,7 @@ impl Database {
/// Returns whether the given user is a busy (on a call).
pub async fn is_user_busy(&self, user_id: UserId) -> Result<bool> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let participant = room_participant::Entity::find()
.filter(room_participant::Column::UserId.eq(user_id))
.one(&*tx)

View File

@@ -321,6 +321,9 @@ impl Database {
provides_snippets: ActiveValue::Set(
version.provides.contains(&ExtensionProvides::Snippets),
),
provides_debug_adapters: ActiveValue::Set(
version.provides.contains(&ExtensionProvides::DebugAdapters),
),
download_count: ActiveValue::NotSet,
}
}))
@@ -431,6 +434,10 @@ fn apply_provides_filter(
condition = condition.add(extension_version::Column::ProvidesSnippets.eq(true));
}
if provides_filter.contains(&ExtensionProvides::DebugAdapters) {
condition = condition.add(extension_version::Column::ProvidesDebugAdapters.eq(true));
}
condition
}

View File

@@ -98,7 +98,9 @@ impl Database {
user_id: ActiveValue::set(participant.user_id),
replica_id: ActiveValue::set(ReplicaId(replica_id)),
is_host: ActiveValue::set(true),
..Default::default()
id: ActiveValue::NotSet,
committer_name: ActiveValue::Set(None),
committer_email: ActiveValue::Set(None),
}
.insert(&*tx)
.await?;
@@ -784,13 +786,27 @@ impl Database {
project_id: ProjectId,
connection: ConnectionId,
user_id: UserId,
committer_name: Option<String>,
committer_email: Option<String>,
) -> Result<TransactionGuard<(Project, ReplicaId)>> {
self.project_transaction(project_id, |tx| async move {
let (project, role) = self
.access_project(project_id, connection, Capability::ReadOnly, &tx)
.await?;
self.join_project_internal(project, user_id, connection, role, &tx)
self.project_transaction(project_id, move |tx| {
let committer_name = committer_name.clone();
let committer_email = committer_email.clone();
async move {
let (project, role) = self
.access_project(project_id, connection, Capability::ReadOnly, &tx)
.await?;
self.join_project_internal(
project,
user_id,
committer_name,
committer_email,
connection,
role,
&tx,
)
.await
}
})
.await
}
@@ -799,6 +815,8 @@ impl Database {
&self,
project: project::Model,
user_id: UserId,
committer_name: Option<String>,
committer_email: Option<String>,
connection: ConnectionId,
role: ChannelRole,
tx: &DatabaseTransaction,
@@ -822,7 +840,9 @@ impl Database {
user_id: ActiveValue::set(user_id),
replica_id: ActiveValue::set(replica_id),
is_host: ActiveValue::set(false),
..Default::default()
id: ActiveValue::NotSet,
committer_name: ActiveValue::set(committer_name),
committer_email: ActiveValue::set(committer_email),
}
.insert(tx)
.await?;
@@ -1026,6 +1046,8 @@ impl Database {
user_id: collaborator.user_id,
replica_id: collaborator.replica_id,
is_host: collaborator.is_host,
committer_name: collaborator.committer_name,
committer_email: collaborator.committer_email,
})
.collect(),
worktrees,

View File

@@ -80,7 +80,7 @@ impl Database {
&self,
user_id: UserId,
) -> Result<Option<proto::IncomingCall>> {
self.transaction(|tx| async move {
self.weak_transaction(|tx| async move {
let pending_participant = room_participant::Entity::find()
.filter(
room_participant::Column::UserId
@@ -553,6 +553,8 @@ impl Database {
user_id: collaborator.user_id,
replica_id: collaborator.replica_id,
is_host: collaborator.is_host,
committer_name: collaborator.committer_name.clone(),
committer_email: collaborator.committer_email.clone(),
})
.collect(),
worktrees: reshared_project.worktrees.clone(),
@@ -857,6 +859,8 @@ impl Database {
user_id: collaborator.user_id,
replica_id: collaborator.replica_id,
is_host: collaborator.is_host,
committer_name: collaborator.committer_name,
committer_email: collaborator.committer_email,
})
.collect::<Vec<_>>();

View File

@@ -27,6 +27,7 @@ pub struct Model {
pub provides_slash_commands: bool,
pub provides_indexed_docs_providers: bool,
pub provides_snippets: bool,
pub provides_debug_adapters: bool,
}
impl Model {
@@ -68,6 +69,10 @@ impl Model {
provides.insert(ExtensionProvides::Snippets);
}
if self.provides_debug_adapters {
provides.insert(ExtensionProvides::DebugAdapters);
}
provides
}
}

View File

@@ -13,6 +13,8 @@ pub struct Model {
pub user_id: UserId,
pub replica_id: ReplicaId,
pub is_host: bool,
pub committer_name: Option<String>,
pub committer_email: Option<String>,
}
impl Model {

View File

@@ -126,12 +126,16 @@ async fn test_channel_buffers(db: &Arc<Database>) {
peer_id: Some(rpc::proto::PeerId { id: 1, owner_id }),
replica_id: 0,
is_host: false,
committer_name: None,
committer_email: None,
},
rpc::proto::Collaborator {
user_id: b_id.to_proto(),
peer_id: Some(rpc::proto::PeerId { id: 2, owner_id }),
replica_id: 1,
is_host: false,
committer_name: None,
committer_email: None,
}
]
);

View File

@@ -7,6 +7,12 @@ pub use token::*;
pub const AGENT_EXTENDED_TRIAL_FEATURE_FLAG: &str = "agent-extended-trial";
/// The name of the feature flag that bypasses the account age check.
pub const BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG: &str = "bypass-account-age-check";
/// The minimum account age an account must have in order to use the LLM service.
pub const MIN_ACCOUNT_AGE_FOR_LLM_USE: chrono::Duration = chrono::Duration::days(30);
/// The default value to use for maximum spend per month if the user did not
/// explicitly set a maximum spend.
///

View File

@@ -1,6 +1,6 @@
use crate::db::billing_subscription::SubscriptionKind;
use crate::db::{billing_customer, billing_subscription, user};
use crate::llm::AGENT_EXTENDED_TRIAL_FEATURE_FLAG;
use crate::llm::{AGENT_EXTENDED_TRIAL_FEATURE_FLAG, BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG};
use crate::{Config, db::billing_preference};
use anyhow::{Context as _, Result};
use chrono::{NaiveDateTime, Utc};
@@ -84,7 +84,7 @@ impl LlmTokenClaims {
.any(|flag| flag == "llm-closed-beta"),
bypass_account_age_check: feature_flags
.iter()
.any(|flag| flag == "bypass-account-age-check"),
.any(|flag| flag == BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG),
can_use_web_search_tool: true,
use_llm_request_queue: feature_flags.iter().any(|flag| flag == "llm-request-queue"),
plan,

View File

@@ -4,14 +4,17 @@ use crate::api::billing::find_or_create_billing_customer;
use crate::api::{CloudflareIpCountryHeader, SystemIdHeader};
use crate::db::billing_subscription::SubscriptionKind;
use crate::llm::db::LlmDatabase;
use crate::llm::{AGENT_EXTENDED_TRIAL_FEATURE_FLAG, LlmTokenClaims};
use crate::llm::{
AGENT_EXTENDED_TRIAL_FEATURE_FLAG, BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG, LlmTokenClaims,
MIN_ACCOUNT_AGE_FOR_LLM_USE,
};
use crate::stripe_client::StripeCustomerId;
use crate::{
AppState, Error, Result, auth,
db::{
self, BufferId, Capability, Channel, ChannelId, ChannelRole, ChannelsForUser,
CreatedChannelMessage, Database, InviteMemberResult, MembershipUpdated, MessageId,
NotificationId, Project, ProjectId, RejoinedProject, RemoveChannelMemberResult, ReplicaId,
NotificationId, ProjectId, RejoinedProject, RemoveChannelMemberResult,
RespondToChannelInvite, RoomId, ServerId, UpdatedChannelMessage, User, UserId,
},
executor::Executor,
@@ -65,7 +68,7 @@ use std::{
rc::Rc,
sync::{
Arc, OnceLock,
atomic::{AtomicBool, Ordering::SeqCst},
atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
},
time::{Duration, Instant},
};
@@ -86,10 +89,36 @@ pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(15);
const MESSAGE_COUNT_PER_PAGE: usize = 100;
const MAX_MESSAGE_LEN: usize = 1024;
const NOTIFICATION_COUNT_PER_PAGE: usize = 50;
const MAX_CONCURRENT_CONNECTIONS: usize = 512;
static CONCURRENT_CONNECTIONS: AtomicUsize = AtomicUsize::new(0);
type MessageHandler =
Box<dyn Send + Sync + Fn(Box<dyn AnyTypedEnvelope>, Session) -> BoxFuture<'static, ()>>;
pub struct ConnectionGuard;
impl ConnectionGuard {
pub fn try_acquire() -> Result<Self, ()> {
let current_connections = CONCURRENT_CONNECTIONS.fetch_add(1, SeqCst);
if current_connections >= MAX_CONCURRENT_CONNECTIONS {
CONCURRENT_CONNECTIONS.fetch_sub(1, SeqCst);
tracing::error!(
"too many concurrent connections: {}",
current_connections + 1
);
return Err(());
}
Ok(ConnectionGuard)
}
}
impl Drop for ConnectionGuard {
fn drop(&mut self) {
CONCURRENT_CONNECTIONS.fetch_sub(1, SeqCst);
}
}
struct Response<R> {
peer: Arc<Peer>,
receipt: Receipt<R>,
@@ -312,6 +341,7 @@ impl Server {
.add_request_handler(
forward_read_only_project_request::<proto::LanguageServerIdForName>,
)
.add_request_handler(forward_read_only_project_request::<proto::GetDocumentDiagnostics>)
.add_request_handler(
forward_mutating_project_request::<proto::RegisterBufferWithLanguageServers>,
)
@@ -354,6 +384,9 @@ impl Server {
.add_message_handler(broadcast_project_message_from_host::<proto::BufferReloaded>)
.add_message_handler(broadcast_project_message_from_host::<proto::BufferSaved>)
.add_message_handler(broadcast_project_message_from_host::<proto::UpdateDiffBases>)
.add_message_handler(
broadcast_project_message_from_host::<proto::PullWorkspaceDiagnostics>,
)
.add_request_handler(get_users)
.add_request_handler(fuzzy_search_users)
.add_request_handler(request_contact)
@@ -718,6 +751,7 @@ impl Server {
system_id: Option<String>,
send_connection_id: Option<oneshot::Sender<ConnectionId>>,
executor: Executor,
connection_guard: Option<ConnectionGuard>,
) -> impl Future<Output = ()> + use<> {
let this = self.clone();
let span = info_span!("handle connection", %address,
@@ -738,6 +772,7 @@ impl Server {
tracing::error!("server is tearing down");
return
}
let (connection_id, handle_io, mut incoming_rx) = this
.peer
.add_connection(connection, {
@@ -779,6 +814,7 @@ impl Server {
tracing::error!(?error, "failed to send initial client update");
return;
}
drop(connection_guard);
let handle_io = handle_io.fuse();
futures::pin_mut!(handle_io);
@@ -1150,6 +1186,19 @@ pub async fn handle_websocket_request(
}
let socket_address = socket_address.to_string();
// Acquire connection guard before WebSocket upgrade
let connection_guard = match ConnectionGuard::try_acquire() {
Ok(guard) => guard,
Err(()) => {
return (
StatusCode::SERVICE_UNAVAILABLE,
"Too many concurrent connections",
)
.into_response();
}
};
ws.on_upgrade(move |socket| {
let socket = socket
.map_ok(to_tungstenite_message)
@@ -1167,6 +1216,7 @@ pub async fn handle_websocket_request(
system_id_header.map(|header| header.to_string()),
None,
Executor::Production,
Some(connection_guard),
)
.await;
}
@@ -1840,28 +1890,16 @@ async fn join_project(
let db = session.db().await;
let (project, replica_id) = &mut *db
.join_project(project_id, session.connection_id, session.user_id())
.join_project(
project_id,
session.connection_id,
session.user_id(),
request.committer_name.clone(),
request.committer_email.clone(),
)
.await?;
drop(db);
tracing::info!(%project_id, "join remote project");
join_project_internal(response, session, project, replica_id)
}
trait JoinProjectInternalResponse {
fn send(self, result: proto::JoinProjectResponse) -> Result<()>;
}
impl JoinProjectInternalResponse for Response<proto::JoinProject> {
fn send(self, result: proto::JoinProjectResponse) -> Result<()> {
Response::<proto::JoinProject>::send(self, result)
}
}
fn join_project_internal(
response: impl JoinProjectInternalResponse,
session: Session,
project: &mut Project,
replica_id: &ReplicaId,
) -> Result<()> {
let collaborators = project
.collaborators
.iter()
@@ -1889,6 +1927,8 @@ fn join_project_internal(
replica_id: replica_id.0 as u32,
user_id: guest_user_id.to_proto(),
is_host: false,
committer_name: request.committer_name.clone(),
committer_email: request.committer_email.clone(),
}),
};
@@ -2517,7 +2557,6 @@ async fn get_users(
id: user.id.to_proto(),
avatar_url: format!("https://github.com/{}.png?size=128", user.github_login),
github_login: user.github_login,
email: user.email_address,
name: user.name,
})
.collect();
@@ -2551,7 +2590,6 @@ async fn fuzzy_search_users(
avatar_url: format!("https://github.com/{}.png?size=128", user.github_login),
github_login: user.github_login,
name: user.name,
email: user.email_address,
})
.collect();
response.send(proto::UsersResponse { users })?;
@@ -2769,8 +2807,12 @@ async fn make_update_user_plan_message(
(None, None)
};
let account_too_young =
!matches!(plan, proto::Plan::ZedPro) && user.account_age() < MIN_ACCOUNT_AGE_FOR_LLM_USE;
let bypass_account_age_check = feature_flags
.iter()
.any(|flag| flag == BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG);
let account_too_young = !matches!(plan, proto::Plan::ZedPro)
&& !bypass_account_age_check
&& user.account_age() < MIN_ACCOUNT_AGE_FOR_LLM_USE;
Ok(proto::UpdateUserPlan {
plan: plan.into(),
@@ -4071,9 +4113,6 @@ async fn accept_terms_of_service(
Ok(())
}
/// The minimum account age an account must have in order to use the LLM service.
pub const MIN_ACCOUNT_AGE_FOR_LLM_USE: chrono::Duration = chrono::Duration::days(30);
async fn get_llm_api_token(
_request: proto::GetLlmToken,
response: Response<proto::GetLlmToken>,

View File

@@ -7,7 +7,7 @@ use editor::{
Editor, RowInfo,
actions::{
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst,
ExpandMacroRecursively, Redo, Rename, ToggleCodeActions, Undo,
ExpandMacroRecursively, Redo, Rename, SelectAll, ToggleCodeActions, Undo,
},
test::{
editor_test_context::{AssertionContextManager, EditorTestContext},
@@ -2712,7 +2712,7 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes
params.text_document.uri,
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
);
assert_eq!(params.position, lsp::Position::new(0, 0),);
assert_eq!(params.position, lsp::Position::new(0, 0));
Ok(Some(ExpandedMacro {
name: "test_macro_name".to_string(),
expansion: "test_macro_expansion on the host".to_string(),
@@ -2747,7 +2747,11 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes
params.text_document.uri,
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
);
assert_eq!(params.position, lsp::Position::new(0, 0),);
assert_eq!(
params.position,
lsp::Position::new(0, 12),
"editor_b has selected the entire text and should query for a different position"
);
Ok(Some(ExpandedMacro {
name: "test_macro_name".to_string(),
expansion: "test_macro_expansion on the client".to_string(),
@@ -2756,6 +2760,7 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes
);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.select_all(&SelectAll, window, cx);
expand_macro_recursively(editor, &ExpandMacroRecursively, window, cx)
});
expand_request_b.next().await.unwrap();

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