Compare commits

..

104 Commits

Author SHA1 Message Date
Zed Bot
0890516c1f Bump to 0.183.13 for @osiewicz 2025-04-30 11:04:01 +00:00
gcp-cherry-pick-bot[bot]
570585d917 Revert "python: Enable subroot detection for pylsp and pyright (#27364)" (cherry-pick #29658) (#29662)
Cherry-picked Revert "python: Enable subroot detection for pylsp and
pyright (#27364)" (#29658)

This reverts commit e661a0afd6.

Closes #ISSUE

Release Notes:

- Reverted changes to Python subroot detection which could have caused
multiple python processes to be spawned when working in projects with
multiple `pyproject.toml` files.

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-04-30 12:59:47 +02:00
Peter Tripp
34cb4a1d67 zed 0.183.12 2025-04-29 09:13:40 -04:00
Ben Kunkle
267c74db5b Fix data loss when project settings opened with ".zed" in file_scan_exclusions (#29578)
Closes #28640

Before creating an entry for a file opened with `open_local_file`, make
sure it doesn't exist, in addition to checking that it isn't already
tracked in the workspace

Release Notes:

- Fixed an issue where the project settings file would be truncated when
opened with `zed: open project settings` if the ".zed" directory was
excluded from the files scanned in a workspace (in
"file_scan_exclusions")
2025-04-29 09:10:45 -04:00
Peter Tripp
5d140dee49 Fix ctrl-enter opening inline-assistant in assistant text threads (#29313)
Closes: https://github.com/zed-industries/zed/issues/24501

This has been broken for a while on linux (at least since Feb 8th!) for Assistant1.
It is also broken for Text Threads in Assitant2 (on macos and linux).

This should fix both.

Potentially related:
- https://github.com/zed-industries/zed/pull/29107

Release Notes:

- Fix for `ctrl-enter` shortcut in Assistant text threads incorrectly
opening inline assist instead of triggering Send.

Co-authored-by: Conrad Irwin <conrad@zed.dev>
2025-04-28 21:49:13 -04:00
Peter Tripp
f74c86dc49 ollama: Add Qwen3 and Gemma3 (default to 16K context) (#29580)
If you have the VRAM you can increase the context by adding this to your
settings.json:

```json
  "language_models": {
    "ollama": {
      "available_models": [
        { "max_tokens": 65536, "name": "qwen3", "display_name": "Qwen3-64k" }
      ]
    }
  },
```

Release Notes:

- ollama: Add support for Qwen3. Defaults to 16K token context. See:
[Assistant Configuration
Docs](https://zed.dev/docs/assistant/configuration#ollama-context) to
increase.
2025-04-28 21:47:14 -04:00
shenjack
68c15fbad6 ollama: Add DeepSeek v3 max token length (#29156)
Add deepseek-v3 max token length for ollama

Release Notes:

- N/A
2025-04-28 21:47:05 -04:00
Zed Bot
84f21f46b6 Bump to 0.183.11 for @bennetbo 2025-04-25 13:20:48 +00:00
gcp-cherry-pick-bot[bot]
75feba2107 assistant: Fix issue when using inline assistant with Gemini models (cherry-pick #29407) (#29408)
Cherry-picked assistant: Fix issue when using inline assistant with
Gemini models (#29407)

Closes #29020

Release Notes:

- assistant: Fix issue when using inline assistant with Gemini models

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
2025-04-25 14:47:08 +02:00
Smit Barmase
f26b3337f6 editor: Revert flattening of code actions in mouse context menu (#28988)
In light of making context not move dynamically, reverting back these
changes.

- Doing it async will lead to a loading state, which moves the context
menu.
- Doing it sync introduces noticeable lag in opening the context menu.

Future idea is to introduce fixed code actions like refactor, rewrite,
etc depending on code action kind [(see
more)](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionKind)
which will use submenus.

Release Notes:

- N/A
2025-04-24 02:27:52 +05:30
Ben Kunkle
52db5223c7 editor: Dismiss mouse context menus on selections change (#28729)
Closes #ISSUE

Adds an extra subscription for mouse context menus (i.e. right click context menu) so that when selections change in the editor while the context menu is open (e.g. with vim motions), the context menu closes.

Release Notes:

- N/A
2025-04-24 02:07:29 +05:30
Joseph T. Lyons
7736c850ae v0.183.x stable 2025-04-23 11:57:41 -04:00
gcp-cherry-pick-bot[bot]
28bfcc603c Fix panic in vim selection restoration (cherry-pick #29251) (#29254)
Cherry-picked Fix panic in vim selection restoration (#29251)

Closes #27986

Closes #ISSUE

Release Notes:

- vim: Fixed a panic when using `gv` after `p` in visual line mode

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-04-23 09:06:58 -06:00
Marko Kungla
11392e4bd5 Add zed to Flatpak config and data directories (#28952)
Closes #28944 

Release Notes:

- linux: Fixed incorrect config directory being used when Zed is
installed via Flatpak

Signed-off-by: Marko Kungla <marko.kungla@gmail.com>
2025-04-23 09:40:22 -04:00
gcp-cherry-pick-bot[bot]
17ca3f8e9a Fix panic when collaborating with new multibuffers (cherry-pick #29245) (#29252)
Cherry-picked Fix panic when collaborating with new multibuffers
(#29245)

Before this change, when syncing a multibuffer (such as
find-all-references) to a remote, we would renumber the excerpts from 1.
This did not matter in the past because the buffers' list of excerpts
could not change. In #27876, I added the ability for excerpts to merge,
which meant that the excerpt list could change. This manifested as
people seeing "invalid excerpt id" panics when syncing.

The initial fix to this (to re-use the excerpt ids from the host) ran
into problems because `insert_excerpts_with_ids_after` assumes that you
call it in excerpt-id order. This change de-optimizes that code to
insert the excerpts 1-by-1 in excerpt-id order, but with the
insert_after set to preserve the correct UI order.

I hope to soon remove this code path and use something more like
set-excerpts-for-path for syncing, but in the meantime we should not
panic.

Release Notes:

- Fix a panic when joining a project with a multibuffer with merged
excerpts

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-04-22 22:28:24 -06:00
Joseph T. Lyons
6874c0d483 zed 0.183.10 2025-04-22 16:22:24 -04:00
Smit Barmase
2fb5f57afb language: Fix language_scope_at for markdown code comments (#29230)
Closes #29176

This PR fix an issue where uncommenting a code block in Markdown would
add Markdown comments instead of removing the language-specific
comments.

Why?
`language_scope_at` for comments in a code block in Markdown would
result in the language being detected as Markdown. This happens because
the smallest range, such as `//` or `#` on the Markdown layer, is
preferred over `// whole comment line` for any other language. This
results in language detection as Markdown for that point.

To fix this, we also use a depth factor and try to prefer the layer with
greater depth over one with lesser depth. In this case, the code block's
language depth would be preferred over Markdown. The smallest range is
now used as a tiebreaker.

Added test for this case.

Release Notes:

- Fixed issue where uncommenting a code block in Markdown would add
Markdown comments instead of removing the language comments.
2025-04-22 16:21:44 -04:00
Bennet Bo Fenner
2a72164262 agent: Improve the review changes UX (#29221)
Release Notes:

- agent: Improved the AI-generated changes review UX by clearly exposing
the generating state in the multibuffer tab.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-04-22 13:01:21 -04:00
Danilo Leal
f7130ebf21 agent: Add small design tweaks (#29218)
Nothing too serious over here, just spacing and other small-ish tweaks.

Release Notes:

- N/A
2025-04-22 13:01:15 -04:00
Richard Feldman
57cbea7b93 Streaming tool calls (#29179)
https://github.com/user-attachments/assets/7854a737-ef83-414c-b397-45122e4f32e8



Release Notes:

- Create file and edit file tools now stream their tool descriptions, so
you can see what they're doing sooner.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-04-22 13:00:07 -04:00
Danilo Leal
a4665c2db6 agent: Show project name in the Agent notification (#29211)
Release Notes:

- agent: Added the project name in the Agent Panel notification.
2025-04-22 12:59:29 -04:00
Joseph T. Lyons
7bd0822135 zed 0.183.9 2025-04-22 09:29:13 -04:00
Nate Butler
89384cafad Add example agent tool preview (#28984)
This PR adds an example of rendering previews for tools using the new
Agent ToolCard style.

![CleanShot 2025-04-17 at 13 03
12@2x](https://github.com/user-attachments/assets/d4c7d266-cc32-4038-9170-f3e070fce60e)

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-04-22 09:14:12 -04:00
Danilo Leal
c3239ca4a6 agent: Refine the web search tool call UI (#29190)
This PR refines a bit the web search tool UI by introducing a component
(`ToolCallCardHeader`) that aims to standardize the heading element of
tool calls in the thread.

In terms of next steps, I plan to evolve this component further soon
(e.g., building a full-blown "tool call card" component), and even move
it to a place where I can re-use it in the active_thread as well without
making the `assistant_tools` a dependency of it.

Release Notes:

- N/A
2025-04-22 09:11:04 -04:00
Danilo Leal
029b3434ff agent: Simplify user message design (#29165)
Mainly removing the "You" label, which didn't add a lot of value. Still
figuring out an issue with font size Markdown rendering before merging
this PR.

Release Notes:

- N/A
2025-04-22 09:11:04 -04:00
Bennet Bo Fenner
d3113ef126 agent: Support inserting selections as context via @selection (#29045)
WIP

Release Notes:

- N/A
2025-04-22 09:11:04 -04:00
Stephan Seidt
7cb9b46eb1 agent: Add support for google gemini 2.5 flash preview (#29205)
Adds support for the new gemini-2.5-flash-preview-04-17

Release Notes:

- agent: Added support for gemini-2.5-flash-preview
2025-04-22 09:11:03 -04:00
Bennet Bo Fenner
9c3ea6d86e gemini: Add support for passing images as part of the prompt (#29203)
Release Notes:

- agent: Add support for adding images as context when using Google
Gemini
2025-04-22 09:11:03 -04:00
Bennet Bo Fenner
cc8d096cf3 agent: Support pasting images as context (#29177)
https://github.com/user-attachments/assets/d6a27b05-3590-4f40-a820-f6f99f6bd581

Release Notes:

- agent: Added support for pasting images as context

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-04-22 09:11:03 -04:00
Agus Zubiaga
937f57a862 agent: Do not add <using_tool> placeholder (#29194)
Our provider code in `language_models` filters out messages for which
`LanguageModelRequestMessage::contents_empty` returns `false`. This
doesn't seem wrong by itself, but `contents_empty` was returning `false`
for messages whose first segment didn't contain non-whitespace text even
if they contained other non-empty segments. This caused requests to fail
when a message with a tool call didn't contain any preceding text.

Release Notes:

- N/A
2025-04-22 09:11:03 -04:00
Michael Sloan
06c4720055 agent: Fix file context renames affecting display + simplify loading code (#29192)
Release Notes:

- N/A
2025-04-22 09:11:03 -04:00
Michael Sloan
1255a1b355 agent: Make directory context display update on rename (#29189)
Release Notes:

- N/A
2025-04-22 09:11:03 -04:00
Zed Bot
aefa9e73d8 Bump to 0.183.8 for @probably-neb 2025-04-22 00:26:44 +00:00
gcp-cherry-pick-bot[bot]
2009abb22a editor: Expand selection to word under cursor before expanding to next enclosing syntax node (cherry-pick #28864) (#29184)
Cherry-picked editor: Expand selection to word under cursor before
expanding to next enclosing syntax node (#28864)

Closes #27995

For strings in any language and Markdown, `select_larger_syntax_node`
will first select the word and then expand from there if:
- The cursor is on the word.
- The selection is inside the word.

It will not select the word and will directly proceed to expand if:
- The word is already selected.
- Multiple partial words are selected.

Todo:
- [x] Tests

Release Notes:

- Fixed `select_larger_syntax_node` to first expand to the word within a
string, and then to the larger syntax node.

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-04-21 19:50:22 -04:00
gcp-cherry-pick-bot[bot]
f0f56d72b5 Use buffer size for markdown preview (cherry-pick #29172) (#29183)
Cherry-picked Use buffer size for markdown preview (#29172)

Note:

This is implemented in a very hacky and one-off manner. The primary
change is to pass a rem size through the markdown render tree, and scale
all sizing (rems & pixels) based on the passed in rem size manually.
This required copying in the `CheckBox` component from `ui::CheckBox` to
make it use the manual rem scaling without modifying the `CheckBox`
implementation directly as it is used elsewhere.

A better solution is required, likely involving `window.with_rem_size`
and/or _actual_ `em` units that allow text-size-relative scaling.

Release Notes:

- Made it so Markdown preview uses the _buffer_ font size instead of the
_ui_ font size.

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Nate Butler <nate@zed.dev>

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Nate Butler <nate@zed.dev>
2025-04-21 19:48:22 -04:00
Michael Sloan
6c53ee23c5 Add ability to attach rules as context (#29109)
Release Notes:

- agent: Added support for adding rules as context.
2025-04-21 18:08:11 -04:00
gcp-cherry-pick-bot[bot]
18ac67372e Fix ctrl-c in vim normal mode (cherry-pick #29167) (#29169)
Cherry-picked Fix ctrl-c in vim normal mode (#29167)

This was broken when we added helix keybindings because we populate the
menu's shortcut based on the "last" seen binding for an action ignoring
context.

Release Notes:

- Fix `ctrl-c` in vim normal mode

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-04-21 14:15:16 -06:00
Joseph T. Lyons
4464589942 zed 0.183.7 2025-04-21 13:30:52 -04:00
Agus Zubiaga
754c5f2eb5 agent: Migrate tool names in settings (#29168)
Release Notes:

- agent: Add migration to rename `find_replace_file` tool to
`edit_file`, and `regex_search` to `grep`.
2025-04-21 13:29:49 -04:00
Marshall Bowers
0860283cba agent: Add additional fields to Agent Tool Finished telemetry event (#29163)
This PR adds additional fields to the `Agent Tool Finished` telemetry
event:

- `model`
- `model_provider`
- `thread_id`
- `prompt_id`

Release Notes:

- N/A
2025-04-21 13:29:49 -04:00
Danilo Leal
b1ac0d9390 agent: Update Switch color in the settings view (#29154)
Just using the color method for the Switch component added in
https://github.com/zed-industries/zed/pull/29074.

Release Notes:

- N/A
2025-04-21 11:37:45 -04:00
Agus Zubiaga
98f2208314 edit tool: Handle over-indentation in replace_with_flexible_indent (#29153)
Release Notes:

- agent: Correct over-indentation in search/replace strings from model
2025-04-21 11:37:45 -04:00
Danilo Leal
2084e0b339 ui: Add .color method to the Switch (#29074)
This allows to pass, for example, `.color(SwitchColor::Accent)` to the
Switch component and have it render differently.

<img
src="https://github.com/user-attachments/assets/c60bac8a-c5ae-4693-912a-c754e5081f45"
width="550"/>

Release Notes:

- N/A
2025-04-21 11:37:45 -04:00
Agus Zubiaga
7c0db88457 inline assistant: Fix model picker (#29136)
Release Notes:

- inline assistant: Fixed a bug where the default model would be used
even when a specific inline assistant model was configured
2025-04-21 08:48:38 -04:00
Nathan Sobo
933032013b Rename regex search tool to grep and accept an include glob pattern (#29100)
This PR renames the `regex_search` tool to `grep` because I think it
conveys more meaning to the model, the idea of searching the filesystem
with a regular expression. It's also one word and the model seems to be
using it effectively after some additional prompt tuning.

It also takes an include pattern to filter on the specific files we try
to search. I'd like to encourage the model to scope its searches more
aggressively, as in my testing, I'm only seeing it filter on file
extension.

Release Notes:

- N/A
2025-04-21 08:48:05 -04:00
Smit Barmase
5ac0baa536 editor: Improve selection highlights speed (#29097)
Before, we used to debounce selection highlight because it needed to
search the whole file to show gutter line highlights, etc. This
experience felt extremely laggy.

This PR introduces a new approach where:
1. We query only visible rows without debounce. The search function
itself is async and runs in a background thread, so it's not blocking
anything. With no debounce and such a small search space, highlights
feel realtime.
2. In parallel, we also query the whole file (still debounced, like
before). Once this query resolves, it updates highlights across the
file, making scrollbar markers visible.

This hybrid way gives the feeling of realtime, while keeping the same
functionality.


https://github.com/user-attachments/assets/432b65f1-89d2-4658-ad5e-048921b06a23

P.S. I have removed the user setting for custom debounce delay, because
(one) now it doesn't really make sense to configure that, and (two) the
whole logic is based on the assumption that the fast query will resolve
before the debounced query. A static debounce time makes sure of that.
Configuring it might lead to cases where the fast query resolves after
the debounced query, and we end up only seeing visible viewport
highlights.

Release Notes:

- Improved selection highlight speed.
2025-04-21 08:47:55 -04:00
Michael Sloan
05b8a6da25 agent: Reorder some linux keybindings to match mac keybindings (#29107)
Release Notes:

- Made keybindings for agent panel closer to the precedence order used
on Mac. This fixes use of `enter` to add context from the menu triggered
by `@` referencing.
2025-04-21 08:45:18 -04:00
Michael Sloan
e0e46daa62 Default to fast model for thread summaries and titles + don't include system prompt / context / thinking segments (#29102)
* Adds a fast / cheaper model to providers and defaults thread
summarization to this model. Initial motivation for this was that
https://github.com/zed-industries/zed/pull/29099 would cause these
requests to fail when used with a thinking model. It doesn't seem
correct to use a thinking model for summarization.

* Skips system prompt, context, and thinking segments.

* If tool use is happening, allows 2 tool uses + one more agent response
before summarizing.

Downside of this is that there was potential for some prefix cache reuse
before, especially for title summarization (thread summarization omitted
tool results and so would not share a prefix for those). This seems fine
as these requests should typically be fairly small. Even for full thread
summarization, skipping all tool use / context should greatly reduce the
token use.

Release Notes:

- N/A
2025-04-21 08:43:33 -04:00
Nathan Sobo
220b2cd959 Don't send dummy user text with tool results (#29099)
Previously, we were including the dummy text "Here are the tool
results." whenever reporting tool call results. I'm worried this is
adding noise and confusing the model, because the user didn't actually
say anything. This inserts an empty message to be populated later. My
preference would be something less stateful, where tool results (or
batches of them requested simultaneously) would be sent to the model as
soon as they were ready, without bothering to do this message
association dance. But for now, this seems to work.

Release Notes:

- N/A
2025-04-21 08:43:24 -04:00
Bennet Bo Fenner
9375cb2277 agent: Preserve thinking blocks between requests (#29055)
Looks like the required backend component of this was deployed.

https://github.com/zed-industries/monorepo/actions/runs/14541199197

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Agus Zubiaga <hi@aguz.me>
Co-authored-by: Richard Feldman <oss@rtfeldman.com>
Co-authored-by: Nathan Sobo <nathan@zed.dev>
2025-04-21 08:43:13 -04:00
Danilo Leal
9e8af50cd8 agent: Add item to add custom MCP server in the panel's menu (#29091)
This is based on user feedback that the Agent Panel menu was only
linking to extensions as a way to add MCP servers while we also support
adding "custom" servers, too, which don't go through the extensions
flow.

Release Notes:

- N/A
2025-04-21 08:43:02 -04:00
Michael Sloan
b7eb695a09 Simplify language model registry + only emit change events on change (#29086)
* Now only does default fallback logic in the registry

* Only emits change events when there is actually a change

Release Notes:

- N/A
2025-04-21 08:42:55 -04:00
Nathan Sobo
bf64cd4eb4 Systematically optimize agentic editing performance (#28961)
Now that we've established a proper eval in tree, this PR is reboots of
our agent loop back to a set of minimal tools and simpler prompts. We
should aim to get this branch feeling subjectively competitive with
what's on main and then merge it, and build from there.

Let's invest in our eval and use it to drive better performance of the
agent loop. How you can help: Pick an example, and then make the outcome
faster or better. It's fine to even use your own subjective judgment, as
our evaluation criteria likely need tuning as well at this point. Focus
on making the agent work better in your own subjective experience first.
Let's focus on simple/practical improvements to make this thing work
better, then determine how we can craft our judgment criteria to lock
those improvements in.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Agus <agus@zed.dev>
Co-authored-by: Richard <richard@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Michael Sloan <mgsloan@gmail.com>
2025-04-21 08:42:13 -04:00
Danilo Leal
0b9ae6e7b3 agent: Make copy button show while hovering the codeblock container (#29075) 2025-04-21 08:42:13 -04:00
Marshall Bowers
ba9c033770 language_models: Fix passing of thread_id and prompt_id (#29071)
This PR is a follow-up to
https://github.com/zed-industries/zed/pull/29069 that fixes an issue
where the thread ID and prompt ID were not being sent up correctly.

Release Notes:

- N/A
2025-04-18 17:27:34 -04:00
Marshall Bowers
07ca5a6a33 agent: Attach thread ID and prompt ID to telemetry events (#29069)
This PR attaches the thread ID and the new prompt ID to telemetry events
for completions in the Agent panel.

Release Notes:

- N/A

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-04-18 17:27:34 -04:00
Michael Sloan
0c13b42c3a Add hidden prompt_to_focus field to OpenPromptLibrary action (#29062)
Release Notes:

- N/A
2025-04-18 17:27:34 -04:00
Michael Sloan
bb1b132922 Init prompt store in agent eval (#29068)
Needed after #28915

Release Notes:

- N/A
2025-04-18 16:18:11 -04:00
Danilo Leal
eefdcb36be agent: Simplify design of the settings view (#29041)
Containing everything in boxes wasn't super necessary here. Want to
still improve the switch color contrast here, but will probably do that
in a separate PR.

<img
src="https://github.com/user-attachments/assets/f826a7a8-beaf-45d0-9dc2-36dc210c418e"
width="700"/>

Release Notes:

- N/A
2025-04-18 13:39:13 -04:00
Michael Sloan
2ac8a84c9f agent: Use default prompts from prompt library in system prompt (#28915)
Related to #28490.

- Default prompts from the prompt library are now included as "user
rules" in the system prompt.
- Presence of these user rules is shown at the beginning of the thread
in the UI.
_ Now uses an `Entity<PromptStore>` instead of an `Arc<PromptStore>`.
Motivation for this is emitting a `PromptsUpdatedEvent`.
- Now disallows concurrent reloading of the system prompt. Before this
change it was possible for reloads to race.

Release Notes:

- agent: Added support for including default prompts from the Prompt
Library as "user rules" in the system prompt.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-04-18 13:39:13 -04:00
Kirill Bulatov
d35ffc7e10 debugger: Fix gutter tasks display for users without the debugger feature flag (#29056) 2025-04-18 11:20:13 -06:00
Joseph T. Lyons
093248ae05 zed 0.183.6 2025-04-18 09:39:04 -04:00
Bennet Bo Fenner
21ff2bb39a agent: Do not insert selection as context when selection is empty (#29031)
Release Notes:

- N/A
2025-04-18 09:14:31 -04:00
Bennet Bo Fenner
843a621b1c agent: Remove selections as context once message is sent (#29030)
Release Notes:

- N/A
2025-04-18 09:14:31 -04:00
gcp-cherry-pick-bot[bot]
bbe956f750 Make Copy and Trim ignore empty lines, and fix vim line selections (cherry-pick #29019) (#29023)
Cherry-picked Make Copy and Trim ignore empty lines, and fix vim line
selections (#29019)

Close #28519 

Release Notes:

Update `editor: copy and trim` command:

1. Ignore empty lines in the middle:

    ```
      Line 1

      Line 2
    ```

    Will copy text to clipboard:

    ```
    Line 1

    Line 2
    ```

    Before this commit trim not performed

1. Fix select use vim line selections, trim not works

Co-authored-by: redforks <redforks@gmail.com>
2025-04-17 21:50:04 -06:00
Marshall Bowers
0179e4c511 agent: Report usage from thread summarization requests (#29012)
This PR makes it so the thread summarization also reports the model
request usage, to prevent the case where the count would appear to jump
by 2 the next time a message was sent after summarization.

Release Notes:

- N/A
2025-04-17 22:43:16 -04:00
Marshall Bowers
df49cad705 agent: Show request usage in the panel (#29006)
This PR adds a banner showing request usage in the Agent panel:

<img width="640" alt="Screenshot 2025-04-17 at 5 51 46 PM"
src="https://github.com/user-attachments/assets/e0eb036c-57c1-441c-bbab-7dab1c6e56d9"
/>

Only visible to users on the new billing.

Note to Joseph: Doesn't need to be cherry-picked to Preview.

Release Notes:

- N/A

---------

Co-authored-by: Nate <nate@zed.dev>
2025-04-17 22:43:16 -04:00
Marshall Bowers
13b3beb4d8 agent: Extract usage information from response headers (#29002)
This PR updates the Agent to extract the usage information from the
response headers, if they are present.

For now we just log the information, but we'll be using this soon to
populate some UI.

Release Notes:

- N/A
2025-04-17 22:43:16 -04:00
Marshall Bowers
5f8efc9370 zeta: Extract usage information from response headers (#28999)
This PR updates the Zeta provider to extract the usage information from
the response headers, if they are present.

For now we just log the information, but we'll need to figure out where
this needs to get threaded through to in order to display it in the UI.

Release Notes:

- N/A
2025-04-17 22:43:16 -04:00
Marshall Bowers
a1d643103a Use more types/constants from zed_llm_client (#28909)
This PR makes it so we use more types and constants from the
`zed_llm_client` crate to avoid duplicating information.

Also updates the current usage endpoint to use limits derived from the
`Plan`.

Release Notes:

- N/A
2025-04-17 22:43:16 -04:00
Marshall Bowers
220d853dba rpc: Remove llm module in favor of zed_llm_client (#28900)
This PR removes the `llm` module of the `rpc` crate in favor of using
the types from the `zed_llm_client`.

Release Notes:

- N/A
2025-04-17 22:43:16 -04:00
Marshall Bowers
911f329303 collab: Add plan column to subscription_usages (#28889)
This PR adds a `plan` column to the `subscription_usages` table.

These tables don't have any records in them yet, so it's fine to make
the column required without a default.

Release Notes:

- N/A
2025-04-17 22:43:16 -04:00
Marshall Bowers
1bdcf318a6 proto: Add ZedProTrial to Plan (#28885)
This PR adds the `ZedProTrial` member to the `Plan` enum.

Release Notes:

- N/A
2025-04-17 22:43:16 -04:00
Zed Bot
d4f44c1137 Bump to 0.183.5 for @probably-neb 2025-04-17 21:02:02 +00:00
gcp-cherry-pick-bot[bot]
e0dc131418 Fix multiline completions when surrounding text doesn't match completion text (cherry-pick #28995) (#28997)
Cherry-picked Fix multiline completions when surroundings don't match
completion text (#28995)

Follow up to the scenarios I overlooked in
https://github.com/zed-industries/zed/pull/28586.

Release Notes:

- N/A

Co-authored-by: João Marcos <marcospb19@hotmail.com>
2025-04-17 15:50:37 -03:00
gcp-cherry-pick-bot[bot]
5054d0768d Revert "git_panel: Pad end of list to avoid obscuring final entry with horizontal scrollbar (#28823)" (cherry-pick #28971) (#28985)
Cherry-picked Revert "git_panel: Pad end of list to avoid obscuring
final entry with horizontal scrollbar (#28823)" (#28971)

This reverts commit 1d98b33ae0.

Not sure why, but seems like this breaks the binary search used to
correlate items to each other in the lists.

Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-04-17 14:02:28 -04:00
gcp-cherry-pick-bot[bot]
40add8682a Escape all runnables' cargo extra arguments coming from rust-analyzer (cherry-pick #28977) (#28981)
Cherry-picked Escape all runnables' cargo extra arguments coming from
rust-analyzer (#28977)

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

Release Notes:

- Fixed certain doctests not being run properly

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-04-17 11:07:25 -06:00
Danilo Leal
d168fb5a16 agent: Add design tweaks (#28963)
One more batch of fine-tuning the agent panel's design.

Release Notes:

- N/A
2025-04-17 12:08:11 -04:00
Bennet Bo Fenner
6bfd2593c9 agent: Support adding selection as context (#28964)
https://github.com/user-attachments/assets/42ebe911-3392-48f7-8583-caab285aca09

Release Notes:

- agent: Support adding selections via @selection or `assistant: Quote
selection` as context
2025-04-17 11:21:24 -04:00
Umesh Yadav
6db3b9c2e7 Add support for OpenAI o3 and o4-mini models (#28881)
Release Notes:

- Add support for OpenAI o3 and o4-mini models via OpenAI API and
Copilot Chat providers.

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-04-17 10:59:13 -04:00
redforks
01daf6e7d4 Fix snippets from extensions being listed twice (#28940)
lookup_snippets() merges global snippets and extension snippets, but
global_snippets::lookup_snippets() also returns extension snippets, make
them double

Closes #28661 

Release Notes:

- Fixed a bug where extension provided snippets were being displayed in
duplicate.
2025-04-17 10:59:10 -04:00
Joseph T. Lyons
e50872ca26 zed 0.183.4 2025-04-17 09:33:41 -04:00
Danilo Leal
8b288aa98d agent: Fix "open thread as markdown" button (#28962)
Just now realized that the reason this button wasn't working reliably is
because we weren't passing the index to it. It's now fixed.

Release Notes:

- N/A
2025-04-17 09:31:06 -04:00
Agus Zubiaga
aa1d400024 edit prediction: Assign providers when client status changes (#28919)
There was recently a change that caused the Zed Edit Prediction provider
to only be assigned when the client was connected. However, this check
happened too early, resulting in restored buffers never getting
registered. We'll now subscribe to client status changes and reassign
providers accordingly.

Release Notes:

- edit prediction: Fixed bug disabling prediction in restored buffers
2025-04-17 08:56:35 -04:00
Bennet Bo Fenner
fd6e093827 agent: Show context server name in incompatible tool warning (#28954)
<img width="410" alt="image"
src="https://github.com/user-attachments/assets/e29a0ba8-3d37-4e66-b90c-398b24da0453"
/>


Release Notes:

- N/A
2025-04-17 08:16:29 -04:00
Zed Bot
91581d6d2b Bump to 0.183.3 for @bennetbo 2025-04-17 09:12:51 +00:00
gcp-cherry-pick-bot[bot]
7102d40414 gemini: Fix invalid field name in request (cherry-pick #28949) (#28950)
Cherry-picked agent: Fix system instructions typo (#28949)

See #28793, the name of the field is actually `systemInstruction` not
`systemInstructions`.

Release Notes:

- Fixed an issue where Gemini requests would fail

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
2025-04-17 11:10:20 +02:00
gcp-cherry-pick-bot[bot]
718e0a9851 Fix panic when diagnostics first opens (cherry-pick #28935) (#28939)
Cherry-picked Fix panic when diagnostics first opens (#28935)

Closes #ISSUE

Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-04-16 22:26:12 -06:00
Zed Bot
2f4bd2a24b Bump to 0.183.2 for @ConradIrwin 2025-04-16 22:44:23 +00:00
gcp-cherry-pick-bot[bot]
3aac735cb2 Fix more inlay/excerpt race conditions (cherry-pick #28914) (#28916)
Cherry-picked Fix more inlay/excerpt race conditions (#28914)

Closes #ISSUE

Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-04-16 16:41:38 -06:00
Agus Zubiaga
82fb597b95 agent: Fix conversation token usage and estimate unsent message (#28878)
The UI was mistakenly using the cumulative token usage for the token
counter. It will now display the last request token count, plus an
estimation of the tokens in the message editor and context entries that
haven't been sent yet.


https://github.com/user-attachments/assets/0438c501-b850-4397-9135-57214ca3c07a

Additionally, when the user edits a message, we'll display the actual
token count up to it and estimate the tokens in the new message.

Note: We don't currently estimate the delta when switching profiles. In
the future, we want to use the count tokens API to measure every part of
the request and display a breakdown.

Release Notes:

- agent: Made the token count more accurate and added back estimation of
used tokens as you type and add context.

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-04-16 16:05:57 -04:00
Thomas Mickley-Doyle
07a0d91ea2 agent: Add git commit ID to the eval telemetry data (#28895)
Release Notes:

- N/A
2025-04-16 16:05:57 -04:00
Bennet Bo Fenner
88ddd7be46 agent: Allow quoting selection when text thread is active (#28887)
This makes the `assistant: Quote selection` work again for text threads.
Next up is supporting this also in normal threads.

Release Notes:

- agent: Add support for inserting selections (assistant: Quote
selection) into text threads
2025-04-16 16:05:57 -04:00
Conrad Irwin
f701d69233 Show all warnings (#28899)
Release Notes:

- (preview only) Fixes a bug where some warnings were not rendered
correctly in the Diagnostics view
2025-04-16 14:03:32 -06:00
gcp-cherry-pick-bot[bot]
a8a99414d0 Fix anchor_in_excerpt on replaced excerpts (cherry-pick #28880) (#28892)
Cherry-picked Fix anchor_in_excerpt on replaced excerpts (#28880)

Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-04-16 14:01:45 -06:00
Mikayla Maki
83ce1712dc zed 0.183.1 2025-04-16 12:00:52 -07:00
Mikayla Maki
9a54d111ef Remove bottom dock layout button (#28876)
Release Notes:

- Preview: Removed the layout button from the title bar. The
`bottom_dock_layout` setting still functions.
- Added a setting, `bottom_dock_layout`, for controlling the
relationship between the bottom dock and the left and right docks.
2025-04-16 11:59:53 -07:00
Bennet Bo Fenner
c2ff375787 agent: Improve fuzzy matching for @mentions (#28883)
Make fuzzy search in @-mention match paths and context kinds as well
(e.g., typing "sym" should let me select the "Symbols" label, as opposed
to just paths)

Release Notes:

- agent: Improve fuzzy-matching when using @mentions
2025-04-16 14:05:13 -04:00
Danilo Leal
1a81946137 agent: Add item to open Prompt Library in the panel's menu (#28877)
Release Notes:

- agent: Added a menu item to open the Prompt Library from the panel's
dropdown menu on the top right.
2025-04-16 14:05:13 -04:00
Bennet Bo Fenner
36ca5ab7c2 agent: Add websearch tool (#28621)
Staff only for now. We'll work on making this usable for non zed.dev
users later

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-04-16 14:05:13 -04:00
Danilo Leal
ad3a319465 agent: Add small design tweaks (#28874)
Some small adjustments to simplify the agent panel's design.

Release Notes:

- N/A
2025-04-16 12:40:07 -04:00
gcp-cherry-pick-bot[bot]
19b7c1ae89 Fix more panics when removing excerpts (cherry-pick #28836) (#28873)
Cherry-picked Fix more panics when removing excerpts (#28836)

Release Notes:

- Fixed a panic when an excerpt removed has an edit suggestion inlay in
it

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-04-16 10:06:28 -06:00
Marshall Bowers
9f8320f3a3 agent: Show an error when the model requests limit has been reached (#28868)
This PR adds an error message when the model requests limit has been
hit.

Release Notes:

- N/A

Co-authored-by: Oleksiy Syvokon <oleksiy.syvokon@gmail.com>
2025-04-16 11:33:31 -04:00
Joseph T. Lyons
7c483b231d v0.183.x preview 2025-04-16 08:45:21 -04:00
1413 changed files with 58357 additions and 148299 deletions

View File

@@ -13,6 +13,12 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
[target.aarch64-apple-darwin]
rustflags = ["-C", "link-args=-all_load"]
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-args=-all_load"]
[target.'cfg(target_os = "windows")']
rustflags = [
"--cfg",

View File

@@ -1 +0,0 @@
.rules

View File

@@ -1 +0,0 @@
.rules

View File

@@ -30,7 +30,3 @@ ffdda588b41f7d9d270ffe76cab116f828ad545e
# 2024-07-05 Improved formatting of default keymaps (single line per bind)
# https://github.com/zed-industries/zed/pull/13887
813cc3f5e537372fc86720b5e71b6e1c815440ab
# 2024-07-24 docs: Format docs
# https://github.com/zed-industries/zed/pull/15352
3a44a59f8ec114ac1ba22f7da1652717ef7e4e5c

View File

@@ -1,8 +1,8 @@
name: Bug Report (Debugger)
description: Zed Debugger-Related Bugs
name: Bug Report (Agent Panel)
description: Zed Agent Panel Bugs
type: "Bug"
labels: ["debugger"]
title: "Debugger: <a short description of the Debugger bug>"
labels: ["agent", "ai"]
title: "Agent Panel: <a short description of the Agent Panel bug>"
body:
- type: textarea
attributes:
@@ -14,6 +14,7 @@ body:
### Description
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
<!-- Please include the LLM provider and model name you are using -->
Steps to trigger the problem:
1.
2.
@@ -21,15 +22,15 @@ body:
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"'
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
placeholder: |
Output of "zed: copy system specs into clipboard"
Output of "zed: Copy System Specs Into Clipboard"
validations:
required: true

View File

@@ -1,42 +0,0 @@
name: Bug Report (AI Related)
description: Zed Agent Panel Bugs
type: "Bug"
labels: ["ai"]
title: "AI: <a short description of the AI Related 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:
### Model Provider Details
- Provider: (Anthropic via ZedPro, Anthropic via API key, Copilot Chat, Mistral, OpenAI, etc)
- Model Name:
- Mode: (Agent Panel, Inline Assistant, Terminal Assistant or Text Threads)
- MCP Servers in-use:
- Other Details:
validations:
required: true
- 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

@@ -29,8 +29,8 @@ body:
id: environment
attributes:
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: copy system specs into clipboard"'
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
placeholder: |
Output of "zed: copy system specs into clipboard"
Output of "zed: Copy System Specs Into Clipboard"
validations:
required: true

View File

@@ -28,8 +28,8 @@ body:
id: environment
attributes:
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: copy system specs into clipboard"'
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
placeholder: |
Output of "zed: copy system specs into clipboard"
Output of "zed: Copy System Specs Into Clipboard"
validations:
required: true

View File

@@ -49,8 +49,8 @@ body:
attributes:
label: Zed Version and System Specs
description: |
Open Zed, from the command palette select "zed: copy system specs into clipboard"
Open Zed, from the command palette select "zed: Copy System Specs Into Clipboard"
placeholder: |
Output of "zed: copy system specs into clipboard"
Output of "zed: Copy System Specs Into Clipboard"
validations:
required: true

View File

@@ -26,9 +26,9 @@ body:
id: environment
attributes:
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: copy system specs into clipboard"'
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
placeholder: |
Output of "zed: copy system specs into clipboard"
Output of "zed: Copy System Specs Into Clipboard"
validations:
required: true
- type: textarea

View File

@@ -10,7 +10,7 @@ runs:
cargo install cargo-nextest --locked
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version: "18"

View File

@@ -16,7 +16,7 @@ runs:
run: cargo install cargo-nextest --locked
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version: "18"

View File

@@ -162,23 +162,13 @@ jobs:
working-directory: ./docs
run: |
pnpm dlx prettier@${PRETTIER_VERSION} . --check || {
echo "To fix, run from the root of the Zed repo:"
echo "To fix, run from the root of the zed repo:"
echo " cd docs && pnpm dlx prettier@${PRETTIER_VERSION} . --write && cd .."
false
}
env:
PRETTIER_VERSION: 3.5.0
- name: Prettier Check on default.json
run: |
pnpm dlx prettier@${PRETTIER_VERSION} assets/settings/default.json --check || {
echo "To fix, run from the root of the Zed repo:"
echo " pnpm dlx prettier@${PRETTIER_VERSION} assets/settings/default.json --write"
false
}
env:
PRETTIER_VERSION: 3.5.0
# To support writing comments that they will certainly be revisited.
- name: Check for todo! and FIXME comments
run: script/check-todos
@@ -482,9 +472,7 @@ jobs:
- macos_tests
- windows_clippy
- windows_tests
if: |
github.repository_owner == 'zed-industries' &&
always()
if: always()
steps:
- name: Check all tests passed
run: |
@@ -526,11 +514,12 @@ jobs:
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version: "18"
@@ -612,6 +601,7 @@ jobs:
needs: [linux_tests]
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
@@ -669,6 +659,7 @@ jobs:
needs: [linux_tests]
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
@@ -716,13 +707,49 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
nix-build:
name: Build with Nix
uses: ./.github/workflows/nix.yml
timeout-minutes: 60
name: Nix Build
continue-on-error: true
if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix')
with:
flake-output: debug
# excludes the final package to only cache dependencies
cachix-filter: "-zed-editor-[0-9.]*-nightly"
strategy:
fail-fast: false
matrix:
system:
- os: x86 Linux
runner: buildjet-16vcpu-ubuntu-2204
install_nix: true
- os: arm Mac
runner: [macOS, ARM64, test]
install_nix: false
runs-on: ${{ matrix.system.runner }}
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
GIT_LFS_SKIP_SMUDGE: 1 # breaks the livekit rust sdk examples which we don't actually depend on
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Set path
if: ${{ ! matrix.system.install_nix }}
run: |
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
- uses: cachix/install-nix-action@d1ca217b388ee87b2507a9a93bf01368bde7cec2 # v31
if: ${{ matrix.system.install_nix }}
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
name: zed-industries
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
skipPush: true
- run: nix build .#debug
- name: Limit /nix/store to 50GB
run: "[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d"
auto-release-preview:
name: Auto release preview

View File

@@ -22,7 +22,7 @@ jobs:
version: 9
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version: "20"
cache: "pnpm"

View File

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

View File

@@ -18,7 +18,7 @@ jobs:
version: 9
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version: "20"
cache: "pnpm"

View File

@@ -1,66 +0,0 @@
name: "Nix build"
on:
workflow_call:
inputs:
flake-output:
type: string
default: "default"
cachix-filter:
type: string
default: ""
jobs:
nix-build:
timeout-minutes: 60
name: (${{ matrix.system.os }}) Nix Build
continue-on-error: true # TODO: remove when we want this to start blocking CI
strategy:
fail-fast: false
matrix:
system:
- os: x86 Linux
runner: buildjet-16vcpu-ubuntu-2204
install_nix: true
- os: arm Mac
runner: [macOS, ARM64, test]
install_nix: false
if: github.repository_owner == 'zed-industries'
runs-on: ${{ matrix.system.runner }}
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
GIT_LFS_SKIP_SMUDGE: 1 # breaks the livekit rust sdk examples which we don't actually depend on
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
# on our macs we manually install nix. for some reason the cachix action is running
# under a non-login /bin/bash shell which doesn't source the proper script to add the
# nix profile to PATH, so we manually add them here
- name: Set path
if: ${{ ! matrix.system.install_nix }}
run: |
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
if: ${{ matrix.system.install_nix }}
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
name: zed
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
pushFilter: "${{ inputs.cachix-filter }}"
cachixArgs: '-v'
- run: nix build .#${{ inputs.flake-output }} -L --accept-flake-config
- name: Limit /nix/store to 50GB on macs
if: ${{ ! matrix.system.install_nix }}
run: |
[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d || :

View File

@@ -23,7 +23,7 @@ jobs:
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version: "18"

View File

@@ -68,9 +68,10 @@ jobs:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version: "18"
@@ -103,6 +104,7 @@ jobs:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -142,6 +144,7 @@ jobs:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -168,9 +171,53 @@ jobs:
run: script/upload-nightly linux-targz
bundle-nix:
name: Build and cache Nix package
timeout-minutes: 60
name: (${{ matrix.system.os }}) Nix Build
continue-on-error: true
strategy:
fail-fast: false
matrix:
system:
- os: x86 Linux
runner: buildjet-16vcpu-ubuntu-2204
install_nix: true
- os: arm Mac
runner: [macOS, ARM64, test]
install_nix: false
if: github.repository_owner == 'zed-industries'
runs-on: ${{ matrix.system.runner }}
needs: tests
uses: ./.github/workflows/nix.yml
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
GIT_LFS_SKIP_SMUDGE: 1 # breaks the livekit rust sdk examples which we don't actually depend on
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
# on our macs we manually install nix. for some reason the cachix action is running
# under a non-login /bin/bash shell which doesn't source the proper script to add the
# nix profile to PATH, so we manually add them here
- name: Set path
if: ${{ ! matrix.system.install_nix }}
run: |
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
- uses: cachix/install-nix-action@d1ca217b388ee87b2507a9a93bf01368bde7cec2 # v31
if: ${{ matrix.system.install_nix }}
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
with:
name: zed-industries
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: nix build
- name: Limit /nix/store to 50GB
run: '[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d'
update-nightly-tag:
name: Update nightly tag

4
.gitignore vendored
View File

@@ -2,7 +2,6 @@
**/cargo-target
**/target
**/venv
**/.direnv
*.wasm
*.xcodeproj
.DS_Store
@@ -19,12 +18,12 @@
.venv
.vscode
.wrangler
/.direnv
/assets/*licenses.*
/crates/collab/seed.json
/crates/theme/schemas/theme.json
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
/dev.zed.Zed*.json
/node_modules/
/plugins/bin
/script/node_modules
/snap
@@ -34,5 +33,4 @@ Packages
xcuserdata/
# Don't commit any secrets to the repo.
.env
.env.secret.toml

View File

@@ -19,8 +19,6 @@ amtoaer <amtoaer@gmail.com>
amtoaer <amtoaer@gmail.com> <amtoaer@outlook.com>
Andrei Zvonimir Crnković <andrei@0x7f.dev>
Andrei Zvonimir Crnković <andrei@0x7f.dev> <andreicek@0x7f.dev>
Angelk90 <angelo.k90@hotmail.it>
Angelk90 <angelo.k90@hotmail.it> <20476002+Angelk90@users.noreply.github.com>
Antonio Scandurra <me@as-cii.com>
Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
Ben Kunkle <ben@zed.dev>
@@ -40,8 +38,6 @@ Dairon Medina <dairon.medina@gmail.com>
Danilo Leal <danilo@zed.dev>
Danilo Leal <danilo@zed.dev> <67129314+danilo-leal@users.noreply.github.com>
Edwin Aronsson <75266237+4teapo@users.noreply.github.com>
Elvis Pranskevichus <elvis@geldata.com>
Elvis Pranskevichus <elvis@geldata.com> <elvis@magic.io>
Evren Sen <nervenes@icloud.com>
Evren Sen <nervenes@icloud.com> <146845123+evrensen467@users.noreply.github.com>
Evren Sen <nervenes@icloud.com> <146845123+evrsen@users.noreply.github.com>
@@ -73,8 +69,6 @@ Lilith Iris <itslirissama@gmail.com> <83819417+Irilith@users.noreply.github.com>
LoganDark <contact@logandark.mozmail.com>
LoganDark <contact@logandark.mozmail.com> <git@logandark.mozmail.com>
LoganDark <contact@logandark.mozmail.com> <github@logandark.mozmail.com>
Marko Kungla <marko.kungla@gmail.com>
Marko Kungla <marko.kungla@gmail.com> <marko@mkungla.dev>
Marshall Bowers <git@maxdeviant.com>
Marshall Bowers <git@maxdeviant.com> <elliott.codes@gmail.com>
Marshall Bowers <git@maxdeviant.com> <marshall@zed.dev>
@@ -90,7 +84,6 @@ Michael Sloan <michael@zed.dev> <mgsloan@google.com>
Mikayla Maki <mikayla@zed.dev>
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@gmail.com>
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@icloud.com>
Morgan Krey <morgan@zed.dev>
Muhammad Talal Anwar <mail@talal.io>
Muhammad Talal Anwar <mail@talal.io> <talalanwar@outlook.com>
Nate Butler <iamnbutler@gmail.com>
@@ -123,18 +116,11 @@ Shish <webmaster@shishnet.org>
Shish <webmaster@shishnet.org> <shish@shishnet.org>
Smit Barmase <0xtimsb@gmail.com>
Smit Barmase <0xtimsb@gmail.com> <smit@zed.dev>
Thomas <github.thomaub@gmail.com>
Thomas <github.thomaub@gmail.com> <thomas.aubry94@gmail.com>
Thomas <github.thomaub@gmail.com> <thomas.aubry@paylead.fr>
Thomas Heartman <thomasheartman+github@gmail.com>
Thomas Heartman <thomasheartman+github@gmail.com> <thomas@getunleash.io>
Thomas Mickley-Doyle <tmickleydoyle@gmail.com>
Thomas Mickley-Doyle <tmickleydoyle@gmail.com> <thomas@zed.dev>
Thorben Kröger <dev@thorben.net>
Thorben Kröger <dev@thorben.net> <thorben.kroeger@hexagon.com>
Thorsten Ball <mrnugget@gmail.com>
Thorsten Ball <mrnugget@gmail.com> <me@thorstenball.com>
Thorsten Ball <mrnugget@gmail.com> <thorsten@zed.dev>
Thorsten Ball <thorsten@zed.dev>
Thorsten Ball <thorsten@zed.dev> <me@thorstenball.com>
Thorsten Ball <thorsten@zed.dev> <mrnugget@gmail.com>
Tristan Hume <tris.hume@gmail.com>
Tristan Hume <tris.hume@gmail.com> <tristan@anthropic.com>
Uladzislau Kaminski <i@uladkaminski.com>

View File

@@ -1,3 +0,0 @@
{
"printWidth": 120
}

126
.rules
View File

@@ -1,126 +0,0 @@
# Rust coding guidelines
* Prioritize code correctness and clarity. Speed and efficiency are secondary priorities unless otherwise specified.
* Do not write organizational or comments that summarize the code. Comments should only be written in order to explain "why" the code is written in some way in the case there is a reason that is tricky / non-obvious.
* Prefer implementing functionality in existing files unless it is a new logical component. Avoid creating many small files.
* Avoid using functions that panic like `unwrap()`, instead use mechanisms like `?` to propagate errors.
* Be careful with operations like indexing which may panic if the indexes are out of bounds.
* Never create files with `mod.rs` paths - prefer `src/some_module.rs` instead of `src/some_module/mod.rs`.
# GPUI
GPUI is a UI framework which also provides primitives for state and concurrency management.
## Context
Context types allow interaction with global state, windows, entities, and system services. They are typically passed to functions as the argument named `cx`. When a function takes callbacks they come after the `cx` parameter.
* `App` is the root context type, providing access to global state and read and update of entities.
* `Context<T>` is provided when updating an `Entity<T>`. This context dereferences into `App`, so functions which take `&App` can also take `&Context<T>`.
* `AsyncApp` and `AsyncWindowContext` are provided by `cx.spawn` and `cx.spawn_in`. These can be held across await points.
## `Window`
`Window` provides access to the state of an application window. It is passed to functions as an argument named `window` and comes before `cx` when present. It is used for managing focus, dispatching actions, directly drawing, getting user input state, etc.
## Entities
An `Entity<T>` is a handle to state of type `T`. With `thing: Entity<T>`:
* `thing.entity_id()` returns `EntityId`
* `thing.downgrade()` returns `WeakEntity<T>`
* `thing.read(cx: &App)` returns `&T`.
* `thing.read_with(cx, |thing: &T, cx: &App| ...)` returns the closure's return value.
* `thing.update(cx, |thing: &mut T, cx: &mut Context<T>| ...)` allows the closure to mutate the state, and provides a `Context<T>` for interacting with the entity. It returns the closure's return value.
* `thing.update_in(cx, |thing: &mut T, window: &mut Window, cx: &mut Context<T>| ...)` takes a `AsyncWindowContext` or `VisualTestContext`. It's the same as `update` while also providing the `Window`.
Within the closures, the inner `cx` provided to the closure must be used instead of the outer `cx` to avoid issues with multiple borrows.
Trying to update an entity while it's already being updated must be avoided as this will cause a panic.
When `read_with`, `update`, or `update_in` are used with an async context, the closure's return value is wrapped in an `anyhow::Result`.
`WeakEntity<T>` is a weak handle. It has `read_with`, `update`, and `update_in` methods that work the same, but always return an `anyhow::Result` so that they can fail if the entity no longer exists. This can be useful to avoid memory leaks - if entities have mutually recursive handles to eachother they will never be dropped.
## Concurrency
All use of entities and UI rendering occurs on a single foreground thread.
`cx.spawn(async move |cx| ...)` runs an async closure on the foreground thread. Within the closure, `cx` is an async context like `AsyncApp` or `AsyncWindowContext`.
When the outer cx is a `Context<T>`, the use of `spawn` instead looks like `cx.spawn(async move |handle, cx| ...)`, where `handle: WeakEntity<T>`.
To do work on other threads, `cx.background_spawn(async move { ... })` is used. Often this background task is awaited on by a foreground task which uses the results to update state.
Both `cx.spawn` and `cx.background_spawn` return a `Task<R>`, which is a future that can be awaited upon. If this task is dropped, then its work is cancelled. To prevent this one of the following must be done:
* Awaiting the task in some other async context.
* Detaching the task via `task.detach()` or `task.detach_and_log_err(cx)`, allowing it to run indefinitely.
* Storing the task in a field, if the work should be halted when the struct is dropped.
A task which doesn't do anything but provide a value can be created with `Task::ready(value)`.
## Elements
The `Render` trait is used to render some state into an element tree that is laid out using flexbox layout. An `Entity<T>` where `T` implements `Render` is sometimes called a "view".
Example:
```
struct TextWithBorder(SharedString);
impl Render for TextWithBorder {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().border_1().child(self.0.clone())
}
}
```
Since `impl IntoElement for SharedString` exists, it can be used as an argument to `child`. `SharedString` is used to avoid copying strings, and is either an `&'static str` or `Arc<str>`.
UI components that are constructed just to be turned into elements can instead implement the `RenderOnce` trait, which is similar to `Render`, but its `render` method takes ownership of `self`. Types that implement this trait can use `#[derive(IntoElement)]` to use them directly as children.
The style methods on elements are similar to those used by Tailwind CSS.
If some attributes or children of an element tree are conditional, `.when(condition, |this| ...)` can be used to run the closure only when `condition` is true. Similarly, `.when_some(option, |this, value| ...)` runs the closure when the `Option` has a value.
## Input events
Input event handlers can be registered on an element via methods like `.on_click(|event, window, cx: &mut App| ...)`.
Often event handlers will want to update the entity that's in the current `Context<T>`. The `cx.listener` method provides this - its use looks like `.on_click(cx.listener(|this: &mut T, event, window, cx: &mut Context<T>| ...)`.
## Actions
Actions are dispatched via user keyboard interaction or in code via `window.dispatch_action(SomeAction.boxed_clone(), cx)` or `focus_handle.dispatch_action(&SomeAction, window, cx)`.
Actions which have no data inside are created and registered with the `actions!(some_namespace, [SomeAction, AnotherAction])` macro call.
Actions that do have data must implement `Clone, Default, PartialEq, Deserialize, JsonSchema` and can be registered with an `impl_actions!(some_namespace, [SomeActionWithData])` macro call.
Action handlers can be registered on an element via the event handler `.on_action(|action, window, cx| ...)`. Like other event handlers, this is often used with `cx.listener`.
## Notify
When a view's state has changed in a way that may affect its rendering, it should call `cx.notify()`. This will cause the view to be rerendered. It will also cause any observe callbacks registered for the entity with `cx.observe` to be called.
## Entity events
While updating an entity (`cx: Context<T>`), it can emit an event using `cx.emit(event)`. Entities register which events they can emit by declaring `impl EventEmittor<EventType> for EntityType {}`.
Other entities can then register a callback to handle these events by doing `cx.subscribe(other_entity, |this, other_entity, event, cx| ...)`. This will return a `Subscription` which deregisters the callback when dropped. Typically `cx.subscribe` happens when creating a new entity and the subscriptions are stored in a `_subscriptions: Vec<Subscription>` field.
## Recent API changes
GPUI has had some changes to its APIs. Always write code using the new APIs:
* `spawn` methods now take async closures (`AsyncFn`), and so should be called like `cx.spawn(async move |cx| ...)`.
* Use `Entity<T>`. This replaces `Model<T>` and `View<T>` which no longer exist and should NEVER be used.
* Use `App` references. This replaces `AppContext` which no longer exists and should NEVER be used.
* Use `Context<T>` references. This replaces `ModelContext<T>` which no longer exists and should NEVER be used.
* `Window` is now passed around explicitly. The new interface adds a `Window` reference parameter to some methods, and adds some new "*_in" methods for plumbing `Window`. The old types `WindowContext` and `ViewContext<T>` should NEVER be used.
## General guidelines
- Use `./script/clippy` instead of `cargo clippy`

View File

@@ -1 +0,0 @@
.rules

View File

@@ -2,11 +2,18 @@
{
"label": "Debug Zed (CodeLLDB)",
"adapter": "CodeLLDB",
"build": { "label": "Build Zed", "command": "cargo", "args": ["build"] }
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT"
},
{
"label": "Debug Zed (GDB)",
"adapter": "GDB",
"build": { "label": "Build Zed", "command": "cargo", "args": ["build"] }
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT",
"initialize_args": {
"stopAtBeginningOfMainSubprogram": true
}
}
]

View File

@@ -45,18 +45,5 @@
"hard_tabs": false,
"formatter": "auto",
"remove_trailing_whitespace_on_save": true,
"ensure_final_newline_on_save": true,
"file_scan_exclusions": [
"crates/eval/worktrees/",
"crates/eval/repos/",
"**/.git",
"**/.svn",
"**/.hg",
"**/.jj",
"**/CVS",
"**/.DS_Store",
"**/Thumbs.db",
"**/.classpath",
"**/.settings"
]
"ensure_final_newline_on_save": true
}

View File

@@ -1 +0,0 @@
.rules

3089
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,11 +3,12 @@ resolver = "2"
members = [
"crates/activity_indicator",
"crates/agent",
"crates/agent_settings",
"crates/anthropic",
"crates/askpass",
"crates/assets",
"crates/assistant",
"crates/assistant_context_editor",
"crates/assistant_settings",
"crates/assistant_slash_command",
"crates/assistant_slash_commands",
"crates/assistant_tool",
@@ -31,20 +32,22 @@ members = [
"crates/command_palette",
"crates/command_palette_hooks",
"crates/component",
"crates/component_preview",
"crates/context_server",
"crates/context_server_settings",
"crates/copilot",
"crates/credentials_provider",
"crates/dap",
"crates/dap_adapters",
"crates/db",
"crates/debug_adapter_extension",
"crates/debugger_tools",
"crates/debugger_ui",
"crates/db",
"crates/deepseek",
"crates/diagnostics",
"crates/docs_preprocessor",
"crates/editor",
"crates/eval",
"crates/evals",
"crates/extension",
"crates/extension_api",
"crates/extension_cli",
@@ -73,14 +76,12 @@ members = [
"crates/indexed_docs",
"crates/inline_completion",
"crates/inline_completion_button",
"crates/inspector_ui",
"crates/install_cli",
"crates/jj",
"crates/jj_ui",
"crates/journal",
"crates/language",
"crates/language_extension",
"crates/language_model",
"crates/language_model_selector",
"crates/language_models",
"crates/language_selector",
"crates/language_tools",
@@ -109,6 +110,7 @@ members = [
"crates/project",
"crates/project_panel",
"crates/project_symbols",
"crates/prompt_library",
"crates/prompt_store",
"crates/proto",
"crates/recent_projects",
@@ -122,7 +124,6 @@ members = [
"crates/rich_text",
"crates/rope",
"crates/rpc",
"crates/rules_library",
"crates/schema_generator",
"crates/search",
"crates/semantic_index",
@@ -211,12 +212,13 @@ edition = "2024"
activity_indicator = { path = "crates/activity_indicator" }
agent = { path = "crates/agent" }
agent_settings = { path = "crates/agent_settings" }
ai = { path = "crates/ai" }
anthropic = { path = "crates/anthropic" }
askpass = { path = "crates/askpass" }
assets = { path = "crates/assets" }
assistant = { path = "crates/assistant" }
assistant_context_editor = { path = "crates/assistant_context_editor" }
assistant_settings = { path = "crates/assistant_settings" }
assistant_slash_command = { path = "crates/assistant_slash_command" }
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
assistant_tool = { path = "crates/assistant_tool" }
@@ -228,7 +230,6 @@ auto_update_ui = { path = "crates/auto_update_ui" }
aws_http_client = { path = "crates/aws_http_client" }
bedrock = { path = "crates/bedrock" }
breadcrumbs = { path = "crates/breadcrumbs" }
buffer_diff = { path = "crates/buffer_diff" }
call = { path = "crates/call" }
channel = { path = "crates/channel" }
cli = { path = "crates/cli" }
@@ -240,17 +241,19 @@ collections = { path = "crates/collections" }
command_palette = { path = "crates/command_palette" }
command_palette_hooks = { path = "crates/command_palette_hooks" }
component = { path = "crates/component" }
component_preview = { path = "crates/component_preview" }
context_server = { path = "crates/context_server" }
context_server_settings = { path = "crates/context_server_settings" }
copilot = { path = "crates/copilot" }
credentials_provider = { path = "crates/credentials_provider" }
dap = { path = "crates/dap" }
dap_adapters = { path = "crates/dap_adapters" }
db = { path = "crates/db" }
debug_adapter_extension = { path = "crates/debug_adapter_extension" }
debugger_tools = { path = "crates/debugger_tools" }
debugger_ui = { path = "crates/debugger_ui" }
debugger_tools = { path = "crates/debugger_tools" }
deepseek = { path = "crates/deepseek" }
diagnostics = { path = "crates/diagnostics" }
buffer_diff = { path = "crates/buffer_diff" }
editor = { path = "crates/editor" }
extension = { path = "crates/extension" }
extension_host = { path = "crates/extension_host" }
@@ -280,14 +283,12 @@ image_viewer = { path = "crates/image_viewer" }
indexed_docs = { path = "crates/indexed_docs" }
inline_completion = { path = "crates/inline_completion" }
inline_completion_button = { path = "crates/inline_completion_button" }
inspector_ui = { path = "crates/inspector_ui" }
install_cli = { path = "crates/install_cli" }
jj = { path = "crates/jj" }
jj_ui = { path = "crates/jj_ui" }
journal = { path = "crates/journal" }
language = { path = "crates/language" }
language_extension = { path = "crates/language_extension" }
language_model = { path = "crates/language_model" }
language_model_selector = { path = "crates/language_model_selector" }
language_models = { path = "crates/language_models" }
language_selector = { path = "crates/language_selector" }
language_tools = { path = "crates/language_tools" }
@@ -309,8 +310,8 @@ ollama = { path = "crates/ollama" }
open_ai = { path = "crates/open_ai" }
outline = { path = "crates/outline" }
outline_panel = { path = "crates/outline_panel" }
panel = { path = "crates/panel" }
paths = { path = "crates/paths" }
panel = { path = "crates/panel" }
picker = { path = "crates/picker" }
plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
@@ -318,6 +319,7 @@ prettier = { path = "crates/prettier" }
project = { path = "crates/project" }
project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" }
prompt_library = { path = "crates/prompt_library" }
prompt_store = { path = "crates/prompt_store" }
proto = { path = "crates/proto" }
recent_projects = { path = "crates/recent_projects" }
@@ -330,7 +332,6 @@ reqwest_client = { path = "crates/reqwest_client" }
rich_text = { path = "crates/rich_text" }
rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" }
rules_library = { path = "crates/rules_library" }
search = { path = "crates/search" }
semantic_index = { path = "crates/semantic_index" }
semantic_version = { path = "crates/semantic_version" }
@@ -414,9 +415,10 @@ aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
base64 = "0.22"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blade-util = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
blade-util = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
naga = { version = "23.1.0", features = ["wgsl-in"] }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.19"
@@ -426,18 +428,15 @@ circular-buffer = "1.0"
clap = { version = "4.4", features = ["derive"] }
cocoa = "0.26"
cocoa-foundation = "0.2.0"
core-video = { version = "0.4.3", features = ["metal"] }
convert_case = "0.8.0"
core-foundation = "0.10.0"
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" }
dashmap = "6.0"
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "be69a016ba710191b9fdded28c8b042af4b617f7" }
derive_more = "0.99.17"
dirs = "4.0"
documented = "0.9.1"
dotenv = "0.15.0"
ec4rs = "1.1"
emojis = "0.6.1"
env_logger = "0.11"
@@ -449,14 +448,13 @@ futures-batch = "0.6.1"
futures-lite = "1.13"
git2 = { version = "0.20.1", default-features = false }
globset = "0.4"
hashbrown = "0.15.3"
handlebars = "4.3"
heck = "0.5"
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
hex = "0.4.3"
html5ever = "0.27.0"
http = "1.1"
hyper = "0.14"
http = "1.1"
ignore = "0.4.22"
image = "0.25.1"
imara-diff = "0.1.8"
@@ -464,41 +462,34 @@ indexmap = { version = "2.7.0", features = ["serde"] }
indoc = "2"
inventory = "0.3.19"
itertools = "0.14.0"
jj-lib = { git = "https://github.com/jj-vcs/jj", rev = "e18eb8e05efaa153fad5ef46576af145bba1807f" }
json_dotpath = "1.1"
jsonschema = "0.30.0"
jsonwebtoken = "9.3"
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed" ,rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
linkme = "0.3.31"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "c9c189f1c5dd53c624a419ce35bc77ad6a908d18" }
markup5ever_rcdom = "0.3.0"
metal = "0.29"
mlua = { version = "0.10", features = ["lua54", "vendored", "async", "send"] }
moka = { version = "0.12.10", features = ["sync"] }
naga = { version = "25.0", features = ["wgsl-in"] }
nanoid = "0.4"
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
nix = "0.29"
num-format = "0.4.4"
objc = "0.2"
open = "5.0.0"
num-format = "0.4.4"
ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
partial-json-fixer = "0.5.3"
pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
portable-pty = "0.9.0"
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
proc-macro2 = "1.0.93"
@@ -510,10 +501,9 @@ pulldown-cmark = { version = "0.12.0", default-features = false }
quote = "1.0.9"
rand = "0.8.5"
rayon = "1.8"
ref-cast = "1.0.24"
regex = "1.5"
repair_json = "0.1.0"
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c770a32f1998d6e999cef3e59e0013e6c4415", default-features = false, features = [
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29", default-features = false, features = [
"charset",
"http2",
"macos-system-configuration",
@@ -525,8 +515,8 @@ rsa = "0.9.6"
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
"async-dispatcher-runtime",
] }
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustc-demangle = "0.1.23"
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustc-hash = "2.1.0"
rustls = { version = "0.23.26" }
rustls-platform-verifier = "0.5.0"
@@ -553,13 +543,13 @@ streaming-iterator = "0.1"
strsim = "0.11"
strum = { version = "0.27.0", features = ["derive"] }
subtle = "2.5.0"
syn = { version = "2.0.101", features = ["full", "extra-traits"] }
syn = { version = "1.0.72", features = ["full", "extra-traits"] }
sys-locale = "0.3.1"
sysinfo = "0.31.0"
take-until = "0.2.0"
tempfile = "3.20.0"
tempfile = "3.9.0"
thiserror = "2.0.12"
tiktoken-rs = "0.7.0"
tiktoken-rs = "0.6.0"
time = { version = "0.3", features = [
"macros",
"parsing",
@@ -568,16 +558,15 @@ time = { version = "0.3", features = [
"formatting",
] }
tiny_http = "0.8"
toml = "0.8"
tokio = { version = "1" }
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
toml = "0.8"
tower-http = "0.4.4"
tree-sitter = { version = "0.25.5", features = ["wasm"] }
tree-sitter = { version = "0.25.3", features = ["wasm"] }
tree-sitter-bash = "0.23"
tree-sitter-c = "0.23"
tree-sitter-cpp = "0.23"
tree-sitter-css = "0.23"
tree-sitter-diff = "0.1.0"
tree-sitter-elixir = "0.3"
tree-sitter-embedded-template = "0.23.0"
tree-sitter-gitcommit = { git = "https://github.com/zed-industries/tree-sitter-git-commit", rev = "88309716a69dd13ab83443721ba6e0b491d37ee9" }
@@ -585,6 +574,7 @@ tree-sitter-go = "0.23"
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "6efb59652d30e0e9cd5f3b3a669afd6f1a926d3c", package = "tree-sitter-gomod" }
tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
tree-sitter-heex = { git = "https://github.com/zed-industries/tree-sitter-heex", rev = "1dd45142fbb05562e35b2040c6129c9bca346592" }
tree-sitter-diff = "0.1.0"
tree-sitter-html = "0.23"
tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.24"
@@ -596,29 +586,29 @@ tree-sitter-rust = "0.24"
tree-sitter-typescript = "0.23"
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
unicase = "2.6"
unicode-script = "0.5.7"
unicode-segmentation = "1.10"
unindent = "0.2.0"
unicode-segmentation = "1.10"
unicode-script = "0.5.7"
url = "2.2"
urlencoding = "2.1.2"
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
walkdir = "2.5"
wasm-encoder = "0.221"
walkdir = "2.3"
wasmparser = "0.221"
wasm-encoder = "0.221"
wasmtime = { version = "29", default-features = false, features = [
"async",
"demangle",
"runtime",
"cranelift",
"component-model",
"incremental-cache",
"parallel-compilation",
] }
wasmtime-wasi = "29"
which = "6.0.0"
wit-component = "0.221"
workspace-hack = "0.1.0"
zed_llm_client = "0.8.4"
zed_llm_client = "0.6.1"
zstd = "0.11"
metal = "0.29"
[workspace.dependencies.async-stripe]
git = "https://github.com/zed-industries/async-stripe"
@@ -707,6 +697,7 @@ breadcrumbs = { codegen-units = 1 }
collections = { codegen-units = 1 }
command_palette = { codegen-units = 1 }
command_palette_hooks = { codegen-units = 1 }
evals = { codegen-units = 1 }
extension_cli = { codegen-units = 1 }
feature_flags = { codegen-units = 1 }
file_icons = { codegen-units = 1 }
@@ -797,9 +788,6 @@ let_underscore_future = "allow"
# running afoul of the borrow checker.
too_many_arguments = "allow"
# We often have large enum variants yet we rarely actually bother with splitting them up.
large_enum_variant = "allow"
[workspace.metadata.cargo-machete]
ignored = [
"bindgen",
@@ -807,6 +795,6 @@ ignored = [
"prost_build",
"serde",
"component",
"documented",
"linkme",
"workspace-hack",
]

View File

@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.2
FROM rust:1.87-bookworm as builder
FROM rust:1.86-bookworm as builder
WORKDIR app
COPY . .

View File

@@ -8,6 +8,10 @@ Welcome to Zed, a high-performance, multiplayer code editor from the creators of
### Installation
<a href="https://repology.org/project/zed-editor/versions">
<img src="https://repology.org/badge/vertical-allrepos/zed-editor.svg?minversion=0.143.5" alt="Packaging status" align="right">
</a>
On macOS and Linux you can [download Zed directly](https://zed.dev/download) or [install Zed via your local package manager](https://zed.dev/docs/linux#installing-via-a-package-manager).
Other platforms are not yet available:

View File

@@ -1,3 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.3 1.75L3 7.35H5.8L4.7 12.25L11 6.65H8.2L9.3 1.75Z" stroke="black" stroke-width="1.25" stroke-linejoin="round"/>
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.76019 3.50003H6.50231C6.71012 3.50003 6.89761 3.62971 6.95698 3.82346C7.04292 4.01876 6.98823 4.23906 6.83199 4.37656L2.83214 7.87643C2.65558 8.02954 2.39731 8.04204 2.20857 7.90455C2.01967 7.76705 1.95092 7.51706 2.04295 7.30301L3.24462 4.49999H1.48844C1.29423 4.49999 1.10767 4.37031 1.0344 4.17657C0.961132 3.98126 1.01643 3.76096 1.17323 3.62346L5.17261 0.123753C5.34917 -0.0299914 5.60697 -0.0417097 5.79603 0.0954726C5.98508 0.232749 6.05383 0.482177 5.96165 0.69695L4.76013 3.49981L4.76019 3.50003Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 227 B

After

Width:  |  Height:  |  Size: 633 B

View File

@@ -1,3 +0,0 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.76019 3.50003H6.50231C6.71012 3.50003 6.89761 3.62971 6.95698 3.82346C7.04292 4.01876 6.98823 4.23906 6.83199 4.37656L2.83214 7.87643C2.65558 8.02954 2.39731 8.04204 2.20857 7.90455C2.01967 7.76705 1.95092 7.51706 2.04295 7.30301L3.24462 4.49999H1.48844C1.29423 4.49999 1.10767 4.37031 1.0344 4.17657C0.961132 3.98126 1.01643 3.76096 1.17323 3.62346L5.17261 0.123753C5.34917 -0.0299914 5.60697 -0.0417097 5.79603 0.0954726C5.98508 0.232749 6.05383 0.482177 5.96165 0.69695L4.76013 3.49981L4.76019 3.50003Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 633 B

View File

@@ -1,7 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 12C9.76142 12 12 9.76142 12 7C12 4.23858 9.76142 2 7 2C4.23858 2 2 4.23858 2 7C2 9.76142 4.23858 12 7 12Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.5 7H9" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.5 7H2" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 4.5V2" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 11.5V9" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 720 B

View File

@@ -1,4 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 13H10.4C9.76346 13 9.15302 12.7893 8.70296 12.4142C8.25284 12.0391 8 11.5304 8 11V5C8 4.46957 8.25284 3.96086 8.70296 3.58579C9.15302 3.21071 9.76346 3 10.4 3H11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 13H5.6C6.23654 13 6.84698 12.7893 7.29704 12.4142C7.74716 12.0391 8 11.5304 8 11V5C8 4.46957 7.74716 3.96086 7.29704 3.58579C6.84698 3.21071 6.23654 3 5.6 3H5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 20H16C14.9391 20 13.9217 19.6629 13.1716 19.0627C12.4214 18.4626 12 17.6487 12 16.8V7.2C12 6.35131 12.4214 5.53737 13.1716 4.93726C13.9217 4.33714 14.9391 4 16 4H17" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 20H8C9.06087 20 10.0783 19.5786 10.8284 18.8284C11.5786 18.0783 12 17.0609 12 16V15" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 4H8C9.06087 4 10.0783 4.42143 10.8284 5.17157C11.5786 5.92172 12 6.93913 12 8V9" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 617 B

After

Width:  |  Height:  |  Size: 715 B

View File

Before

Width:  |  Height:  |  Size: 474 B

After

Width:  |  Height:  |  Size: 474 B

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
<path fill-rule="evenodd" d="M4.5 2A1.5 1.5 0 0 0 3 3.5v13A1.5 1.5 0 0 0 4.5 18h11a1.5 1.5 0 0 0 1.5-1.5V7.621a1.5 1.5 0 0 0-.44-1.06l-4.12-4.122A1.5 1.5 0 0 0 11.378 2H4.5Zm2.25 8.5a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5h-6.5Zm0 3a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5h-6.5Z" clip-rule="evenodd" />
</svg>

Before

Width:  |  Height:  |  Size: 412 B

View File

@@ -1 +0,0 @@
<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-hammer-icon lucide-hammer"><path d="m15 12-8.373 8.373a1 1 0 1 1-3-3L12 9"/><path d="m18 15 4-4"/><path d="m21.5 11.5-1.914-1.914A2 2 0 0 1 19 8.172V7l-2.26-2.26a6 6 0 0 0-4.202-1.756L9 2.96l.92.82A6.18 6.18 0 0 1 12 8.4V10l2 2h1.172a2 2 0 0 1 1.414.586L18.5 14.5"/></svg>

Before

Width:  |  Height:  |  Size: 475 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-collapse-icon lucide-list-collapse"><path d="m3 10 2.5-2.5L3 5"/><path d="m3 19 2.5-2.5L3 14"/><path d="M10 6h11"/><path d="M10 12h11"/><path d="M10 18h11"/></svg>

Before

Width:  |  Height:  |  Size: 371 B

View File

@@ -1 +0,0 @@
<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-loader-circle-icon lucide-loader-circle"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>

Before

Width:  |  Height:  |  Size: 289 B

View File

@@ -1,5 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 12H16" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 6H20" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 18H12" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 402 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 3L13 8L4 13V3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 214 B

View File

@@ -1,8 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 12C2.35977 11.85 1 10.575 1 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.00875 15.2C1.00875 13.625 0.683456 12.275 4.00001 12.2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 9C7 10.575 5.62857 11.85 4 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 12.2C6.98117 12.2 7 13.625 7 15.2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="2.5" y="9" width="3" height="6" rx="1.5" fill="black"/>
<path d="M9 10L13 8L4 3V7.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 813 B

View File

@@ -1 +0,0 @@
<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-scissors-icon lucide-scissors"><circle cx="6" cy="6" r="3"/><path d="M8.12 8.12 12 12"/><path d="M20 4 8.12 15.88"/><circle cx="6" cy="18" r="3"/><path d="M14.8 14.8 20 20"/></svg>

Before

Width:  |  Height:  |  Size: 383 B

View File

@@ -1,8 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 5H4" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M8 5L14 5" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M12 11L14 11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M2 11H8" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="6" cy="5" r="2" fill="black" fill-opacity="0.1" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="10" cy="11" r="2" fill="black" fill-opacity="0.1" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.36667 3.79167C5.53364 3.79167 4.85833 4.46697 4.85833 5.3C4.85833 6.13303 5.53364 6.80833 6.36667 6.80833C7.1997 6.80833 7.875 6.13303 7.875 5.3C7.875 4.46697 7.1997 3.79167 6.36667 3.79167ZM2.1 5.925H3.67944C3.9626 7.14732 5.05824 8.05833 6.36667 8.05833C7.67509 8.05833 8.77073 7.14732 9.05389 5.925H14.9C15.2452 5.925 15.525 5.64518 15.525 5.3C15.525 4.95482 15.2452 4.675 14.9 4.675H9.05389C8.77073 3.45268 7.67509 2.54167 6.36667 2.54167C5.05824 2.54167 3.9626 3.45268 3.67944 4.675H2.1C1.75482 4.675 1.475 4.95482 1.475 5.3C1.475 5.64518 1.75482 5.925 2.1 5.925ZM13.3206 12.325C13.0374 13.5473 11.9418 14.4583 10.6333 14.4583C9.32491 14.4583 8.22927 13.5473 7.94611 12.325H2.1C1.75482 12.325 1.475 12.0452 1.475 11.7C1.475 11.3548 1.75482 11.075 2.1 11.075H7.94611C8.22927 9.85268 9.32491 8.94167 10.6333 8.94167C11.9418 8.94167 13.0374 9.85268 13.3206 11.075H14.9C15.2452 11.075 15.525 11.3548 15.525 11.7C15.525 12.0452 15.2452 12.325 14.9 12.325H13.3206ZM9.125 11.7C9.125 10.867 9.8003 10.1917 10.6333 10.1917C11.4664 10.1917 12.1417 10.867 12.1417 11.7C12.1417 12.533 11.4664 13.2083 10.6333 13.2083C9.8003 13.2083 9.125 12.533 9.125 11.7Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 657 B

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,3 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
<path d="M18.905 12.75a1.25 1.25 0 1 1-2.5 0v-7.5a1.25 1.25 0 0 1 2.5 0v7.5ZM8.905 17v1.3c0 .268-.14.526-.395.607A2 2 0 0 1 5.905 17c0-.995.182-1.948.514-2.826.204-.54-.166-1.174-.744-1.174h-2.52c-1.243 0-2.261-1.01-2.146-2.247.193-2.08.651-4.082 1.341-5.974C2.752 3.678 3.833 3 5.005 3h3.192a3 3 0 0 1 1.341.317l2.734 1.366A3 3 0 0 0 13.613 5h1.292v7h-.963c-.685 0-1.258.482-1.612 1.068a4.01 4.01 0 0 1-2.166 1.73c-.432.143-.853.386-1.011.814-.16.432-.248.9-.248 1.388Z" />
</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-thumbs-down"><path d="M17 14V2"/><path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z"/></svg>

Before

Width:  |  Height:  |  Size: 580 B

After

Width:  |  Height:  |  Size: 405 B

View File

@@ -1,3 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
<path d="M1 8.25a1.25 1.25 0 1 1 2.5 0v7.5a1.25 1.25 0 1 1-2.5 0v-7.5ZM11 3V1.7c0-.268.14-.526.395-.607A2 2 0 0 1 14 3c0 .995-.182 1.948-.514 2.826-.204.54.166 1.174.744 1.174h2.52c1.243 0 2.261 1.01 2.146 2.247a23.864 23.864 0 0 1-1.341 5.974C17.153 16.323 16.072 17 14.9 17h-3.192a3 3 0 0 1-1.341-.317l-2.734-1.366A3 3 0 0 0 6.292 15H5V8h.963c.685 0 1.258-.483 1.612-1.068a4.011 4.011 0 0 1 2.166-1.73c.432-.143.853-.386 1.011-.814.16-.432.248-.9.248-1.388Z" />
</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-thumbs-up"><path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"/></svg>

Before

Width:  |  Height:  |  Size: 569 B

After

Width:  |  Height:  |  Size: 404 B

View File

@@ -1 +0,0 @@
<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-user-round-check-icon lucide-user-round-check"><path d="M2 21a8 8 0 0 1 13.292-6"/><circle cx="10" cy="8" r="5"/><path d="m16 19 2 2 4-4"/></svg>

Before

Width:  |  Height:  |  Size: 348 B

View File

@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 2L6.72534 5.87534C6.6601 6.07367 6.5492 6.25392 6.40155 6.40155C6.25392 6.5492 6.07367 6.6601 5.87534 6.72534L2 8L5.87534 9.27466C6.07367 9.3399 6.25392 9.4508 6.40155 9.59845C6.5492 9.74608 6.6601 9.92633 6.72534 10.1247L8 14L9.27466 10.1247C9.3399 9.92633 9.4508 9.74608 9.59845 9.59845C9.74608 9.4508 9.92633 9.3399 10.1247 9.27466L14 8L10.1247 6.72534C9.92633 6.6601 9.74608 6.5492 9.59845 6.40155C9.4508 6.25392 9.3399 6.07367 9.27466 5.87534L8 2Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.33334 2V4.66666M2 3.33334H4.66666" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.6665 11.3333V14M11.3333 12.6666H13.9999" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 1.75L5.88467 5.14092C5.82759 5.31446 5.73055 5.47218 5.60136 5.60136C5.47218 5.73055 5.31446 5.82759 5.14092 5.88467L1.75 7L5.14092 8.11533C5.31446 8.17241 5.47218 8.26945 5.60136 8.39864C5.73055 8.52782 5.82759 8.68554 5.88467 8.85908L7 12.25L8.11533 8.85908C8.17241 8.68554 8.26945 8.52782 8.39864 8.39864C8.52782 8.26945 8.68554 8.17241 8.85908 8.11533L12.25 7L8.85908 5.88467C8.68554 5.82759 8.52782 5.73055 8.39864 5.60136C8.26945 5.47218 8.17241 5.31446 8.11533 5.14092L7 1.75Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.91667 1.75V4.08333M1.75 2.91667H4.08333" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.0833 9.91667V12.25M9.91667 11.0833H12.25" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 998 B

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.99207 8.14741C5.37246 8.14741 5.73726 7.9963 6.00623 7.72733C6.27521 7.45836 6.42631 7.09355 6.42631 6.71317C6.42631 5.92147 6.13946 5.56578 5.85262 4.99208C5.23761 3.76265 5.72411 2.66631 7.00001 1.5499C7.28686 2.98414 8.1474 4.36101 9.2948 5.27893C10.4422 6.19684 11.0159 7.28687 11.0159 8.43426C11.0159 8.96163 10.912 9.48384 10.7102 9.97107C10.5084 10.4583 10.2126 10.901 9.83967 11.2739C9.46676 11.6468 9.02405 11.9426 8.53682 12.1444C8.04959 12.3463 7.52738 12.4501 7.00001 12.4501C6.47264 12.4501 5.95043 12.3463 5.4632 12.1444C4.97597 11.9426 4.53326 11.6468 4.16035 11.2739C3.78745 10.901 3.49164 10.4583 3.28982 9.97107C3.088 9.48384 2.98413 8.96163 2.98413 8.43426C2.98413 7.77279 3.23254 7.1182 3.55783 6.71317C3.55783 7.09355 3.70894 7.45836 3.97791 7.72733C4.24688 7.9963 4.61169 8.14741 4.99207 8.14741Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1018 B

View File

@@ -1,13 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2595_5640)">
<path d="M4.99207 8.14741C5.37246 8.14741 5.73726 7.9963 6.00623 7.72733C6.27521 7.45836 6.42631 7.09355 6.42631 6.71317C6.42631 5.92147 6.13946 5.56578 5.85262 4.99208C5.23761 3.76265 5.72411 2.66631 7.00001 1.5499C7.28686 2.98414 8.1474 4.36101 9.2948 5.27893C10.4422 6.19684 11.0159 7.28687 11.0159 8.43426C11.0159 8.96163 10.912 9.48384 10.7102 9.97107C10.5084 10.4583 10.2126 10.901 9.83967 11.2739C9.46676 11.6468 9.02405 11.9426 8.53682 12.1444C8.04959 12.3463 7.52738 12.4501 7.00001 12.4501C6.47264 12.4501 5.95043 12.3463 5.4632 12.1444C4.97597 11.9426 4.53326 11.6468 4.16035 11.2739C3.78745 10.901 3.49164 10.4583 3.28982 9.97107C3.088 9.48384 2.98413 8.96163 2.98413 8.43426C2.98413 7.77279 3.23254 7.1182 3.55783 6.71317C3.55783 7.09355 3.70894 7.45836 3.97791 7.72733C4.24688 7.9963 4.61169 8.14741 4.99207 8.14741Z" fill="black" fill-opacity="0.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 4C2.55228 4 3 3.55228 3 3C3 2.44772 2.55228 2 2 2C1.44772 2 1 2.44772 1 3C1 3.55228 1.44772 4 2 4Z" fill="black"/>
<path d="M10 2C10.5523 2 11 1.55228 11 1C11 0.44772 10.5523 0 10 0C9.44772 0 9 0.44772 9 1C9 1.55228 9.44772 2 10 2Z" fill="black"/>
<path d="M13 5C13.5522 5 14 4.55228 14 4C14 3.44772 13.5522 3 13 3C12.4478 3 12 3.44772 12 4C12 4.55228 12.4478 5 13 5Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_2595_5640">
<rect width="14" height="14" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,334 +0,0 @@
<svg width="400" height="92" viewBox="0 0 400 92" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2501_1466)">
<path d="M73.6743 -4.41071L75.5416 -1.32632L73.4126 1.58358" stroke="black" stroke-width="1.5"/>
<path d="M71.6108 -2.99939L72.5445 -1.4572L71.48 -0.00224495" stroke="black" stroke-width="1.5"/>
<path d="M69.0085 -0.710689L68.9169 1.38731C66.8498 1.29706 65.9887 1.25947 63.9216 1.16922L63.9478 0.569787L69.1524 -4.00755L69.1786 -4.60698L64.1833 -4.82507L64.0917 -2.72707" stroke="black" stroke-width="1.5"/>
<path d="M91.1332 -5.23706C90.8807 -5.51263 90.529 -5.67673 90.1555 -5.69303C89.7821 -5.70934 89.4174 -5.57672 89.1418 -5.3242C88.8661 -5.07169 88.7021 -4.72001 88.6858 -4.34656C88.6694 -3.97312 88.8021 -3.60849 89.0546 -3.33281L95.4319 3.62501C95.5424 3.74604 95.681 3.83803 95.8356 3.89288L97.9807 4.64767C98.0234 4.66247 98.0692 4.66544 98.1133 4.65629C98.1576 4.64713 98.1984 4.62618 98.2317 4.59567C98.2649 4.56516 98.2894 4.52621 98.3023 4.48296C98.3152 4.43971 98.3161 4.39377 98.305 4.35003L97.7405 2.14679C97.6998 1.98827 97.6207 1.84218 97.5104 1.72135L91.1332 -5.23706Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M88.9944 0.874369L87.9954 0.83075" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M91.8171 5.0014L91.8608 4.00236" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M89.9062 2.91609L88.8635 3.87152" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<g clip-path="url(#clip1_2501_1466)">
<mask id="mask0_2501_1466" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="109" y="-8" width="17" height="17">
<path d="M125.843 -7.13772L109.858 -7.83563L109.16 8.14914L125.145 8.84705L125.843 -7.13772Z" fill="white"/>
</mask>
<g mask="url(#mask0_2501_1466)">
<path d="M120.459 1.53575L120.368 3.63375C117.887 3.52545 116.854 3.48034 114.374 3.37204L114.4 2.77261L120.603 -1.76111L120.63 -2.36054L114.635 -2.62225L114.544 -0.524252" stroke="black" stroke-width="1.5"/>
<path d="M110.899 2.71985L110.724 6.71604L114.221 6.86871" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M123.886 3.28688L123.712 7.28308L120.215 7.13041" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
</g>
</g>
<path d="M141.74 -4.44171L140.298 -0.625665C140.224 -0.430368 140.105 -0.255126 139.951 -0.114073C139.798 0.026993 139.613 0.129928 139.412 0.186449L135.484 1.29086L139.3 2.73335C139.496 2.80717 139.671 2.92583 139.812 3.07978C139.953 3.23371 140.056 3.41863 140.113 3.61962L141.217 7.54686L142.659 3.73082C142.733 3.53552 142.852 3.36028 143.006 3.21922C143.16 3.07816 143.345 2.97522 143.546 2.9187L147.473 1.81429L143.657 0.371802C143.462 0.297979 143.286 0.179319 143.145 0.0253739C143.004 -0.12856 142.901 -0.313477 142.845 -0.514465L141.74 -4.44171Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M145.995 5.08642L145.879 7.75054M144.605 6.36028L147.269 6.4766" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M169.583 -0.223267L171.45 2.86112L169.321 5.77102" stroke="black" stroke-width="1.5"/>
<path d="M167.52 1.18805L168.453 2.73024L167.389 4.18519" stroke="black" stroke-width="1.5"/>
<path d="M164.917 3.47675L164.826 5.57475C162.759 5.4845 161.897 5.4469 159.83 5.35666L159.856 4.75723L165.061 0.179892L165.087 -0.419537L160.092 -0.637634L160 1.46037" stroke="black" stroke-width="1.5"/>
<g clip-path="url(#clip2_2501_1466)">
<mask id="mask1_2501_1466" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="181" y="-5" width="17" height="17">
<path d="M197.774 -3.99716L181.79 -4.69507L181.092 11.2897L197.076 11.9876L197.774 -3.99716Z" fill="white"/>
</mask>
<g mask="url(#mask1_2501_1466)">
<path d="M192.391 4.67631L192.299 6.77432C189.819 6.66602 188.785 6.6209 186.305 6.5126L186.331 5.91317L192.535 1.37946L192.561 0.780027L186.567 0.518311L186.475 2.61631" stroke="black" stroke-width="1.5"/>
<path d="M183.048 0.86515L183.223 -3.13104L186.719 -2.97837" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M196.036 1.43219L196.21 -2.56401L192.714 -2.71667" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M182.83 5.86041L182.656 9.85661L186.152 10.0093" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M195.818 6.42745L195.643 10.4236L192.147 10.271" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
</g>
</g>
<path d="M211.019 -0.00268162C210.766 -0.278258 210.415 -0.442354 210.041 -0.458659C209.668 -0.474965 209.303 -0.342345 209.028 -0.0898278C208.752 0.162689 208.588 0.514369 208.571 0.887811C208.555 1.26125 208.688 1.62589 208.94 1.90157L215.318 8.85938C215.428 8.98042 215.567 9.0724 215.721 9.12725L217.866 9.88204C217.909 9.89685 217.955 9.89982 217.999 9.89067C218.043 9.88151 218.084 9.86056 218.117 9.83004C218.151 9.79953 218.175 9.76058 218.188 9.71733C218.201 9.67408 218.202 9.62815 218.191 9.5844L217.626 7.38117C217.586 7.22265 217.506 7.07656 217.396 6.95572L211.019 -0.00268162Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M209.501 2.52063L211.587 0.609772" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M208.88 6.10874L207.881 6.06512" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M218.174 3.32983L219.173 3.37345" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M211.703 10.2358L211.747 9.23673" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M215.351 -0.797205L215.307 0.201843" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M209.792 8.15047L208.749 9.1059" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M217.262 1.28815L218.305 0.332718" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M241.514 2.91736L243.382 6.00174L241.253 8.91165" stroke="black" stroke-width="1.5"/>
<path d="M239.451 4.32867L240.385 5.87087L239.32 7.32582" stroke="black" stroke-width="1.5"/>
<path d="M236.849 6.61738L236.757 8.71538C234.69 8.62513 233.829 8.58753 231.762 8.49728L231.788 7.89785L236.993 3.32052L237.019 2.72109L232.023 2.50299L231.932 4.60099" stroke="black" stroke-width="1.5"/>
<path d="M258.973 2.09101C258.721 1.81543 258.369 1.65134 257.996 1.63503C257.622 1.61872 257.258 1.75134 256.982 2.00386C256.706 2.25638 256.542 2.60806 256.526 2.9815C256.509 3.35494 256.642 3.71958 256.895 3.99525L263.272 10.9531C263.382 11.0741 263.521 11.1661 263.676 11.2209L265.821 11.9757C265.863 11.9905 265.909 11.9935 265.953 11.9844C265.998 11.9752 266.039 11.9542 266.072 11.9237C266.105 11.8932 266.129 11.8543 266.142 11.811C266.155 11.7678 266.156 11.7218 266.145 11.6781L265.581 9.47486C265.54 9.31634 265.461 9.17025 265.35 9.04941L258.973 2.09101Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M257.456 4.61432L259.541 2.70346" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M256.834 8.20243L255.835 8.15881" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M266.128 5.42352L267.127 5.46714" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M259.657 12.3295L259.701 11.3304" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M263.305 1.29648L263.262 2.29553" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M257.746 10.2442L256.704 11.1996" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M265.216 3.38184L266.259 2.42641" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<g clip-path="url(#clip3_2501_1466)">
<mask id="mask2_2501_1466" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="277" y="-1" width="17" height="18">
<path d="M293.683 0.190342L277.698 -0.507568L277 15.4772L292.985 16.1751L293.683 0.190342Z" fill="white"/>
</mask>
<g mask="url(#mask2_2501_1466)">
<path d="M288.3 8.86381L288.208 10.9618C285.727 10.8535 284.694 10.8084 282.214 10.7001L282.24 10.1007L288.443 5.56696L288.47 4.96753L282.475 4.70581L282.384 6.80381" stroke="black" stroke-width="1.5"/>
<path d="M278.957 5.05265L279.131 1.05646L282.628 1.20913" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M291.945 5.61969L292.119 1.62349L288.622 1.47083" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M278.739 10.0479L278.564 14.0441L282.061 14.1968" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M291.726 10.6149L291.552 14.6111L288.055 14.4585" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
</g>
</g>
<g clip-path="url(#clip4_2501_1466)">
<path d="M309.58 2.88629L308.138 6.70234C308.064 6.89763 307.945 7.07288 307.792 7.21393C307.638 7.355 307.453 7.45793 307.252 7.51445L303.324 8.61886L307.141 10.0614C307.336 10.1352 307.511 10.2538 307.652 10.4078C307.793 10.5617 307.896 10.7466 307.953 10.9476L309.057 14.8749L310.5 11.0588C310.573 10.8635 310.692 10.6883 310.846 10.5472C311 10.4062 311.185 10.3032 311.386 10.2467L315.313 9.14229L311.497 7.6998C311.302 7.62598 311.126 7.50732 310.985 7.35338C310.844 7.19944 310.741 7.01453 310.685 6.81354L309.58 2.88629Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M304.918 2.68273L304.802 5.34686M303.528 3.95664L306.192 4.07296" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M313.836 12.4144L313.719 15.0785M312.446 13.6882L315.11 13.8045" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<path d="M337.423 7.1048L339.29 10.1892L337.161 13.0991" stroke="black" stroke-width="1.5"/>
<path d="M335.36 8.51611L336.293 10.0583L335.229 11.5133" stroke="black" stroke-width="1.5"/>
<path d="M332.757 10.8048L332.666 12.9028C330.599 12.8126 329.737 12.775 327.67 12.6847L327.697 12.0853L332.901 7.50796L332.927 6.90853L327.932 6.69043L327.841 8.78843" stroke="black" stroke-width="1.5"/>
<g clip-path="url(#clip5_2501_1466)">
<mask id="mask3_2501_1466" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="348" y="2" width="18" height="18">
<path d="M365.614 3.33091L349.63 2.633L348.932 18.6178L364.917 19.3157L365.614 3.33091Z" fill="white"/>
</mask>
<g mask="url(#mask3_2501_1466)">
<path d="M360.231 12.0044L360.139 14.1024C357.659 13.9941 356.625 13.949 354.145 13.8407L354.171 13.2412L360.375 8.70752L360.401 8.10809L354.407 7.84637L354.315 9.94438" stroke="black" stroke-width="1.5"/>
<path d="M350.888 8.19321L351.063 4.19702L354.559 4.34969" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M363.876 8.76025L364.05 4.76406L360.554 4.61139" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M350.67 13.1885L350.496 17.1847L353.992 17.3373" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M363.658 13.7555L363.483 17.7517L359.987 17.599" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
</g>
</g>
<path d="M378.859 7.32532C378.606 7.04974 378.255 6.88565 377.881 6.86934C377.508 6.85304 377.143 6.98566 376.868 7.23818C376.592 7.49069 376.428 7.84237 376.412 8.21581C376.395 8.58926 376.528 8.95389 376.78 9.22957L383.158 16.1874C383.268 16.3084 383.407 16.4004 383.561 16.4553L385.707 17.21C385.749 17.2249 385.795 17.2278 385.839 17.2187C385.883 17.2095 385.924 17.1886 385.958 17.158C385.991 17.1275 386.015 17.0886 386.028 17.0453C386.041 17.0021 386.042 16.9562 386.031 16.9124L385.466 14.7092C385.426 14.5507 385.347 14.4046 385.236 14.2837L378.859 7.32532Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M377.341 9.84863L379.427 7.93778" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M376.72 13.4367L375.721 13.3931" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M386.014 10.6578L387.013 10.7015" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M379.543 17.5638L379.587 16.5647" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M383.191 6.5308L383.147 7.52985" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M377.632 15.4785L376.589 16.4339" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M385.102 8.61615L386.145 7.66072" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<g clip-path="url(#clip6_2501_1466)">
<mask id="mask4_2501_1466" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="58" y="46" width="18" height="17">
<path d="M75.446 46.7152L59.4612 46.0173L58.7633 62.0021L74.748 62.7L75.446 46.7152Z" fill="white"/>
</mask>
<g mask="url(#mask4_2501_1466)">
<path d="M70.0625 55.3887L69.9709 57.4867C67.4904 57.3784 66.457 57.3333 63.9766 57.225L64.0027 56.6256L70.2064 52.0919L70.2326 51.4924L64.2383 51.2307L64.1467 53.3287" stroke="black" stroke-width="1.5"/>
<path d="M60.7198 51.5776L60.8943 47.5814L64.391 47.734" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M73.7074 52.1446L73.8819 48.1484L70.3853 47.9957" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M60.5017 56.5728L60.3272 60.569L63.8239 60.7217" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M73.4893 57.1399L73.3149 61.136L69.8182 60.9834" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
</g>
</g>
<path d="M91.3434 49.4113L89.9009 53.2273C89.8271 53.4226 89.7084 53.5978 89.5545 53.7389C89.4006 53.88 89.2156 53.9829 89.0147 54.0394L85.0874 55.1438L88.9035 56.5863C89.0988 56.6601 89.274 56.7788 89.415 56.9327C89.5561 57.0867 89.659 57.2716 89.7156 57.4726L90.82 61.3998L92.2625 57.5838C92.3363 57.3885 92.4549 57.2132 92.6089 57.0722C92.7628 56.9311 92.9477 56.8282 93.1487 56.7717L97.076 55.6673L93.2599 54.2248C93.0646 54.1509 92.8894 54.0323 92.7483 53.8783C92.6073 53.7244 92.5043 53.5395 92.4478 53.3385L91.3434 49.4113Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M86.6812 49.2077L86.5649 51.8718M85.291 50.4816L87.9551 50.5979" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M95.5984 58.9394L95.4821 61.6035M94.2082 60.2132L96.8723 60.3296" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M112.668 51.7565C112.415 51.481 112.063 51.3169 111.69 51.3006C111.316 51.2843 110.952 51.4169 110.676 51.6694C110.401 51.9219 110.237 52.2736 110.22 52.647C110.204 53.0205 110.337 53.3851 110.589 53.6608L116.966 60.6186C117.077 60.7396 117.215 60.8316 117.37 60.8865L119.515 61.6413C119.558 61.6561 119.604 61.659 119.648 61.6499C119.692 61.6407 119.733 61.6198 119.766 61.5893C119.799 61.5587 119.824 61.5198 119.837 61.4765C119.85 61.4333 119.85 61.3874 119.839 61.3436L119.275 59.1404C119.234 58.9819 119.155 58.8358 119.045 58.7149L112.668 51.7565Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M111.15 54.2798L113.235 52.369" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M110.529 57.868L109.53 57.8243" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M119.822 55.0891L120.821 55.1327" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M113.352 61.995L113.395 60.9959" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M117 50.962L116.956 51.9611" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M111.441 59.9097L110.398 60.8651" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M118.911 53.0474L119.953 52.0919" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M143.163 54.6766L145.03 57.761L142.901 60.6709" stroke="black" stroke-width="1.5"/>
<path d="M141.1 56.088L142.033 57.6301L140.969 59.0851" stroke="black" stroke-width="1.5"/>
<path d="M138.497 58.3767L138.406 60.4747C136.339 60.3844 135.477 60.3468 133.41 60.2566L133.437 59.6571L138.641 55.0798L138.667 54.4804L133.672 54.2623L133.581 56.3603" stroke="black" stroke-width="1.5"/>
<g clip-path="url(#clip7_2501_1466)">
<mask id="mask5_2501_1466" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="154" y="50" width="18" height="17">
<path d="M171.355 50.9027L155.37 50.2048L154.672 66.1895L170.657 66.8875L171.355 50.9027Z" fill="white"/>
</mask>
<g mask="url(#mask5_2501_1466)">
<path d="M165.971 59.5762L165.88 61.6742C163.399 61.5659 162.366 61.5207 159.885 61.4124L159.911 60.813L166.115 56.2793L166.141 55.6799L160.147 55.4182L160.055 57.5162" stroke="black" stroke-width="1.5"/>
<path d="M156.629 55.765L156.803 51.7688L160.3 51.9215" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M169.616 56.332L169.791 52.3358L166.294 52.1832" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M156.41 60.7603L156.236 64.7564L159.733 64.9091" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M169.398 61.3273L169.224 65.3235L165.727 65.1708" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
</g>
</g>
<path d="M187.252 53.5987L185.81 57.4147C185.736 57.61 185.617 57.7853 185.463 57.9263C185.309 58.0674 185.124 58.1703 184.923 58.2269L180.996 59.3313L184.812 60.7738C185.007 60.8476 185.183 60.9662 185.324 61.1202C185.465 61.2741 185.568 61.459 185.624 61.66L186.729 65.5873L188.171 61.7712C188.245 61.5759 188.364 61.4007 188.518 61.2596C188.672 61.1186 188.856 61.0156 189.057 60.9591L192.985 59.8547L189.169 58.4122C188.973 58.3384 188.798 58.2197 188.657 58.0658C188.516 57.9118 188.413 57.7269 188.357 57.5259L187.252 53.5987Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M182.59 53.3951L182.474 56.0593M181.2 54.669L183.864 54.7854" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M191.507 63.1268L191.391 65.7909M190.117 64.4007L192.781 64.517" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M208.576 55.944C208.324 55.6685 207.972 55.5044 207.599 55.4881C207.225 55.4718 206.861 55.6044 206.585 55.8569C206.309 56.1094 206.145 56.4611 206.129 56.8345C206.113 57.208 206.245 57.5726 206.498 57.8483L212.875 64.8061C212.986 64.9271 213.124 65.0191 213.279 65.074L215.424 65.8288C215.466 65.8436 215.512 65.8465 215.556 65.8374C215.601 65.8282 215.642 65.8073 215.675 65.7768C215.708 65.7462 215.732 65.7073 215.745 65.664C215.758 65.6208 215.759 65.5749 215.748 65.5311L215.184 63.3279C215.143 63.1694 215.064 63.0233 214.953 62.9024L208.576 55.944Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M207.059 58.4673L209.144 56.5565" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M206.438 62.0555L205.438 62.0118" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M215.731 59.2766L216.73 59.3202" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M209.26 66.1825L209.304 65.1834" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M212.908 55.1495L212.865 56.1486" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M207.349 64.0972L206.307 65.0526" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M214.819 57.2349L215.862 56.2794" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M239.072 58.8641L240.939 61.9485L238.81 64.8584" stroke="black" stroke-width="1.5"/>
<path d="M237.008 60.2754L237.942 61.8176L236.877 63.2725" stroke="black" stroke-width="1.5"/>
<path d="M234.406 62.5641L234.314 64.6621C232.247 64.5718 231.386 64.5342 229.319 64.444L229.345 63.8446L234.55 59.2672L234.576 58.6678L229.581 58.4497L229.489 60.5477" stroke="black" stroke-width="1.5"/>
<g clip-path="url(#clip8_2501_1466)">
<mask id="mask6_2501_1466" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="250" y="54" width="18" height="18">
<path d="M267.263 55.0902L251.278 54.3923L250.58 70.377L266.565 71.075L267.263 55.0902Z" fill="white"/>
</mask>
<g mask="url(#mask6_2501_1466)">
<path d="M261.88 63.7637L261.788 65.8617C259.308 65.7534 258.274 65.7082 255.794 65.5999L255.82 65.0005L262.024 60.4668L262.05 59.8674L256.055 59.6057L255.964 61.7037" stroke="black" stroke-width="1.5"/>
<path d="M252.537 59.9525L252.711 55.9563L256.208 56.109" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M265.525 60.5195L265.699 56.5233L262.202 56.3707" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M252.319 64.9478L252.144 68.9439L255.641 69.0966" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M265.306 65.5148L265.132 69.511L261.635 69.3583" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
</g>
</g>
<g clip-path="url(#clip9_2501_1466)">
<path d="M283.161 57.7862L281.718 61.6022C281.644 61.7975 281.526 61.9728 281.372 62.1138C281.218 62.2549 281.033 62.3578 280.832 62.4144L276.905 63.5188L280.721 64.9613C280.916 65.0351 281.091 65.1537 281.232 65.3077C281.373 65.4616 281.476 65.6465 281.533 65.8475L282.637 69.7748L284.08 65.9587C284.154 65.7634 284.272 65.5882 284.426 65.4471C284.58 65.3061 284.765 65.2031 284.966 65.1466L288.893 64.0422L285.077 62.5997C284.882 62.5259 284.707 62.4072 284.566 62.2533C284.425 62.0993 284.322 61.9144 284.265 61.7134L283.161 57.7862Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M278.499 57.5826L278.382 60.2468M277.108 58.8565L279.772 58.9729" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M287.416 67.3143L287.3 69.9784M286.026 68.5881L288.69 68.7044" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<path d="M304.485 60.1315C304.232 59.8559 303.881 59.6918 303.507 59.6755C303.134 59.6592 302.769 59.7918 302.494 60.0443C302.218 60.2968 302.054 60.6485 302.038 61.022C302.021 61.3954 302.154 61.76 302.406 62.0357L308.784 68.9935C308.894 69.1146 309.033 69.2066 309.187 69.2614L311.332 70.0162C311.375 70.031 311.421 70.034 311.465 70.0248C311.509 70.0157 311.55 69.9947 311.583 69.9642C311.617 69.9337 311.641 69.8947 311.654 69.8515C311.667 69.8082 311.668 69.7623 311.657 69.7186L311.092 67.5153C311.052 67.3568 310.973 67.2107 310.862 67.0899L304.485 60.1315Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M302.967 62.6548L305.053 60.7439" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M302.346 66.2429L301.347 66.1993" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M311.64 63.464L312.639 63.5076" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M305.169 70.3699L305.213 69.3709" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M308.817 59.337L308.773 60.336" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M303.258 68.2846L302.215 69.2401" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M310.728 61.4223L311.771 60.4669" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<g clip-path="url(#clip10_2501_1466)">
<path d="M331.115 59.8799L329.673 63.6959C329.599 63.8912 329.48 64.0665 329.326 64.2075C329.172 64.3486 328.987 64.4515 328.786 64.508L324.859 65.6125L328.675 67.0549C328.87 67.1288 329.046 67.2474 329.187 67.4014C329.328 67.5553 329.431 67.7402 329.487 67.9412L330.592 71.8685L332.034 68.0524C332.108 67.8571 332.227 67.6819 332.381 67.5408C332.535 67.3998 332.719 67.2968 332.92 67.2403L336.848 66.1359L333.032 64.6934C332.836 64.6196 332.661 64.5009 332.52 64.347C332.379 64.193 332.276 64.0081 332.22 63.8071L331.115 59.8799Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M326.453 59.6763L326.337 62.3404M325.063 60.9502L327.727 61.0666" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M335.37 69.4079L335.254 72.0721M333.98 70.6818L336.644 70.7981" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<path d="M358.958 64.0984L360.825 67.1828L358.696 70.0927" stroke="black" stroke-width="1.5"/>
<path d="M356.894 65.5097L357.828 67.0519L356.763 68.5068" stroke="black" stroke-width="1.5"/>
<path d="M354.292 67.7984L354.2 69.8964C352.133 69.8062 351.272 69.7686 349.205 69.6783L349.231 69.0789L354.436 64.5015L354.462 63.9021L349.467 63.684L349.375 65.782" stroke="black" stroke-width="1.5"/>
<g clip-path="url(#clip11_2501_1466)">
<mask id="mask7_2501_1466" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="370" y="59" width="18" height="18">
<path d="M387.149 60.3245L371.164 59.6266L370.466 75.6114L386.451 76.3093L387.149 60.3245Z" fill="white"/>
</mask>
<g mask="url(#mask7_2501_1466)">
<path d="M381.766 68.998L381.674 71.096C379.194 70.9877 378.16 70.9426 375.68 70.8343L375.706 70.2348L381.91 65.7011L381.936 65.1017L375.941 64.84L375.85 66.938" stroke="black" stroke-width="1.5"/>
<path d="M372.423 65.1868L372.597 61.1906L376.094 61.3433" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M385.411 65.7538L385.585 61.7576L382.088 61.605" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M372.205 70.1821L372.03 74.1783L375.527 74.3309" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M385.192 70.7491L385.018 74.7453L381.521 74.5926" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
</g>
</g>
<path d="M402.246 69.8922L402.154 71.9902C400.087 71.8999 399.226 71.8623 397.159 71.7721L397.185 71.1726L402.39 66.5953L402.416 65.9959L397.421 65.7778L397.329 67.8758" stroke="black" stroke-width="1.5"/>
<path d="M53.9459 21.166C53.6934 20.8904 53.3417 20.7263 52.9683 20.71C52.5948 20.6937 52.2302 20.8263 51.9545 21.0788C51.6789 21.3313 51.5149 21.683 51.4985 22.0565C51.4821 22.4299 51.6148 22.7945 51.8673 23.0702L58.2446 30.028C58.3551 30.1491 58.4938 30.241 58.6483 30.2959L60.7934 31.0507C60.8361 31.0655 60.8819 31.0685 60.9261 31.0593C60.9703 31.0501 61.0112 31.0292 61.0444 30.9987C61.0777 30.9682 61.1021 30.9292 61.115 30.886C61.1279 30.8427 61.1288 30.7968 61.1177 30.753L60.5533 28.5498C60.5126 28.3913 60.4335 28.2452 60.3231 28.1244L53.9459 21.166Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M52.4282 23.6893L54.5136 21.7784" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M51.8072 27.2774L50.8081 27.2338" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M61.1006 24.4985L62.0996 24.5421" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M54.6299 31.4044L54.6735 30.4054" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M58.278 20.3714L58.2344 21.3705" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M52.7189 29.3191L51.6763 30.2745" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M60.189 22.4568L61.2316 21.5014" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M84.4414 24.0861L86.3086 27.1704L84.1797 30.0803" stroke="black" stroke-width="1.5"/>
<path d="M82.3779 25.4974L83.3115 27.0396L82.2471 28.4945" stroke="black" stroke-width="1.5"/>
<path d="M79.7756 27.7861L79.684 29.8841C77.6169 29.7938 76.7558 29.7562 74.6887 29.666L74.7149 29.0666L79.9195 24.4892L79.9457 23.8898L74.9504 23.6717L74.8588 25.7697" stroke="black" stroke-width="1.5"/>
<path d="M104.553 21.9613L103.111 25.7773C103.037 25.9726 102.918 26.1479 102.764 26.2889C102.611 26.43 102.426 26.5329 102.225 26.5895L98.2974 27.6939L102.113 29.1364C102.309 29.2102 102.484 29.3288 102.625 29.4828C102.766 29.6367 102.869 29.8216 102.926 30.0226L104.03 33.9499L105.472 30.1338C105.546 29.9385 105.665 29.7633 105.819 29.6222C105.973 29.4812 106.158 29.3782 106.359 29.3217L110.286 28.2173L106.47 26.7748C106.275 26.701 106.099 26.5823 105.958 26.4284C105.817 26.2745 105.714 26.0895 105.658 25.8885L104.553 21.9613Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M99.8912 21.7577L99.7748 24.4219M98.5009 23.0317L101.165 23.148" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M108.808 31.4894L108.692 34.1536M107.418 32.7633L110.082 32.8796" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<g clip-path="url(#clip12_2501_1466)">
<mask id="mask8_2501_1466" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="119" y="20" width="18" height="18">
<path d="M136.61 21.359L120.625 20.6611L119.927 36.6459L135.912 37.3438L136.61 21.359Z" fill="white"/>
</mask>
<g mask="url(#mask8_2501_1466)">
<path d="M131.227 30.0325L131.135 32.1305C128.654 32.0222 127.621 31.9771 125.141 31.8688L125.167 31.2694L131.37 26.7357L131.397 26.1362L125.402 25.8745L125.311 27.9725" stroke="black" stroke-width="1.5"/>
<path d="M121.884 26.2214L122.058 22.2252L125.555 22.3778" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M134.872 26.7884L135.046 22.7922L131.549 22.6395" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M121.666 31.2166L121.491 35.2128L124.988 35.3655" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M134.653 31.7836L134.479 35.7798L130.982 35.6272" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
</g>
</g>
<path d="M149.855 25.3535C149.602 25.0779 149.25 24.9138 148.877 24.8975C148.504 24.8812 148.139 25.0138 147.863 25.2663C147.588 25.5188 147.424 25.8705 147.407 26.244C147.391 26.6174 147.524 26.982 147.776 27.2577L154.153 34.2155C154.264 34.3366 154.402 34.4285 154.557 34.4834L156.702 35.2382C156.745 35.253 156.791 35.256 156.835 35.2468C156.879 35.2376 156.92 35.2167 156.953 35.1862C156.986 35.1557 157.011 35.1167 157.024 35.0735C157.037 35.0302 157.038 34.9843 157.026 34.9405L156.462 32.7373C156.421 32.5788 156.342 32.4327 156.232 32.3119L149.855 25.3535Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M148.337 27.8768L150.422 25.9659" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M147.716 31.4649L146.717 31.4213" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M157.009 28.686L158.008 28.7296" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M150.539 35.5919L150.582 34.5929" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M154.187 24.5589L154.143 25.558" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M148.628 33.5066L147.585 34.462" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M156.098 26.6443L157.14 25.6889" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M180.35 28.2735L182.217 31.3579L180.088 34.2678" stroke="black" stroke-width="1.5"/>
<path d="M178.287 29.6848L179.22 31.227L178.156 32.682" stroke="black" stroke-width="1.5"/>
<path d="M175.684 31.9735L175.593 34.0715C173.526 33.9813 172.664 33.9437 170.597 33.8534L170.624 33.254L175.828 28.6767L175.854 28.0772L170.859 27.8591L170.768 29.9571" stroke="black" stroke-width="1.5"/>
<path d="M200.462 26.1487L199.02 29.9648C198.946 30.1601 198.827 30.3353 198.673 30.4764C198.519 30.6174 198.334 30.7204 198.133 30.7769L194.206 31.8813L198.022 33.3238C198.217 33.3976 198.393 33.5163 198.534 33.6702C198.675 33.8242 198.778 34.0091 198.834 34.2101L199.939 38.1373L201.381 34.3213C201.455 34.126 201.574 33.9507 201.728 33.8097C201.881 33.6686 202.066 33.5657 202.267 33.5092L206.195 32.4047L202.379 30.9623C202.183 30.8884 202.008 30.7698 201.867 30.6158C201.726 30.4619 201.623 30.277 201.566 30.076L200.462 26.1487Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M195.8 25.9452L195.684 28.6093M194.41 27.2191L197.074 27.3354" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M204.717 35.6769L204.601 38.341M203.327 36.9507L205.991 37.0671" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<g clip-path="url(#clip13_2501_1466)">
<mask id="mask9_2501_1466" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="215" y="24" width="18" height="18">
<path d="M232.519 25.5465L216.534 24.8486L215.836 40.8333L231.821 41.5313L232.519 25.5465Z" fill="white"/>
</mask>
<g mask="url(#mask9_2501_1466)">
<path d="M227.135 34.22L227.044 36.318C224.563 36.2097 223.53 36.1645 221.049 36.0562L221.075 35.4568L227.279 30.9231L227.305 30.3237L221.311 30.062L221.219 32.16" stroke="black" stroke-width="1.5"/>
<path d="M217.793 30.4088L217.967 26.4126L221.464 26.5653" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M230.78 30.9758L230.955 26.9796L227.458 26.827" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M217.574 35.4041L217.4 39.4002L220.897 39.5529" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M230.562 35.9711L230.388 39.9673L226.891 39.8146" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
</g>
</g>
<path d="M245.763 29.5409C245.511 29.2653 245.159 29.1012 244.786 29.0849C244.412 29.0686 244.048 29.2012 243.772 29.4538C243.496 29.7063 243.332 30.0579 243.316 30.4314C243.3 30.8048 243.432 31.1695 243.685 31.4451L250.062 38.403C250.173 38.524 250.311 38.616 250.466 38.6708L252.611 39.4256C252.654 39.4404 252.699 39.4434 252.743 39.4342C252.788 39.4251 252.829 39.4041 252.862 39.3736C252.895 39.3431 252.919 39.3042 252.932 39.2609C252.945 39.2177 252.946 39.1717 252.935 39.128L252.371 36.9247C252.33 36.7662 252.251 36.6201 252.141 36.4993L245.763 29.5409Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M244.246 32.0642L246.331 30.1534" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M243.625 35.6523L242.625 35.6087" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M252.918 32.8734L253.917 32.917" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M246.447 39.7794L246.491 38.7803" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M250.095 28.7464L250.052 29.7454" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M244.536 37.694L243.494 38.6495" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M252.006 30.8317L253.049 29.8763" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M276.259 32.461L278.126 35.5454L275.997 38.4553" stroke="black" stroke-width="1.5"/>
<path d="M274.195 33.8723L275.129 35.4145L274.064 36.8695" stroke="black" stroke-width="1.5"/>
<path d="M271.593 36.161L271.501 38.259C269.434 38.1688 268.573 38.1312 266.506 38.0409L266.532 37.4415L271.737 32.8642L271.763 32.2647L266.768 32.0466L266.676 34.1446" stroke="black" stroke-width="1.5"/>
<g clip-path="url(#clip14_2501_1466)">
<mask id="mask10_2501_1466" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="287" y="27" width="18" height="18">
<path d="M304.45 28.687L288.465 27.9891L287.767 43.9739L303.752 44.6718L304.45 28.687Z" fill="white"/>
</mask>
<g mask="url(#mask10_2501_1466)">
<path d="M299.067 37.3605L298.975 39.4585C296.495 39.3502 295.461 39.3051 292.981 39.1968L293.007 38.5974L299.211 34.0637L299.237 33.4642L293.242 33.2025L293.151 35.3005" stroke="black" stroke-width="1.5"/>
<path d="M289.724 33.5494L289.898 29.5532L293.395 29.7058" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M302.712 34.1164L302.886 30.1202L299.389 29.9675" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M289.506 38.5446L289.331 42.5408L292.828 42.6935" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
<path d="M302.493 39.1117L302.319 43.1078L298.822 42.9552" stroke="black" stroke-opacity="0.5" stroke-width="1.5"/>
</g>
</g>
<path d="M317.695 32.6815C317.442 32.4059 317.091 32.2419 316.717 32.2255C316.344 32.2092 315.979 32.3419 315.703 32.5944C315.428 32.8469 315.264 33.1986 315.247 33.572C315.231 33.9455 315.364 34.3101 315.616 34.5858L321.993 41.5436C322.104 41.6646 322.243 41.7566 322.397 41.8115L324.542 42.5662C324.585 42.5811 324.631 42.584 324.675 42.5749C324.719 42.5657 324.76 42.5448 324.793 42.5142C324.826 42.4837 324.851 42.4448 324.864 42.4015C324.877 42.3583 324.878 42.3124 324.866 42.2686L324.302 40.0654C324.261 39.9069 324.182 39.7608 324.072 39.6399L317.695 32.6815Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M316.177 35.2048L318.262 33.294" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M315.556 38.7929L314.557 38.7493" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M324.849 36.014L325.848 36.0577" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M318.379 42.92L318.422 41.9209" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M322.027 31.887L321.983 32.886" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M316.468 40.8347L315.425 41.7901" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M323.938 33.9724L324.98 33.0169" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M344.325 32.4299L342.882 36.246C342.809 36.4413 342.69 36.6165 342.536 36.7576C342.382 36.8986 342.197 37.0016 341.996 37.0581L338.069 38.1625L341.885 39.605C342.08 39.6788 342.255 39.7975 342.396 39.9514C342.538 40.1054 342.64 40.2903 342.697 40.4913L343.801 44.4185L345.244 40.6025C345.318 40.4072 345.436 40.2319 345.59 40.0909C345.744 39.9498 345.929 39.8469 346.13 39.7903L350.057 38.6859L346.241 37.2434C346.046 37.1696 345.871 37.051 345.73 36.897C345.589 36.7431 345.486 36.5582 345.429 36.3572L344.325 32.4299Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M339.663 32.2264L339.546 34.8905M338.272 33.5003L340.937 33.6166" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M348.58 41.9579L348.464 44.6221M347.19 43.2318L349.854 43.3481" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M372.167 36.6484L374.035 39.7328L371.906 42.6427" stroke="black" stroke-width="1.5"/>
<path d="M370.104 38.0598L371.038 39.6019L369.973 41.0569" stroke="black" stroke-width="1.5"/>
<path d="M367.502 40.3485L367.41 42.4465C365.343 42.3562 364.482 42.3186 362.415 42.2284L362.441 41.6289L367.646 37.0516L367.672 36.4522L362.677 36.2341L362.585 38.3321" stroke="black" stroke-width="1.5"/>
<path d="M392.279 34.5237L390.837 38.3397C390.763 38.535 390.644 38.7103 390.49 38.8513C390.336 38.9924 390.151 39.0953 389.95 39.1518L386.023 40.2563L389.839 41.6987C390.035 41.7726 390.21 41.8912 390.351 42.0452C390.492 42.1991 390.595 42.384 390.651 42.585L391.756 46.5123L393.198 42.6962C393.272 42.5009 393.391 42.3257 393.545 42.1846C393.699 42.0435 393.884 41.9406 394.085 41.8841L398.012 40.7797L394.196 39.3372C394 39.2634 393.825 39.1447 393.684 38.9908C393.543 38.8368 393.44 38.6519 393.384 38.4509L392.279 34.5237Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M387.617 34.3201L387.501 36.9842M386.227 35.594L388.891 35.7103" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M396.534 44.0517L396.418 46.7159M395.144 45.3256L397.808 45.4419" stroke="black" stroke-opacity="0.75" stroke-width="1.42857" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_2501_1466">
<rect width="400" height="92" fill="white"/>
</clipPath>
<clipPath id="clip1_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(109.858 -7.83563) rotate(2.5)"/>
</clipPath>
<clipPath id="clip2_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(181.79 -4.69507) rotate(2.5)"/>
</clipPath>
<clipPath id="clip3_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(277.698 -0.507568) rotate(2.5)"/>
</clipPath>
<clipPath id="clip4_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(301.675 0.539246) rotate(2.5)"/>
</clipPath>
<clipPath id="clip5_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(349.63 2.633) rotate(2.5)"/>
</clipPath>
<clipPath id="clip6_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(59.4612 46.0173) rotate(2.5)"/>
</clipPath>
<clipPath id="clip7_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(155.37 50.2048) rotate(2.5)"/>
</clipPath>
<clipPath id="clip8_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(251.278 54.3923) rotate(2.5)"/>
</clipPath>
<clipPath id="clip9_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(275.256 55.4391) rotate(2.5)"/>
</clipPath>
<clipPath id="clip10_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(323.21 57.5328) rotate(2.5)"/>
</clipPath>
<clipPath id="clip11_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(371.164 59.6266) rotate(2.5)"/>
</clipPath>
<clipPath id="clip12_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(120.625 20.6611) rotate(2.5)"/>
</clipPath>
<clipPath id="clip13_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(216.534 24.8486) rotate(2.5)"/>
</clipPath>
<clipPath id="clip14_2501_1466">
<rect width="16" height="16" fill="white" transform="translate(288.465 27.9891) rotate(2.5)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -1,153 +0,0 @@
<svg width="441" height="167" viewBox="0 0 441 167" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2500_1167)">
<path d="M440 3H441V98H440V3Z" fill="black"/>
<path d="M429 3H430V98H429V3Z" fill="black"/>
<path d="M418 3H419V98H418V3Z" fill="black"/>
<path d="M407 3H408V98H407V3Z" fill="black"/>
<path d="M396 3H397V98H396V3Z" fill="black"/>
<path d="M385 3H386V98H385V3Z" fill="black"/>
<path d="M374 0H375V95H374V0Z" fill="black"/>
<path d="M308 0H309V95H308V0Z" fill="black"/>
<path d="M242 0H243V95H242V0Z" fill="black"/>
<path d="M429 -2H430V93H429V-2Z" fill="black"/>
<path d="M429 -2H430V93H429V-2Z" fill="black"/>
<path d="M441 11V12L202 12V11L441 11Z" fill="black"/>
<path d="M441 22V23L202 23V22L441 22Z" fill="black"/>
<path d="M441 33V34L202 34V33L441 33Z" fill="black"/>
<path d="M441 44V45L202 45V44L441 44Z" fill="black"/>
<path d="M441 55V56L202 56V55L441 55Z" fill="black"/>
<path d="M440 66V67L201 67V66L440 66Z" fill="black"/>
<path d="M440 77V78L201 78V77L440 77Z" fill="black"/>
<path d="M363 0H364V95H363V0Z" fill="black"/>
<path d="M297 0H298V95H297V0Z" fill="black"/>
<path d="M231 0H232V95H231V0Z" fill="black"/>
<path d="M418 -2H419V93H418V-2Z" fill="black"/>
<path d="M352 0H353V95H352V0Z" fill="black"/>
<path d="M286 0H287V95H286V0Z" fill="black"/>
<path d="M220 0H221V95H220V0Z" fill="black"/>
<path d="M407 -2H408V93H407V-2Z" fill="black"/>
<path d="M341 0H342V95H341V0Z" fill="black"/>
<path d="M275 0H276V95H275V0Z" fill="black"/>
<path d="M396 -2H397V93H396V-2Z" fill="black"/>
<path d="M330 0H331V95H330V0Z" fill="black"/>
<path d="M264 0H265V95H264V0Z" fill="black"/>
<path d="M198 0H199V95H198V0Z" fill="black"/>
<path d="M385 -2H386V93H385V-2Z" fill="black"/>
<path d="M319 0H320V95H319V0Z" fill="black"/>
<path d="M253 0H254V95H253V0Z" fill="black"/>
<path d="M187 0H188V95H187V0Z" fill="black"/>
<path d="M231 3H232V98H231V3Z" fill="black"/>
<path d="M220 3H221V98H220V3Z" fill="black"/>
<path d="M209 3H210V98H209V3Z" fill="black"/>
<path d="M198 3H199V98H198V3Z" fill="black"/>
<path d="M187 3H188V98H187V3Z" fill="black"/>
<path d="M176 3H177V98H176V3Z" fill="black"/>
<path d="M165 0H166V95H165V0Z" fill="black"/>
<path d="M99 0H100V95H99V0Z" fill="black"/>
<path d="M33 0H34V95H33V0Z" fill="black"/>
<path d="M220 -2H221V93H220V-2Z" fill="black"/>
<path d="M220 -2H221V93H220V-2Z" fill="black"/>
<path d="M232 11V12L-7 12V11L232 11Z" fill="black"/>
<path d="M232 22V23L-7 23V22L232 22Z" fill="black"/>
<path d="M232 33V34L-7 34V33L232 33Z" fill="black"/>
<path d="M232 44V45L-7 45V44L232 44Z" fill="black"/>
<path d="M232 55V56L-7 56V55L232 55Z" fill="black"/>
<path d="M231 66V67L-8 67V66L231 66Z" fill="black"/>
<path d="M231 77V78L-8 78V77L231 77Z" fill="black"/>
<path d="M154 0H155V95H154V0Z" fill="black"/>
<path d="M88 0H89V95H88V0Z" fill="black"/>
<path d="M22 0H23V95H22V0Z" fill="black"/>
<path d="M209 -2H210V93H209V-2Z" fill="black"/>
<path d="M143 0H144V95H143V0Z" fill="black"/>
<path d="M77 0H78V95H77V0Z" fill="black"/>
<path d="M11 0H12V95H11V0Z" fill="black"/>
<path d="M198 -2H199V93H198V-2Z" fill="black"/>
<path d="M132 0H133V95H132V0Z" fill="black"/>
<path d="M66 0H67V95H66V0Z" fill="black"/>
<path d="M187 -2H188V93H187V-2Z" fill="black"/>
<path d="M121 0H122V95H121V0Z" fill="black"/>
<path d="M55 0H56V95H55V0Z" fill="black"/>
<path d="M-11 0H-10V95H-11V0Z" fill="black"/>
<path d="M176 -2H177V93H176V-2Z" fill="black"/>
<path d="M110 0H111V95H110V0Z" fill="black"/>
<path d="M44 0H45V95H44V0Z" fill="black"/>
<path d="M-22 0H-21V95H-22V0Z" fill="black"/>
<path d="M440 81H441V176H440V81Z" fill="black"/>
<path d="M429 81H430V176H429V81Z" fill="black"/>
<path d="M418 81H419V176H418V81Z" fill="black"/>
<path d="M407 81H408V176H407V81Z" fill="black"/>
<path d="M396 81H397V176H396V81Z" fill="black"/>
<path d="M385 81H386V176H385V81Z" fill="black"/>
<path d="M374 78H375V173H374V78Z" fill="black"/>
<path d="M308 78H309V173H308V78Z" fill="black"/>
<path d="M242 78H243V173H242V78Z" fill="black"/>
<path d="M429 76H430V171H429V76Z" fill="black"/>
<path d="M429 76H430V171H429V76Z" fill="black"/>
<path d="M441 89V90L202 90V89L441 89Z" fill="black"/>
<path d="M441 100V101L202 101V100L441 100Z" fill="black"/>
<path d="M441 111V112L202 112V111L441 111Z" fill="black"/>
<path d="M441 122V123L202 123V122L441 122Z" fill="black"/>
<path d="M441 133V134L202 134V133L441 133Z" fill="black"/>
<path d="M440 144V145L201 145V144L440 144Z" fill="black"/>
<path d="M440 155V156L201 156V155L440 155Z" fill="black"/>
<path d="M363 78H364V173H363V78Z" fill="black"/>
<path d="M297 78H298V173H297V78Z" fill="black"/>
<path d="M231 78L232 78V173H231V78Z" fill="black"/>
<path d="M418 76H419V171H418V76Z" fill="black"/>
<path d="M352 78H353V173H352V78Z" fill="black"/>
<path d="M286 78H287V173H286V78Z" fill="black"/>
<path d="M220 78H221V173H220V78Z" fill="black"/>
<path d="M407 76H408V171H407V76Z" fill="black"/>
<path d="M341 78H342V173H341V78Z" fill="black"/>
<path d="M275 78H276V173H275V78Z" fill="black"/>
<path d="M396 76H397V171H396V76Z" fill="black"/>
<path d="M330 78H331V173H330V78Z" fill="black"/>
<path d="M264 78H265V173H264V78Z" fill="black"/>
<path d="M198 78H199V173H198V78Z" fill="black"/>
<path d="M385 76H386V171H385V76Z" fill="black"/>
<path d="M319 78H320V173H319V78Z" fill="black"/>
<path d="M253 78H254V173H253V78Z" fill="black"/>
<path d="M187 78H188V173H187V78Z" fill="black"/>
<path d="M231 81H232V176H231V81Z" fill="black"/>
<path d="M220 81H221V176H220V81Z" fill="black"/>
<path d="M209 81H210V176H209V81Z" fill="black"/>
<path d="M198 81H199V176H198V81Z" fill="black"/>
<path d="M187 81H188V176H187V81Z" fill="black"/>
<path d="M176 81H177V176H176V81Z" fill="black"/>
<path d="M165 78H166V173H165V78Z" fill="black"/>
<path d="M99 78H100V173H99V78Z" fill="black"/>
<path d="M33 78H34V173H33V78Z" fill="black"/>
<path d="M220 76H221V171H220V76Z" fill="black"/>
<path d="M220 76H221V171H220V76Z" fill="black"/>
<path d="M232 89V90L-7 90V89L232 89Z" fill="black"/>
<path d="M232 100V101L-7 101V100L232 100Z" fill="black"/>
<path d="M232 111V112L-7 112V111L232 111Z" fill="black"/>
<path d="M232 122V123L-7 123V122L232 122Z" fill="black"/>
<path d="M232 133V134L-7 134V133L232 133Z" fill="black"/>
<path d="M231 144V145L-8 145V144L231 144Z" fill="black"/>
<path d="M231 155V156L-8 156V155L231 155Z" fill="black"/>
<path d="M154 78H155V173H154V78Z" fill="black"/>
<path d="M88 78H89V173H88V78Z" fill="black"/>
<path d="M22 78H23V173H22V78Z" fill="black"/>
<path d="M209 76H210V171H209V76Z" fill="black"/>
<path d="M143 78H144V173H143V78Z" fill="black"/>
<path d="M77 78H78V173H77V78Z" fill="black"/>
<path d="M11 78H12V173H11V78Z" fill="black"/>
<path d="M198 76H199V171H198V76Z" fill="black"/>
<path d="M132 78H133V173H132V78Z" fill="black"/>
<path d="M66 78H67V173H66V78Z" fill="black"/>
<path d="M187 76H188V171H187V76Z" fill="black"/>
<path d="M121 78H122V173H121V78Z" fill="black"/>
<path d="M55 78H56V173H55V78Z" fill="black"/>
<path d="M-11 78H-10V173H-11V78Z" fill="black"/>
<path d="M176 76H177V171H176V76Z" fill="black"/>
<path d="M110 78H111V173H110V78Z" fill="black"/>
<path d="M44 78H45V173H44V78Z" fill="black"/>
<path d="M-22 78H-21V173H-22V78Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_2500_1167">
<rect width="441" height="167" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -31,8 +31,8 @@
"ctrl-,": "zed::OpenSettings",
"ctrl-q": "zed::Quit",
"f4": "debugger::Start",
"f5": "debugger::Continue",
"shift-f5": "debugger::Stop",
"ctrl-shift-f5": "debugger::Restart",
"f6": "debugger::Pause",
"f7": "debugger::StepOver",
"cmd-f11": "debugger::StepInto",
@@ -144,8 +144,6 @@
"ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
"ctrl-shift-alt-backspace": "editor::GoToNextChange",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
}
},
@@ -194,16 +192,6 @@
"alt-shift-y": "git::UnstageAndNext"
}
},
{
"context": "Editor && editor_agent_diff",
"bindings": {
"ctrl-y": "agent::Keep",
"ctrl-n": "agent::Reject",
"ctrl-shift-y": "agent::KeepAll",
"ctrl-shift-n": "agent::RejectAll",
"shift-ctrl-r": "agent::OpenAgentDiff"
}
},
{
"context": "AgentDiff",
"bindings": {
@@ -213,22 +201,35 @@
"ctrl-shift-n": "agent::RejectAll"
}
},
{
"context": "AssistantPanel",
"bindings": {
"ctrl-k c": "assistant::CopyCode",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-g": "search::SelectNextMatch",
"ctrl-shift-g": "search::SelectPreviousMatch",
"ctrl-alt-/": "assistant::ToggleModelSelector",
"ctrl-k h": "assistant::DeployHistory",
"ctrl-k l": "assistant::OpenPromptLibrary",
"new": "assistant::NewChat",
"ctrl-t": "assistant::NewChat",
"ctrl-n": "assistant::NewChat"
}
},
{
"context": "ContextEditor > Editor",
"bindings": {
"ctrl-enter": "assistant::Assist",
"ctrl-shift-enter": "assistant::Edit",
"ctrl-s": "workspace::Save",
"save": "workspace::Save",
"ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-/": "assistant::ToggleModelSelector",
"shift-enter": "assistant::Split",
"ctrl-r": "assistant::CycleMessageRole",
"enter": "assistant::ConfirmCommand",
"alt-enter": "editor::Newline",
"ctrl-k c": "assistant::CopyCode",
"ctrl-g": "search::SelectNextMatch",
"ctrl-shift-g": "search::SelectPreviousMatch",
"ctrl-k l": "agent::OpenRulesLibrary"
"alt-enter": "editor::Newline"
}
},
{
@@ -238,24 +239,13 @@
"ctrl-alt-n": "agent::NewTextThread",
"ctrl-shift-h": "agent::OpenHistory",
"ctrl-alt-c": "agent::OpenConfiguration",
"ctrl-alt-p": "agent::OpenRulesLibrary",
"ctrl-alt-p": "assistant::OpenPromptLibrary",
"ctrl-i": "agent::ToggleProfileSelector",
"ctrl-alt-/": "agent::ToggleModelSelector",
"ctrl-alt-/": "assistant::ToggleModelSelector",
"ctrl-shift-a": "agent::ToggleContextPicker",
"ctrl-shift-j": "agent::ToggleNavigationMenu",
"ctrl-shift-i": "agent::ToggleOptionsMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl-alt-e": "agent::RemoveAllContext",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
"alt-enter": "agent::ContinueWithBurnMode",
"ctrl-alt-b": "agent::ToggleBurnMode"
}
},
{
"context": "AgentPanel > NavigationMenu",
"bindings": {
"shift-backspace": "agent::DeleteRecentlyOpenThread"
"shift-escape": "agent::ExpandMessageEditor",
"ctrl-e": "agent::ChatMode",
"ctrl-alt-e": "agent::RemoveAllContext"
}
},
{
@@ -276,7 +266,6 @@
"context": "MessageEditor > Editor",
"bindings": {
"enter": "agent::Chat",
"ctrl-enter": "agent::ChatWithFollow",
"ctrl-i": "agent::ToggleProfileSelector",
"shift-ctrl-r": "agent::OpenAgentDiff"
}
@@ -317,9 +306,9 @@
{
"context": "PromptLibrary",
"bindings": {
"new": "rules_library::NewRule",
"ctrl-n": "rules_library::NewRule",
"ctrl-shift-s": "rules_library::ToggleDefaultRule"
"new": "prompt_library::NewPrompt",
"ctrl-n": "prompt_library::NewPrompt",
"ctrl-shift-s": "prompt_library::ToggleDefaultPrompt"
}
},
{
@@ -516,8 +505,6 @@
"alt-ctrl-o": "projects::OpenRecent",
"alt-shift-open": "projects::OpenRemote",
"alt-ctrl-shift-o": "projects::OpenRemote",
// Change to open path modal for existing remote connection by setting the parameter
// "alt-ctrl-shift-o": "["projects::OpenRemote", { "from_existing_connection": true }]",
"alt-ctrl-shift-b": "branches::OpenRecent",
"alt-shift-enter": "toast::RunAction",
"ctrl-~": "workspace::NewTerminal",
@@ -531,7 +518,6 @@
"shift-new": "workspace::NewWindow",
"ctrl-shift-n": "workspace::NewWindow",
"ctrl-`": "terminal_panel::ToggleFocus",
"f10": ["app_menu::OpenApplicationMenu", "Zed"],
"alt-1": ["workspace::ActivatePane", 0],
"alt-2": ["workspace::ActivatePane", 1],
"alt-3": ["workspace::ActivatePane", 2],
@@ -562,8 +548,7 @@
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-b": "outline_panel::ToggleFocus",
"ctrl-shift-g": "git_panel::ToggleFocus",
"ctrl-shift-d": "debug_panel::ToggleFocus",
"ctrl-?": "agent::ToggleFocus",
"ctrl-?": "assistant::ToggleFocus",
"alt-save": "workspace::SaveAll",
"ctrl-alt-s": "workspace::SaveAll",
"ctrl-k m": "language_selector::Toggle",
@@ -581,30 +566,16 @@
"ctrl-alt-r": "task::Rerun",
"alt-t": "task::Rerun",
"alt-shift-t": "task::Spawn",
"alt-shift-r": ["task::Spawn", { "reveal_target": "center" }],
"alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
// also possible to spawn tasks by name:
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
// or by tag:
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
"f5": "debugger::RerunLastSession"
}
},
{
"context": "Workspace && debugger_running",
"bindings": {
"f5": "zed::NoAction"
}
},
{
"context": "Workspace && debugger_stopped",
"bindings": {
"f5": "debugger::Continue"
}
},
{
"context": "ApplicationMenu",
"bindings": {
"f10": "menu::Cancel",
"left": "app_menu::ActivateMenuLeft",
"right": "app_menu::ActivateMenuRight"
}
@@ -613,6 +584,7 @@
{
"context": "Editor",
"bindings": {
"ctrl-shift-d": "editor::DuplicateLineDown",
"ctrl-shift-j": "editor::JoinLines",
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
@@ -691,8 +663,7 @@
{
"bindings": {
"ctrl-alt-shift-f": "workspace::FollowNextCollaborator",
// Only available in debug builds: opens an element inspector for development.
"ctrl-alt-i": "dev::ToggleInspector"
"ctrl-alt-i": "zed::DebugElements"
}
},
{
@@ -717,8 +688,8 @@
{
"context": "PromptEditor",
"bindings": {
"ctrl-[": "agent::CyclePreviousInlineAssist",
"ctrl-]": "agent::CycleNextInlineAssist",
"ctrl-[": "assistant::CyclePreviousInlineAssist",
"ctrl-]": "assistant::CycleNextInlineAssist",
"ctrl-alt-e": "agent::RemoveAllContext"
}
},
@@ -748,7 +719,7 @@
"alt-shift-copy": "workspace::CopyRelativePath",
"alt-ctrl-shift-c": "workspace::CopyRelativePath",
"alt-ctrl-r": "outline_panel::RevealInFileManager",
"space": "outline_panel::OpenSelectedEntry",
"space": "outline_panel::Open",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious",
"alt-enter": "editor::OpenExcerpts",
@@ -786,7 +757,7 @@
"alt-ctrl-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
"shift-find": "project_panel::NewSearchInDirectory",
"ctrl-alt-shift-f": "project_panel::NewSearchInDirectory",
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious",
"escape": "menu::Cancel"
@@ -880,31 +851,6 @@
"alt-l": "git::GenerateCommitMessage"
}
},
{
"context": "DebugPanel",
"bindings": {
"ctrl-t": "debugger::ToggleThreadPicker",
"ctrl-i": "debugger::ToggleSessionPicker",
"shift-alt-escape": "debugger::ToggleExpandItem"
}
},
{
"context": "VariableList",
"bindings": {
"left": "variable_list::CollapseSelectedEntry",
"right": "variable_list::ExpandSelectedEntry",
"enter": "variable_list::EditVariable",
"ctrl-c": "variable_list::CopyVariableValue",
"ctrl-alt-c": "variable_list::CopyVariableName"
}
},
{
"context": "BreakpointList",
"bindings": {
"space": "debugger::ToggleEnableBreakpoint",
"backspace": "debugger::UnsetBreakpoint"
}
},
{
"context": "CollabPanel && not_editing",
"bindings": {
@@ -940,13 +886,6 @@
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "FileFinder",
"bindings": {
"ctrl-shift-a": "file_finder::ToggleSplitMenu",
"ctrl-shift-i": "file_finder::ToggleFilterMenu"
}
},
{
"context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)",
"bindings": {
@@ -979,15 +918,12 @@
"ctrl-enter": "assistant::InlineAssist",
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
"alt-.": ["terminal::SendText", "\u001b."],
"ctrl-delete": ["terminal::SendText", "\u001bd"],
// Overrides for conflicting keybindings
"ctrl-b": ["terminal::SendKeystroke", "ctrl-b"],
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
"ctrl-e": ["terminal::SendKeystroke", "ctrl-e"],
"ctrl-o": ["terminal::SendKeystroke", "ctrl-o"],
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
"ctrl-backspace": ["terminal::SendKeystroke", "ctrl-w"],
"ctrl-shift-a": "editor::SelectAll",
"find": "buffer_search::Deploy",
"ctrl-shift-f": "buffer_search::Deploy",
@@ -1005,10 +941,7 @@
"shift-down": "terminal::ScrollLineDown",
"shift-home": "terminal::ScrollToTop",
"shift-end": "terminal::ScrollToBottom",
"ctrl-shift-space": "terminal::ToggleViMode",
"ctrl-shift-r": "terminal::RerunTask",
"ctrl-alt-r": "terminal::RerunTask",
"alt-t": "terminal::RerunTask"
"ctrl-shift-space": "terminal::ToggleViMode"
}
},
{
@@ -1016,34 +949,5 @@
"bindings": {
"escape": "menu::Cancel"
}
},
{
"context": "ConfigureContextServerModal > Editor",
"bindings": {
"escape": "menu::Cancel",
"enter": "editor::Newline",
"ctrl-enter": "menu::Confirm"
}
},
{
"context": "Diagnostics",
"use_key_equivalents": true,
"bindings": {
"ctrl-r": "diagnostics::ToggleDiagnosticsRefresh"
}
},
{
"context": "DebugConsole > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "menu::Confirm"
}
},
{
"context": "RunModal",
"bindings": {
"ctrl-tab": "pane::ActivateNextItem",
"ctrl-shift-tab": "pane::ActivatePreviousItem"
}
}
]

View File

@@ -1,11 +1,22 @@
[
// Moved before Standard macOS bindings so that `cmd-w` is not the last binding for
// `workspace::CloseWindow` and displayed/intercepted by macOS
{
"context": "PromptLibrary",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "prompt_library::NewPrompt",
"cmd-shift-s": "prompt_library::ToggleDefaultPrompt",
"cmd-w": "workspace::CloseWindow"
}
},
// Standard macOS bindings
{
"use_key_equivalents": true,
"bindings": {
"f4": "debugger::Start",
"f5": "debugger::Continue",
"shift-f5": "debugger::Stop",
"shift-cmd-f5": "debugger::Restart",
"f6": "debugger::Pause",
"f7": "debugger::StepOver",
"f11": "debugger::StepInto",
@@ -237,14 +248,18 @@
}
},
{
"context": "Editor && editor_agent_diff",
"context": "AssistantPanel",
"use_key_equivalents": true,
"bindings": {
"cmd-y": "agent::Keep",
"cmd-n": "agent::Reject",
"cmd-shift-y": "agent::KeepAll",
"cmd-shift-n": "agent::RejectAll",
"shift-ctrl-r": "agent::OpenAgentDiff"
"cmd-k c": "assistant::CopyCode",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-g": "search::SelectNextMatch",
"cmd-shift-g": "search::SelectPreviousMatch",
"cmd-alt-/": "assistant::ToggleModelSelector",
"cmd-k h": "assistant::DeployHistory",
"cmd-k l": "assistant::OpenPromptLibrary",
"cmd-t": "assistant::NewChat",
"cmd-n": "assistant::NewChat"
}
},
{
@@ -252,17 +267,15 @@
"use_key_equivalents": true,
"bindings": {
"cmd-enter": "assistant::Assist",
"cmd-shift-enter": "assistant::Edit",
"cmd-s": "workspace::Save",
"cmd->": "assistant::QuoteSelection",
"cmd-<": "assistant::InsertIntoEditor",
"cmd-alt-/": "assistant::ToggleModelSelector",
"shift-enter": "assistant::Split",
"ctrl-r": "assistant::CycleMessageRole",
"enter": "assistant::ConfirmCommand",
"alt-enter": "editor::Newline",
"cmd-k c": "assistant::CopyCode",
"cmd-g": "search::SelectNextMatch",
"cmd-shift-g": "search::SelectPreviousMatch",
"cmd-k l": "agent::OpenRulesLibrary"
"alt-enter": "editor::Newline"
}
},
{
@@ -273,24 +286,13 @@
"cmd-alt-n": "agent::NewTextThread",
"cmd-shift-h": "agent::OpenHistory",
"cmd-alt-c": "agent::OpenConfiguration",
"cmd-alt-p": "agent::OpenRulesLibrary",
"cmd-alt-p": "assistant::OpenPromptLibrary",
"cmd-i": "agent::ToggleProfileSelector",
"cmd-alt-/": "agent::ToggleModelSelector",
"cmd-alt-/": "assistant::ToggleModelSelector",
"cmd-shift-a": "agent::ToggleContextPicker",
"cmd-shift-j": "agent::ToggleNavigationMenu",
"cmd-shift-i": "agent::ToggleOptionsMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"cmd-alt-e": "agent::RemoveAllContext",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-shift-enter": "agent::ContinueThread",
"alt-enter": "agent::ContinueWithBurnMode",
"cmd-alt-b": "agent::ToggleBurnMode"
}
},
{
"context": "AgentPanel > NavigationMenu",
"bindings": {
"shift-backspace": "agent::DeleteRecentlyOpenThread"
"shift-escape": "agent::ExpandMessageEditor",
"cmd-e": "agent::ChatMode",
"cmd-alt-e": "agent::RemoveAllContext"
}
},
{
@@ -313,7 +315,6 @@
"use_key_equivalents": true,
"bindings": {
"enter": "agent::Chat",
"cmd-enter": "agent::ChatWithFollow",
"cmd-i": "agent::ToggleProfileSelector",
"shift-ctrl-r": "agent::OpenAgentDiff"
}
@@ -361,18 +362,15 @@
}
},
{
"context": "ThreadHistory > Editor",
"context": "ThreadHistory",
"bindings": {
"shift-backspace": "agent::RemoveSelectedThread"
"ctrl--": "pane::GoBack"
}
},
{
"context": "PromptLibrary",
"use_key_equivalents": true,
"context": "ThreadHistory > Editor",
"bindings": {
"cmd-n": "rules_library::NewRule",
"cmd-shift-s": "rules_library::ToggleDefaultRule",
"cmd-w": "workspace::CloseWindow"
"shift-backspace": "agent::RemoveSelectedThread"
}
},
{
@@ -552,9 +550,7 @@
"use_key_equivalents": true,
"bindings": {
"cmd-shift-o": "outline::Toggle",
"ctrl-g": "go_to_line::Toggle",
"cmd-shift-backspace": "editor::GoToPreviousChange",
"cmd-shift-alt-backspace": "editor::GoToNextChange"
"ctrl-g": "go_to_line::Toggle"
}
},
{
@@ -584,7 +580,6 @@
// "alt-cmd-o": ["projects::OpenRecent", {"create_new_window": true }],
"alt-cmd-o": "projects::OpenRecent",
"ctrl-cmd-o": "projects::OpenRemote",
"ctrl-cmd-shift-o": ["projects::OpenRemote", { "from_existing_connection": true }],
"alt-cmd-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",
"cmd-s": "workspace::Save",
@@ -620,8 +615,7 @@
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-shift-b": "outline_panel::ToggleFocus",
"ctrl-shift-g": "git_panel::ToggleFocus",
"cmd-shift-d": "debug_panel::ToggleFocus",
"cmd-?": "agent::ToggleFocus",
"cmd-?": "assistant::ToggleFocus",
"cmd-alt-s": "workspace::SaveAll",
"cmd-k m": "language_selector::Toggle",
"escape": "workspace::Unfollow",
@@ -633,8 +627,7 @@
"cmd-k shift-right": "workspace::SwapPaneRight",
"cmd-k shift-up": "workspace::SwapPaneUp",
"cmd-k shift-down": "workspace::SwapPaneDown",
"cmd-shift-x": "zed::Extensions",
"f5": "debugger::RerunLastSession"
"cmd-shift-x": "zed::Extensions"
}
},
{
@@ -651,20 +644,6 @@
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
}
},
{
"context": "Workspace && debugger_running",
"use_key_equivalents": true,
"bindings": {
"f5": "zed::NoAction"
}
},
{
"context": "Workspace && debugger_stopped",
"use_key_equivalents": true,
"bindings": {
"f5": "debugger::Continue"
}
},
// Bindings from Sublime Text
{
"context": "Editor",
@@ -753,8 +732,7 @@
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
// TODO: Move this to a dock open action
"cmd-shift-c": "collab_panel::ToggleFocus",
// Only available in debug builds: opens an element inspector for development.
"cmd-alt-i": "dev::ToggleInspector"
"cmd-alt-i": "zed::DebugElements"
}
},
{
@@ -776,10 +754,10 @@
"use_key_equivalents": true,
"bindings": {
"cmd-shift-a": "agent::ToggleContextPicker",
"cmd-alt-/": "agent::ToggleModelSelector",
"cmd-alt-/": "assistant::ToggleModelSelector",
"cmd-alt-e": "agent::RemoveAllContext",
"ctrl-[": "agent::CyclePreviousInlineAssist",
"ctrl-]": "agent::CycleNextInlineAssist"
"ctrl-[": "assistant::CyclePreviousInlineAssist",
"ctrl-]": "assistant::CycleNextInlineAssist"
}
},
{
@@ -809,7 +787,7 @@
"cmd-alt-c": "workspace::CopyPath",
"alt-cmd-shift-c": "workspace::CopyRelativePath",
"alt-cmd-r": "outline_panel::RevealInFileManager",
"space": "outline_panel::OpenSelectedEntry",
"space": "outline_panel::Open",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious",
"alt-enter": "editor::OpenExcerpts",
@@ -839,7 +817,7 @@
"alt-cmd-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-alt-shift-f": "project_panel::NewSearchInDirectory",
"cmd-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious",
"escape": "menu::Cancel"
@@ -857,10 +835,7 @@
"use_key_equivalents": true,
"bindings": {
"left": "variable_list::CollapseSelectedEntry",
"right": "variable_list::ExpandSelectedEntry",
"enter": "variable_list::EditVariable",
"cmd-c": "variable_list::CopyVariableValue",
"cmd-alt-c": "variable_list::CopyVariableName"
"right": "variable_list::ExpandSelectedEntry"
}
},
{
@@ -945,21 +920,6 @@
"alt-tab": "git::GenerateCommitMessage"
}
},
{
"context": "DebugPanel",
"bindings": {
"cmd-t": "debugger::ToggleThreadPicker",
"cmd-i": "debugger::ToggleSessionPicker",
"shift-alt-escape": "debugger::ToggleExpandItem"
}
},
{
"context": "BreakpointList",
"bindings": {
"space": "debugger::ToggleEnableBreakpoint",
"backspace": "debugger::UnsetBreakpoint"
}
},
{
"context": "CollabPanel && not_editing",
"use_key_equivalents": true,
@@ -1001,14 +961,6 @@
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "FileFinder",
"use_key_equivalents": true,
"bindings": {
"cmd-shift-a": "file_finder::ToggleSplitMenu",
"cmd-shift-i": "file_finder::ToggleFilterMenu"
}
},
{
"context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)",
"use_key_equivalents": true,
@@ -1051,7 +1003,6 @@
"alt-right": ["terminal::SendText", "\u001bf"],
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
"ctrl-delete": ["terminal::SendText", "\u001bd"],
// There are conflicting bindings for these keys in the global context.
// these bindings override them, remove at your own risk:
"up": ["terminal::SendKeystroke", "up"],
@@ -1061,7 +1012,6 @@
"escape": ["terminal::SendKeystroke", "escape"],
"enter": ["terminal::SendKeystroke", "enter"],
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
"ctrl-backspace": ["terminal::SendKeystroke", "ctrl-w"],
"shift-pageup": "terminal::ScrollPageUp",
"cmd-up": "terminal::ScrollPageUp",
"shift-pagedown": "terminal::ScrollPageDown",
@@ -1075,11 +1025,10 @@
// Using `ctrl-shift-space` in Zed requires disabling the macOS global shortcut.
// System Preferences->Keyboard->Keyboard Shortcuts->Input Sources->Select the previous input source (uncheck)
"ctrl-shift-space": "terminal::ToggleViMode",
"ctrl-alt-up": "pane::SplitUp",
"ctrl-alt-down": "pane::SplitDown",
"ctrl-alt-left": "pane::SplitLeft",
"ctrl-alt-right": "pane::SplitRight",
"cmd-alt-r": "terminal::RerunTask"
"ctrl-k up": "pane::SplitUp",
"ctrl-k down": "pane::SplitDown",
"ctrl-k left": "pane::SplitLeft",
"ctrl-k right": "pane::SplitRight"
}
},
{
@@ -1108,36 +1057,5 @@
"bindings": {
"escape": "menu::Cancel"
}
},
{
"context": "ConfigureContextServerModal > Editor",
"use_key_equivalents": true,
"bindings": {
"escape": "menu::Cancel",
"enter": "editor::Newline",
"cmd-enter": "menu::Confirm"
}
},
{
"context": "Diagnostics",
"use_key_equivalents": true,
"bindings": {
"ctrl-r": "diagnostics::ToggleDiagnosticsRefresh"
}
},
{
"context": "DebugConsole > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "menu::Confirm"
}
},
{
"context": "RunModal",
"use_key_equivalents": true,
"bindings": {
"ctrl-tab": "pane::ActivateNextItem",
"ctrl-shift-tab": "pane::ActivatePreviousItem"
}
}
]

View File

@@ -1,85 +0,0 @@
[
// Cursor for MacOS. See: https://docs.cursor.com/kbd
{
"context": "Workspace",
"use_key_equivalents": true,
"bindings": {
"ctrl-i": "agent::ToggleFocus",
"ctrl-shift-i": "agent::ToggleFocus",
"ctrl-l": "agent::ToggleFocus",
"ctrl-shift-l": "agent::ToggleFocus",
"ctrl-alt-b": "agent::ToggleFocus",
"ctrl-shift-j": "agent::OpenConfiguration"
}
},
{
"context": "Editor && mode == full",
"use_key_equivalents": true,
"bindings": {
"ctrl-i": "agent::ToggleFocus",
"ctrl-shift-i": "agent::ToggleFocus",
"ctrl-shift-l": "assistant::QuoteSelection", // In cursor uses "Ask" mode
"ctrl-l": "assistant::QuoteSelection", // In cursor uses "Agent" mode
"ctrl-k": "assistant::InlineAssist",
"ctrl-shift-k": "assistant::InsertIntoEditor"
}
},
{
"context": "InlineAssistEditor",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-backspace": "editor::Cancel"
// "alt-enter": // Quick Question
// "ctrl-shift-enter": // Full File Context
// "ctrl-shift-k": // Toggle input focus (editor <> inline assist)
}
},
{
"context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-i": "workspace::ToggleRightDock",
"ctrl-shift-i": "workspace::ToggleRightDock",
"ctrl-l": "workspace::ToggleRightDock",
"ctrl-shift-l": "workspace::ToggleRightDock",
"ctrl-alt-b": "workspace::ToggleRightDock",
"ctrl-w": "workspace::ToggleRightDock", // technically should close chat
"ctrl-.": "agent::ToggleProfileSelector",
"ctrl-/": "agent::ToggleModelSelector",
"ctrl-shift-backspace": "editor::Cancel",
"ctrl-r": "agent::NewThread",
"ctrl-shift-v": "editor::Paste",
"ctrl-shift-k": "assistant::InsertIntoEditor"
// "escape": "agent::ToggleFocus"
///// Enable when Zed supports multiple thread tabs
// "ctrl-t": // new thread tab
// "ctrl-[": // next thread tab
// "ctrl-]": // next thread tab
///// Enable if Zed adds support for keyboard navigation of thread elements
// "tab": // cycle to next message
// "shift-tab": // cycle to previous message
}
},
{
"context": "Editor && editor_agent_diff",
"use_key_equivalents": true,
"bindings": {
"ctrl-enter": "agent::KeepAll",
"ctrl-backspace": "agent::RejectAll"
}
},
{
"context": "Editor && mode == full && edit_prediction",
"use_key_equivalents": true,
"bindings": {
"ctrl-right": "editor::AcceptPartialEditPrediction"
}
},
{
"context": "Terminal",
"use_key_equivalents": true,
"bindings": {
"ctrl-k": "assistant::InlineAssist"
}
}
]

View File

@@ -72,9 +72,7 @@
"alt-left": "editor::SelectToPreviousWordStart",
"alt-right": "editor::SelectToNextWordEnd",
"pagedown": "editor::SelectPageDown",
"ctrl-v": "editor::SelectPageDown",
"pageup": "editor::SelectPageUp",
"alt-v": "editor::SelectPageUp",
"ctrl-f": "editor::SelectRight",
"ctrl-b": "editor::SelectLeft",
"ctrl-n": "editor::SelectDown",

View File

@@ -52,10 +52,8 @@
"shift-alt-m": "markdown::OpenPreviewToTheSide",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd",
"alt-right": "editor::MoveToNextSubwordEnd",
"alt-left": "editor::MoveToPreviousSubwordStart",
"alt-shift-right": "editor::SelectToNextSubwordEnd",
"alt-shift-left": "editor::SelectToPreviousSubwordStart"
"f3": "editor::FindNextMatch",
"shift-f3": "editor::FindPreviousMatch"
}
},
{

View File

@@ -1,85 +0,0 @@
[
// Cursor for MacOS. See: https://docs.cursor.com/kbd
{
"context": "Workspace",
"use_key_equivalents": true,
"bindings": {
"cmd-i": "agent::ToggleFocus",
"cmd-shift-i": "agent::ToggleFocus",
"cmd-l": "agent::ToggleFocus",
"cmd-shift-l": "agent::ToggleFocus",
"cmd-alt-b": "agent::ToggleFocus",
"cmd-shift-j": "agent::OpenConfiguration"
}
},
{
"context": "Editor && mode == full",
"use_key_equivalents": true,
"bindings": {
"cmd-i": "agent::ToggleFocus",
"cmd-shift-i": "agent::ToggleFocus",
"cmd-shift-l": "assistant::QuoteSelection", // In cursor uses "Ask" mode
"cmd-l": "assistant::QuoteSelection", // In cursor uses "Agent" mode
"cmd-k": "assistant::InlineAssist",
"cmd-shift-k": "assistant::InsertIntoEditor"
}
},
{
"context": "InlineAssistEditor",
"use_key_equivalents": true,
"bindings": {
"cmd-shift-backspace": "editor::Cancel"
// "alt-enter": // Quick Question
// "cmd-shift-enter": // Full File Context
// "cmd-shift-k": // Toggle input focus (editor <> inline assist)
}
},
{
"context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
"use_key_equivalents": true,
"bindings": {
"cmd-i": "workspace::ToggleRightDock",
"cmd-shift-i": "workspace::ToggleRightDock",
"cmd-l": "workspace::ToggleRightDock",
"cmd-shift-l": "workspace::ToggleRightDock",
"cmd-alt-b": "workspace::ToggleRightDock",
"cmd-w": "workspace::ToggleRightDock", // technically should close chat
"cmd-.": "agent::ToggleProfileSelector",
"cmd-/": "agent::ToggleModelSelector",
"cmd-shift-backspace": "editor::Cancel",
"cmd-r": "agent::NewThread",
"cmd-shift-v": "editor::Paste",
"cmd-shift-k": "assistant::InsertIntoEditor"
// "escape": "agent::ToggleFocus"
///// Enable when Zed supports multiple thread tabs
// "cmd-t": // new thread tab
// "cmd-[": // next thread tab
// "cmd-]": // next thread tab
///// Enable if Zed adds support for keyboard navigation of thread elements
// "tab": // cycle to next message
// "shift-tab": // cycle to previous message
}
},
{
"context": "Editor && editor_agent_diff",
"use_key_equivalents": true,
"bindings": {
"cmd-enter": "agent::KeepAll",
"cmd-backspace": "agent::RejectAll"
}
},
{
"context": "Editor && mode == full && edit_prediction",
"use_key_equivalents": true,
"bindings": {
"cmd-right": "editor::AcceptPartialEditPrediction"
}
},
{
"context": "Terminal",
"use_key_equivalents": true,
"bindings": {
"cmd-k": "assistant::InlineAssist"
}
}
]

View File

@@ -72,9 +72,7 @@
"alt-left": "editor::SelectToPreviousWordStart",
"alt-right": "editor::SelectToNextWordEnd",
"pagedown": "editor::SelectPageDown",
"ctrl-v": "editor::SelectPageDown",
"pageup": "editor::SelectPageUp",
"alt-v": "editor::SelectPageUp",
"ctrl-f": "editor::SelectRight",
"ctrl-b": "editor::SelectLeft",
"ctrl-n": "editor::SelectDown",

View File

@@ -54,10 +54,8 @@
"shift-alt-m": "markdown::OpenPreviewToTheSide",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd",
"ctrl-right": "editor::MoveToNextSubwordEnd",
"ctrl-left": "editor::MoveToPreviousSubwordStart",
"ctrl-shift-right": "editor::SelectToNextSubwordEnd",
"ctrl-shift-left": "editor::SelectToPreviousSubwordStart"
"cmd-g": "editor::FindNextMatch",
"cmd-shift-g": "editor::FindPreviousMatch"
}
},
{

View File

@@ -50,12 +50,6 @@
"] -": "vim::NextLesserIndent",
"] +": "vim::NextGreaterIndent",
"] =": "vim::NextSameIndent",
"] b": "pane::ActivateNextItem",
"[ b": "pane::ActivatePreviousItem",
"] shift-b": "pane::ActivateLastItem",
"[ shift-b": ["pane::ActivateItem", 0],
"] space": "vim::InsertEmptyLineBelow",
"[ space": "vim::InsertEmptyLineAbove",
// Word motions
"w": "vim::NextWordStart",
"e": "vim::NextWordEnd",
@@ -114,11 +108,7 @@
"ctrl-e": "vim::LineDown",
"ctrl-y": "vim::LineUp",
// "g" commands
"g shift-r": "vim::PushReplaceWithRegister",
"g r n": "editor::Rename",
"g r r": "editor::FindAllReferences",
"g r i": "editor::GoToImplementation",
"g r a": "editor::ToggleCodeActions",
"g r": "vim::PushReplaceWithRegister",
"g g": "vim::StartOfDocument",
"g h": "editor::Hover",
"g t": "pane::ActivateNextItem",
@@ -137,7 +127,6 @@
"g <": ["editor::SelectPrevious", { "replace_newest": true }],
"g a": "editor::SelectAllMatches",
"g s": "outline::Toggle",
"g shift-o": "outline::Toggle",
"g shift-s": "project_symbols::Toggle",
"g .": "editor::ToggleCodeActions", // zed specific
"g shift-a": "editor::FindAllReferences", // zed specific
@@ -152,7 +141,6 @@
"g end": ["vim::EndOfLine", { "display_lines": true }],
"g 0": ["vim::StartOfLine", { "display_lines": true }],
"g home": ["vim::StartOfLine", { "display_lines": true }],
"g shift-m": ["vim::MiddleOfLine", { "display_lines": true }],
"g ^": ["vim::FirstNonWhitespace", { "display_lines": true }],
"g v": "vim::RestoreVisualSelection",
"g ]": "editor::GoToDiagnostic",
@@ -202,8 +190,7 @@
"ctrl-w g shift-d": "editor::GoToTypeDefinitionSplit",
"ctrl-w space": "editor::OpenExcerptsSplit",
"ctrl-w g space": "editor::OpenExcerptsSplit",
"ctrl-6": "pane::AlternateFile",
"ctrl-^": "pane::AlternateFile"
"ctrl-6": "pane::AlternateFile"
}
},
{
@@ -317,7 +304,7 @@
"!": "vim::ShellCommand",
"i": ["vim::PushObject", { "around": false }],
"a": ["vim::PushObject", { "around": true }],
"g shift-r": ["vim::Paste", { "preserve_clipboard": true }],
"g r": ["vim::Paste", { "preserve_clipboard": true }],
"g c": "vim::ToggleComments",
"g q": "vim::Rewrap",
"g ?": "vim::ConvertToRot13",
@@ -351,8 +338,7 @@
"ctrl-shift-q": ["vim::PushLiteral", {}],
"ctrl-r": "vim::PushRegister",
"insert": "vim::ToggleReplace",
"ctrl-o": "vim::TemporaryNormal",
"ctrl-s": "editor::ShowSignatureHelp"
"ctrl-o": "vim::TemporaryNormal"
}
},
{
@@ -517,14 +503,12 @@
"'": "vim::Quotes",
"`": "vim::BackQuotes",
"\"": "vim::DoubleQuotes",
// "q": "vim::AnyQuotes",
"q": "vim::MiniQuotes",
"q": "vim::AnyQuotes",
"|": "vim::VerticalBars",
"(": "vim::Parentheses",
")": "vim::Parentheses",
"b": "vim::Parentheses",
// "b": "vim::AnyBrackets",
// "b": "vim::MiniBrackets",
"[": "vim::SquareBrackets",
"]": "vim::SquareBrackets",
"r": "vim::SquareBrackets",
@@ -645,10 +629,9 @@
}
},
{
"context": "vim_operator == gR",
"context": "vim_operator == gr",
"bindings": {
"r": "vim::CurrentLine",
"shift-r": "vim::CurrentLine"
"r": "vim::CurrentLine"
}
},
{
@@ -709,7 +692,7 @@
}
},
{
"context": "GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || DebugPanel",
"context": "GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
"bindings": {
// window related commands (ctrl-w X)
"ctrl-w": null,
@@ -803,7 +786,8 @@
"{": "project_panel::SelectPrevDirectory",
"shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst",
"-": "project_panel::SelectParent"
"-": "project_panel::SelectParent",
"ctrl-6": "pane::AlternateFile"
}
},
{
@@ -838,19 +822,6 @@
"tab": "editor::AcceptEditPrediction"
}
},
{
"context": "MessageEditor > Editor && VimControl",
"bindings": {
"enter": "agent::Chat",
// TODO: Implement search
"/": null,
"?": null,
"#": null,
"*": null,
"n": null,
"shift-n": null
}
},
{
"context": "os != macos && Editor && edit_prediction_conflict",
"bindings": {
@@ -859,5 +830,13 @@
// and Windows.
"alt-l": "editor::AcceptEditPrediction"
}
},
{
// Fixes https://github.com/zed-industries/zed/issues/29095 by ensuring that
// the last binding for editor::ToggleComments is not ctrl-c.
"context": "hack_to_fix_ctrl-c",
"bindings": {
"g c": "editor::ToggleComments"
}
}
]

View File

@@ -3,12 +3,11 @@ You are a highly skilled software engineer with extensive knowledge in many prog
## Communication
1. Be conversational but professional.
2. Refer to the user in the second person and yourself in the first person.
2. Refer to the USER in the second person and yourself in the first person.
3. Format your responses in markdown. Use backticks to format file, directory, function, and class names.
4. NEVER lie or make things up.
5. Refrain from apologizing all the time when results are unexpected. Instead, just try your best to proceed or explain the circumstances to the user without apologizing.
{{#if has_tools}}
## Tool Use
1. Make sure to adhere to the tools schema.
@@ -16,113 +15,23 @@ You are a highly skilled software engineer with extensive knowledge in many prog
3. DO NOT use tools to access items that are already available in the context section.
4. Use only the tools that are currently available.
5. DO NOT use a tool that is not available just because it appears in the conversation. This means the user turned it off.
6. NEVER run commands that don't terminate on their own such as web servers (like `npm run start`, `npm run dev`, `python -m http.server`, etc) or file watchers.
## Searching and Reading
If you are unsure how to fulfill the user's request, gather more information with tool calls and/or clarifying questions.
{{! TODO: If there are files, we should mention it but otherwise omit that fact }}
{{#if has_tools}}
If appropriate, use tool calls to explore the current project, which contains the following root directories:
{{#each worktrees}}
- `{{root_name}}`
{{/each}}
- Bias towards not asking the user for help if you can find the answer yourself.
- When providing paths to tools, the path should always begin with a path that starts with a project root directory listed above.
- Before you read or edit a file, you must first find the full path. DO NOT ever guess a file path!
{{# if (has_tool 'grep') }}
- When looking for symbols in the project, prefer the `grep` tool.
- As you learn about the structure of the project, use that information to scope `grep` searches to targeted subtrees of the project.
- The user might specify a partial file path. If you don't know the full path, use `find_path` (not `grep`) before you read the file.
{{/if}}
{{/if}}
{{else}}
You are being tasked with providing a response, but you have no ability to use tools or to read or write any aspect of the user's system (other than any context the user might have provided to you).
- Bias towards not asking the user for help if you can find the answer yourself.
As such, if you need the user to perform any actions for you, you must request them explicitly. Bias towards giving a response to the best of your ability, and then making requests for the user to take action (e.g. to give you more context) only optionally.
The one exception to this is if the user references something you don't know about - for example, the name of a source code file, function, type, or other piece of code that you have no awareness of. In this case, you MUST NOT MAKE SOMETHING UP, or assume you know what that thing is or how it works. Instead, you must ask the user for clarification rather than giving a response.
{{/if}}
## Code Block Formatting
Whenever you mention a code block, you MUST use ONLY use the following format:
```path/to/Something.blah#L123-456
(code goes here)
```
The `#L123-456` means the line number range 123 through 456, and the path/to/Something.blah
is a path in the project. (If there is no valid path in the project, then you can use
/dev/null/path.extension for its path.) This is the ONLY valid way to format code blocks, because the Markdown parser
does not understand the more common ```language syntax, or bare ``` blocks. It only
understands this path-based syntax, and if the path is missing, then it will error and you will have to do it over again.
Just to be really clear about this, if you ever find yourself writing three backticks followed by a language name, STOP!
You have made a mistake. You can only ever put paths after triple backticks!
<example>
Based on all the information I've gathered, here's a summary of how this system works:
1. The README file is loaded into the system.
2. The system finds the first two headers, including everything in between. In this case, that would be:
```path/to/README.md#L8-12
# First Header
This is the info under the first header.
## Sub-header
```
3. Then the system finds the last header in the README:
```path/to/README.md#L27-29
## Last Header
This is the last header in the README.
```
4. Finally, it passes this information on to the next process.
</example>
<example>
In Markdown, hash marks signify headings. For example:
```/dev/null/example.md#L1-3
# Level 1 heading
## Level 2 heading
### Level 3 heading
```
</example>
Here are examples of ways you must never render code blocks:
<bad_example_do_not_do_this>
In Markdown, hash marks signify headings. For example:
```
# Level 1 heading
## Level 2 heading
### Level 3 heading
```
</bad_example_do_not_do_this>
This example is unacceptable because it does not include the path.
<bad_example_do_not_do_this>
In Markdown, hash marks signify headings. For example:
```markdown
# Level 1 heading
## Level 2 heading
### Level 3 heading
```
</bad_example_do_not_do_this>
This example is unacceptable because it has the language instead of the path.
<bad_example_do_not_do_this>
In Markdown, hash marks signify headings. For example:
# Level 1 heading
## Level 2 heading
### Level 3 heading
</bad_example_do_not_do_this>
This example is unacceptable because it uses indentation to mark the code block
instead of backticks with a path.
<bad_example_do_not_do_this>
In Markdown, hash marks signify headings. For example:
```markdown
/dev/null/example.md#L1-3
# Level 1 heading
## Level 2 heading
### Level 3 heading
```
</bad_example_do_not_do_this>
This example is unacceptable because the path is in the wrong place. The path must be directly after the opening backticks.
{{#if has_tools}}
## Fixing Diagnostics
1. Make 1-2 attempts at fixing diagnostics, then defer to the user.
@@ -136,11 +45,10 @@ Otherwise, follow debugging best practices:
2. Add descriptive logging statements and error messages to track variable and code state.
3. Add test functions and statements to isolate the problem.
{{/if}}
## Calling External APIs
1. Unless explicitly requested by the user, use the best suited external APIs and packages to solve the task. There is no need to ask the user for permission.
2. When selecting which version of an API or package to use, choose one that is compatible with the user's dependency management file(s). If no such file exists or if the package is not present, use the latest version that is in your training data.
2. When selecting which version of an API or package to use, choose one that is compatible with the user's dependency management file. If no such file exists or if the package is not present, use the latest version that is in your training data.
3. If an external API requires an API Key, be sure to point this out to the user. Adhere to best security practices (e.g. DO NOT hardcode an API key in a place where it can be exposed)
## System Information
@@ -148,10 +56,10 @@ Otherwise, follow debugging best practices:
Operating System: {{os}}
Default Shell: {{shell}}
{{#if (or has_rules has_user_rules)}}
{{#if (or has_rules has_default_user_rules)}}
## User's Custom Instructions
The following additional instructions are provided by the user, and should be followed to the best of your ability{{#if has_tools}} without interfering with the tool use guidelines{{/if}}.
The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the tool use guidelines.
{{#if has_rules}}
There are project rules that apply to these root directories:

View File

@@ -49,9 +49,10 @@ And here's the section to rewrite based on that prompt again for reference:
</rewrite_this>
{{#if diagnostic_errors}}
{{#each diagnostic_errors}}
Below are the diagnostic errors visible to the user. If the user requests problems to be fixed, use this information, but do not try to fix these errors if the user hasn't asked you to.
{{#each diagnostic_errors}}
<diagnostic_error>
<line_number>{{line_number}}</line_number>
<error_message>{{error_message}}</error_message>

View File

@@ -0,0 +1,206 @@
<task_description>
The user of a code editor wants to make a change to their codebase.
You must describe the change using the following XML structure:
- <patch> - A group of related code changes.
Child tags:
- <title> (required) - A high-level description of the changes. This should be as short
as possible, possibly using common abbreviations.
- <edit> (1 or more) - An edit to make at a particular range within a file.
Includes the following child tags:
- <path> (required) - The path to the file that will be changed.
- <description> (optional) - An arbitrarily-long comment that describes the purpose
of this edit.
- <old_text> (optional) - An excerpt from the file's current contents that uniquely
identifies a range within the file where the edit should occur. Required for all operations
except `create`.
- <new_text> (required) - The new text to insert into the file.
- <operation> (required) - The type of change that should occur at the given range
of the file. Must be one of the following values:
- `update`: Replaces the entire range with the new text.
- `insert_before`: Inserts the new text before the range.
- `insert_after`: Inserts new text after the range.
- `create`: Creates or overwrites a file with the given path and the new text.
- `delete`: Deletes the specified range from the file.
<guidelines>
- Never provide multiple edits whose ranges intersect each other. Instead, merge them into one edit.
- Prefer multiple edits to smaller, disjoint ranges, rather than one edit to a larger range.
- There's no need to escape angle brackets within XML tags.
- Always ensure imports are added if you're referencing symbols that are not in scope.
</guidelines>
Here are some concrete examples.
<example>
<message role="user">
```rs src/shapes.rs
pub mod rectangle;
pub mod circle;
```
```rs src/shapes/rectangle.rs
pub struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
pub fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
}
```
```rs src/shapes/circle.rs
pub struct Circle {
radius: f64,
}
impl Circle {
pub fn new(radius: f64) -> Self {
Circle { radius }
}
}
```
Update all shapes to store their origin as an (x, y) tuple and implement Display.
</message>
<message role="assistant">
We'll need to update both the rectangle and circle modules.
<patch>
<title>Add origins and display impls to shapes</title>
<edit>
<path>src/shapes/rectangle.rs</path>
<description>Add the origin field to Rectangle struct</description>
<operation>insert_after</operation>
<old_text>
pub struct Rectangle {
</old_text>
<new_text>
origin: (f64, f64),
</new_text>
</edit>
<edit>
<path>src/shapes/rectangle.rs</path>
<operation>update</operation>
<old_text>
fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
</old_text>
<new_text>
fn new(origin: (f64, f64), width: f64, height: f64) -> Self {
Rectangle { origin, width, height }
}
</new_text>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<description>Add the origin field to Circle struct</description>
<operation>insert_after</operation>
<old_text>
pub struct Circle {
radius: f64,
</old_text>
<new_text>
origin: (f64, f64),
</new_text>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<operation>update</operation>
<old_text>
fn new(radius: f64) -> Self {
Circle { radius }
}
</old_text>
<new_text>
fn new(origin: (f64, f64), radius: f64) -> Self {
Circle { origin, radius }
}
</new_text>
</edit>
</step>
<edit>
<path>src/shapes/rectangle.rs</path>
<operation>insert_before</operation>
<old_text>
struct Rectangle {
</old_text>
<new_text>
use std::fmt;
</new_text>
</edit>
<edit>
<path>src/shapes/rectangle.rs</path>
<description>
Add a manual Display implementation for Rectangle.
Currently, this is the same as a derived Display implementation.
</description>
<operation>insert_after</operation>
<old_text>
Rectangle { width, height }
}
}
</old_text>
<new_text>
impl fmt::Display for Rectangle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.format_struct(f, "Rectangle")
.field("origin", &self.origin)
.field("width", &self.width)
.field("height", &self.height)
.finish()
}
}
</new_text>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<operation>insert_before</operation>
<old_text>
struct Circle {
</old_text>
<new_text>
use std::fmt;
</new_text>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<operation>insert_after</operation>
<old_text>
Circle { radius }
}
}
</old_text>
<new_text>
impl fmt::Display for Rectangle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.format_struct(f, "Rectangle")
.field("origin", &self.origin)
.field("width", &self.width)
.field("height", &self.height)
.finish()
}
}
</new_text>
</edit>
</patch>
</message>
</example>
</task_description>

View File

@@ -67,8 +67,6 @@
"ui_font_weight": 400,
// The default font size for text in the UI
"ui_font_size": 16,
// The default font size for text in the agent panel
"agent_font_size": 16,
// How much to fade out unused code.
"unnecessary_code_fade": 0.3,
// Active pane styling settings.
@@ -113,8 +111,8 @@
// Whether to show the informational hover box when moving the mouse
// over symbols in the editor.
"hover_popover_enabled": true,
// Time to wait in milliseconds before showing the informational hover box.
"hover_popover_delay": 300,
// Time to wait before showing the informational hover box
"hover_popover_delay": 350,
// Whether to confirm before quitting Zed.
"confirm_quit": false,
// Whether to restore last closed project when fresh Zed instance is opened.
@@ -128,8 +126,6 @@
//
// Default: true
"restore_on_file_reopen": true,
// Whether to automatically close files that have been deleted on disk.
"close_on_file_delete": false,
// Size of the drop target in the editor.
"drop_target_size": 0.2,
// Whether the window should be closed when using 'close active item' on a window with no tabs.
@@ -171,23 +167,7 @@
// Default: not set, defaults to "bar"
"cursor_shape": null,
// Determines when the mouse cursor should be hidden in an editor or input box.
//
// 1. Never hide the mouse cursor:
// "never"
// 2. Hide only when typing:
// "on_typing"
// 3. Hide on both typing and cursor movement:
// "on_typing_and_movement"
"hide_mouse": "on_typing_and_movement",
// Determines how snippets are sorted relative to other completion items.
//
// 1. Place snippets at the top of the completion list:
// "top"
// 2. Place snippets normally without any preference:
// "inline"
// 3. Place snippets at the bottom of the completion list:
// "bottom"
"snippet_sort_order": "inline",
// How to highlight the current line in the editor.
//
// 1. Don't highlight the current line:
@@ -215,30 +195,11 @@
// Whether to show the signature help after completion or a bracket pair inserted.
// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
"show_signature_help_after_edits": false,
// Whether to show code action button at start of buffer line.
"inline_code_actions": true,
// What to do when go to definition yields no results.
//
// 1. Do nothing: `none`
// 2. Find references for the same symbol: `find_all_references` (default)
"go_to_definition_fallback": "find_all_references",
// Which level to use to filter out diagnostics displayed in the editor.
//
// Affects the editor rendering only, and does not interrupt
// the functionality of diagnostics fetching and project diagnostics editor.
// Which files containing diagnostic errors/warnings to mark in the tabs.
// Diagnostics are only shown when file icons are also active.
// This setting only works when can take the following three values:
//
// Which diagnostic indicators to show in the scrollbar, their level should be more or equal to the specified severity level.
// Possible values:
// - "off" — no diagnostics are allowed
// - "error"
// - "warning"
// - "info"
// - "hint"
// - null — allow all diagnostics (default)
"diagnostics_max_severity": null,
// Whether to show wrap guides (vertical rulers) in the editor.
// Setting this to true will show a guide at the 'preferred_line_length' value
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
@@ -249,7 +210,7 @@
// Hide the values of in variables from visual display in private files
"redact_private_values": false,
// The default number of lines to expand excerpts in the multibuffer by.
"expand_excerpt_lines": 5,
"expand_excerpt_lines": 3,
// Globs to match against file paths to determine if a file is private.
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
// Whether to use additional LSP queries to format (and amend) the code after
@@ -323,27 +284,8 @@
"breadcrumbs": true,
// Whether to show quick action buttons.
"quick_actions": true,
// Whether to show the Selections menu in the editor toolbar.
"selections_menu": true,
// Whether to show agent review buttons in the editor toolbar.
"agent_review": true,
// Whether to show code action buttons in the editor toolbar.
"code_actions": false
},
// Titlebar related settings
"title_bar": {
// Whether to show the branch icon beside branch switcher in the titlebar.
"show_branch_icon": false,
// Whether to show the branch name button in the titlebar.
"show_branch_name": true,
// Whether to show the project host and name in the titlebar.
"show_project_items": true,
// Whether to show onboarding banners in the titlebar.
"show_onboarding_banner": true,
// Whether to show user picture in the titlebar.
"show_user_picture": true,
// Whether to show the sign in button in the titlebar.
"show_sign_in": true
// Whether to show the Selections menu in the editor toolbar
"selections_menu": true
},
// Scrollbar related settings
"scrollbar": {
@@ -385,45 +327,6 @@
"vertical": true
}
},
// Minimap related settings
"minimap": {
// When to show the minimap in the editor.
// This setting can take three values:
// 1. Show the minimap if the editor's scrollbar is visible:
// "auto"
// 2. Always show the minimap:
// "always"
// 3. Never show the minimap:
// "never" (default)
"show": "never",
// When to show the minimap thumb.
// This setting can take two values:
// 1. Show the minimap thumb if the mouse is over the minimap:
// "hover"
// 2. Always show the minimap thumb:
// "always" (default)
"thumb": "always",
// How the minimap thumb border should look.
// This setting can take five values:
// 1. Display a border on all sides of the thumb:
// "thumb_border": "full"
// 2. Display a border on all sides except the left side of the thumb:
// "thumb_border": "left_open" (default)
// 3. Display a border on all sides except the right side of the thumb:
// "thumb_border": "right_open"
// 4. Display a border only on the left side of the thumb:
// "thumb_border": "left_only"
// 5. Display the thumb without any border:
// "thumb_border": "none"
"thumb_border": "left_open",
// How to highlight the current line in the minimap.
// This setting can take the following values:
//
// 1. `null` to inherit the editor `current_line_highlight` setting (default)
// 2. "line" or "all" to highlight the current line in the minimap.
// 3. "gutter" or "none" to not highlight the current line in the minimap.
"current_line_highlight": null
},
// Enable middle-click paste on Linux.
"middle_click_paste": true,
// What to do when multibuffer is double clicked in some of its excerpts
@@ -438,6 +341,8 @@
"gutter": {
// Whether to show line numbers in the gutter.
"line_numbers": true,
// Whether to show code action buttons in the gutter.
"code_actions": true,
// Whether to show runnables buttons in the gutter.
"runnables": true,
// Whether to show breakpoints in the gutter.
@@ -477,17 +382,11 @@
// Scroll sensitivity multiplier. This multiplier is applied
// to both the horizontal and vertical delta values while scrolling.
"scroll_sensitivity": 1.0,
// Scroll sensitivity multiplier for fast scrolling. This multiplier is applied
// to both the horizontal and vertical delta values while scrolling. Fast scrolling
// happens when a user holds the alt or option key while scrolling.
"fast_scroll_sensitivity": 4.0,
"relative_line_numbers": false,
// If 'search_wrap' is disabled, search result do not wrap around the end of the file.
"search_wrap": true,
// Search options to enable by default when opening new project and buffer searches.
"search": {
// Whether to show the project search button in the status bar.
"button": true,
"whole_word": false,
"case_sensitive": false,
"include_ignored": false,
@@ -686,11 +585,6 @@
//
// Default: main
"fallback_branch_name": "main",
// Whether to sort entries in the panel by path
// or by status (the default).
//
// Default: false
"sort_by_path": false,
"scrollbar": {
// When to show the scrollbar in the git panel.
//
@@ -711,82 +605,37 @@
// Default width of the notification panel.
"default_width": 380
},
"agent": {
"assistant": {
// Version of this setting.
"version": "2",
// Whether the agent is enabled.
// Whether the assistant is enabled.
"enabled": true,
/// What completion mode to start new threads in, if available. Can be 'normal' or 'burn'.
"preferred_completion_mode": "normal",
// Whether to show the agent panel button in the status bar.
// Whether to show the assistant panel button in the status bar.
"button": true,
// Where to dock the agent panel. Can be 'left', 'right' or 'bottom'.
// Where to dock the assistant panel. Can be 'left', 'right' or 'bottom'.
"dock": "right",
// Default width when the agent panel is docked to the left or right.
// Default width when the assistant is docked to the left or right.
"default_width": 640,
// Default height when the agent panel is docked to the bottom.
// Default height when the assistant is docked to the bottom.
"default_height": 320,
// The default model to use when creating new threads.
// The default model to use when creating new chats.
"default_model": {
// The provider to use.
"provider": "zed.dev",
// The model to use.
"model": "claude-sonnet-4"
"model": "claude-3-7-sonnet-latest"
},
// The model to use when applying edits from the assistant.
"editor_model": {
// The provider to use.
"provider": "zed.dev",
// The model to use.
"model": "claude-3-7-sonnet-latest"
},
// Additional parameters for language model requests. When making a request to a model, parameters will be taken
// from the last entry in this list that matches the model's provider and name. In each entry, both provider
// and model are optional, so that you can specify parameters for either one.
"model_parameters": [
// To set parameters for all requests to OpenAI models:
// {
// "provider": "openai",
// "temperature": 0.5
// }
//
// To set parameters for all requests in general:
// {
// "temperature": 0
// }
//
// To set parameters for a specific provider and model:
// {
// "provider": "zed.dev",
// "model": "claude-sonnet-4",
// "temperature": 1.0
// }
],
// When enabled, the agent can run potentially destructive actions without asking for your confirmation.
"always_allow_tool_actions": false,
// When enabled, the agent will stream edits.
"stream_edits": false,
// When enabled, agent edits will be displayed in single-file editors for review
"single_file_review": true,
// When enabled, show voting thumbs for feedback on agent edits.
"enable_feedback": true,
"default_profile": "write",
"profiles": {
"write": {
"name": "Write",
"enable_all_context_servers": true,
"tools": {
"copy_path": true,
"create_directory": true,
"create_file": true,
"delete_path": true,
"diagnostics": true,
"edit_file": true,
"fetch": true,
"list_directory": true,
"move_path": true,
"now": true,
"find_path": true,
"read_file": true,
"grep": true,
"terminal": true,
"thinking": true,
"web_search": true
}
},
"ask": {
"name": "Ask",
// We don't know which of the context server tools are safe for the "Ask" profile, so we don't enable them by default.
@@ -795,34 +644,50 @@
"contents": true,
"diagnostics": true,
"fetch": true,
"list_directory": true,
"list_directory": false,
"now": true,
"find_path": true,
"path_search": true,
"read_file": true,
"open": true,
"grep": true,
"thinking": true,
"web_search": true
}
},
"minimal": {
"name": "Minimal",
"enable_all_context_servers": false,
"tools": {}
"write": {
"name": "Write",
"enable_all_context_servers": true,
"tools": {
"batch_tool": false,
"code_actions": false,
"code_symbols": false,
"contents": false,
"copy_path": false,
"create_file": true,
"delete_path": false,
"diagnostics": true,
"edit_file": true,
"fetch": true,
"list_directory": true,
"move_path": false,
"now": false,
"path_search": true,
"read_file": true,
"grep": true,
"rename": false,
"symbol_info": false,
"terminal": true,
"thinking": true,
"web_search": true
}
}
},
// Where to show notifications when the agent has either completed
// Where to show notifications when an agent has either completed
// its response, or else needs confirmation before it can run a
// tool action.
// "primary_screen" - Show the notification only on your primary screen (default)
// "all_screens" - Show these notifications on all screens
// "never" - Never show these notifications
"notify_when_agent_waiting": "primary_screen",
// Whether to play a sound when the agent has either completed
// its response, or needs user input.
// Default: false
"play_sound_when_agent_done": false
"notify_when_agent_waiting": "primary_screen"
},
// The settings for slash commands.
"slash_commands": {
@@ -941,30 +806,7 @@
// "modal_max_width": "full"
//
// Default: small
"modal_max_width": "small",
// Determines whether the file finder should skip focus for the active file in search results.
// There are 2 possible values:
//
// 1. true: When searching for files, if the currently active file appears as the first result,
// auto-focus will skip it and focus the second result instead.
// "skip_focus_for_active_in_search": true
//
// 2. false: When searching for files, the first result will always receive focus,
// even if it's the currently active file.
// "skip_focus_for_active_in_search": false
//
// Default: true
"skip_focus_for_active_in_search": true,
// Whether to show the git status in the file finder.
"git_status": true,
// Whether to use gitignored files when searching.
// Only the file Zed had indexed will be used, not necessary all the gitignored files.
//
// Can accept 3 values:
// * `true`: Use all gitignored files
// * `false`: Use only the files Zed had indexed
// * `null`: Be smart and search for ignored when called from a gitignored worktree
"include_ignored": null
"modal_max_width": "small"
},
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
@@ -1016,8 +858,6 @@
"hard_tabs": false,
// How many columns a tab should occupy.
"tab_size": 4,
// What debuggers are preferred by default for all languages.
"debuggers": [],
// Control what info is collected by Zed.
"telemetry": {
// Send debug info like crash reports.
@@ -1030,8 +870,6 @@
"auto_update": true,
// Diagnostics configuration.
"diagnostics": {
// Whether to show the project diagnostics button in the status bar.
"button": true,
// Whether to show warnings or not by default.
"include_warnings": true,
// Settings for inline diagnostics
@@ -1049,13 +887,8 @@
// longer than this value will still push diagnostics further to the right.
"min_column": 0,
// The minimum severity of the diagnostics to show inline.
// Inherits editor's diagnostics' max severity settings when `null`.
// Shows all diagnostics when not specified.
"max_severity": null
},
"cargo": {
// When enabled, Zed disables rust-analyzer's check on save and starts to query
// Cargo diagnostics separately.
"fetch_cargo_diagnostics": false
}
},
// Files or globs of files that will be excluded by Zed entirely. They will be skipped during file
@@ -1135,9 +968,9 @@
// 2. Display predictions inline only when holding a modifier key (alt by default).
// "mode": "subtle"
"mode": "eager",
// Whether edit predictions are enabled when editing text threads.
// Whether edit predictions are enabled in the assistant panel.
// This setting has no effect if globally disabled.
"enabled_in_text_threads": true
"enabled_in_assistant": true
},
// Settings specific to journaling
"journal": {
@@ -1309,17 +1142,7 @@
// Settings related to running tasks.
"tasks": {
"variables": {},
"enabled": true,
// Use LSP tasks over Zed language extension ones.
// If no LSP tasks are returned due to error/timeout or regular execution,
// Zed language extension tasks will be used instead.
//
// Other Zed tasks will still be shown:
// * Zed task from either of the task config file
// * Zed task from history (e.g. one-off task was spawned before)
//
// Default: true
"prefer_lsp": true
"enabled": true
},
// An object whose keys are language names, and whose values
// are arrays of filenames or extensions of files that should
@@ -1337,22 +1160,21 @@
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
"Shell Script": [".env.*"]
},
// Settings for which version of Node.js and NPM to use when installing
// language servers and Copilot.
//
// Note: changing this setting currently requires restarting Zed.
"node": {
// By default, Zed will look for `node` and `npm` on your `$PATH`, and use the
// existing executables if their version is recent enough. Set this to `true`
// to prevent this, and force Zed to always download and install its own
// version of Node.
"ignore_system_version": false,
// You can also specify alternative paths to Node and NPM. If you specify
// `path`, but not `npm_path`, Zed will assume that `npm` is located at
// `${path}/../npm`.
"path": null,
"npm_path": null
},
// By default use a recent system version of node, or install our own.
// You can override this to use a version of node that is not in $PATH with:
// {
// "node": {
// "path": "/path/to/node"
// "npm_path": "/path/to/npm" (defaults to node_path/../npm)
// }
// }
// or to ensure Zed always downloads and installs an isolated version of node:
// {
// "node": {
// "ignore_system_version": true,
// }
// NOTE: changing this setting currently requires restarting Zed.
"node": {},
// The extensions that Zed should automatically install on startup.
//
// If you don't want any of these extensions, add this field to your settings
@@ -1450,9 +1272,6 @@
"Elixir": {
"language_servers": ["elixir-ls", "!next-ls", "!lexical", "..."]
},
"Elm": {
"tab_size": 4
},
"Erlang": {
"language_servers": ["erlang-ls", "!elp", "..."]
},
@@ -1670,12 +1489,7 @@
"use_multiline_find": false,
"use_smartcase_find": false,
"highlight_on_yank_duration": 200,
"custom_digraphs": {},
// Cursor shape for the each mode.
// Specify the mode as the key and the shape as the value.
// The mode can be one of the following: "normal", "replace", "insert", "visual".
// The shape can be one of the following: "block", "bar", "underline", "hollow".
"cursor_shape": {}
"custom_digraphs": {}
},
// The server to connect to. If the environment variable
// ZED_SERVER_URL is set, it will override this setting.
@@ -1727,6 +1541,8 @@
// "W": "workspace::Save"
// }
"command_aliases": {},
// Whether to show user picture in titlebar.
"show_user_picture": true,
// ssh_connections is an array of ssh connections.
// You can configure these from `project: Open Remote` in the command palette.
// Zed's ssh support will pull configuration from your ~/.ssh too.
@@ -1743,9 +1559,7 @@
// }
// ]
"ssh_connections": [],
// Whether to read ~/.ssh/config for ssh connection sources.
"read_ssh_config": true,
// Configures context servers for use by the agent.
// Configures context servers for use in the Assistant.
"context_servers": {},
"debugger": {
"stepping_granularity": "line",

View File

@@ -1,34 +1,32 @@
// Some example tasks for common languages.
//
// For more documentation on how to configure debug tasks,
// see: https://zed.dev/docs/debugger
[
{
"label": "Debug active PHP file",
"adapter": "PHP",
"adapter": "php",
"program": "$ZED_FILE",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT"
},
{
"label": "Debug active Python file",
"adapter": "Debugpy",
"adapter": "python",
"program": "$ZED_FILE",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT"
},
{
"label": "Debug active JavaScript file",
"adapter": "JavaScript",
"adapter": "javascript",
"program": "$ZED_FILE",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT"
},
{
"label": "JavaScript debug terminal",
"adapter": "JavaScript",
"adapter": "javascript",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT",
"console": "integratedTerminal"
"initialize_args": {
"console": "integratedTerminal"
}
}
]

View File

@@ -1,5 +0,0 @@
// Project-local debug tasks
//
// For more documentation on how to configure debug tasks,
// see: https://zed.dev/docs/debugger
[]

Binary file not shown.

View File

@@ -1,7 +1,2 @@
allow-private-module-inception = true
avoid-breaking-exported-api = false
ignore-interior-mutability = [
# Suppresses clippy::mutable_key_type, which is a false positive as the Eq
# and Hash impls do not use fields with interior mutability.
"agent::context::AgentContextKey"
]

View File

@@ -24,9 +24,8 @@ project.workspace = true
smallvec.workspace = true
ui.workspace = true
util.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
workspace-hack.workspace = true
[dev-dependencies]
editor = { workspace = true, features = ["test-support"] }
release_channel.workspace = true

View File

@@ -1,4 +1,4 @@
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage, VersionCheckType};
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
use editor::Editor;
use extension_host::ExtensionStore;
use futures::StreamExt;
@@ -43,7 +43,6 @@ pub struct ActivityIndicator {
context_menu_handle: PopoverMenuHandle<ContextMenu>,
}
#[derive(Debug)]
struct ServerStatus {
name: SharedString,
status: BinaryStatus,
@@ -60,7 +59,6 @@ struct Content {
message: String,
on_click:
Option<Arc<dyn Fn(&mut ActivityIndicator, &mut Window, &mut Context<ActivityIndicator>)>>,
tooltip_message: Option<String>,
}
impl ActivityIndicator {
@@ -72,7 +70,6 @@ impl ActivityIndicator {
) -> Entity<ActivityIndicator> {
let project = workspace.project().clone();
let auto_updater = AutoUpdater::get(cx);
let workspace_handle = cx.entity();
let this = cx.new(|cx| {
let mut status_events = languages.language_server_binary_statuses();
cx.spawn(async move |this, cx| {
@@ -87,23 +84,17 @@ impl ActivityIndicator {
})
.detach();
cx.subscribe_in(
&workspace_handle,
window,
|activity_indicator, _, event, window, cx| match event {
workspace::Event::ClearActivityIndicator { .. } => {
if activity_indicator.statuses.pop().is_some() {
activity_indicator.dismiss_error_message(
&DismissErrorMessage,
window,
cx,
);
cx.notify();
}
}
_ => {}
},
)
let mut status_events = languages.dap_server_binary_statuses();
cx.spawn(async move |this, cx| {
while let Some((name, status)) = status_events.next().await {
this.update(cx, |this, cx| {
this.statuses.retain(|s| s.name != name);
this.statuses.push(ServerStatus { name, status });
cx.notify();
})?;
}
anyhow::Ok(())
})
.detach();
cx.subscribe(
@@ -137,7 +128,7 @@ impl ActivityIndicator {
}
Self {
statuses: Vec::new(),
statuses: Default::default(),
project: project.clone(),
auto_updater,
context_menu_handle: Default::default(),
@@ -207,8 +198,11 @@ impl ActivityIndicator {
cx: &mut Context<Self>,
) {
if let Some(updater) = &self.auto_updater {
updater.update(cx, |updater, cx| updater.dismiss_error(cx));
updater.update(cx, |updater, cx| {
updater.dismiss_error(cx);
});
}
cx.notify();
}
fn pending_language_server_work<'a>(
@@ -263,7 +257,6 @@ impl ActivityIndicator {
});
window.dispatch_action(Box::new(workspace::OpenLog), cx);
})),
tooltip_message: None,
});
}
// Show any language server has pending activity.
@@ -307,32 +300,6 @@ impl ActivityIndicator {
),
message,
on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),
tooltip_message: None,
});
}
if let Some(session) = self
.project
.read(cx)
.dap_store()
.read(cx)
.sessions()
.find(|s| !s.read(cx).is_started())
{
return Some(Content {
icon: Some(
Icon::new(IconName::ArrowCircle)
.size(IconSize::Small)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(2)).repeat(),
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
)
.into_any_element(),
),
message: format!("Debug: {}", session.read(cx).adapter()),
tooltip_message: Some(session.read(cx).label().to_string()),
on_click: None,
});
}
@@ -360,7 +327,6 @@ impl ActivityIndicator {
),
message: job_info.message.into(),
on_click: None,
tooltip_message: None,
});
}
}
@@ -403,7 +369,6 @@ impl ActivityIndicator {
.retain(|status| !downloading.contains(&status.name));
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: None,
});
}
@@ -432,7 +397,6 @@ impl ActivityIndicator {
.retain(|status| !checking_for_update.contains(&status.name));
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: None,
});
}
@@ -459,7 +423,6 @@ impl ActivityIndicator {
on_click: Some(Arc::new(|this, window, cx| {
this.show_error_message(&Default::default(), window, cx)
})),
tooltip_message: None,
});
}
@@ -478,7 +441,6 @@ impl ActivityIndicator {
});
window.dispatch_action(Box::new(workspace::OpenLog), cx);
})),
tooltip_message: None,
});
}
@@ -495,9 +457,8 @@ impl ActivityIndicator {
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: None,
}),
AutoUpdateStatus::Downloading { version } => Some(Content {
AutoUpdateStatus::Downloading => Some(Content {
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
@@ -507,9 +468,8 @@ impl ActivityIndicator {
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: Some(Self::version_tooltip_message(&version)),
}),
AutoUpdateStatus::Installing { version } => Some(Content {
AutoUpdateStatus::Installing => Some(Content {
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
@@ -519,12 +479,8 @@ impl ActivityIndicator {
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: Some(Self::version_tooltip_message(&version)),
}),
AutoUpdateStatus::Updated {
binary_path,
version,
} => Some(Content {
AutoUpdateStatus::Updated { binary_path } => Some(Content {
icon: None,
message: "Click to restart and update Zed".to_string(),
on_click: Some(Arc::new({
@@ -533,7 +489,6 @@ impl ActivityIndicator {
};
move |_, _, cx| workspace::reload(&reload, cx)
})),
tooltip_message: Some(Self::version_tooltip_message(&version)),
}),
AutoUpdateStatus::Errored => Some(Content {
icon: Some(
@@ -545,7 +500,6 @@ impl ActivityIndicator {
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: None,
}),
AutoUpdateStatus::Idle => None,
};
@@ -565,7 +519,6 @@ impl ActivityIndicator {
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
})),
tooltip_message: None,
});
}
}
@@ -573,17 +526,6 @@ impl ActivityIndicator {
None
}
fn version_tooltip_message(version: &VersionCheckType) -> String {
format!("Version: {}", {
match version {
auto_update::VersionCheckType::Sha(sha) => format!("{}", sha.short()),
auto_update::VersionCheckType::Semantic(semantic_version) => {
semantic_version.to_string()
}
}
})
}
fn toggle_language_server_work_context_menu(
&mut self,
window: &mut Window,
@@ -628,14 +570,7 @@ impl Render for ActivityIndicator {
)
.tooltip(Tooltip::text(content.message))
} else {
button
.child(Label::new(content.message).size(LabelSize::Small))
.when_some(
content.tooltip_message,
|this, tooltip_message| {
this.tooltip(Tooltip::text(tooltip_message))
},
)
button.child(Label::new(content.message).size(LabelSize::Small))
}
})
.when_some(content.on_click, |this, handler| {
@@ -715,26 +650,3 @@ impl StatusItemView for ActivityIndicator {
) {
}
}
#[cfg(test)]
mod tests {
use gpui::SemanticVersion;
use release_channel::AppCommitSha;
use super::*;
#[test]
fn test_version_tooltip_message() {
let message = ActivityIndicator::version_tooltip_message(&VersionCheckType::Semantic(
SemanticVersion::new(1, 0, 0),
));
assert_eq!(message, "Version: 1.0.0");
let message = ActivityIndicator::version_tooltip_message(&VersionCheckType::Sha(
AppCommitSha::new("14d9a4189f058d8736339b06ff2340101eaea5af".to_string()),
));
assert_eq!(message, "Version: 14d9a41…");
}
}

View File

@@ -9,7 +9,7 @@ license = "GPL-3.0-or-later"
workspace = true
[lib]
path = "src/agent.rs"
path = "src/assistant.rs"
doctest = false
[features]
@@ -19,24 +19,23 @@ test-support = [
]
[dependencies]
agent_settings.workspace = true
anyhow.workspace = true
assistant_context_editor.workspace = true
assistant_settings.workspace = true
assistant_slash_command.workspace = true
assistant_slash_commands.workspace = true
assistant_tool.workspace = true
async-watch.workspace = true
audio.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
client.workspace = true
clock.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
component.workspace = true
context_server.workspace = true
convert_case.workspace = true
db.workspace = true
editor.workspace = true
extension.workspace = true
feature_flags.workspace = true
file_icons.workspace = true
fs.workspace = true
@@ -46,40 +45,34 @@ git.workspace = true
gpui.workspace = true
heed.workspace = true
html_to_markdown.workspace = true
indoc.workspace = true
http_client.workspace = true
indexed_docs.workspace = true
inventory.workspace = true
indexmap.workspace = true
itertools.workspace = true
jsonschema.workspace = true
language.workspace = true
language_model.workspace = true
language_model_selector.workspace = true
linkme.workspace = true
log.workspace = true
lsp.workspace = true
markdown.workspace = true
menu.workspace = true
multi_buffer.workspace = true
notifications.workspace = true
ordered-float.workspace = true
parking_lot.workspace = true
paths.workspace = true
picker.workspace = true
postage.workspace = true
project.workspace = true
prompt_library.workspace = true
prompt_store.workspace = true
proto.workspace = true
ref-cast.workspace = true
release_channel.workspace = true
rope.workspace = true
rules_library.workspace = true
schemars.workspace = true
search.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
settings.workspace = true
smallvec.workspace = true
smol.workspace = true
sqlez.workspace = true
streaming_diff.workspace = true
telemetry.workspace = true
telemetry_events.workspace = true
@@ -92,14 +85,12 @@ time.workspace = true
time_format.workspace = true
ui.workspace = true
ui_input.workspace = true
urlencoding.workspace = true
util.workspace = true
uuid.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true
zed_llm_client.workspace = true
zstd.workspace = true
[dev-dependencies]
buffer_diff = { workspace = true, features = ["test-support"] }

File diff suppressed because it is too large Load Diff

View File

@@ -1,703 +0,0 @@
mod add_context_server_modal;
mod configure_context_server_modal;
mod manage_profiles_modal;
mod tool_picker;
use std::{sync::Arc, time::Duration};
use agent_settings::AgentSettings;
use assistant_tool::{ToolSource, ToolWorkingSet};
use collections::HashMap;
use context_server::ContextServerId;
use fs::Fs;
use gpui::{
Action, Animation, AnimationExt as _, AnyView, App, Entity, EventEmitter, FocusHandle,
Focusable, ScrollHandle, Subscription, pulsating_between,
};
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
use project::context_server_store::{ContextServerStatus, ContextServerStore};
use settings::{Settings, update_settings_file};
use ui::{
Disclosure, ElevationIndex, Indicator, Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip,
prelude::*,
};
use util::ResultExt as _;
use zed_actions::ExtensionCategoryFilter;
pub(crate) use add_context_server_modal::AddContextServerModal;
pub(crate) use configure_context_server_modal::ConfigureContextServerModal;
pub(crate) use manage_profiles_modal::ManageProfilesModal;
use crate::AddContextServer;
pub struct AgentConfiguration {
fs: Arc<dyn Fs>,
focus_handle: FocusHandle,
configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
context_server_store: Entity<ContextServerStore>,
expanded_context_server_tools: HashMap<ContextServerId, bool>,
expanded_provider_configurations: HashMap<LanguageModelProviderId, bool>,
tools: Entity<ToolWorkingSet>,
_registry_subscription: Subscription,
scroll_handle: ScrollHandle,
scrollbar_state: ScrollbarState,
}
impl AgentConfiguration {
pub fn new(
fs: Arc<dyn Fs>,
context_server_store: Entity<ContextServerStore>,
tools: Entity<ToolWorkingSet>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let focus_handle = cx.focus_handle();
let registry_subscription = cx.subscribe_in(
&LanguageModelRegistry::global(cx),
window,
|this, _, event: &language_model::Event, window, cx| match event {
language_model::Event::AddedProvider(provider_id) => {
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
if let Some(provider) = provider {
this.add_provider_configuration_view(&provider, window, cx);
}
}
language_model::Event::RemovedProvider(provider_id) => {
this.remove_provider_configuration_view(provider_id);
}
_ => {}
},
);
let scroll_handle = ScrollHandle::new();
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
let mut this = Self {
fs,
focus_handle,
configuration_views_by_provider: HashMap::default(),
context_server_store,
expanded_context_server_tools: HashMap::default(),
expanded_provider_configurations: HashMap::default(),
tools,
_registry_subscription: registry_subscription,
scroll_handle,
scrollbar_state,
};
this.build_provider_configuration_views(window, cx);
this
}
fn build_provider_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let providers = LanguageModelRegistry::read_global(cx).providers();
for provider in providers {
self.add_provider_configuration_view(&provider, window, cx);
}
}
fn remove_provider_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
self.configuration_views_by_provider.remove(provider_id);
self.expanded_provider_configurations.remove(provider_id);
}
fn add_provider_configuration_view(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let configuration_view = provider.configuration_view(window, cx);
self.configuration_views_by_provider
.insert(provider.id(), configuration_view);
}
}
impl Focusable for AgentConfiguration {
fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
pub enum AssistantConfigurationEvent {
NewThread(Arc<dyn LanguageModelProvider>),
}
impl EventEmitter<AssistantConfigurationEvent> for AgentConfiguration {}
impl AgentConfiguration {
fn render_provider_configuration_block(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
cx: &mut Context<Self>,
) -> impl IntoElement + use<> {
let provider_id = provider.id().0.clone();
let provider_name = provider.name().0.clone();
let configuration_view = self
.configuration_views_by_provider
.get(&provider.id())
.cloned();
let is_expanded = self
.expanded_provider_configurations
.get(&provider.id())
.copied()
.unwrap_or(false);
v_flex()
.pt_3()
.gap_1p5()
.border_t_1()
.border_color(cx.theme().colors().border.opacity(0.6))
.child(
h_flex()
.justify_between()
.child(
h_flex()
.gap_2()
.child(
Icon::new(provider.icon())
.size(IconSize::Small)
.color(Color::Muted),
)
.child(Label::new(provider_name.clone()).size(LabelSize::Large))
.when(provider.is_authenticated(cx) && !is_expanded, |parent| {
parent.child(Icon::new(IconName::Check).color(Color::Success))
}),
)
.child(
h_flex()
.gap_1()
.when(provider.is_authenticated(cx), |parent| {
parent.child(
Button::new(
SharedString::from(format!("new-thread-{provider_id}")),
"Start New Thread",
)
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.layer(ElevationIndex::ModalSurface)
.label_size(LabelSize::Small)
.on_click(cx.listener({
let provider = provider.clone();
move |_this, _event, _window, cx| {
cx.emit(AssistantConfigurationEvent::NewThread(
provider.clone(),
))
}
})),
)
})
.child(
Disclosure::new(
SharedString::from(format!(
"provider-disclosure-{provider_id}"
)),
is_expanded,
)
.opened_icon(IconName::ChevronUp)
.closed_icon(IconName::ChevronDown)
.on_click(cx.listener({
let provider_id = provider.id().clone();
move |this, _event, _window, _cx| {
let is_expanded = this
.expanded_provider_configurations
.entry(provider_id.clone())
.or_insert(false);
*is_expanded = !*is_expanded;
}
})),
),
),
)
.when(is_expanded, |parent| match configuration_view {
Some(configuration_view) => parent.child(configuration_view),
None => parent.child(Label::new(format!(
"No configuration view for {provider_name}",
))),
})
}
fn render_provider_configuration_section(
&mut self,
cx: &mut Context<Self>,
) -> impl IntoElement {
let providers = LanguageModelRegistry::read_global(cx).providers();
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.gap_4()
.border_b_1()
.border_color(cx.theme().colors().border)
.child(
v_flex()
.gap_0p5()
.child(Headline::new("LLM Providers"))
.child(
Label::new("Add at least one provider to use AI-powered features.")
.color(Color::Muted),
),
)
.children(
providers
.into_iter()
.map(|provider| self.render_provider_configuration_block(&provider, cx)),
)
}
fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let always_allow_tool_actions = AgentSettings::get_global(cx).always_allow_tool_actions;
h_flex()
.gap_4()
.justify_between()
.flex_wrap()
.child(
v_flex()
.gap_0p5()
.max_w_5_6()
.child(Label::new("Allow running editing tools without asking for confirmation"))
.child(
Label::new(
"The agent can perform potentially destructive actions without asking for your confirmation.",
)
.color(Color::Muted),
),
)
.child(
Switch::new(
"always-allow-tool-actions-switch",
always_allow_tool_actions.into(),
)
.color(SwitchColor::Accent)
.on_click({
let fs = self.fs.clone();
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _| {
settings.set_always_allow_tool_actions(allow);
},
);
}
}),
)
}
fn render_single_file_review(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let single_file_review = AgentSettings::get_global(cx).single_file_review;
h_flex()
.gap_4()
.justify_between()
.flex_wrap()
.child(
v_flex()
.gap_0p5()
.max_w_5_6()
.child(Label::new("Enable single-file agent reviews"))
.child(
Label::new(
"Agent edits are also displayed in single-file editors for review.",
)
.color(Color::Muted),
),
)
.child(
Switch::new("single-file-review-switch", single_file_review.into())
.color(SwitchColor::Accent)
.on_click({
let fs = self.fs.clone();
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _| {
settings.set_single_file_review(allow);
},
);
}
}),
)
}
fn render_sound_notification(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let play_sound_when_agent_done = AgentSettings::get_global(cx).play_sound_when_agent_done;
h_flex()
.gap_4()
.justify_between()
.flex_wrap()
.child(
v_flex()
.gap_0p5()
.max_w_5_6()
.child(Label::new("Play sound when finished generating"))
.child(
Label::new(
"Hear a notification sound when the agent is done generating changes or needs your input.",
)
.color(Color::Muted),
),
)
.child(
Switch::new("play-sound-notification-switch", play_sound_when_agent_done.into())
.color(SwitchColor::Accent)
.on_click({
let fs = self.fs.clone();
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _| {
settings.set_play_sound_when_agent_done(allow);
},
);
}
}),
)
}
fn render_general_settings_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.gap_2p5()
.border_b_1()
.border_color(cx.theme().colors().border)
.child(Headline::new("General Settings"))
.child(self.render_command_permission(cx))
.child(self.render_single_file_review(cx))
.child(self.render_sound_notification(cx))
}
fn render_context_servers_section(
&mut self,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let context_server_ids = self.context_server_store.read(cx).all_server_ids().clone();
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.gap_2()
.border_b_1()
.border_color(cx.theme().colors().border)
.child(
v_flex()
.gap_0p5()
.child(Headline::new("Model Context Protocol (MCP) Servers"))
.child(Label::new("Connect to context servers via the Model Context Protocol either via Zed extensions or directly.").color(Color::Muted)),
)
.children(
context_server_ids.into_iter().map(|context_server_id| {
self.render_context_server(context_server_id, window, cx)
}),
)
.child(
h_flex()
.justify_between()
.gap_2()
.child(
h_flex().w_full().child(
Button::new("add-context-server", "Add Custom Server")
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.full_width()
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.icon_position(IconPosition::Start)
.on_click(|_event, window, cx| {
window.dispatch_action(AddContextServer.boxed_clone(), cx)
}),
),
)
.child(
h_flex().w_full().child(
Button::new(
"install-context-server-extensions",
"Install MCP Extensions",
)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.full_width()
.icon(IconName::Hammer)
.icon_size(IconSize::Small)
.icon_position(IconPosition::Start)
.on_click(|_event, window, cx| {
window.dispatch_action(
zed_actions::Extensions {
category_filter: Some(
ExtensionCategoryFilter::ContextServers,
),
}
.boxed_clone(),
cx,
)
}),
),
),
)
}
fn render_context_server(
&self,
context_server_id: ContextServerId,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl use<> + IntoElement {
let tools_by_source = self.tools.read(cx).tools_by_source(cx);
let server_status = self
.context_server_store
.read(cx)
.status_for_server(&context_server_id)
.unwrap_or(ContextServerStatus::Stopped);
let is_running = matches!(server_status, ContextServerStatus::Running);
let item_id = SharedString::from(context_server_id.0.clone());
let error = if let ContextServerStatus::Error(error) = server_status.clone() {
Some(error)
} else {
None
};
let are_tools_expanded = self
.expanded_context_server_tools
.get(&context_server_id)
.copied()
.unwrap_or_default();
let tools = tools_by_source
.get(&ToolSource::ContextServer {
id: context_server_id.0.clone().into(),
})
.map_or([].as_slice(), |tools| tools.as_slice());
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)
.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()),
)
.into_any_element(),
"Server is starting.",
),
ContextServerStatus::Running => (
Indicator::dot().color(Color::Success).into_any_element(),
"Server is running.",
),
ContextServerStatus::Error(_) => (
Indicator::dot().color(Color::Error).into_any_element(),
"Server has an error.",
),
ContextServerStatus::Stopped => (
Indicator::dot().color(Color::Muted).into_any_element(),
"Server is stopped.",
),
};
v_flex()
.id(item_id.clone())
.border_1()
.rounded_md()
.border_color(border_color)
.bg(cx.theme().colors().background.opacity(0.2))
.overflow_hidden()
.child(
h_flex()
.p_1()
.justify_between()
.when(
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",
are_tools_expanded || error.is_some(),
)
.disabled(tool_count == 0)
.on_click(cx.listener({
let context_server_id = context_server_id.clone();
move |this, _event, _window, _cx| {
let is_open = this
.expanded_context_server_tools
.entry(context_server_id.clone())
.or_insert(false);
*is_open = !*is_open;
}
})),
)
.child(
div()
.id(item_id.clone())
.tooltip(Tooltip::text(tooltip_text))
.child(status_indicator),
)
.child(Label::new(context_server_id.0.clone()).ml_0p5())
.when(is_running, |this| {
this.child(
Label::new(if tool_count == 1 {
SharedString::from("1 tool")
} else {
SharedString::from(format!("{} tools", tool_count))
})
.color(Color::Muted)
.size(LabelSize::Small),
)
}),
)
.child(
Switch::new("context-server-switch", is_running.into())
.color(SwitchColor::Accent)
.on_click({
let context_server_manager = self.context_server_store.clone();
let context_server_id = context_server_id.clone();
move |state, _window, cx| match state {
ToggleState::Unselected | ToggleState::Indeterminate => {
context_server_manager.update(cx, |this, cx| {
this.stop_server(&context_server_id, cx).log_err();
});
}
ToggleState::Selected => {
context_server_manager.update(cx, |this, cx| {
if let Some(server) =
this.get_server(&context_server_id)
{
this.start_server(server, cx).log_err();
}
})
}
}
}),
),
)
.map(|parent| {
if let Some(error) = error {
return parent.child(
h_flex()
.p_2()
.gap_2()
.items_start()
.child(
h_flex()
.flex_none()
.h(window.line_height() / 1.6_f32)
.justify_center()
.child(
Icon::new(IconName::XCircle)
.size(IconSize::XSmall)
.color(Color::Error),
),
)
.child(
div().w_full().child(
Label::new(error)
.buffer_font(cx)
.color(Color::Muted)
.size(LabelSize::Small),
),
),
);
}
if !are_tools_expanded || tools.is_empty() {
return parent;
}
parent.child(v_flex().py_1p5().px_1().gap_1().children(
tools.into_iter().enumerate().map(|(ix, tool)| {
h_flex()
.id(("tool-item", ix))
.px_1()
.gap_2()
.justify_between()
.hover(|style| style.bg(cx.theme().colors().element_hover))
.rounded_sm()
.child(
Label::new(tool.name())
.buffer_font(cx)
.size(LabelSize::Small),
)
.child(
Icon::new(IconName::Info)
.size(IconSize::Small)
.color(Color::Ignored),
)
.tooltip(Tooltip::text(tool.description()))
}),
))
})
}
}
impl Render for AgentConfiguration {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.id("assistant-configuration")
.key_context("AgentConfiguration")
.track_focus(&self.focus_handle(cx))
.relative()
.size_full()
.pb_8()
.bg(cx.theme().colors().panel_background)
.child(
v_flex()
.id("assistant-configuration-content")
.track_scroll(&self.scroll_handle)
.size_full()
.overflow_y_scroll()
.child(self.render_general_settings_section(cx))
.child(self.render_context_servers_section(window, cx))
.child(self.render_provider_configuration_section(cx)),
)
.child(
div()
.id("assistant-configuration-scrollbar")
.occlude()
.absolute()
.right(px(3.))
.top_0()
.bottom_0()
.pb_6()
.w(px(12.))
.cursor_default()
.on_mouse_move(cx.listener(|_, _, _window, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _window, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _window, cx| {
cx.stop_propagation();
})
.on_scroll_wheel(cx.listener(|_, _, _window, cx| {
cx.notify();
}))
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
)
}
}

View File

@@ -1,553 +0,0 @@
use std::{
sync::{Arc, Mutex},
time::Duration,
};
use anyhow::Context as _;
use context_server::ContextServerId;
use editor::{Editor, EditorElement, EditorStyle};
use gpui::{
Animation, AnimationExt, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task,
TextStyle, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, percentage,
};
use language::{Language, LanguageRegistry};
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
use notifications::status_toast::{StatusToast, ToastIcon};
use project::{
context_server_store::{ContextServerStatus, ContextServerStore},
project_settings::{ContextServerConfiguration, ProjectSettings},
};
use settings::{Settings as _, update_settings_file};
use theme::ThemeSettings;
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
use util::ResultExt;
use workspace::{ModalView, Workspace};
pub(crate) struct ConfigureContextServerModal {
workspace: WeakEntity<Workspace>,
focus_handle: FocusHandle,
context_servers_to_setup: Vec<ContextServerSetup>,
context_server_store: Entity<ContextServerStore>,
}
enum Configuration {
NotAvailable,
Required(ConfigurationRequiredState),
}
struct ConfigurationRequiredState {
installation_instructions: Entity<markdown::Markdown>,
settings_validator: Option<jsonschema::Validator>,
settings_editor: Entity<Editor>,
last_error: Option<SharedString>,
waiting_for_context_server: bool,
}
struct ContextServerSetup {
id: ContextServerId,
repository_url: Option<SharedString>,
configuration: Configuration,
}
impl ConfigureContextServerModal {
pub fn new(
configurations: impl Iterator<Item = crate::context_server_configuration::Configuration>,
context_server_store: Entity<ContextServerStore>,
jsonc_language: Option<Arc<Language>>,
language_registry: Arc<LanguageRegistry>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let context_servers_to_setup = configurations
.map(|config| match config {
crate::context_server_configuration::Configuration::NotAvailable(
context_server_id,
repository_url,
) => ContextServerSetup {
id: context_server_id,
repository_url,
configuration: Configuration::NotAvailable,
},
crate::context_server_configuration::Configuration::Required(
context_server_id,
repository_url,
config,
) => {
let jsonc_language = jsonc_language.clone();
let settings_validator = jsonschema::validator_for(&config.settings_schema)
.context("Failed to load JSON schema for context server settings")
.log_err();
let state = ConfigurationRequiredState {
installation_instructions: cx.new(|cx| {
Markdown::new(
config.installation_instructions.clone().into(),
Some(language_registry.clone()),
None,
cx,
)
}),
settings_validator,
settings_editor: cx.new(|cx| {
let mut editor = Editor::auto_height(16, window, cx);
editor.set_text(config.default_settings.trim(), window, cx);
editor.set_show_gutter(false, cx);
editor.set_soft_wrap_mode(
language::language_settings::SoftWrap::None,
cx,
);
if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
buffer.update(cx, |buffer, cx| {
buffer.set_language(jsonc_language, cx)
})
}
editor
}),
waiting_for_context_server: false,
last_error: None,
};
ContextServerSetup {
id: context_server_id,
repository_url,
configuration: Configuration::Required(state),
}
}
})
.collect::<Vec<_>>();
Self {
workspace,
focus_handle: cx.focus_handle(),
context_servers_to_setup,
context_server_store,
}
}
}
impl ConfigureContextServerModal {
pub fn confirm(&mut self, cx: &mut Context<Self>) {
if self.context_servers_to_setup.is_empty() {
self.dismiss(cx);
return;
}
let Some(workspace) = self.workspace.upgrade() else {
return;
};
let id = self.context_servers_to_setup[0].id.clone();
let configuration = match &mut self.context_servers_to_setup[0].configuration {
Configuration::NotAvailable => {
self.context_servers_to_setup.remove(0);
if self.context_servers_to_setup.is_empty() {
self.dismiss(cx);
}
return;
}
Configuration::Required(state) => state,
};
configuration.last_error.take();
if configuration.waiting_for_context_server {
return;
}
let settings_value = match serde_json_lenient::from_str::<serde_json::Value>(
&configuration.settings_editor.read(cx).text(cx),
) {
Ok(value) => value,
Err(error) => {
configuration.last_error = Some(error.to_string().into());
cx.notify();
return;
}
};
if let Some(validator) = configuration.settings_validator.as_ref() {
if let Err(error) = validator.validate(&settings_value) {
configuration.last_error = Some(error.to_string().into());
cx.notify();
return;
}
}
let id = id.clone();
let settings_changed = ProjectSettings::get_global(cx)
.context_servers
.get(&id.0)
.map_or(true, |config| {
config.settings.as_ref() != Some(&settings_value)
});
let is_running = self.context_server_store.read(cx).status_for_server(&id)
== Some(ContextServerStatus::Running);
if !settings_changed && is_running {
self.complete_setup(id, cx);
return;
}
configuration.waiting_for_context_server = true;
let task = wait_for_context_server(&self.context_server_store, id.clone(), cx);
cx.spawn({
let id = id.clone();
async move |this, cx| {
let result = task.await;
this.update(cx, |this, cx| match result {
Ok(_) => {
this.complete_setup(id, cx);
}
Err(err) => {
if let Some(setup) = this.context_servers_to_setup.get_mut(0) {
match &mut setup.configuration {
Configuration::NotAvailable => {}
Configuration::Required(state) => {
state.last_error = Some(err.into());
state.waiting_for_context_server = false;
}
}
} else {
this.dismiss(cx);
}
cx.notify();
}
})
}
})
.detach();
// When we write the settings to the file, the context server will be restarted.
update_settings_file::<ProjectSettings>(workspace.read(cx).app_state().fs.clone(), cx, {
let id = id.clone();
|settings, _| {
if let Some(server_config) = settings.context_servers.get_mut(&id.0) {
server_config.settings = Some(settings_value);
} else {
settings.context_servers.insert(
id.0,
ContextServerConfiguration {
settings: Some(settings_value),
..Default::default()
},
);
}
}
});
}
fn complete_setup(&mut self, id: ContextServerId, cx: &mut Context<Self>) {
self.context_servers_to_setup.remove(0);
cx.notify();
if !self.context_servers_to_setup.is_empty() {
return;
}
self.workspace
.update(cx, {
|workspace, cx| {
let status_toast = StatusToast::new(
format!("{} configured successfully.", id),
cx,
|this, _cx| {
this.icon(ToastIcon::new(IconName::Hammer).color(Color::Muted))
.action("Dismiss", |_, _| {})
},
);
workspace.toggle_status_toast(status_toast, cx);
}
})
.log_err();
self.dismiss(cx);
}
fn dismiss(&self, cx: &mut Context<Self>) {
cx.emit(DismissEvent);
}
}
fn wait_for_context_server(
context_server_store: &Entity<ContextServerStore>,
context_server_id: ContextServerId,
cx: &mut App,
) -> Task<Result<(), Arc<str>>> {
let (tx, rx) = futures::channel::oneshot::channel();
let tx = Arc::new(Mutex::new(Some(tx)));
let subscription = cx.subscribe(context_server_store, move |_, event, _cx| match event {
project::context_server_store::Event::ServerStatusChanged { server_id, status } => {
match status {
ContextServerStatus::Running => {
if server_id == &context_server_id {
if let Some(tx) = tx.lock().unwrap().take() {
let _ = tx.send(Ok(()));
}
}
}
ContextServerStatus::Stopped => {
if server_id == &context_server_id {
if let Some(tx) = tx.lock().unwrap().take() {
let _ = tx.send(Err("Context server stopped running".into()));
}
}
}
ContextServerStatus::Error(error) => {
if server_id == &context_server_id {
if let Some(tx) = tx.lock().unwrap().take() {
let _ = tx.send(Err(error.clone()));
}
}
}
_ => {}
}
}
});
cx.spawn(async move |_cx| {
let result = rx.await.unwrap();
drop(subscription);
result
})
}
impl Render for ConfigureContextServerModal {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let Some(setup) = self.context_servers_to_setup.first() else {
return div().into_any_element();
};
let focus_handle = self.focus_handle(cx);
div()
.elevation_3(cx)
.w(rems(42.))
.key_context("ConfigureContextServerModal")
.track_focus(&focus_handle)
.on_action(cx.listener(|this, _: &menu::Confirm, _window, cx| this.confirm(cx)))
.on_action(cx.listener(|this, _: &menu::Cancel, _window, cx| this.dismiss(cx)))
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
this.focus_handle(cx).focus(window);
}))
.child(
Modal::new("configure-context-server", None)
.header(ModalHeader::new().headline(format!("Configure {}", setup.id)))
.section(match &setup.configuration {
Configuration::NotAvailable => Section::new().child(
Label::new(
"No configuration options available for this context server. Visit the Repository for any further instructions.",
)
.color(Color::Muted),
),
Configuration::Required(configuration) => Section::new()
.child(div().pb_2().text_sm().child(MarkdownElement::new(
configuration.installation_instructions.clone(),
default_markdown_style(window, cx),
)))
.child(
div()
.p_2()
.rounded_md()
.border_1()
.border_color(cx.theme().colors().border_variant)
.bg(cx.theme().colors().editor_background)
.gap_1()
.child({
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().text,
font_family: settings.buffer_font.family.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size(cx).into(),
font_weight: settings.buffer_font.weight,
line_height: relative(
settings.buffer_line_height.value(),
),
..Default::default()
};
EditorElement::new(
&configuration.settings_editor,
EditorStyle {
background: cx.theme().colors().editor_background,
local_player: cx.theme().players().local(),
text: text_style,
syntax: cx.theme().syntax().clone(),
..Default::default()
},
)
})
.when_some(configuration.last_error.clone(), |this, error| {
this.child(
h_flex()
.gap_2()
.px_2()
.py_1()
.child(
Icon::new(IconName::Warning)
.size(IconSize::XSmall)
.color(Color::Warning),
)
.child(
div().w_full().child(
Label::new(error)
.size(LabelSize::Small)
.color(Color::Muted),
),
),
)
}),
)
.when(configuration.waiting_for_context_server, |this| {
this.child(
h_flex()
.gap_1p5()
.child(
Icon::new(IconName::ArrowCircle)
.size(IconSize::XSmall)
.color(Color::Info)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(2)).repeat(),
|icon, delta| {
icon.transform(Transformation::rotate(
percentage(delta),
))
},
)
.into_any_element(),
)
.child(
Label::new("Waiting for Context Server")
.size(LabelSize::Small)
.color(Color::Muted),
),
)
}),
})
.footer(
ModalFooter::new()
.when_some(setup.repository_url.clone(), |this, repository_url| {
this.start_slot(
h_flex().w_full().child(
Button::new("open-repository", "Open Repository")
.icon(IconName::ArrowUpRight)
.icon_color(Color::Muted)
.icon_size(IconSize::XSmall)
.tooltip({
let repository_url = repository_url.clone();
move |window, cx| {
Tooltip::with_meta(
"Open Repository",
None,
repository_url.clone(),
window,
cx,
)
}
})
.on_click(move |_, _, cx| cx.open_url(&repository_url)),
),
)
})
.end_slot(match &setup.configuration {
Configuration::NotAvailable => Button::new("dismiss", "Dismiss")
.key_binding(
KeyBinding::for_action_in(
&menu::Cancel,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(
cx.listener(|this, _event, _window, cx| this.dismiss(cx)),
)
.into_any_element(),
Configuration::Required(state) => h_flex()
.gap_2()
.child(
Button::new("cancel", "Cancel")
.key_binding(
KeyBinding::for_action_in(
&menu::Cancel,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(cx.listener(|this, _event, _window, cx| {
this.dismiss(cx)
})),
)
.child(
Button::new("configure-server", "Configure MCP")
.disabled(state.waiting_for_context_server)
.key_binding(
KeyBinding::for_action_in(
&menu::Confirm,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(cx.listener(|this, _event, _window, cx| {
this.confirm(cx)
})),
)
.into_any_element(),
}),
),
).into_any_element()
}
}
pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
let theme_settings = ThemeSettings::get_global(cx);
let colors = cx.theme().colors();
let mut text_style = window.text_style();
text_style.refine(&TextStyleRefinement {
font_family: Some(theme_settings.ui_font.family.clone()),
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
font_features: Some(theme_settings.ui_font.features.clone()),
font_size: Some(TextSize::XSmall.rems(cx).into()),
color: Some(colors.text_muted),
..Default::default()
});
MarkdownStyle {
base_text_style: text_style.clone(),
selection_background_color: cx.theme().players().local().selection,
link: TextStyleRefinement {
background_color: Some(colors.editor_foreground.opacity(0.025)),
underline: Some(UnderlineStyle {
color: Some(colors.text_accent.opacity(0.5)),
thickness: px(1.),
..Default::default()
}),
..Default::default()
},
..Default::default()
}
}
impl ModalView for ConfigureContextServerModal {}
impl EventEmitter<DismissEvent> for ConfigureContextServerModal {}
impl Focusable for ConfigureContextServerModal {
fn focus_handle(&self, cx: &App) -> FocusHandle {
if let Some(current) = self.context_servers_to_setup.first() {
match &current.configuration {
Configuration::NotAvailable => self.focus_handle.clone(),
Configuration::Required(configuration) => {
configuration.settings_editor.read(cx).focus_handle(cx)
}
}
} else {
self.focus_handle.clone()
}
}
}

View File

@@ -1,42 +0,0 @@
use ui::prelude::*;
#[derive(IntoElement)]
pub struct ProfileModalHeader {
label: SharedString,
icon: Option<IconName>,
}
impl ProfileModalHeader {
pub fn new(label: impl Into<SharedString>, icon: Option<IconName>) -> Self {
Self {
label: label.into(),
icon,
}
}
}
impl RenderOnce for ProfileModalHeader {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let mut container = h_flex()
.w_full()
.px(DynamicSpacing::Base12.rems(cx))
.pt(DynamicSpacing::Base08.rems(cx))
.pb(DynamicSpacing::Base04.rems(cx))
.rounded_t_sm()
.gap_1p5();
if let Some(icon) = self.icon {
container = container.child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted));
}
container.child(
h_flex().gap_1().overflow_x_hidden().child(
div()
.max_w_96()
.overflow_x_hidden()
.text_ellipsis()
.child(Headline::new(self.label).size(HeadlineSize::XSmall)),
),
)
}
}

View File

@@ -1,377 +0,0 @@
use std::{collections::BTreeMap, sync::Arc};
use agent_settings::{
AgentProfile, AgentProfileContent, AgentProfileId, 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 ui::{ListItem, ListItemSpacing, prelude::*};
use util::ResultExt as _;
use crate::ThreadStore;
pub struct ToolPicker {
picker: Entity<Picker<ToolPickerDelegate>>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ToolPickerMode {
BuiltinTools,
McpTools,
}
impl ToolPicker {
pub fn builtin_tools(
delegate: ToolPickerDelegate,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false));
Self { picker }
}
pub fn mcp_tools(
delegate: ToolPickerDelegate,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let picker = cx.new(|cx| Picker::list(delegate, window, cx).modal(false));
Self { picker }
}
}
impl EventEmitter<DismissEvent> for ToolPicker {}
impl Focusable for ToolPicker {
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for ToolPicker {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
v_flex().w(rems(34.)).child(self.picker.clone())
}
}
#[derive(Debug, Clone)]
pub enum PickerItem {
Tool {
server_id: Option<Arc<str>>,
name: Arc<str>,
},
ContextServer {
server_id: Arc<str>,
},
}
pub struct ToolPickerDelegate {
tool_picker: WeakEntity<ToolPicker>,
thread_store: WeakEntity<ThreadStore>,
fs: Arc<dyn Fs>,
items: Arc<Vec<PickerItem>>,
profile_id: AgentProfileId,
profile: AgentProfile,
filtered_items: Vec<PickerItem>,
selected_index: usize,
mode: ToolPickerMode,
}
impl ToolPickerDelegate {
pub fn new(
mode: ToolPickerMode,
fs: Arc<dyn Fs>,
tool_set: Entity<ToolWorkingSet>,
thread_store: WeakEntity<ThreadStore>,
profile_id: AgentProfileId,
profile: AgentProfile,
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,
filtered_items: Vec::new(),
selected_index: 0,
mode,
}
}
fn resolve_items(
mode: ToolPickerMode,
tool_set: &Entity<ToolWorkingSet>,
cx: &mut App,
) -> Vec<PickerItem> {
let mut items = Vec::new();
for (source, tools) in tool_set.read(cx).tools_by_source(cx) {
match source {
ToolSource::Native => {
if mode == ToolPickerMode::BuiltinTools {
items.extend(tools.into_iter().map(|tool| PickerItem::Tool {
name: tool.name().into(),
server_id: None,
}));
}
}
ToolSource::ContextServer { id } => {
if mode == ToolPickerMode::McpTools && !tools.is_empty() {
let server_id: Arc<str> = id.clone().into();
items.push(PickerItem::ContextServer {
server_id: server_id.clone(),
});
items.extend(tools.into_iter().map(|tool| PickerItem::Tool {
name: tool.name().into(),
server_id: Some(server_id.clone()),
}));
}
}
}
}
items
}
}
impl PickerDelegate for ToolPickerDelegate {
type ListItem = AnyElement;
fn match_count(&self) -> usize {
self.filtered_items.len()
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) {
self.selected_index = ix;
}
fn can_select(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) -> bool {
let item = &self.filtered_items[ix];
match item {
PickerItem::Tool { .. } => true,
PickerItem::ContextServer { .. } => false,
}
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
match self.mode {
ToolPickerMode::BuiltinTools => "Search built-in tools…",
ToolPickerMode::McpTools => "Search MCP tools…",
}
.into()
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let all_items = self.items.clone();
cx.spawn_in(window, async move |this, cx| {
let filtered_items = cx
.background_spawn(async move {
let mut tools_by_provider: BTreeMap<Option<Arc<str>>, Vec<Arc<str>>> =
BTreeMap::default();
for item in all_items.iter() {
if let PickerItem::Tool { server_id, name } = item.clone() {
if name.contains(&query) {
tools_by_provider.entry(server_id).or_default().push(name);
}
}
}
let mut items = Vec::new();
for (server_id, names) in tools_by_provider {
if let Some(server_id) = server_id.clone() {
items.push(PickerItem::ContextServer { server_id });
}
for name in names {
items.push(PickerItem::Tool {
server_id: server_id.clone(),
name,
});
}
}
items
})
.await;
this.update(cx, |this, _cx| {
this.delegate.filtered_items = filtered_items;
this.delegate.selected_index = this
.delegate
.selected_index
.min(this.delegate.filtered_items.len().saturating_sub(1));
})
.log_err();
})
}
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
if self.filtered_items.is_empty() {
self.dismissed(window, cx);
return;
}
let item = &self.filtered_items[self.selected_index];
let PickerItem::Tool {
name: tool_name,
server_id,
} = item
else {
return;
};
let is_currently_enabled = if let Some(server_id) = server_id.clone() {
let preset = self.profile.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;
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 server_id = server_id.clone();
let tool_name = tool_name.clone();
move |settings: &mut AgentSettingsContent, _cx| {
settings
.v2_setting(|v2_settings| {
let profiles = v2_settings.profiles.get_or_insert_default();
let profile =
profiles
.entry(profile_id)
.or_insert_with(|| AgentProfileContent {
name: default_profile.name.into(),
tools: default_profile.tools,
enable_all_context_servers: Some(
default_profile.enable_all_context_servers,
),
context_servers: default_profile
.context_servers
.into_iter()
.map(|(server_id, preset)| {
(
server_id,
ContextServerPresetContent {
tools: preset.tools,
},
)
})
.collect(),
});
if let Some(server_id) = server_id {
let preset = profile.context_servers.entry(server_id).or_default();
*preset.tools.entry(tool_name).or_default() = !is_currently_enabled;
} else {
*profile.tools.entry(tool_name).or_default() = !is_currently_enabled;
}
Ok(())
})
.ok();
}
});
}
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
self.tool_picker
.update(cx, |_this, cx| cx.emit(DismissEvent))
.log_err();
}
fn render_match(
&self,
ix: usize,
selected: bool,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let item = &self.filtered_items[ix];
match item {
PickerItem::ContextServer { server_id, .. } => Some(
div()
.px_2()
.pb_1()
.when(ix > 1, |this| {
this.mt_1()
.pt_2()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
})
.child(
Label::new(server_id)
.size(LabelSize::XSmall)
.color(Color::Muted),
)
.into_any_element(),
),
PickerItem::Tool { name, server_id } => {
let is_enabled = if let Some(server_id) = server_id {
self.profile
.context_servers
.get(server_id.as_ref())
.and_then(|preset| preset.tools.get(name))
.copied()
.unwrap_or(self.profile.enable_all_context_servers)
} else {
self.profile.tools.get(name).copied().unwrap_or(false)
};
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.child(Label::new(name.clone()))
.end_slot::<Icon>(is_enabled.then(|| {
Icon::new(IconName::Check)
.size(IconSize::Small)
.color(Color::Success)
}))
.into_any_element(),
)
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,135 +0,0 @@
use agent_settings::AgentSettings;
use fs::Fs;
use gpui::{Entity, FocusHandle, SharedString};
use picker::popover_menu::PickerPopoverMenu;
use crate::Thread;
use assistant_context_editor::language_model_selector::{
LanguageModelSelector, ToggleModelSelector, language_model_selector,
};
use language_model::{ConfiguredModel, LanguageModelRegistry};
use settings::update_settings_file;
use std::sync::Arc;
use ui::{PopoverMenuHandle, Tooltip, prelude::*};
#[derive(Clone)]
pub enum ModelType {
Default(Entity<Thread>),
InlineAssistant,
}
pub struct AgentModelSelector {
selector: Entity<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle,
}
impl AgentModelSelector {
pub(crate) fn new(
fs: Arc<dyn Fs>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle,
model_type: ModelType,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
Self {
selector: cx.new(move |cx| {
let fs = fs.clone();
language_model_selector(
{
let model_type = model_type.clone();
move |cx| match &model_type {
ModelType::Default(thread) => thread.read(cx).configured_model(),
ModelType::InlineAssistant => {
LanguageModelRegistry::read_global(cx).inline_assistant_model()
}
}
},
move |model, cx| {
let provider = model.provider_id().0.to_string();
let model_id = model.id().0.to_string();
match &model_type {
ModelType::Default(thread) => {
thread.update(cx, |thread, cx| {
let registry = LanguageModelRegistry::read_global(cx);
if let Some(provider) = registry.provider(&model.provider_id())
{
thread.set_configured_model(
Some(ConfiguredModel {
provider,
model: model.clone(),
}),
cx,
);
}
});
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _cx| {
settings.set_model(model.clone());
},
);
}
ModelType::InlineAssistant => {
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _cx| {
settings.set_inline_assistant_model(
provider.clone(),
model_id.clone(),
);
},
);
}
}
},
window,
cx,
)
}),
menu_handle,
focus_handle,
}
}
pub fn toggle(&self, window: &mut Window, cx: &mut Context<Self>) {
self.menu_handle.toggle(window, cx);
}
}
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"));
PickerPopoverMenu::new(
self.selector.clone(),
Button::new("active-model", model_name)
.label_size(LabelSize::Small)
.color(Color::Muted)
.icon(IconName::ChevronDown)
.icon_size(IconSize::XSmall)
.icon_position(IconPosition::End)
.icon_color(Color::Muted),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
gpui::Corner::BottomRight,
cx,
)
.with_handle(self.menu_handle.clone())
.render(window, cx)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,147 @@
mod active_thread;
mod agent_diff;
mod assistant_configuration;
mod assistant_model_selector;
mod assistant_panel;
mod buffer_codegen;
mod context;
mod context_picker;
mod context_store;
mod context_strip;
mod history_store;
mod inline_assistant;
mod inline_prompt_editor;
mod message_editor;
mod profile_selector;
mod terminal_codegen;
mod terminal_inline_assistant;
mod thread;
mod thread_history;
mod thread_store;
mod tool_compatibility;
mod tool_use;
mod ui;
use std::sync::Arc;
use assistant_settings::{AgentProfileId, AssistantSettings};
use client::Client;
use command_palette_hooks::CommandPaletteFilter;
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
use fs::Fs;
use gpui::{App, actions, impl_actions};
use prompt_store::PromptBuilder;
use schemars::JsonSchema;
use serde::Deserialize;
use settings::Settings as _;
use thread::ThreadId;
pub use crate::active_thread::ActiveThread;
use crate::assistant_configuration::{AddContextServerModal, ManageProfilesModal};
pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate};
pub use crate::inline_assistant::InlineAssistant;
pub use crate::thread::{Message, Thread, ThreadEvent};
pub use crate::thread_store::ThreadStore;
pub use agent_diff::{AgentDiff, AgentDiffToolbar};
actions!(
agent,
[
NewTextThread,
ToggleContextPicker,
ToggleProfileSelector,
RemoveAllContext,
ExpandMessageEditor,
OpenHistory,
AddContextServer,
RemoveSelectedThread,
Chat,
ChatMode,
CycleNextInlineAssist,
CyclePreviousInlineAssist,
FocusUp,
FocusDown,
FocusLeft,
FocusRight,
RemoveFocusedContext,
AcceptSuggestedContext,
OpenActiveThreadAsMarkdown,
OpenAgentDiff,
Keep,
Reject,
RejectAll,
KeepAll
]
);
#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema)]
pub struct NewThread {
#[serde(default)]
from_thread_id: Option<ThreadId>,
}
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
pub struct ManageProfiles {
#[serde(default)]
pub customize_tools: Option<AgentProfileId>,
}
impl ManageProfiles {
pub fn customize_tools(profile_id: AgentProfileId) -> Self {
Self {
customize_tools: Some(profile_id),
}
}
}
impl_actions!(agent, [NewThread, ManageProfiles]);
const NAMESPACE: &str = "agent";
/// Initializes the `agent` crate.
pub fn init(
fs: Arc<dyn Fs>,
client: Arc<Client>,
prompt_builder: Arc<PromptBuilder>,
cx: &mut App,
) {
AssistantSettings::register(cx);
thread_store::init(cx);
assistant_panel::init(cx);
inline_assistant::init(
fs.clone(),
prompt_builder.clone(),
client.telemetry().clone(),
cx,
);
terminal_inline_assistant::init(
fs.clone(),
prompt_builder.clone(),
client.telemetry().clone(),
cx,
);
cx.observe_new(AddContextServerModal::register).detach();
cx.observe_new(ManageProfilesModal::register).detach();
feature_gate_agent_actions(cx);
}
fn feature_gate_agent_actions(cx: &mut App) {
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(NAMESPACE);
});
cx.observe_flag::<Assistant2FeatureFlag, _>(move |is_enabled, cx| {
if is_enabled {
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.show_namespace(NAMESPACE);
});
} else {
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(NAMESPACE);
});
}
})
.detach();
}

View File

@@ -0,0 +1,501 @@
mod add_context_server_modal;
mod manage_profiles_modal;
mod tool_picker;
use std::sync::Arc;
use assistant_settings::AssistantSettings;
use assistant_tool::{ToolSource, ToolWorkingSet};
use collections::HashMap;
use context_server::manager::ContextServerManager;
use fs::Fs;
use gpui::{
Action, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle, Subscription,
};
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
use settings::{Settings, update_settings_file};
use ui::{
Disclosure, Divider, DividerColor, ElevationIndex, Indicator, Scrollbar, ScrollbarState,
Switch, SwitchColor, Tooltip, prelude::*,
};
use util::ResultExt as _;
use zed_actions::ExtensionCategoryFilter;
pub(crate) use add_context_server_modal::AddContextServerModal;
pub(crate) use manage_profiles_modal::ManageProfilesModal;
use crate::AddContextServer;
pub struct AssistantConfiguration {
fs: Arc<dyn Fs>,
focus_handle: FocusHandle,
configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
context_server_manager: Entity<ContextServerManager>,
expanded_context_server_tools: HashMap<Arc<str>, bool>,
tools: Entity<ToolWorkingSet>,
_registry_subscription: Subscription,
scroll_handle: ScrollHandle,
scrollbar_state: ScrollbarState,
}
impl AssistantConfiguration {
pub fn new(
fs: Arc<dyn Fs>,
context_server_manager: Entity<ContextServerManager>,
tools: Entity<ToolWorkingSet>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let focus_handle = cx.focus_handle();
let registry_subscription = cx.subscribe_in(
&LanguageModelRegistry::global(cx),
window,
|this, _, event: &language_model::Event, window, cx| match event {
language_model::Event::AddedProvider(provider_id) => {
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
if let Some(provider) = provider {
this.add_provider_configuration_view(&provider, window, cx);
}
}
language_model::Event::RemovedProvider(provider_id) => {
this.remove_provider_configuration_view(provider_id);
}
_ => {}
},
);
let scroll_handle = ScrollHandle::new();
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
let mut this = Self {
fs,
focus_handle,
configuration_views_by_provider: HashMap::default(),
context_server_manager,
expanded_context_server_tools: HashMap::default(),
tools,
_registry_subscription: registry_subscription,
scroll_handle,
scrollbar_state,
};
this.build_provider_configuration_views(window, cx);
this
}
fn build_provider_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let providers = LanguageModelRegistry::read_global(cx).providers();
for provider in providers {
self.add_provider_configuration_view(&provider, window, cx);
}
}
fn remove_provider_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
self.configuration_views_by_provider.remove(provider_id);
}
fn add_provider_configuration_view(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let configuration_view = provider.configuration_view(window, cx);
self.configuration_views_by_provider
.insert(provider.id(), configuration_view);
}
}
impl Focusable for AssistantConfiguration {
fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
pub enum AssistantConfigurationEvent {
NewThread(Arc<dyn LanguageModelProvider>),
}
impl EventEmitter<AssistantConfigurationEvent> for AssistantConfiguration {}
impl AssistantConfiguration {
fn render_provider_configuration_block(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
cx: &mut Context<Self>,
) -> impl IntoElement + use<> {
let provider_id = provider.id().0.clone();
let provider_name = provider.name().0.clone();
let configuration_view = self
.configuration_views_by_provider
.get(&provider.id())
.cloned();
v_flex()
.pt_3()
.pb_1()
.gap_1p5()
.border_t_1()
.border_color(cx.theme().colors().border.opacity(0.6))
.child(
h_flex()
.justify_between()
.child(
h_flex()
.gap_2()
.child(
Icon::new(provider.icon())
.size(IconSize::Small)
.color(Color::Muted),
)
.child(Label::new(provider_name.clone()).size(LabelSize::Large)),
)
.when(provider.is_authenticated(cx), |parent| {
parent.child(
Button::new(
SharedString::from(format!("new-thread-{provider_id}")),
"Start New Thread",
)
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.label_size(LabelSize::Small)
.on_click(cx.listener({
let provider = provider.clone();
move |_this, _event, _window, cx| {
cx.emit(AssistantConfigurationEvent::NewThread(
provider.clone(),
))
}
})),
)
}),
)
.map(|parent| match configuration_view {
Some(configuration_view) => parent.child(configuration_view),
None => parent.child(div().child(Label::new(format!(
"No configuration view for {provider_name}",
)))),
})
}
fn render_provider_configuration_section(
&mut self,
cx: &mut Context<Self>,
) -> impl IntoElement {
let providers = LanguageModelRegistry::read_global(cx).providers();
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.gap_4()
.flex_1()
.child(
v_flex()
.gap_0p5()
.child(Headline::new("LLM Providers"))
.child(
Label::new("Add at least one provider to use AI-powered features.")
.color(Color::Muted),
),
)
.children(
providers
.into_iter()
.map(|provider| self.render_provider_configuration_block(&provider, cx)),
)
}
fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let always_allow_tool_actions = AssistantSettings::get_global(cx).always_allow_tool_actions;
const HEADING: &str = "Allow running editing tools without asking for confirmation";
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.gap_2()
.flex_1()
.child(Headline::new("General Settings"))
.child(
h_flex()
.gap_4()
.justify_between()
.flex_wrap()
.child(
v_flex()
.gap_0p5()
.max_w_5_6()
.child(Label::new(HEADING))
.child(Label::new("When enabled, the agent can perform potentially destructive actions without asking for your confirmation.").color(Color::Muted)),
)
.child(
Switch::new(
"always-allow-tool-actions-switch",
always_allow_tool_actions.into(),
)
.color(SwitchColor::Accent)
.on_click({
let fs = self.fs.clone();
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _| {
settings.set_always_allow_tool_actions(allow);
},
);
}
}),
),
)
}
fn render_context_servers_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let context_servers = self.context_server_manager.read(cx).all_servers().clone();
let tools_by_source = self.tools.read(cx).tools_by_source(cx);
let empty = Vec::new();
const SUBHEADING: &str = "Connect to context servers via the Model Context Protocol either via Zed extensions or directly.";
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.gap_2()
.flex_1()
.child(
v_flex()
.gap_0p5()
.child(Headline::new("Model Context Protocol (MCP) Servers"))
.child(Label::new(SUBHEADING).color(Color::Muted)),
)
.children(context_servers.into_iter().map(|context_server| {
let is_running = context_server.client().is_some();
let are_tools_expanded = self
.expanded_context_server_tools
.get(&context_server.id())
.copied()
.unwrap_or_default();
let tools = tools_by_source
.get(&ToolSource::ContextServer {
id: context_server.id().into(),
})
.unwrap_or_else(|| &empty);
let tool_count = tools.len();
v_flex()
.id(SharedString::from(context_server.id()))
.border_1()
.rounded_md()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().background.opacity(0.25))
.child(
h_flex()
.p_1()
.justify_between()
.when(are_tools_expanded && tool_count > 1, |element| {
element
.border_b_1()
.border_color(cx.theme().colors().border)
})
.child(
h_flex()
.gap_2()
.child(
Disclosure::new("tool-list-disclosure", are_tools_expanded)
.disabled(tool_count == 0)
.on_click(cx.listener({
let context_server_id = context_server.id();
move |this, _event, _window, _cx| {
let is_open = this
.expanded_context_server_tools
.entry(context_server_id.clone())
.or_insert(false);
*is_open = !*is_open;
}
})),
)
.child(Indicator::dot().color(if is_running {
Color::Success
} else {
Color::Error
}))
.child(Label::new(context_server.id()))
.child(
Label::new(format!("{tool_count} tools"))
.color(Color::Muted)
.size(LabelSize::Small),
),
)
.child(
Switch::new("context-server-switch", is_running.into())
.color(SwitchColor::Accent)
.on_click({
let context_server_manager =
self.context_server_manager.clone();
let context_server = context_server.clone();
move |state, _window, cx| match state {
ToggleState::Unselected
| ToggleState::Indeterminate => {
context_server_manager.update(cx, |this, cx| {
this.stop_server(context_server.clone(), cx)
.log_err();
});
}
ToggleState::Selected => {
cx.spawn({
let context_server_manager =
context_server_manager.clone();
let context_server = context_server.clone();
async move |cx| {
if let Some(start_server_task) =
context_server_manager
.update(cx, |this, cx| {
this.start_server(
context_server,
cx,
)
})
.log_err()
{
start_server_task.await.log_err();
}
}
})
.detach();
}
}
}),
),
)
.map(|parent| {
if !are_tools_expanded {
return parent;
}
parent.child(v_flex().py_1p5().px_1().gap_1().children(
tools.into_iter().enumerate().map(|(ix, tool)| {
h_flex()
.id(("tool-item", ix))
.px_1()
.gap_2()
.justify_between()
.hover(|style| style.bg(cx.theme().colors().element_hover))
.rounded_sm()
.child(
Label::new(tool.name())
.buffer_font(cx)
.size(LabelSize::Small),
)
.child(
Icon::new(IconName::Info)
.size(IconSize::Small)
.color(Color::Ignored),
)
.tooltip(Tooltip::text(tool.description()))
}),
))
})
}))
.child(
h_flex()
.justify_between()
.gap_2()
.child(
h_flex().w_full().child(
Button::new("add-context-server", "Add Custom Server")
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.full_width()
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.icon_position(IconPosition::Start)
.on_click(|_event, window, cx| {
window.dispatch_action(AddContextServer.boxed_clone(), cx)
}),
),
)
.child(
h_flex().w_full().child(
Button::new(
"install-context-server-extensions",
"Install MCP Extensions",
)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.full_width()
.icon(IconName::DatabaseZap)
.icon_size(IconSize::Small)
.icon_position(IconPosition::Start)
.on_click(|_event, window, cx| {
window.dispatch_action(
zed_actions::Extensions {
category_filter: Some(
ExtensionCategoryFilter::ContextServers,
),
}
.boxed_clone(),
cx,
)
}),
),
),
)
}
}
impl Render for AssistantConfiguration {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.id("assistant-configuration")
.key_context("AgentConfiguration")
.track_focus(&self.focus_handle(cx))
.relative()
.size_full()
.pb_8()
.bg(cx.theme().colors().panel_background)
.child(
v_flex()
.id("assistant-configuration-content")
.track_scroll(&self.scroll_handle)
.size_full()
.overflow_y_scroll()
.child(self.render_command_permission(cx))
.child(Divider::horizontal().color(DividerColor::Border))
.child(self.render_context_servers_section(cx))
.child(Divider::horizontal().color(DividerColor::Border))
.child(self.render_provider_configuration_section(cx)),
)
.child(
div()
.id("assistant-configuration-scrollbar")
.occlude()
.absolute()
.right(px(3.))
.top_0()
.bottom_0()
.pb_6()
.w(px(12.))
.cursor_default()
.on_mouse_move(cx.listener(|_, _, _window, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _window, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _window, cx| {
cx.stop_propagation();
})
.on_scroll_wheel(cx.listener(|_, _, _window, cx| {
cx.notify();
}))
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
)
}
}

View File

@@ -1,6 +1,5 @@
use context_server::ContextServerCommand;
use context_server::{ContextServerSettings, ServerCommand, ServerConfig};
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity, prelude::*};
use project::project_settings::{ContextServerConfiguration, ProjectSettings};
use serde_json::json;
use settings::update_settings_file;
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
@@ -78,11 +77,11 @@ impl AddContextServerModal {
if let Some(workspace) = self.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
let fs = workspace.app_state().fs.clone();
update_settings_file::<ProjectSettings>(fs.clone(), cx, |settings, _| {
update_settings_file::<ContextServerSettings>(fs.clone(), cx, |settings, _| {
settings.context_servers.insert(
name.into(),
ContextServerConfiguration {
command: Some(ContextServerCommand {
ServerConfig {
command: Some(ServerCommand {
path,
args,
env: None,
@@ -147,50 +146,47 @@ impl Render for AddContextServerModal {
),
)
.footer(
ModalFooter::new().end_slot(
h_flex()
.gap_2()
.child(
Button::new("cancel", "Cancel")
.key_binding(
KeyBinding::for_action_in(
&menu::Cancel,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
ModalFooter::new()
.start_slot(
Button::new("cancel", "Cancel")
.key_binding(
KeyBinding::for_action_in(
&menu::Cancel,
&focus_handle,
window,
cx,
)
.on_click(cx.listener(|this, _event, _window, cx| {
this.cancel(&menu::Cancel, cx)
})),
)
.child(
Button::new("add-server", "Add Server")
.disabled(is_name_empty || is_command_empty)
.key_binding(
KeyBinding::for_action_in(
&menu::Confirm,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(cx.listener(|this, _event, _window, cx| {
this.cancel(&menu::Cancel, cx)
})),
)
.end_slot(
Button::new("add-server", "Add Server")
.disabled(is_name_empty || is_command_empty)
.key_binding(
KeyBinding::for_action_in(
&menu::Confirm,
&focus_handle,
window,
cx,
)
.map(|button| {
if is_name_empty {
button.tooltip(Tooltip::text("Name is required"))
} else if is_command_empty {
button.tooltip(Tooltip::text("Command is required"))
} else {
button
}
})
.on_click(cx.listener(|this, _event, _window, cx| {
this.confirm(&menu::Confirm, cx)
})),
),
),
.map(|kb| kb.size(rems_from_px(12.))),
)
.map(|button| {
if is_name_empty {
button.tooltip(Tooltip::text("Name is required"))
} else if is_command_empty {
button.tooltip(Tooltip::text("Command is required"))
} else {
button
}
})
.on_click(cx.listener(|this, _event, _window, cx| {
this.confirm(&menu::Confirm, cx)
})),
),
),
)
}

View File

@@ -2,7 +2,7 @@ mod profile_modal_header;
use std::sync::Arc;
use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profiles};
use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings};
use assistant_tool::ToolWorkingSet;
use convert_case::{Case, Casing as _};
use editor::Editor;
@@ -18,11 +18,9 @@ use ui::{
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 super::tool_picker::ToolPickerMode;
use crate::assistant_configuration::manage_profiles_modal::profile_modal_header::ProfileModalHeader;
use crate::assistant_configuration::tool_picker::{ToolPicker, ToolPickerDelegate};
use crate::{AssistantPanel, ManageProfiles, ThreadStore};
enum Mode {
ChooseProfile(ChooseProfileMode),
@@ -33,39 +31,26 @@ enum Mode {
tool_picker: Entity<ToolPicker>,
_subscription: Subscription,
},
ConfigureMcps {
profile_id: AgentProfileId,
tool_picker: Entity<ToolPicker>,
_subscription: Subscription,
},
}
impl Mode {
pub fn choose_profile(_window: &mut Window, cx: &mut Context<ManageProfilesModal>) -> Self {
let settings = AgentSettings::get_global(cx);
let settings = AssistantSettings::get_global(cx);
let mut builtin_profiles = Vec::new();
let mut custom_profiles = Vec::new();
let mut profiles = settings.profiles.clone();
profiles.sort_unstable_by(|_, a, _, b| a.name.cmp(&b.name));
for (profile_id, profile) in settings.profiles.iter() {
let entry = ProfileEntry {
id: profile_id.clone(),
name: profile.name.clone(),
let profiles = profiles
.into_iter()
.map(|(id, profile)| ProfileEntry {
id,
name: profile.name,
navigation: NavigableEntry::focusable(cx),
};
if builtin_profiles::is_builtin(profile_id) {
builtin_profiles.push(entry);
} else {
custom_profiles.push(entry);
}
}
builtin_profiles.sort_unstable_by(|a, b| a.name.cmp(&b.name));
custom_profiles.sort_unstable_by(|a, b| a.name.cmp(&b.name));
})
.collect::<Vec<_>>();
Self::ChooseProfile(ChooseProfileMode {
builtin_profiles,
custom_profiles,
profiles,
add_new_profile: NavigableEntry::focusable(cx),
})
}
@@ -80,8 +65,7 @@ struct ProfileEntry {
#[derive(Clone)]
pub struct ChooseProfileMode {
builtin_profiles: Vec<ProfileEntry>,
custom_profiles: Vec<ProfileEntry>,
profiles: Vec<ProfileEntry>,
add_new_profile: NavigableEntry,
}
@@ -90,8 +74,6 @@ pub struct ViewProfileMode {
profile_id: AgentProfileId,
fork_profile: NavigableEntry,
configure_tools: NavigableEntry,
configure_mcps: NavigableEntry,
cancel_item: NavigableEntry,
}
#[derive(Clone)]
@@ -115,7 +97,7 @@ impl ManageProfilesModal {
_cx: &mut Context<Workspace>,
) {
workspace.register_action(|workspace, action: &ManageProfiles, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
let fs = workspace.app_state().fs.clone();
let thread_store = panel.read(cx).thread_store();
let tools = thread_store.read(cx).tools();
@@ -124,7 +106,7 @@ impl ManageProfilesModal {
let mut this = Self::new(fs, tools, thread_store, window, cx);
if let Some(profile_id) = action.customize_tools.clone() {
this.configure_builtin_tools(profile_id, window, cx);
this.configure_tools(profile_id, window, cx);
}
this
@@ -184,26 +166,23 @@ impl ManageProfilesModal {
profile_id,
fork_profile: NavigableEntry::focusable(cx),
configure_tools: NavigableEntry::focusable(cx),
configure_mcps: NavigableEntry::focusable(cx),
cancel_item: NavigableEntry::focusable(cx),
});
self.focus_handle(cx).focus(window);
}
fn configure_mcp_tools(
fn configure_tools(
&mut self,
profile_id: AgentProfileId,
window: &mut Window,
cx: &mut Context<Self>,
) {
let settings = AgentSettings::get_global(cx);
let settings = AssistantSettings::get_global(cx);
let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
return;
};
let tool_picker = cx.new(|cx| {
let delegate = ToolPickerDelegate::new(
ToolPickerMode::McpTools,
self.fs.clone(),
self.tools.clone(),
self.thread_store.clone(),
@@ -211,45 +190,7 @@ impl ManageProfilesModal {
profile,
cx,
);
ToolPicker::mcp_tools(delegate, window, cx)
});
let dismiss_subscription = cx.subscribe_in(&tool_picker, window, {
let profile_id = profile_id.clone();
move |this, _tool_picker, _: &DismissEvent, window, cx| {
this.view_profile(profile_id.clone(), window, cx);
}
});
self.mode = Mode::ConfigureMcps {
profile_id,
tool_picker,
_subscription: dismiss_subscription,
};
self.focus_handle(cx).focus(window);
}
fn configure_builtin_tools(
&mut self,
profile_id: AgentProfileId,
window: &mut Window,
cx: &mut Context<Self>,
) {
let settings = AgentSettings::get_global(cx);
let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
return;
};
let tool_picker = cx.new(|cx| {
let delegate = ToolPickerDelegate::new(
ToolPickerMode::BuiltinTools,
self.fs.clone(),
self.tools.clone(),
self.thread_store.clone(),
profile_id.clone(),
profile,
cx,
);
ToolPicker::builtin_tools(delegate, window, cx)
ToolPicker::new(delegate, window, cx)
});
let dismiss_subscription = cx.subscribe_in(&tool_picker, window, {
let profile_id = profile_id.clone();
@@ -270,7 +211,7 @@ impl ManageProfilesModal {
match &self.mode {
Mode::ChooseProfile { .. } => {}
Mode::NewProfile(mode) => {
let settings = AgentSettings::get_global(cx);
let settings = AssistantSettings::get_global(cx);
let base_profile = mode
.base_profile_id
@@ -300,7 +241,6 @@ impl ManageProfilesModal {
}
Mode::ViewProfile(_) => {}
Mode::ConfigureTools { .. } => {}
Mode::ConfigureMcps { .. } => {}
}
}
@@ -317,12 +257,7 @@ impl ManageProfilesModal {
}
}
Mode::ViewProfile(_) => self.choose_profile(window, cx),
Mode::ConfigureTools { profile_id, .. } => {
self.view_profile(profile_id.clone(), window, cx)
}
Mode::ConfigureMcps { profile_id, .. } => {
self.view_profile(profile_id.clone(), window, cx)
}
Mode::ConfigureTools { .. } => {}
}
}
@@ -332,7 +267,7 @@ impl ManageProfilesModal {
profile: AgentProfile,
cx: &mut Context<Self>,
) {
update_settings_file::<AgentSettings>(self.fs.clone(), cx, {
update_settings_file::<AssistantSettings>(self.fs.clone(), cx, {
move |settings, _cx| {
settings.create_profile(profile_id, profile).log_err();
}
@@ -349,7 +284,6 @@ impl Focusable for ManageProfilesModal {
Mode::NewProfile(mode) => mode.name_editor.focus_handle(cx),
Mode::ViewProfile(_) => self.focus_handle.clone(),
Mode::ConfigureTools { tool_picker, .. } => tool_picker.focus_handle(cx),
Mode::ConfigureMcps { tool_picker, .. } => tool_picker.focus_handle(cx),
}
}
}
@@ -357,51 +291,6 @@ impl Focusable for ManageProfilesModal {
impl EventEmitter<DismissEvent> for ManageProfilesModal {}
impl ManageProfilesModal {
fn render_profile(
&self,
profile: &ProfileEntry,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement + use<> {
div()
.id(SharedString::from(format!("profile-{}", profile.id)))
.track_focus(&profile.navigation.focus_handle)
.on_action({
let profile_id = profile.id.clone();
cx.listener(move |this, _: &menu::Confirm, window, cx| {
this.view_profile(profile_id.clone(), window, cx);
})
})
.child(
ListItem::new(SharedString::from(format!("profile-{}", profile.id)))
.toggle_state(profile.navigation.focus_handle.contains_focused(window, cx))
.inset(true)
.spacing(ListItemSpacing::Sparse)
.child(Label::new(profile.name.clone()))
.end_slot(
h_flex()
.gap_1()
.child(
Label::new("Customize")
.size(LabelSize::Small)
.color(Color::Muted),
)
.children(KeyBinding::for_action_in(
&menu::Confirm,
&self.focus_handle,
window,
cx,
)),
)
.on_click({
let profile_id = profile.id.clone();
cx.listener(move |this, _, window, cx| {
this.view_profile(profile_id.clone(), window, cx);
})
}),
)
}
fn render_choose_profile(
&mut self,
mode: ChooseProfileMode,
@@ -412,31 +301,57 @@ impl ManageProfilesModal {
div()
.track_focus(&self.focus_handle(cx))
.size_full()
.child(ProfileModalHeader::new("Agent Profiles", None))
.child(ProfileModalHeader::new(
"Agent Profiles",
IconName::ZedAssistant,
))
.child(
v_flex()
.pb_1()
.child(ListSeparator)
.children(
mode.builtin_profiles
.iter()
.map(|profile| self.render_profile(profile, window, cx)),
)
.when(!mode.custom_profiles.is_empty(), |this| {
this.child(ListSeparator)
.children(mode.profiles.iter().map(|profile| {
div()
.id(SharedString::from(format!("profile-{}", profile.id)))
.track_focus(&profile.navigation.focus_handle)
.on_action({
let profile_id = profile.id.clone();
cx.listener(move |this, _: &menu::Confirm, window, cx| {
this.view_profile(profile_id.clone(), window, cx);
})
})
.child(
div().pl_2().pb_1().child(
Label::new("Custom Profiles")
.size(LabelSize::Small)
.color(Color::Muted),
),
ListItem::new(SharedString::from(format!(
"profile-{}",
profile.id
)))
.toggle_state(
profile
.navigation
.focus_handle
.contains_focused(window, cx),
)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.child(Label::new(profile.name.clone()))
.end_slot(
h_flex()
.gap_1()
.child(Label::new("Customize").size(LabelSize::Small))
.children(KeyBinding::for_action_in(
&menu::Confirm,
&self.focus_handle,
window,
cx,
)),
)
.on_click({
let profile_id = profile.id.clone();
cx.listener(move |this, _, window, cx| {
this.view_profile(profile_id.clone(), window, cx);
})
}),
)
.children(
mode.custom_profiles
.iter()
.map(|profile| self.render_profile(profile, window, cx)),
)
})
}))
.child(ListSeparator)
.child(
div()
@@ -467,10 +382,7 @@ impl ManageProfilesModal {
.into_any_element(),
)
.map(|mut navigable| {
for profile in mode.builtin_profiles {
navigable = navigable.entry(profile.navigation);
}
for profile in mode.custom_profiles {
for profile in mode.profiles {
navigable = navigable.entry(profile.navigation);
}
@@ -485,7 +397,7 @@ impl ManageProfilesModal {
_window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let settings = AgentSettings::get_global(cx);
let settings = AssistantSettings::get_global(cx);
let base_profile_name = mode.base_profile_id.as_ref().map(|base_profile_id| {
settings
@@ -499,14 +411,11 @@ impl ManageProfilesModal {
.id("new-profile")
.track_focus(&self.focus_handle(cx))
.child(ProfileModalHeader::new(
match &base_profile_name {
match base_profile_name {
Some(base_profile) => format!("Fork {base_profile}"),
None => "New Profile".into(),
},
match base_profile_name {
Some(_) => Some(IconName::Scissors),
None => Some(IconName::Plus),
},
IconName::Plus,
))
.child(ListSeparator)
.child(h_flex().p_2().child(mode.name_editor.clone()))
@@ -518,26 +427,22 @@ impl ManageProfilesModal {
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let settings = AgentSettings::get_global(cx);
let settings = AssistantSettings::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() {
"write" => IconName::Pencil,
"ask" => IconName::MessageBubbles,
_ => IconName::UserRoundPen,
};
Navigable::new(
div()
.track_focus(&self.focus_handle(cx))
.size_full()
.child(ProfileModalHeader::new(profile_name, Some(icon)))
.child(ProfileModalHeader::new(
profile_name,
IconName::ZedAssistant,
))
.child(
v_flex()
.pb_1()
@@ -561,11 +466,7 @@ impl ManageProfilesModal {
)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.start_slot(
Icon::new(IconName::Scissors)
.size(IconSize::Small)
.color(Color::Muted),
)
.start_slot(Icon::new(IconName::GitBranch))
.child(Label::new("Fork Profile"))
.on_click({
let profile_id = mode.profile_id.clone();
@@ -581,20 +482,16 @@ impl ManageProfilesModal {
)
.child(
div()
.id("configure-builtin-tools")
.id("configure-tools")
.track_focus(&mode.configure_tools.focus_handle)
.on_action({
let profile_id = mode.profile_id.clone();
cx.listener(move |this, _: &menu::Confirm, window, cx| {
this.configure_builtin_tools(
profile_id.clone(),
window,
cx,
);
this.configure_tools(profile_id.clone(), window, cx);
})
})
.child(
ListItem::new("configure-builtin-tools-item")
ListItem::new("configure-tools")
.toggle_state(
mode.configure_tools
.focus_handle
@@ -602,16 +499,12 @@ impl ManageProfilesModal {
)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.start_slot(
Icon::new(IconName::Settings)
.size(IconSize::Small)
.color(Color::Muted),
)
.child(Label::new("Configure Built-in Tools"))
.start_slot(Icon::new(IconName::Cog))
.child(Label::new("Configure Tools"))
.on_click({
let profile_id = mode.profile_id.clone();
cx.listener(move |this, _, window, cx| {
this.configure_builtin_tools(
this.configure_tools(
profile_id.clone(),
window,
cx,
@@ -619,137 +512,18 @@ impl ManageProfilesModal {
})
}),
),
)
.child(
div()
.id("configure-mcps")
.track_focus(&mode.configure_mcps.focus_handle)
.on_action({
let profile_id = mode.profile_id.clone();
cx.listener(move |this, _: &menu::Confirm, window, cx| {
this.configure_mcp_tools(profile_id.clone(), window, cx);
})
})
.child(
ListItem::new("configure-mcp-tools")
.toggle_state(
mode.configure_mcps
.focus_handle
.contains_focused(window, cx),
)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.start_slot(
Icon::new(IconName::Hammer)
.size(IconSize::Small)
.color(Color::Muted),
)
.child(Label::new("Configure MCP Tools"))
.on_click({
let profile_id = mode.profile_id.clone();
cx.listener(move |this, _, window, cx| {
this.configure_mcp_tools(
profile_id.clone(),
window,
cx,
);
})
}),
),
)
.child(ListSeparator)
.child(
div()
.id("cancel-item")
.track_focus(&mode.cancel_item.focus_handle)
.on_action({
cx.listener(move |this, _: &menu::Confirm, window, cx| {
this.cancel(window, cx);
})
})
.child(
ListItem::new("cancel-item")
.toggle_state(
mode.cancel_item
.focus_handle
.contains_focused(window, cx),
)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.start_slot(
Icon::new(IconName::ArrowLeft)
.size(IconSize::Small)
.color(Color::Muted),
)
.child(Label::new("Go Back"))
.end_slot(
div().children(
KeyBinding::for_action_in(
&menu::Cancel,
&self.focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
),
)
.on_click({
cx.listener(move |this, _, window, cx| {
this.cancel(window, cx);
})
}),
),
),
)
.into_any_element(),
)
.entry(mode.fork_profile)
.entry(mode.configure_tools)
.entry(mode.configure_mcps)
.entry(mode.cancel_item)
}
}
impl Render for ManageProfilesModal {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let settings = AgentSettings::get_global(cx);
let go_back_item = div()
.id("cancel-item")
.track_focus(&self.focus_handle)
.on_action({
cx.listener(move |this, _: &menu::Confirm, window, cx| {
this.cancel(window, cx);
})
})
.child(
ListItem::new("cancel-item")
.toggle_state(self.focus_handle.contains_focused(window, cx))
.inset(true)
.spacing(ListItemSpacing::Sparse)
.start_slot(
Icon::new(IconName::ArrowLeft)
.size(IconSize::Small)
.color(Color::Muted),
)
.child(Label::new("Go Back"))
.end_slot(
div().children(
KeyBinding::for_action_in(
&menu::Cancel,
&self.focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
),
)
.on_click({
cx.listener(move |this, _, window, cx| {
this.cancel(window, cx);
})
}),
);
let settings = AssistantSettings::get_global(cx);
div()
.elevation_3(cx)
@@ -782,39 +556,13 @@ impl Render for ManageProfilesModal {
.map(|profile| profile.name.clone())
.unwrap_or_else(|| "Unknown".into());
v_flex()
.pb_1()
div()
.child(ProfileModalHeader::new(
format!("{profile_name} Configure Built-in Tools"),
Some(IconName::Cog),
format!("{profile_name}: Configure Tools"),
IconName::Cog,
))
.child(ListSeparator)
.child(tool_picker.clone())
.child(ListSeparator)
.child(go_back_item)
.into_any_element()
}
Mode::ConfigureMcps {
profile_id,
tool_picker,
..
} => {
let profile_name = settings
.profiles
.get(profile_id)
.map(|profile| profile.name.clone())
.unwrap_or_else(|| "Unknown".into());
v_flex()
.pb_1()
.child(ProfileModalHeader::new(
format!("{profile_name} — Configure MCP Tools"),
Some(IconName::Hammer),
))
.child(ListSeparator)
.child(tool_picker.clone())
.child(ListSeparator)
.child(go_back_item)
.into_any_element()
}
})

View File

@@ -0,0 +1,38 @@
use ui::prelude::*;
#[derive(IntoElement)]
pub struct ProfileModalHeader {
label: SharedString,
icon: IconName,
}
impl ProfileModalHeader {
pub fn new(label: impl Into<SharedString>, icon: IconName) -> Self {
Self {
label: label.into(),
icon,
}
}
}
impl RenderOnce for ProfileModalHeader {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
h_flex()
.w_full()
.px(DynamicSpacing::Base12.rems(cx))
.pt(DynamicSpacing::Base08.rems(cx))
.pb(DynamicSpacing::Base04.rems(cx))
.rounded_t_sm()
.gap_1p5()
.child(Icon::new(self.icon).size(IconSize::XSmall))
.child(
h_flex().gap_1().overflow_x_hidden().child(
div()
.max_w_96()
.overflow_x_hidden()
.text_ellipsis()
.child(Headline::new(self.label).size(HeadlineSize::XSmall)),
),
)
}
}

View File

@@ -0,0 +1,302 @@
use std::sync::Arc;
use assistant_settings::{
AgentProfile, AgentProfileContent, AgentProfileId, AssistantSettings, AssistantSettingsContent,
ContextServerPresetContent, VersionedAssistantSettingsContent,
};
use assistant_tool::{ToolSource, ToolWorkingSet};
use fs::Fs;
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
use gpui::{App, Context, DismissEvent, Entity, EventEmitter, Focusable, Task, WeakEntity, Window};
use picker::{Picker, PickerDelegate};
use settings::{Settings as _, update_settings_file};
use ui::{HighlightedLabel, ListItem, ListItemSpacing, prelude::*};
use util::ResultExt as _;
use crate::ThreadStore;
pub struct ToolPicker {
picker: Entity<Picker<ToolPickerDelegate>>,
}
impl ToolPicker {
pub fn new(delegate: ToolPickerDelegate, window: &mut Window, cx: &mut Context<Self>) -> Self {
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false));
Self { picker }
}
}
impl EventEmitter<DismissEvent> for ToolPicker {}
impl Focusable for ToolPicker {
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for ToolPicker {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
v_flex().w(rems(34.)).child(self.picker.clone())
}
}
#[derive(Debug, Clone)]
pub struct ToolEntry {
pub name: Arc<str>,
pub source: ToolSource,
}
pub struct ToolPickerDelegate {
tool_picker: WeakEntity<ToolPicker>,
thread_store: WeakEntity<ThreadStore>,
fs: Arc<dyn Fs>,
tools: Vec<ToolEntry>,
profile_id: AgentProfileId,
profile: AgentProfile,
matches: Vec<StringMatch>,
selected_index: usize,
}
impl ToolPickerDelegate {
pub fn new(
fs: Arc<dyn Fs>,
tool_set: Entity<ToolWorkingSet>,
thread_store: WeakEntity<ThreadStore>,
profile_id: AgentProfileId,
profile: AgentProfile,
cx: &mut Context<ToolPicker>,
) -> Self {
let mut tool_entries = Vec::new();
for (source, tools) in tool_set.read(cx).tools_by_source(cx) {
tool_entries.extend(tools.into_iter().map(|tool| ToolEntry {
name: tool.name().into(),
source: source.clone(),
}));
}
Self {
tool_picker: cx.entity().downgrade(),
thread_store,
fs,
tools: tool_entries,
profile_id,
profile,
matches: Vec::new(),
selected_index: 0,
}
}
}
impl PickerDelegate for ToolPickerDelegate {
type ListItem = ListItem;
fn match_count(&self) -> usize {
self.matches.len()
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) {
self.selected_index = ix;
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
"Search tools…".into()
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let background = cx.background_executor().clone();
let candidates = self
.tools
.iter()
.enumerate()
.map(|(id, profile)| StringMatchCandidate::new(id, profile.name.as_ref()))
.collect::<Vec<_>>();
cx.spawn_in(window, async move |this, cx| {
let matches = if query.is_empty() {
candidates
.into_iter()
.enumerate()
.map(|(index, candidate)| StringMatch {
candidate_id: index,
string: candidate.string,
positions: Vec::new(),
score: 0.,
})
.collect()
} else {
match_strings(
&candidates,
&query,
false,
100,
&Default::default(),
background,
)
.await
};
this.update(cx, |this, _cx| {
this.delegate.matches = matches;
this.delegate.selected_index = this
.delegate
.selected_index
.min(this.delegate.matches.len().saturating_sub(1));
})
.log_err();
})
}
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
if self.matches.is_empty() {
self.dismissed(window, cx);
return;
}
let candidate_id = self.matches[self.selected_index].candidate_id;
let tool = &self.tools[candidate_id];
let is_enabled = match &tool.source {
ToolSource::Native => {
let is_enabled = self.profile.tools.entry(tool.name.clone()).or_default();
*is_enabled = !*is_enabled;
*is_enabled
}
ToolSource::ContextServer { id } => {
let preset = self
.profile
.context_servers
.entry(id.clone().into())
.or_default();
let is_enabled = preset.tools.entry(tool.name.clone()).or_default();
*is_enabled = !*is_enabled;
*is_enabled
}
};
let active_profile_id = &AssistantSettings::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::<AssistantSettings>(self.fs.clone(), cx, {
let profile_id = self.profile_id.clone();
let default_profile = self.profile.clone();
let tool = tool.clone();
move |settings, _cx| match settings {
AssistantSettingsContent::Versioned(boxed) => {
if let VersionedAssistantSettingsContent::V2(ref mut settings) = **boxed {
let profiles = settings.profiles.get_or_insert_default();
let profile =
profiles
.entry(profile_id)
.or_insert_with(|| AgentProfileContent {
name: default_profile.name.into(),
tools: default_profile.tools,
enable_all_context_servers: Some(
default_profile.enable_all_context_servers,
),
context_servers: default_profile
.context_servers
.into_iter()
.map(|(server_id, preset)| {
(
server_id,
ContextServerPresetContent {
tools: preset.tools,
},
)
})
.collect(),
});
match tool.source {
ToolSource::Native => {
*profile.tools.entry(tool.name).or_default() = is_enabled;
}
ToolSource::ContextServer { id } => {
let preset = profile
.context_servers
.entry(id.clone().into())
.or_default();
*preset.tools.entry(tool.name.clone()).or_default() = is_enabled;
}
}
}
}
_ => {}
}
});
}
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
self.tool_picker
.update(cx, |_this, cx| cx.emit(DismissEvent))
.log_err();
}
fn render_match(
&self,
ix: usize,
selected: bool,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let tool_match = &self.matches[ix];
let tool = &self.tools[tool_match.candidate_id];
let is_enabled = match &tool.source {
ToolSource::Native => self.profile.tools.get(&tool.name).copied().unwrap_or(false),
ToolSource::ContextServer { id } => self
.profile
.context_servers
.get(id.as_ref())
.and_then(|preset| preset.tools.get(&tool.name))
.copied()
.unwrap_or(false),
};
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.child(
h_flex()
.gap_2()
.child(HighlightedLabel::new(
tool_match.string.clone(),
tool_match.positions.clone(),
))
.map(|parent| match &tool.source {
ToolSource::Native => parent,
ToolSource::ContextServer { id } => parent
.child(Label::new(id).size(LabelSize::XSmall).color(Color::Muted)),
}),
)
.end_slot::<Icon>(is_enabled.then(|| {
Icon::new(IconName::Check)
.size(IconSize::Small)
.color(Color::Success)
})),
)
}
}

View File

@@ -0,0 +1,123 @@
use assistant_settings::AssistantSettings;
use fs::Fs;
use gpui::{Entity, FocusHandle, SharedString};
use language_model_selector::{
LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
};
use settings::update_settings_file;
use std::sync::Arc;
use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
pub use language_model_selector::ModelType;
pub struct AssistantModelSelector {
selector: Entity<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle,
}
impl AssistantModelSelector {
pub(crate) fn new(
fs: Arc<dyn Fs>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle,
model_type: ModelType,
window: &mut Window,
cx: &mut App,
) -> Self {
Self {
selector: cx.new(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
let provider = model.provider_id().0.to_string();
let model_id = model.id().0.to_string();
match model_type {
ModelType::Default => {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _cx| {
settings.set_model(model.clone());
},
);
}
ModelType::InlineAssistant => {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _cx| {
settings.set_inline_assistant_model(
provider.clone(),
model_id.clone(),
);
},
);
}
}
},
model_type,
window,
cx,
)
}),
menu_handle,
focus_handle,
}
}
pub fn toggle(&self, window: &mut Window, cx: &mut Context<Self>) {
self.menu_handle.toggle(window, cx);
}
}
impl Render for AssistantModelSelector {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle.clone();
let model = self.selector.read(cx).active_model(cx);
let (model_name, model_icon) = match model {
Some(model) => (model.model.name().0, Some(model.provider.icon())),
_ => (SharedString::from("No model selected"), None),
};
LanguageModelSelectorPopoverMenu::new(
self.selector.clone(),
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.gap_0p5()
.children(
model_icon.map(|icon| {
Icon::new(icon).color(Color::Muted).size(IconSize::Small)
}),
)
.child(
Label::new(model_name)
.size(LabelSize::Small)
.color(Color::Muted)
.ml_1(),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
gpui::Corner::BottomRight,
)
.with_handle(self.menu_handle.clone())
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,12 @@
use crate::context::ContextLoadResult;
use crate::context::attach_context_to_message;
use crate::context_store::ContextStore;
use crate::inline_prompt_editor::CodegenStatus;
use crate::{context::load_context, context_store::ContextStore};
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
use anyhow::Result;
use client::telemetry::Telemetry;
use collections::HashSet;
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
use futures::{
SinkExt, Stream, StreamExt, TryStreamExt as _, channel::mpsc, future::LocalBoxFuture, join,
};
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task, WeakEntity};
use futures::{SinkExt, Stream, StreamExt, channel::mpsc, future::LocalBoxFuture, join};
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task};
use language::{Buffer, IndentKind, Point, TransactionId, line_diff};
use language_model::{
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
@@ -17,9 +14,7 @@ use language_model::{
};
use multi_buffer::MultiBufferRow;
use parking_lot::Mutex;
use project::Project;
use prompt_store::PromptBuilder;
use prompt_store::PromptStore;
use rope::Rope;
use smol::future::FutureExt;
use std::{
@@ -34,7 +29,6 @@ use std::{
};
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
use zed_llm_client::CompletionIntent;
pub struct BufferCodegen {
alternatives: Vec<Entity<CodegenAlternative>>,
@@ -45,8 +39,6 @@ pub struct BufferCodegen {
range: Range<Anchor>,
initial_transaction_id: Option<TransactionId>,
context_store: Entity<ContextStore>,
project: WeakEntity<Project>,
prompt_store: Option<Entity<PromptStore>>,
telemetry: Arc<Telemetry>,
builder: Arc<PromptBuilder>,
pub is_insertion: bool,
@@ -58,8 +50,6 @@ impl BufferCodegen {
range: Range<Anchor>,
initial_transaction_id: Option<TransactionId>,
context_store: Entity<ContextStore>,
project: WeakEntity<Project>,
prompt_store: Option<Entity<PromptStore>>,
telemetry: Arc<Telemetry>,
builder: Arc<PromptBuilder>,
cx: &mut Context<Self>,
@@ -70,8 +60,6 @@ impl BufferCodegen {
range.clone(),
false,
Some(context_store.clone()),
project.clone(),
prompt_store.clone(),
Some(telemetry.clone()),
builder.clone(),
cx,
@@ -87,8 +75,6 @@ impl BufferCodegen {
range,
initial_transaction_id,
context_store,
project,
prompt_store,
telemetry,
builder,
};
@@ -167,8 +153,6 @@ impl BufferCodegen {
self.range.clone(),
false,
Some(self.context_store.clone()),
self.project.clone(),
self.prompt_store.clone(),
Some(self.telemetry.clone()),
self.builder.clone(),
cx,
@@ -245,14 +229,13 @@ pub struct CodegenAlternative {
generation: Task<()>,
diff: Diff,
context_store: Option<Entity<ContextStore>>,
project: WeakEntity<Project>,
prompt_store: Option<Entity<PromptStore>>,
telemetry: Option<Arc<Telemetry>>,
_subscription: gpui::Subscription,
builder: Arc<PromptBuilder>,
active: bool,
edits: Vec<(Range<Anchor>, String)>,
line_operations: Vec<LineOperation>,
request: Option<LanguageModelRequest>,
elapsed_time: Option<f64>,
completion: Option<String>,
pub message_id: Option<String>,
@@ -266,8 +249,6 @@ impl CodegenAlternative {
range: Range<Anchor>,
active: bool,
context_store: Option<Entity<ContextStore>>,
project: WeakEntity<Project>,
prompt_store: Option<Entity<PromptStore>>,
telemetry: Option<Arc<Telemetry>>,
builder: Arc<PromptBuilder>,
cx: &mut Context<Self>,
@@ -309,8 +290,6 @@ impl CodegenAlternative {
generation: Task::ready(()),
diff: Diff::default(),
context_store,
project,
prompt_store,
telemetry,
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
builder,
@@ -318,6 +297,7 @@ impl CodegenAlternative {
edits: Vec::new(),
line_operations: Vec::new(),
range,
request: None,
elapsed_time: None,
completion: None,
}
@@ -385,20 +365,17 @@ impl CodegenAlternative {
if user_prompt.trim().to_lowercase() == "delete" {
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)
let request = self.build_request(user_prompt, cx)?;
self.request = Some(request.clone());
cx.spawn(async move |_, cx| model.stream_completion_text(request, &cx).await)
.boxed_local()
};
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
Ok(())
}
fn build_request(
&self,
model: &Arc<dyn LanguageModel>,
user_prompt: String,
cx: &mut App,
) -> Result<Task<LanguageModelRequest>> {
fn build_request(&self, user_prompt: String, cx: &mut App) -> Result<LanguageModelRequest> {
let buffer = self.buffer.read(cx).snapshot(cx);
let language = buffer.language_at(self.range.start);
let language_name = if let Some(language) = language.as_ref() {
@@ -420,60 +397,41 @@ impl CodegenAlternative {
if start_buffer.remote_id() == end_buffer.remote_id() {
(start_buffer.clone(), start_buffer_offset..end_buffer_offset)
} else {
anyhow::bail!("invalid transformation range");
return Err(anyhow::anyhow!("invalid transformation range"));
}
} else {
anyhow::bail!("invalid transformation range");
return Err(anyhow::anyhow!("invalid transformation range"));
};
let prompt = self
.builder
.generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
.context("generating content prompt")?;
.map_err(|e| anyhow::anyhow!("Failed to generate content prompt: {}", e))?;
let context_task = self.context_store.as_ref().map(|context_store| {
if let Some(project) = self.project.upgrade() {
let context = context_store
.read(cx)
.context()
.cloned()
.collect::<Vec<_>>();
load_context(context, &project, &self.prompt_store, cx)
} else {
Task::ready(ContextLoadResult::default())
}
});
let mut request_message = LanguageModelRequestMessage {
role: Role::User,
content: Vec::new(),
cache: false,
};
let temperature = AgentSettings::temperature_for_model(&model, cx);
if let Some(context_store) = &self.context_store {
attach_context_to_message(
&mut request_message,
context_store.read(cx).context().iter(),
cx,
);
}
Ok(cx.spawn(async move |_cx| {
let mut request_message = LanguageModelRequestMessage {
role: Role::User,
content: Vec::new(),
cache: false,
};
request_message.content.push(prompt.into());
if let Some(context_task) = context_task {
context_task
.await
.loaded_context
.add_to_request_message(&mut request_message);
}
request_message.content.push(prompt.into());
LanguageModelRequest {
thread_id: None,
prompt_id: None,
intent: Some(CompletionIntent::InlineAssist),
mode: None,
tools: Vec::new(),
tool_choice: None,
stop: Vec::new(),
temperature,
messages: vec![request_message],
}
}))
Ok(LanguageModelRequest {
thread_id: None,
prompt_id: None,
tools: Vec::new(),
stop: Vec::new(),
temperature: None,
messages: vec![request_message],
})
}
pub fn handle_stream(
@@ -511,7 +469,7 @@ impl CodegenAlternative {
}
}
let http_client = cx.http_client();
let http_client = cx.http_client().clone();
let telemetry = self.telemetry.clone();
let language_name = {
let multibuffer = self.buffer.read(cx);
@@ -550,9 +508,7 @@ impl CodegenAlternative {
let mut response_latency = None;
let request_start = Instant::now();
let diff = async {
let chunks = StripInvalidSpans::new(
stream?.stream.map_err(|error| error.into()),
);
let chunks = StripInvalidSpans::new(stream?.stream);
futures::pin_mut!(chunks);
let mut diff = StreamingDiff::new(selected_text.to_string());
let mut line_diff = LineDiff::default();
@@ -779,7 +735,7 @@ impl CodegenAlternative {
cx: &mut Context<CodegenAlternative>,
) {
let transaction = self.buffer.update(cx, |buffer, cx| {
// Avoid grouping agent edits with user edits.
// Avoid grouping assistant edits with user edits.
buffer.finalize_last_transaction(cx);
buffer.start_transaction(cx);
buffer.edit(edits, None, cx);
@@ -788,7 +744,7 @@ impl CodegenAlternative {
if let Some(transaction) = transaction {
if let Some(first_transaction) = self.transformation_transaction_id {
// Group all agent edits into the first transaction.
// Group all assistant edits into the first transaction.
self.buffer.update(cx, |buffer, cx| {
buffer.merge_transactions(transaction, first_transaction, cx)
});
@@ -1078,7 +1034,6 @@ impl Diff {
#[cfg(test)]
mod tests {
use super::*;
use fs::FakeFs;
use futures::{
Stream,
stream::{self},
@@ -1102,7 +1057,9 @@ mod tests {
#[gpui::test(iterations = 10)]
async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
init_test(cx);
cx.set_global(cx.update(SettingsStore::test));
cx.update(language_model::LanguageModelRegistry::test);
cx.update(language_settings::init);
let text = indoc! {"
fn main() {
@@ -1119,16 +1076,12 @@ mod tests {
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
});
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, vec![], cx).await;
let codegen = cx.new(|cx| {
CodegenAlternative::new(
buffer.clone(),
range.clone(),
true,
None,
project.downgrade(),
None,
None,
prompt_builder,
cx,
@@ -1172,7 +1125,8 @@ mod tests {
cx: &mut TestAppContext,
mut rng: StdRng,
) {
init_test(cx);
cx.set_global(cx.update(SettingsStore::test));
cx.update(language_settings::init);
let text = indoc! {"
fn main() {
@@ -1186,16 +1140,12 @@ mod tests {
snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6))
});
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, vec![], cx).await;
let codegen = cx.new(|cx| {
CodegenAlternative::new(
buffer.clone(),
range.clone(),
true,
None,
project.downgrade(),
None,
None,
prompt_builder,
cx,
@@ -1241,7 +1191,9 @@ mod tests {
cx: &mut TestAppContext,
mut rng: StdRng,
) {
init_test(cx);
cx.update(LanguageModelRegistry::test);
cx.set_global(cx.update(SettingsStore::test));
cx.update(language_settings::init);
let text = concat!(
"fn main() {\n",
@@ -1255,16 +1207,12 @@ mod tests {
snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2))
});
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, vec![], cx).await;
let codegen = cx.new(|cx| {
CodegenAlternative::new(
buffer.clone(),
range.clone(),
true,
None,
project.downgrade(),
None,
None,
prompt_builder,
cx,
@@ -1307,7 +1255,9 @@ mod tests {
#[gpui::test(iterations = 10)]
async fn test_autoindent_respects_tabs_in_selection(cx: &mut TestAppContext) {
init_test(cx);
cx.update(LanguageModelRegistry::test);
cx.set_global(cx.update(SettingsStore::test));
cx.update(language_settings::init);
let text = indoc! {"
func main() {
@@ -1324,16 +1274,12 @@ mod tests {
snapshot.anchor_before(Point::new(0, 0))..snapshot.anchor_after(Point::new(4, 2))
});
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, vec![], cx).await;
let codegen = cx.new(|cx| {
CodegenAlternative::new(
buffer.clone(),
range.clone(),
true,
None,
project.downgrade(),
None,
None,
prompt_builder,
cx,
@@ -1367,7 +1313,9 @@ mod tests {
#[gpui::test]
async fn test_inactive_codegen_alternative(cx: &mut TestAppContext) {
init_test(cx);
cx.update(LanguageModelRegistry::test);
cx.set_global(cx.update(SettingsStore::test));
cx.update(language_settings::init);
let text = indoc! {"
fn main() {
@@ -1381,16 +1329,12 @@ mod tests {
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 14))
});
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, vec![], cx).await;
let codegen = cx.new(|cx| {
CodegenAlternative::new(
buffer.clone(),
range.clone(),
false,
None,
project.downgrade(),
None,
None,
prompt_builder,
cx,
@@ -1471,13 +1415,6 @@ mod tests {
}
}
fn init_test(cx: &mut TestAppContext) {
cx.update(LanguageModelRegistry::test);
cx.set_global(cx.update(SettingsStore::test));
cx.update(Project::init_settings);
cx.update(language_settings::init);
}
fn simulate_response_stream(
codegen: Entity<CodegenAlternative>,
cx: &mut TestAppContext,

File diff suppressed because it is too large Load Diff

View File

@@ -6,15 +6,12 @@ mod symbol_context_picker;
mod thread_context_picker;
use std::ops::Range;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{Result, anyhow};
pub use completion_provider::ContextPickerCompletionProvider;
use editor::display_map::{Crease, CreaseId, CreaseMetadata, FoldId};
use editor::display_map::{Crease, FoldId};
use editor::{Anchor, AnchorRangeExt as _, Editor, ExcerptId, FoldPlaceholder, ToOffset};
use fetch_context_picker::FetchContextPicker;
use file_context_picker::FileContextPicker;
use file_context_picker::render_file_context_entry;
use gpui::{
App, DismissEvent, Empty, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task,
@@ -22,25 +19,27 @@ use gpui::{
};
use language::Buffer;
use multi_buffer::MultiBufferRow;
use paths::contexts_dir;
use project::{Entry, ProjectPath};
use prompt_store::{PromptStore, UserPromptId};
use rules_context_picker::{RulesContextEntry, RulesContextPicker};
use prompt_store::UserPromptId;
use rules_context_picker::RulesContextEntry;
use symbol_context_picker::SymbolContextPicker;
use thread_context_picker::{
ThreadContextEntry, ThreadContextPicker, render_thread_context_entry, unordered_thread_entries,
};
use thread_context_picker::{ThreadContextEntry, render_thread_context_entry};
use ui::{
ButtonLike, ContextMenu, ContextMenuEntry, ContextMenuItem, Disclosure, TintColor, prelude::*,
};
use uuid::Uuid;
use workspace::{Workspace, notifications::NotifyResultExt};
use crate::AgentPanel;
use crate::AssistantPanel;
use crate::context::RULES_ICON;
pub use crate::context_picker::completion_provider::ContextPickerCompletionProvider;
use crate::context_picker::fetch_context_picker::FetchContextPicker;
use crate::context_picker::file_context_picker::FileContextPicker;
use crate::context_picker::rules_context_picker::RulesContextPicker;
use crate::context_picker::thread_context_picker::ThreadContextPicker;
use crate::context_store::ContextStore;
use crate::thread::ThreadId;
use crate::thread_store::{TextThreadStore, ThreadStore};
use crate::thread_store::ThreadStore;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ContextPickerEntry {
@@ -114,7 +113,7 @@ impl TryFrom<&str> for ContextPickerMode {
"symbol" => Ok(Self::Symbol),
"fetch" => Ok(Self::Fetch),
"thread" => Ok(Self::Thread),
"rule" => Ok(Self::Rules),
"rules" => Ok(Self::Rules),
_ => Err(format!("Invalid context picker mode: {}", value)),
}
}
@@ -127,7 +126,7 @@ impl ContextPickerMode {
Self::Symbol => "symbol",
Self::Fetch => "fetch",
Self::Thread => "thread",
Self::Rules => "rule",
Self::Rules => "rules",
}
}
@@ -167,8 +166,6 @@ pub(super) struct ContextPicker {
workspace: WeakEntity<Workspace>,
context_store: WeakEntity<ContextStore>,
thread_store: Option<WeakEntity<ThreadStore>>,
text_thread_store: Option<WeakEntity<TextThreadStore>>,
prompt_store: Option<Entity<PromptStore>>,
_subscriptions: Vec<Subscription>,
}
@@ -176,7 +173,6 @@ impl ContextPicker {
pub fn new(
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<ThreadStore>>,
text_thread_store: Option<WeakEntity<TextThreadStore>>,
context_store: WeakEntity<ContextStore>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -197,13 +193,6 @@ impl ContextPicker {
)
.collect::<Vec<Subscription>>();
let prompt_store = thread_store.as_ref().and_then(|thread_store| {
thread_store
.read_with(cx, |thread_store, _cx| thread_store.prompt_store().clone())
.ok()
.flatten()
});
ContextPicker {
mode: ContextPickerState::Default(ContextMenu::build(
window,
@@ -213,8 +202,6 @@ impl ContextPicker {
workspace,
context_store,
thread_store,
text_thread_store,
prompt_store,
_subscriptions: subscriptions,
}
}
@@ -239,12 +226,7 @@ impl ContextPicker {
.workspace
.upgrade()
.map(|workspace| {
available_context_picker_entries(
&self.prompt_store,
&self.thread_store,
&workspace,
cx,
)
available_context_picker_entries(&self.thread_store, &workspace, cx)
})
.unwrap_or_default();
@@ -273,7 +255,7 @@ impl ContextPicker {
context_picker.update(cx, |this, cx| this.select_entry(entry, window, cx))
})
}))
.keep_open_on_confirm(true)
.keep_open_on_confirm()
});
cx.subscribe(&menu, move |_, _, _: &DismissEvent, cx| {
@@ -322,10 +304,10 @@ impl ContextPicker {
}));
}
ContextPickerMode::Rules => {
if let Some(prompt_store) = self.prompt_store.as_ref() {
if let Some(thread_store) = self.thread_store.as_ref() {
self.mode = ContextPickerState::Rules(cx.new(|cx| {
RulesContextPicker::new(
prompt_store.clone(),
thread_store.clone(),
context_picker.clone(),
self.context_store.clone(),
window,
@@ -346,15 +328,10 @@ impl ContextPicker {
}));
}
ContextPickerMode::Thread => {
if let Some((thread_store, text_thread_store)) = self
.thread_store
.as_ref()
.zip(self.text_thread_store.as_ref())
{
if let Some(thread_store) = self.thread_store.as_ref() {
self.mode = ContextPickerState::Thread(cx.new(|cx| {
ThreadContextPicker::new(
thread_store.clone(),
text_thread_store.clone(),
context_picker.clone(),
self.context_store.clone(),
window,
@@ -381,16 +358,6 @@ impl ContextPicker {
cx.focus_self(window);
}
pub fn select_first(&mut self, window: &mut Window, cx: &mut Context<Self>) {
match &self.mode {
ContextPickerState::Default(entity) => entity.update(cx, |entity, cx| {
entity.select_first(&Default::default(), window, cx)
}),
// Other variants already select their first entry on open automatically
_ => {}
}
}
fn recent_menu_item(
&self,
context_picker: Entity<ContextPicker>,
@@ -409,7 +376,7 @@ impl ContextPicker {
ContextMenuItem::custom_entry(
move |_window, cx| {
render_file_context_entry(
ElementId::named_usize("ctx-recent", ix),
ElementId::NamedInteger("ctx-recent".into(), ix),
worktree_id,
&path,
&path_prefix,
@@ -435,9 +402,9 @@ impl ContextPicker {
render_thread_context_entry(&view_thread, context_store.clone(), cx)
.into_any()
},
move |window, cx| {
move |_window, cx| {
context_picker.update(cx, |this, cx| {
this.add_recent_thread(thread.clone(), window, cx)
this.add_recent_thread(thread.clone(), cx)
.detach_and_log_err(cx);
})
},
@@ -468,54 +435,30 @@ impl ContextPicker {
fn add_recent_thread(
&self,
entry: ThreadContextEntry,
window: &mut Window,
thread: ThreadContextEntry,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let Some(context_store) = self.context_store.upgrade() else {
return Task::ready(Err(anyhow!("context store not available")));
};
match entry {
ThreadContextEntry::Thread { id, .. } => {
let Some(thread_store) = self
.thread_store
.as_ref()
.and_then(|thread_store| thread_store.upgrade())
else {
return Task::ready(Err(anyhow!("thread store not available")));
};
let Some(thread_store) = self
.thread_store
.as_ref()
.and_then(|thread_store| thread_store.upgrade())
else {
return Task::ready(Err(anyhow!("thread store not available")));
};
let open_thread_task =
thread_store.update(cx, |this, cx| this.open_thread(&id, window, cx));
cx.spawn(async move |this, cx| {
let thread = open_thread_task.await?;
context_store.update(cx, |context_store, cx| {
context_store.add_thread(thread, true, cx);
})?;
this.update(cx, |_this, cx| cx.notify())
})
}
ThreadContextEntry::Context { path, .. } => {
let Some(text_thread_store) = self
.text_thread_store
.as_ref()
.and_then(|thread_store| thread_store.upgrade())
else {
return Task::ready(Err(anyhow!("text thread store not available")));
};
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&thread.id, cx));
cx.spawn(async move |this, cx| {
let thread = open_thread_task.await?;
context_store.update(cx, |context_store, cx| {
context_store.add_thread(thread, true, cx);
})?;
let task = text_thread_store
.update(cx, |this, cx| this.open_local_context(path.clone(), cx));
cx.spawn(async move |this, cx| {
let thread = task.await?;
context_store.update(cx, |context_store, cx| {
context_store.add_text_thread(thread, true, cx);
})?;
this.update(cx, |_this, cx| cx.notify())
})
}
}
this.update(cx, |_this, cx| cx.notify())
})
}
fn recent_entries(&self, cx: &mut App) -> Vec<RecentEntry> {
@@ -527,14 +470,7 @@ impl ContextPicker {
return vec![];
};
recent_context_picker_entries(
context_store,
self.thread_store.clone(),
self.text_thread_store.clone(),
workspace,
None,
cx,
)
recent_context_picker_entries(context_store, self.thread_store.clone(), workspace, cx)
}
fn notify_current_picker(&mut self, cx: &mut Context<Self>) {
@@ -590,7 +526,6 @@ enum RecentEntry {
}
fn available_context_picker_entries(
prompt_store: &Option<Entity<PromptStore>>,
thread_store: &Option<WeakEntity<ThreadStore>>,
workspace: &Entity<Workspace>,
cx: &mut App,
@@ -615,9 +550,6 @@ fn available_context_picker_entries(
if thread_store.is_some() {
entries.push(ContextPickerEntry::Mode(ContextPickerMode::Thread));
}
if prompt_store.is_some() {
entries.push(ContextPickerEntry::Mode(ContextPickerMode::Rules));
}
@@ -629,14 +561,12 @@ fn available_context_picker_entries(
fn recent_context_picker_entries(
context_store: Entity<ContextStore>,
thread_store: Option<WeakEntity<ThreadStore>>,
text_thread_store: Option<WeakEntity<TextThreadStore>>,
workspace: Entity<Workspace>,
exclude_path: Option<ProjectPath>,
cx: &App,
) -> Vec<RecentEntry> {
let mut recent = Vec::with_capacity(6);
let mut current_files = context_store.read(cx).file_paths(cx);
current_files.extend(exclude_path);
let current_files = context_store.read(cx).file_paths(cx);
let workspace = workspace.read(cx);
let project = workspace.project().read(cx);
@@ -655,38 +585,29 @@ fn recent_context_picker_entries(
}),
);
let current_threads = context_store.read(cx).thread_ids();
let mut current_threads = context_store.read(cx).thread_ids();
let active_thread_id = workspace
.panel::<AgentPanel>(cx)
.and_then(|panel| Some(panel.read(cx).active_thread()?.read(cx).id()));
if let Some((thread_store, text_thread_store)) = thread_store
.and_then(|store| store.upgrade())
.zip(text_thread_store.and_then(|store| store.upgrade()))
if let Some(active_thread) = workspace
.panel::<AssistantPanel>(cx)
.map(|panel| panel.read(cx).active_thread(cx))
{
let mut threads = unordered_thread_entries(thread_store, text_thread_store, cx)
.filter(|(_, thread)| match thread {
ThreadContextEntry::Thread { id, .. } => {
Some(id) != active_thread_id && !current_threads.contains(id)
}
ThreadContextEntry::Context { .. } => true,
})
.collect::<Vec<_>>();
const RECENT_COUNT: usize = 2;
if threads.len() > RECENT_COUNT {
threads.select_nth_unstable_by_key(RECENT_COUNT - 1, |(updated_at, _)| {
std::cmp::Reverse(*updated_at)
});
threads.truncate(RECENT_COUNT);
}
threads.sort_unstable_by_key(|(updated_at, _)| std::cmp::Reverse(*updated_at));
current_threads.insert(active_thread.read(cx).id().clone());
}
if let Some(thread_store) = thread_store.and_then(|thread_store| thread_store.upgrade()) {
recent.extend(
threads
thread_store
.read(cx)
.threads()
.into_iter()
.map(|(_, thread)| RecentEntry::Thread(thread)),
.filter(|thread| !current_threads.contains(&thread.id))
.take(2)
.map(|thread| {
RecentEntry::Thread(ThreadContextEntry {
id: thread.id,
summary: thread.summary,
})
}),
);
}
@@ -701,7 +622,9 @@ fn add_selections_as_context(
let selection_ranges = selection_ranges(workspace, cx);
context_store.update(cx, |context_store, cx| {
for (buffer, range) in selection_ranges {
context_store.add_selection(buffer, range, cx);
context_store
.add_selection(buffer, range, cx)
.detach_and_log_err(cx);
}
})
}
@@ -739,20 +662,21 @@ fn selection_ranges(
})
}
pub(crate) fn insert_crease_for_mention(
pub(crate) fn insert_fold_for_mention(
excerpt_id: ExcerptId,
crease_start: text::Anchor,
content_len: usize,
crease_label: SharedString,
crease_icon_path: SharedString,
editor_entity: Entity<Editor>,
window: &mut Window,
cx: &mut App,
) -> Option<CreaseId> {
) {
editor_entity.update(cx, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let start = snapshot.anchor_in_excerpt(excerpt_id, crease_start)?;
let Some(start) = snapshot.anchor_in_excerpt(excerpt_id, crease_start) else {
return;
};
let start = start.bias_right(&snapshot);
let end = snapshot.anchor_before(start.to_offset(&snapshot) + content_len);
@@ -764,11 +688,10 @@ pub(crate) fn insert_crease_for_mention(
editor_entity.downgrade(),
);
let ids = editor.insert_creases(vec![crease.clone()], cx);
editor.fold_creases(vec![crease], false, window, cx);
Some(ids[0])
})
editor.display_map.update(cx, |display_map, cx| {
display_map.fold(vec![crease], cx);
});
});
}
pub fn crease_for_mention(
@@ -778,20 +701,20 @@ pub fn crease_for_mention(
editor_entity: WeakEntity<Editor>,
) -> Crease<Anchor> {
let placeholder = FoldPlaceholder {
render: render_fold_icon_button(icon_path.clone(), label.clone(), editor_entity),
render: render_fold_icon_button(icon_path, label, editor_entity),
merge_adjacent: false,
..Default::default()
};
let render_trailer = move |_row, _unfold, _window: &mut Window, _cx: &mut App| Empty.into_any();
Crease::inline(
let crease = Crease::inline(
range,
placeholder.clone(),
fold_toggle("mention"),
render_trailer,
)
.with_metadata(CreaseMetadata { icon_path, label })
);
crease
}
fn render_fold_icon_button(
@@ -885,8 +808,7 @@ pub enum MentionLink {
Selection(ProjectPath, Range<usize>),
Fetch(String),
Thread(ThreadId),
TextThread(Arc<Path>),
Rule(UserPromptId),
Rules(UserPromptId),
}
impl MentionLink {
@@ -895,9 +817,7 @@ impl MentionLink {
const SELECTION: &str = "@selection";
const THREAD: &str = "@thread";
const FETCH: &str = "@fetch";
const RULE: &str = "@rule";
const TEXT_THREAD_URL_PREFIX: &str = "text-thread://";
const RULES: &str = "@rules";
const SEPARATOR: &str = ":";
@@ -907,7 +827,7 @@ impl MentionLink {
|| url.starts_with(Self::FETCH)
|| url.starts_with(Self::SELECTION)
|| url.starts_with(Self::THREAD)
|| url.starts_with(Self::RULE)
|| url.starts_with(Self::RULES)
}
pub fn for_file(file_name: &str, full_path: &str) -> String {
@@ -938,30 +858,15 @@ impl MentionLink {
}
pub fn for_thread(thread: &ThreadContextEntry) -> String {
match thread {
ThreadContextEntry::Thread { id, title } => {
format!("[@{}]({}:{})", title, Self::THREAD, id)
}
ThreadContextEntry::Context { path, title } => {
let filename = path.file_name().unwrap_or_default().to_string_lossy();
let escaped_filename = urlencoding::encode(&filename);
format!(
"[@{}]({}:{}{})",
title,
Self::THREAD,
Self::TEXT_THREAD_URL_PREFIX,
escaped_filename
)
}
}
format!("[@{}]({}:{})", thread.summary, Self::THREAD, thread.id)
}
pub fn for_fetch(url: &str) -> String {
format!("[@{}]({}:{})", url, Self::FETCH, url)
}
pub fn for_rule(rule: &RulesContextEntry) -> String {
format!("[@{}]({}:{})", rule.title, Self::RULE, rule.prompt_id.0)
pub fn for_rules(rules: &RulesContextEntry) -> String {
format!("[@{}]({}:{})", rules.title, Self::RULES, rules.prompt_id.0)
}
pub fn try_parse(link: &str, workspace: &Entity<Workspace>, cx: &App) -> Option<Self> {
@@ -1015,20 +920,13 @@ impl MentionLink {
Some(MentionLink::Selection(project_path, line_range))
}
Self::THREAD => {
if let Some(encoded_filename) = argument.strip_prefix(Self::TEXT_THREAD_URL_PREFIX)
{
let filename = urlencoding::decode(encoded_filename).ok()?;
let path = contexts_dir().join(filename.as_ref()).into();
Some(MentionLink::TextThread(path))
} else {
let thread_id = ThreadId::from(argument);
Some(MentionLink::Thread(thread_id))
}
let thread_id = ThreadId::from(argument);
Some(MentionLink::Thread(thread_id))
}
Self::FETCH => Some(MentionLink::Fetch(argument.to_string())),
Self::RULE => {
Self::RULES => {
let prompt_id = UserPromptId(Uuid::try_parse(argument).ok()?);
Some(MentionLink::Rule(prompt_id))
Some(MentionLink::Rules(prompt_id))
}
_ => None,
}

View File

@@ -14,24 +14,23 @@ use http_client::HttpClientWithUrl;
use itertools::Itertools;
use language::{Buffer, CodeLabel, HighlightId};
use lsp::CompletionContext;
use project::{Completion, CompletionIntent, CompletionResponse, ProjectPath, Symbol, WorktreeId};
use prompt_store::PromptStore;
use project::{Completion, CompletionIntent, ProjectPath, Symbol, WorktreeId};
use prompt_store::PromptId;
use rope::Point;
use text::{Anchor, OffsetRangeExt, ToPoint};
use ui::prelude::*;
use util::ResultExt as _;
use workspace::Workspace;
use crate::Thread;
use crate::context::{AgentContextHandle, AgentContextKey, ContextCreasesAddon, RULES_ICON};
use crate::context::RULES_ICON;
use crate::context_picker::file_context_picker::search_files;
use crate::context_picker::symbol_context_picker::search_symbols;
use crate::context_store::ContextStore;
use crate::thread_store::{TextThreadStore, ThreadStore};
use crate::thread_store::ThreadStore;
use super::fetch_context_picker::fetch_url_content;
use super::file_context_picker::{FileMatch, search_files};
use super::file_context_picker::FileMatch;
use super::rules_context_picker::{RulesContextEntry, search_rules};
use super::symbol_context_picker::SymbolMatch;
use super::symbol_context_picker::search_symbols;
use super::thread_context_picker::{ThreadContextEntry, ThreadMatch, search_threads};
use super::{
ContextPickerAction, ContextPickerEntry, ContextPickerMode, MentionLink, RecentEntry,
@@ -39,8 +38,8 @@ use super::{
};
pub(crate) enum Match {
File(FileMatch),
Symbol(SymbolMatch),
File(FileMatch),
Thread(ThreadMatch),
Fetch(SharedString),
Rules(RulesContextEntry),
@@ -70,9 +69,7 @@ fn search(
query: String,
cancellation_flag: Arc<AtomicBool>,
recent_entries: Vec<RecentEntry>,
prompt_store: Option<Entity<PromptStore>>,
thread_store: Option<WeakEntity<ThreadStore>>,
text_thread_context_store: Option<WeakEntity<assistant_context_editor::ContextStore>>,
workspace: Entity<Workspace>,
cx: &mut App,
) -> Task<Vec<Match>> {
@@ -88,7 +85,6 @@ fn search(
.collect()
})
}
Some(ContextPickerMode::Symbol) => {
let search_symbols_task =
search_symbols(query.clone(), cancellation_flag.clone(), &workspace, cx);
@@ -100,20 +96,10 @@ fn search(
.collect()
})
}
Some(ContextPickerMode::Thread) => {
if let Some((thread_store, context_store)) = thread_store
.as_ref()
.and_then(|t| t.upgrade())
.zip(text_thread_context_store.as_ref().and_then(|t| t.upgrade()))
{
let search_threads_task = search_threads(
query.clone(),
cancellation_flag.clone(),
thread_store,
context_store,
cx,
);
if let Some(thread_store) = thread_store.as_ref().and_then(|t| t.upgrade()) {
let search_threads_task =
search_threads(query.clone(), cancellation_flag.clone(), thread_store, cx);
cx.background_spawn(async move {
search_threads_task
.await
@@ -125,7 +111,6 @@ fn search(
Task::ready(Vec::new())
}
}
Some(ContextPickerMode::Fetch) => {
if !query.is_empty() {
Task::ready(vec![Match::Fetch(query.into())])
@@ -133,11 +118,10 @@ fn search(
Task::ready(Vec::new())
}
}
Some(ContextPickerMode::Rules) => {
if let Some(prompt_store) = prompt_store.as_ref() {
if let Some(thread_store) = thread_store.as_ref().and_then(|t| t.upgrade()) {
let search_rules_task =
search_rules(query.clone(), cancellation_flag.clone(), prompt_store, cx);
search_rules(query.clone(), cancellation_flag.clone(), thread_store, cx);
cx.background_spawn(async move {
search_rules_task
.await
@@ -149,7 +133,6 @@ fn search(
Task::ready(Vec::new())
}
}
None => {
if query.is_empty() {
let mut matches = recent_entries
@@ -180,7 +163,7 @@ fn search(
.collect::<Vec<_>>();
matches.extend(
available_context_picker_entries(&prompt_store, &thread_store, &workspace, cx)
available_context_picker_entries(&thread_store, &workspace, cx)
.into_iter()
.map(|mode| {
Match::Entry(EntryMatch {
@@ -197,8 +180,7 @@ fn search(
let search_files_task =
search_files(query.clone(), cancellation_flag.clone(), &workspace, cx);
let entries =
available_context_picker_entries(&prompt_store, &thread_store, &workspace, cx);
let entries = available_context_picker_entries(&thread_store, &workspace, cx);
let entry_candidates = entries
.iter()
.enumerate()
@@ -246,9 +228,7 @@ pub struct ContextPickerCompletionProvider {
workspace: WeakEntity<Workspace>,
context_store: WeakEntity<ContextStore>,
thread_store: Option<WeakEntity<ThreadStore>>,
text_thread_store: Option<WeakEntity<TextThreadStore>>,
editor: WeakEntity<Editor>,
excluded_buffer: Option<WeakEntity<Buffer>>,
}
impl ContextPickerCompletionProvider {
@@ -256,17 +236,13 @@ impl ContextPickerCompletionProvider {
workspace: WeakEntity<Workspace>,
context_store: WeakEntity<ContextStore>,
thread_store: Option<WeakEntity<ThreadStore>>,
text_thread_store: Option<WeakEntity<TextThreadStore>>,
editor: WeakEntity<Editor>,
exclude_buffer: Option<WeakEntity<Buffer>>,
) -> Self {
Self {
workspace,
context_store,
thread_store,
text_thread_store,
editor,
excluded_buffer: exclude_buffer,
}
}
@@ -322,29 +298,24 @@ impl ContextPickerCompletionProvider {
})
.collect::<Vec<_>>();
let new_text = format!(
"{} ",
selection_infos.iter().map(|(_, link, _)| link).join(" ")
);
let new_text = selection_infos.iter().map(|(_, link, _)| link).join(" ");
let callback = Arc::new({
let context_store = context_store.clone();
let selections = selections.clone();
let selection_infos = selection_infos.clone();
move |_, window: &mut Window, cx: &mut App| {
move |_, _: &mut Window, cx: &mut App| {
context_store.update(cx, |context_store, cx| {
for (buffer, range) in &selections {
context_store.add_selection(
buffer.clone(),
range.clone(),
cx,
);
context_store
.add_selection(buffer.clone(), range.clone(), cx)
.detach_and_log_err(cx)
}
});
let editor = editor.clone();
let selection_infos = selection_infos.clone();
window.defer(cx, move |window, cx| {
cx.defer(move |cx| {
let mut current_offset = 0;
for (file_name, link, line_range) in selection_infos.iter() {
let snapshot =
@@ -375,8 +346,9 @@ impl ContextPickerCompletionProvider {
);
editor.update(cx, |editor, cx| {
editor.insert_creases(vec![crease.clone()], cx);
editor.fold_creases(vec![crease], false, window, cx);
editor.display_map.update(cx, |display_map, cx| {
display_map.fold(vec![crease], cx);
});
});
current_offset += text_len + 1;
@@ -416,70 +388,44 @@ impl ContextPickerCompletionProvider {
editor: Entity<Editor>,
context_store: Entity<ContextStore>,
thread_store: Entity<ThreadStore>,
text_thread_store: Entity<TextThreadStore>,
) -> Completion {
let icon_for_completion = if recent {
IconName::HistoryRerun
} else {
IconName::MessageBubbles
};
let new_text = format!("{} ", MentionLink::for_thread(&thread_entry));
let new_text = MentionLink::for_thread(&thread_entry);
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
new_text,
label: CodeLabel::plain(thread_entry.title().to_string(), None),
label: CodeLabel::plain(thread_entry.summary.to_string(), None),
documentation: None,
insert_text_mode: None,
source: project::CompletionSource::Custom,
icon_path: Some(icon_for_completion.path().into()),
confirm: Some(confirm_completion_callback(
IconName::MessageBubbles.path().into(),
thread_entry.title().clone(),
thread_entry.summary.clone(),
excerpt_id,
source_range.start,
new_text_len - 1,
new_text_len,
editor.clone(),
context_store.clone(),
move |window, cx| match &thread_entry {
ThreadContextEntry::Thread { id, .. } => {
let thread_id = id.clone();
let context_store = context_store.clone();
let thread_store = thread_store.clone();
window.spawn::<_, Option<_>>(cx, async move |cx| {
let thread: Entity<Thread> = thread_store
.update_in(cx, |thread_store, window, cx| {
thread_store.open_thread(&thread_id, window, cx)
})
.ok()?
.await
.log_err()?;
let context = context_store
.update(cx, |context_store, cx| {
context_store.add_thread(thread, false, cx)
})
.ok()??;
Some(context)
move |cx| {
let thread_id = thread_entry.id.clone();
let context_store = context_store.clone();
let thread_store = thread_store.clone();
cx.spawn(async move |cx| {
let thread = thread_store
.update(cx, |thread_store, cx| {
thread_store.open_thread(&thread_id, cx)
})?
.await?;
context_store.update(cx, |context_store, cx| {
context_store.add_thread(thread, false, cx)
})
}
ThreadContextEntry::Context { path, .. } => {
let path = path.clone();
let context_store = context_store.clone();
let text_thread_store = text_thread_store.clone();
cx.spawn::<_, Option<_>>(async move |cx| {
let thread = text_thread_store
.update(cx, |store, cx| store.open_local_context(path, cx))
.ok()?
.await
.log_err()?;
let context = context_store
.update(cx, |context_store, cx| {
context_store.add_text_thread(thread, false, cx)
})
.ok()??;
Some(context)
})
}
})
.detach_and_log_err(cx);
},
)),
}
@@ -491,8 +437,9 @@ impl ContextPickerCompletionProvider {
source_range: Range<Anchor>,
editor: Entity<Editor>,
context_store: Entity<ContextStore>,
thread_store: Entity<ThreadStore>,
) -> Completion {
let new_text = format!("{} ", MentionLink::for_rule(&rules));
let new_text = MentionLink::for_rules(&rules);
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
@@ -507,15 +454,32 @@ impl ContextPickerCompletionProvider {
rules.title.clone(),
excerpt_id,
source_range.start,
new_text_len - 1,
new_text_len,
editor.clone(),
context_store.clone(),
move |_, cx| {
let user_prompt_id = rules.prompt_id;
let context = context_store.update(cx, |context_store, cx| {
context_store.add_rules(user_prompt_id, false, cx)
});
Task::ready(context)
move |cx| {
let prompt_uuid = rules.prompt_id;
let prompt_id = PromptId::User { uuid: prompt_uuid };
let context_store = context_store.clone();
let Some(prompt_store) = thread_store.read(cx).prompt_store() else {
log::error!("Can't add user rules as prompt store is missing.");
return;
};
let prompt_store = prompt_store.read(cx);
let Some(metadata) = prompt_store.metadata(prompt_id) else {
return;
};
let Some(title) = metadata.title else {
return;
};
let text_task = prompt_store.load(prompt_id, cx);
cx.spawn(async move |cx| {
let text = text_task.await?;
context_store.update(cx, |context_store, cx| {
context_store.add_rules(prompt_uuid, title, text, false, cx)
})
})
.detach_and_log_err(cx);
},
)),
}
@@ -529,7 +493,7 @@ impl ContextPickerCompletionProvider {
context_store: Entity<ContextStore>,
http_client: Arc<HttpClientWithUrl>,
) -> Completion {
let new_text = format!("{} ", MentionLink::for_fetch(&url_to_fetch));
let new_text = MentionLink::for_fetch(&url_to_fetch);
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
@@ -544,35 +508,29 @@ impl ContextPickerCompletionProvider {
url_to_fetch.clone(),
excerpt_id,
source_range.start,
new_text_len - 1,
new_text_len,
editor.clone(),
context_store.clone(),
move |_, cx| {
move |cx| {
let context_store = context_store.clone();
let http_client = http_client.clone();
let url_to_fetch = url_to_fetch.clone();
cx.spawn(async move |cx| {
if let Some(context) = context_store
.read_with(cx, |context_store, _| {
context_store.get_url_context(url_to_fetch.clone())
})
.ok()?
{
return Some(context);
if context_store.update(cx, |context_store, _| {
context_store.includes_url(&url_to_fetch).is_some()
})? {
return Ok(());
}
let content = cx
.background_spawn(fetch_url_content(
http_client,
url_to_fetch.to_string(),
))
.await
.log_err()?;
context_store
.update(cx, |context_store, cx| {
context_store.add_fetched_url(url_to_fetch.to_string(), content, cx)
})
.ok()
.await?;
context_store.update(cx, |context_store, cx| {
context_store.add_fetched_url(url_to_fetch.to_string(), content, cx)
})
})
.detach_and_log_err(cx);
},
)),
}
@@ -614,7 +572,7 @@ impl ContextPickerCompletionProvider {
crease_icon_path.clone()
};
let new_text = format!("{} ", MentionLink::for_file(&file_name, &full_path));
let new_text = MentionLink::for_file(&file_name, &full_path);
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
@@ -629,25 +587,17 @@ impl ContextPickerCompletionProvider {
file_name,
excerpt_id,
source_range.start,
new_text_len - 1,
new_text_len,
editor,
context_store.clone(),
move |_, cx| {
if is_directory {
Task::ready(
context_store
.update(cx, |context_store, cx| {
context_store.add_directory(&project_path, false, cx)
})
.log_err()
.flatten(),
)
} else {
let result = context_store.update(cx, |context_store, cx| {
move |cx| {
context_store.update(cx, |context_store, cx| {
let task = if is_directory {
context_store.add_directory(project_path.clone(), false, cx)
} else {
context_store.add_file_from_path(project_path.clone(), false, cx)
});
cx.spawn(async move |_| result.await.log_err().flatten())
}
};
task.detach_and_log_err(cx);
})
},
)),
}
@@ -685,7 +635,7 @@ impl ContextPickerCompletionProvider {
label.push_str(" ", None);
label.push_str(&file_name, comment_id);
let new_text = format!("{} ", MentionLink::for_symbol(&symbol.name, &full_path));
let new_text = MentionLink::for_symbol(&symbol.name, &full_path);
let new_text_len = new_text.len();
Some(Completion {
replace_range: source_range.clone(),
@@ -700,21 +650,20 @@ impl ContextPickerCompletionProvider {
symbol.name.clone().into(),
excerpt_id,
source_range.start,
new_text_len - 1,
new_text_len,
editor.clone(),
context_store.clone(),
move |_, cx| {
move |cx| {
let symbol = symbol.clone();
let context_store = context_store.clone();
let workspace = workspace.clone();
let result = super::symbol_context_picker::add_symbol(
super::symbol_context_picker::add_symbol(
symbol.clone(),
false,
workspace.clone(),
context_store.downgrade(),
cx,
);
cx.spawn(async move |_| result.await.log_err()?.0)
)
.detach_and_log_err(cx);
},
)),
})
@@ -746,7 +695,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
_trigger: CompletionContext,
_window: &mut Window,
cx: &mut Context<Editor>,
) -> Task<Result<Vec<CompletionResponse>>> {
) -> Task<Result<Option<Vec<Completion>>>> {
let state = buffer.update(cx, |buffer, _cx| {
let position = buffer_position.to_point(buffer);
let line_start = Point::new(position.row, 0);
@@ -756,13 +705,13 @@ impl CompletionProvider for ContextPickerCompletionProvider {
MentionCompletion::try_parse(line, offset_to_line)
});
let Some(state) = state else {
return Task::ready(Ok(Vec::new()));
return Task::ready(Ok(None));
};
let Some((workspace, context_store)) =
self.workspace.upgrade().zip(self.context_store.upgrade())
else {
return Task::ready(Ok(Vec::new()));
return Task::ready(Ok(None));
};
let snapshot = buffer.read(cx).snapshot();
@@ -770,44 +719,25 @@ impl CompletionProvider for ContextPickerCompletionProvider {
..snapshot.anchor_before(state.source_range.end);
let thread_store = self.thread_store.clone();
let text_thread_store = self.text_thread_store.clone();
let editor = self.editor.clone();
let http_client = workspace.read(cx).client().http_client();
let http_client = workspace.read(cx).client().http_client().clone();
let MentionCompletion { mode, argument, .. } = state;
let query = argument.unwrap_or_else(|| "".to_string());
let excluded_path = self
.excluded_buffer
.as_ref()
.and_then(WeakEntity::upgrade)
.and_then(|b| b.read(cx).file())
.map(|file| ProjectPath::from_file(file.as_ref(), cx));
let recent_entries = recent_context_picker_entries(
context_store.clone(),
thread_store.clone(),
text_thread_store.clone(),
workspace.clone(),
excluded_path.clone(),
cx,
);
let prompt_store = thread_store.as_ref().and_then(|thread_store| {
thread_store
.read_with(cx, |thread_store, _cx| thread_store.prompt_store().clone())
.ok()
.flatten()
});
let search_task = search(
mode,
query,
Arc::<AtomicBool>::default(),
recent_entries,
prompt_store,
thread_store.clone(),
text_thread_store.clone(),
workspace.clone(),
cx,
);
@@ -815,25 +745,19 @@ impl CompletionProvider for ContextPickerCompletionProvider {
cx.spawn(async move |_, cx| {
let matches = search_task.await;
let Some(editor) = editor.upgrade() else {
return Ok(Vec::new());
return Ok(None);
};
let completions = cx.update(|cx| {
Ok(Some(cx.update(|cx| {
matches
.into_iter()
.filter_map(|mat| match mat {
Match::File(FileMatch { mat, is_recent }) => {
let project_path = ProjectPath {
worktree_id: WorktreeId::from_usize(mat.worktree_id),
path: mat.path.clone(),
};
if excluded_path.as_ref() == Some(&project_path) {
return None;
}
Some(Self::completion_for_path(
project_path,
ProjectPath {
worktree_id: WorktreeId::from_usize(mat.worktree_id),
path: mat.path.clone(),
},
&mat.path_prefix,
is_recent,
mat.is_dir,
@@ -844,7 +768,6 @@ impl CompletionProvider for ContextPickerCompletionProvider {
cx,
))
}
Match::Symbol(SymbolMatch { symbol, .. }) => Self::completion_for_symbol(
symbol,
excerpt_id,
@@ -854,13 +777,10 @@ impl CompletionProvider for ContextPickerCompletionProvider {
workspace.clone(),
cx,
),
Match::Thread(ThreadMatch {
thread, is_recent, ..
}) => {
let thread_store = thread_store.as_ref().and_then(|t| t.upgrade())?;
let text_thread_store =
text_thread_store.as_ref().and_then(|t| t.upgrade())?;
Some(Self::completion_for_thread(
thread,
excerpt_id,
@@ -869,18 +789,19 @@ impl CompletionProvider for ContextPickerCompletionProvider {
editor.clone(),
context_store.clone(),
thread_store,
text_thread_store,
))
}
Match::Rules(user_rules) => Some(Self::completion_for_rules(
user_rules,
excerpt_id,
source_range.clone(),
editor.clone(),
context_store.clone(),
)),
Match::Rules(user_rules) => {
let thread_store = thread_store.as_ref().and_then(|t| t.upgrade())?;
Some(Self::completion_for_rules(
user_rules,
excerpt_id,
source_range.clone(),
editor.clone(),
context_store.clone(),
thread_store,
))
}
Match::Fetch(url) => Some(Self::completion_for_fetch(
source_range.clone(),
url,
@@ -889,7 +810,6 @@ impl CompletionProvider for ContextPickerCompletionProvider {
context_store.clone(),
http_client.clone(),
)),
Match::Entry(EntryMatch { entry, .. }) => Self::completion_for_entry(
entry,
excerpt_id,
@@ -901,14 +821,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
),
})
.collect()
})?;
Ok(vec![CompletionResponse {
completions,
// Since this does its own filtering (see `filter_completions()` returns false),
// there is no benefit to computing whether this set of completions is incomplete.
is_incomplete: true,
}])
})?))
})
}
@@ -963,47 +876,24 @@ fn confirm_completion_callback(
start: Anchor,
content_len: usize,
editor: Entity<Editor>,
context_store: Entity<ContextStore>,
add_context_fn: impl Fn(&mut Window, &mut App) -> Task<Option<AgentContextHandle>>
+ Send
+ Sync
+ 'static,
add_context_fn: impl Fn(&mut App) -> () + Send + Sync + 'static,
) -> Arc<dyn Fn(CompletionIntent, &mut Window, &mut App) -> bool + Send + Sync> {
Arc::new(move |_, window, cx| {
let context = add_context_fn(window, cx);
Arc::new(move |_, _, cx| {
add_context_fn(cx);
let crease_text = crease_text.clone();
let crease_icon_path = crease_icon_path.clone();
let editor = editor.clone();
let context_store = context_store.clone();
window.defer(cx, move |window, cx| {
let crease_id = crate::context_picker::insert_crease_for_mention(
cx.defer(move |cx| {
crate::context_picker::insert_fold_for_mention(
excerpt_id,
start,
content_len,
crease_text.clone(),
crease_text,
crease_icon_path,
editor.clone(),
window,
editor,
cx,
);
cx.spawn(async move |cx| {
let crease_id = crease_id?;
let context = context.await?;
editor
.update(cx, |editor, cx| {
if let Some(addon) = editor.addon_mut::<ContextCreasesAddon>() {
addon.add_creases(
&context_store,
AgentContextKey(context),
[(crease_id, crease_text)],
cx,
);
}
})
.ok()
})
.detach();
});
false
})
@@ -1158,10 +1048,6 @@ mod tests {
fn include_in_nav_history() -> bool {
false
}
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
"Test".into()
}
}
impl EventEmitter<()> for AtMentionEditor {}
@@ -1208,7 +1094,6 @@ mod tests {
"five.txt": "",
"six.txt": "",
"seven.txt": "",
"eight.txt": "",
}
}),
)
@@ -1223,7 +1108,7 @@ mod tests {
assert_eq!(worktrees.len(), 1);
worktrees.pop().unwrap()
});
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
@@ -1235,12 +1120,9 @@ mod tests {
separator!("b/five.txt"),
separator!("b/six.txt"),
separator!("b/seven.txt"),
separator!("b/eight.txt"),
];
let mut opened_editors = Vec::new();
for path in paths {
let buffer = workspace
workspace
.update_in(&mut cx, |workspace, window, cx| {
workspace.open_path(
ProjectPath {
@@ -1255,7 +1137,6 @@ mod tests {
})
.await
.unwrap();
opened_editors.push(buffer);
}
let editor = workspace.update_in(&mut cx, |workspace, window, cx| {
@@ -1285,24 +1166,12 @@ mod tests {
let editor_entity = editor.downgrade();
editor.update_in(&mut cx, |editor, window, cx| {
let last_opened_buffer = opened_editors.last().and_then(|editor| {
editor
.downcast::<Editor>()?
.read(cx)
.buffer()
.read(cx)
.as_singleton()
.as_ref()
.map(Entity::downgrade)
});
window.focus(&editor.focus_handle(cx));
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
workspace.downgrade(),
context_store.downgrade(),
None,
None,
editor_entity,
last_opened_buffer,
))));
});
@@ -1363,7 +1232,7 @@ mod tests {
});
editor.update(&mut cx, |editor, cx| {
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) ");
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt)",);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
@@ -1374,7 +1243,7 @@ mod tests {
cx.simulate_input(" ");
editor.update(&mut cx, |editor, cx| {
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) ");
assert_eq!(editor.text(cx), "Lorem [@one.txt](@file:dir/a/one.txt) ",);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
@@ -1387,7 +1256,7 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum ",
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum ",
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
@@ -1401,7 +1270,7 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum @file ",
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum @file ",
);
assert!(editor.has_visible_completions_menu());
assert_eq!(
@@ -1419,14 +1288,14 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) "
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt)"
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
vec![
Point::new(0, 6)..Point::new(0, 37),
Point::new(0, 45)..Point::new(0, 80)
Point::new(0, 44)..Point::new(0, 79)
]
);
});
@@ -1436,14 +1305,14 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n@"
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt)\n@"
);
assert!(editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
vec![
Point::new(0, 6)..Point::new(0, 37),
Point::new(0, 45)..Point::new(0, 80)
Point::new(0, 44)..Point::new(0, 79)
]
);
});
@@ -1457,14 +1326,14 @@ mod tests {
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt) \n[@six.txt](@file:dir/b/six.txt) "
"Lorem [@one.txt](@file:dir/a/one.txt) Ipsum [@seven.txt](@file:dir/b/seven.txt)\n[@six.txt](@file:dir/b/six.txt)"
);
assert!(!editor.has_visible_completions_menu());
assert_eq!(
fold_ranges(editor, cx),
vec![
Point::new(0, 6)..Point::new(0, 37),
Point::new(0, 45)..Point::new(0, 80),
Point::new(0, 44)..Point::new(0, 79),
Point::new(1, 0)..Point::new(1, 31)
]
);

View File

@@ -193,7 +193,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
return;
};
let http_client = workspace.read(cx).client().http_client();
let http_client = workspace.read(cx).client().http_client().clone();
let url = self.url.clone();
cx.spawn_in(window, async move |this, cx| {
let text = cx
@@ -227,7 +227,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let added = self.context_store.upgrade().map_or(false, |context_store| {
context_store.read(cx).includes_url(&self.url)
context_store.read(cx).includes_url(&self.url).is_some()
});
Some(

View File

@@ -130,19 +130,21 @@ impl PickerDelegate for FileContextPickerDelegate {
let is_directory = mat.is_dir;
self.context_store
let Some(task) = self
.context_store
.update(cx, |context_store, cx| {
if is_directory {
context_store
.add_directory(&project_path, true, cx)
.log_err();
context_store.add_directory(project_path, true, cx)
} else {
context_store
.add_file_from_path(project_path.clone(), true, cx)
.detach_and_log_err(cx);
context_store.add_file_from_path(project_path, true, cx)
}
})
.ok();
.ok()
else {
return;
};
task.detach_and_log_err(cx);
}
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
@@ -167,7 +169,7 @@ impl PickerDelegate for FileContextPickerDelegate {
.inset(true)
.toggle_state(selected)
.child(render_file_context_entry(
ElementId::named_usize("file-ctx-picker", ix),
ElementId::NamedInteger("file-ctx-picker".into(), ix),
WorktreeId::from_usize(mat.worktree_id),
&mat.path,
&mat.path_prefix,
@@ -323,11 +325,11 @@ pub fn render_file_context_entry(
path: path.clone(),
};
if is_directory {
context_store.read(cx).includes_directory(&project_path)
} else {
context_store
.read(cx)
.path_included_in_directory(&project_path, cx)
} else {
context_store.read(cx).file_path_included(&project_path, cx)
.will_include_file_path(&project_path, cx)
}
});
@@ -355,7 +357,7 @@ pub fn render_file_context_entry(
})),
)
.when_some(added, |el, added| match added {
FileInclusion::Direct => el.child(
FileInclusion::Direct(_) => el.child(
h_flex()
.w_full()
.justify_end()
@@ -367,8 +369,9 @@ pub fn render_file_context_entry(
)
.child(Label::new("Added").size(LabelSize::Small)),
),
FileInclusion::InDirectory { full_path } => {
let directory_full_path = full_path.to_string_lossy().into_owned();
FileInclusion::InDirectory(directory_project_path) => {
// TODO: Consider using worktree full_path to include worktree name.
let directory_path = directory_project_path.path.to_string_lossy().into_owned();
el.child(
h_flex()
@@ -382,7 +385,7 @@ pub fn render_file_context_entry(
)
.child(Label::new("Included").size(LabelSize::Small)),
)
.tooltip(Tooltip::text(format!("in {directory_full_path}")))
.tooltip(Tooltip::text(format!("in {directory_path}")))
}
})
}

View File

@@ -1,15 +1,16 @@
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use anyhow::anyhow;
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
use picker::{Picker, PickerDelegate};
use prompt_store::{PromptId, PromptStore, UserPromptId};
use prompt_store::{PromptId, UserPromptId};
use ui::{ListItem, prelude::*};
use util::ResultExt as _;
use crate::context::RULES_ICON;
use crate::context_picker::ContextPicker;
use crate::context_store::{self, ContextStore};
use crate::thread_store::ThreadStore;
pub struct RulesContextPicker {
picker: Entity<Picker<RulesContextPickerDelegate>>,
@@ -17,13 +18,13 @@ pub struct RulesContextPicker {
impl RulesContextPicker {
pub fn new(
prompt_store: Entity<PromptStore>,
thread_store: WeakEntity<ThreadStore>,
context_picker: WeakEntity<ContextPicker>,
context_store: WeakEntity<context_store::ContextStore>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let delegate = RulesContextPickerDelegate::new(prompt_store, context_picker, context_store);
let delegate = RulesContextPickerDelegate::new(thread_store, context_picker, context_store);
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
RulesContextPicker { picker }
@@ -49,7 +50,7 @@ pub struct RulesContextEntry {
}
pub struct RulesContextPickerDelegate {
prompt_store: Entity<PromptStore>,
thread_store: WeakEntity<ThreadStore>,
context_picker: WeakEntity<ContextPicker>,
context_store: WeakEntity<context_store::ContextStore>,
matches: Vec<RulesContextEntry>,
@@ -58,12 +59,12 @@ pub struct RulesContextPickerDelegate {
impl RulesContextPickerDelegate {
pub fn new(
prompt_store: Entity<PromptStore>,
thread_store: WeakEntity<ThreadStore>,
context_picker: WeakEntity<ContextPicker>,
context_store: WeakEntity<context_store::ContextStore>,
) -> Self {
RulesContextPickerDelegate {
prompt_store,
thread_store,
context_picker,
context_store,
matches: Vec::new(),
@@ -102,12 +103,11 @@ impl PickerDelegate for RulesContextPickerDelegate {
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let search_task = search_rules(
query,
Arc::new(AtomicBool::default()),
&self.prompt_store,
cx,
);
let Some(thread_store) = self.thread_store.upgrade() else {
return Task::ready(());
};
let search_task = search_rules(query, Arc::new(AtomicBool::default()), thread_store, cx);
cx.spawn_in(window, async move |this, cx| {
let matches = search_task.await;
this.update(cx, |this, cx| {
@@ -124,11 +124,31 @@ impl PickerDelegate for RulesContextPickerDelegate {
return;
};
self.context_store
.update(cx, |context_store, cx| {
context_store.add_rules(entry.prompt_id, true, cx)
let Some(thread_store) = self.thread_store.upgrade() else {
return;
};
let prompt_id = entry.prompt_id;
let load_rules_task = thread_store.update(cx, |thread_store, cx| {
thread_store.load_rules(prompt_id, cx)
});
cx.spawn(async move |this, cx| {
let (metadata, text) = load_rules_task.await?;
let Some(title) = metadata.title else {
return Err(anyhow!("Encountered user rule with no title when attempting to add it to agent context."));
};
this.update(cx, |this, cx| {
this.delegate
.context_store
.update(cx, |context_store, cx| {
context_store.add_rules(prompt_id, title, text, true, cx)
})
.ok();
})
.log_err();
})
.detach_and_log_err(cx);
}
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
@@ -159,10 +179,11 @@ pub fn render_thread_context_entry(
context_store: WeakEntity<ContextStore>,
cx: &mut App,
) -> Div {
let added = context_store.upgrade().map_or(false, |context_store| {
context_store
let added = context_store.upgrade().map_or(false, |ctx_store| {
ctx_store
.read(cx)
.includes_user_rules(user_rules.prompt_id)
.includes_user_rules(&user_rules.prompt_id)
.is_some()
});
h_flex()
@@ -197,9 +218,12 @@ pub fn render_thread_context_entry(
pub(crate) fn search_rules(
query: String,
cancellation_flag: Arc<AtomicBool>,
prompt_store: &Entity<PromptStore>,
thread_store: Entity<ThreadStore>,
cx: &mut App,
) -> Task<Vec<RulesContextEntry>> {
let Some(prompt_store) = thread_store.read(cx).prompt_store() else {
return Task::ready(vec![]);
};
let search_task = prompt_store.read(cx).search(query, cancellation_flag, cx);
cx.background_spawn(async move {
search_task

View File

@@ -10,11 +10,11 @@ use gpui::{
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate};
use project::{DocumentSymbol, Symbol};
use text::OffsetRangeExt;
use ui::{ListItem, prelude::*};
use util::ResultExt as _;
use workspace::Workspace;
use crate::context::AgentContextHandle;
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore;
@@ -144,7 +144,7 @@ impl PickerDelegate for SymbolContextPickerDelegate {
let selected_index = self.selected_index;
cx.spawn(async move |this, cx| {
let (_, included) = add_symbol_task.await?;
let included = add_symbol_task.await?;
this.update(cx, |this, _| {
if let Some(mat) = this.delegate.matches.get_mut(selected_index) {
mat.is_included = included;
@@ -172,7 +172,10 @@ impl PickerDelegate for SymbolContextPickerDelegate {
let mat = &self.matches[ix];
Some(ListItem::new(ix).inset(true).toggle_state(selected).child(
render_symbol_context_entry(ElementId::named_usize("symbol-ctx-picker", ix), mat),
render_symbol_context_entry(
ElementId::NamedInteger("symbol-ctx-picker".into(), ix),
mat,
),
))
}
}
@@ -188,7 +191,7 @@ pub(crate) fn add_symbol(
workspace: Entity<Workspace>,
context_store: WeakEntity<ContextStore>,
cx: &mut App,
) -> Task<Result<(Option<AgentContextHandle>, bool)>> {
) -> Task<Result<bool>> {
let project = workspace.read(cx).project().clone();
let open_buffer_task = project.update(cx, |project, cx| {
project.open_buffer(symbol.path.clone(), cx)
@@ -225,16 +228,18 @@ pub(crate) fn add_symbol(
)
})?;
context_store.update(cx, move |context_store, cx| {
context_store.add_symbol(
buffer,
name.into(),
range,
enclosing_range,
remove_if_exists,
cx,
)
})
context_store
.update(cx, move |context_store, cx| {
context_store.add_symbol(
buffer,
name.into(),
range,
enclosing_range,
remove_if_exists,
cx,
)
})?
.await
})
}
@@ -348,13 +353,38 @@ fn compute_symbol_entries(
context_store: &ContextStore,
cx: &App,
) -> Vec<SymbolEntry> {
symbols
.into_iter()
.map(|SymbolMatch { symbol, .. }| SymbolEntry {
is_included: context_store.includes_symbol(&symbol, cx),
let mut symbol_entries = Vec::with_capacity(symbols.len());
for SymbolMatch { symbol, .. } in symbols {
let symbols_for_path = context_store.included_symbols_by_path().get(&symbol.path);
let is_included = if let Some(symbols_for_path) = symbols_for_path {
let mut is_included = false;
for included_symbol_id in symbols_for_path {
if included_symbol_id.name.as_ref() == symbol.name.as_str() {
if let Some(buffer) = context_store.buffer_for_symbol(included_symbol_id) {
let snapshot = buffer.read(cx).snapshot();
let included_symbol_range =
included_symbol_id.range.to_point_utf16(&snapshot);
if included_symbol_range.start == symbol.range.start.0
&& included_symbol_range.end == symbol.range.end.0
{
is_included = true;
break;
}
}
}
}
is_included
} else {
false
};
symbol_entries.push(SymbolEntry {
symbol,
is_included,
})
.collect::<Vec<_>>()
}
symbol_entries
}
pub fn render_symbol_context_entry(id: ElementId, entry: &SymbolEntry) -> Stateful<Div> {

View File

@@ -1,8 +1,6 @@
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use chrono::{DateTime, Utc};
use fuzzy::StringMatchCandidate;
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
use picker::{Picker, PickerDelegate};
@@ -11,7 +9,7 @@ use ui::{ListItem, prelude::*};
use crate::context_picker::ContextPicker;
use crate::context_store::{self, ContextStore};
use crate::thread::ThreadId;
use crate::thread_store::{TextThreadStore, ThreadStore};
use crate::thread_store::ThreadStore;
pub struct ThreadContextPicker {
picker: Entity<Picker<ThreadContextPickerDelegate>>,
@@ -20,18 +18,13 @@ pub struct ThreadContextPicker {
impl ThreadContextPicker {
pub fn new(
thread_store: WeakEntity<ThreadStore>,
text_thread_context_store: WeakEntity<TextThreadStore>,
context_picker: WeakEntity<ContextPicker>,
context_store: WeakEntity<context_store::ContextStore>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let delegate = ThreadContextPickerDelegate::new(
thread_store,
text_thread_context_store,
context_picker,
context_store,
);
let delegate =
ThreadContextPickerDelegate::new(thread_store, context_picker, context_store);
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
ThreadContextPicker { picker }
@@ -51,29 +44,13 @@ impl Render for ThreadContextPicker {
}
#[derive(Debug, Clone)]
pub enum ThreadContextEntry {
Thread {
id: ThreadId,
title: SharedString,
},
Context {
path: Arc<Path>,
title: SharedString,
},
}
impl ThreadContextEntry {
pub fn title(&self) -> &SharedString {
match self {
Self::Thread { title, .. } => title,
Self::Context { title, .. } => title,
}
}
pub struct ThreadContextEntry {
pub id: ThreadId,
pub summary: SharedString,
}
pub struct ThreadContextPickerDelegate {
thread_store: WeakEntity<ThreadStore>,
text_thread_store: WeakEntity<TextThreadStore>,
context_picker: WeakEntity<ContextPicker>,
context_store: WeakEntity<context_store::ContextStore>,
matches: Vec<ThreadContextEntry>,
@@ -83,7 +60,6 @@ pub struct ThreadContextPickerDelegate {
impl ThreadContextPickerDelegate {
pub fn new(
thread_store: WeakEntity<ThreadStore>,
text_thread_store: WeakEntity<TextThreadStore>,
context_picker: WeakEntity<ContextPicker>,
context_store: WeakEntity<context_store::ContextStore>,
) -> Self {
@@ -91,7 +67,6 @@ impl ThreadContextPickerDelegate {
thread_store,
context_picker,
context_store,
text_thread_store,
matches: Vec::new(),
selected_index: 0,
}
@@ -128,21 +103,11 @@ impl PickerDelegate for ThreadContextPickerDelegate {
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let Some((thread_store, text_thread_context_store)) = self
.thread_store
.upgrade()
.zip(self.text_thread_store.upgrade())
else {
let Some(thread_store) = self.thread_store.upgrade() else {
return Task::ready(());
};
let search_task = search_threads(
query,
Arc::new(AtomicBool::default()),
thread_store,
text_thread_context_store,
cx,
);
let search_task = search_threads(query, Arc::new(AtomicBool::default()), thread_store, cx);
cx.spawn_in(window, async move |this, cx| {
let matches = search_task.await;
this.update(cx, |this, cx| {
@@ -154,53 +119,29 @@ impl PickerDelegate for ThreadContextPickerDelegate {
})
}
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
let Some(entry) = self.matches.get(self.selected_index) else {
return;
};
match entry {
ThreadContextEntry::Thread { id, .. } => {
let Some(thread_store) = self.thread_store.upgrade() else {
return;
};
let open_thread_task =
thread_store.update(cx, |this, cx| this.open_thread(&id, window, cx));
let Some(thread_store) = self.thread_store.upgrade() else {
return;
};
cx.spawn(async move |this, cx| {
let thread = open_thread_task.await?;
this.update(cx, |this, cx| {
this.delegate
.context_store
.update(cx, |context_store, cx| {
context_store.add_thread(thread, true, cx)
})
.ok();
})
})
.detach_and_log_err(cx);
}
ThreadContextEntry::Context { path, .. } => {
let Some(text_thread_store) = self.text_thread_store.upgrade() else {
return;
};
let task = text_thread_store
.update(cx, |this, cx| this.open_local_context(path.clone(), cx));
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&entry.id, cx));
cx.spawn(async move |this, cx| {
let thread = task.await?;
this.update(cx, |this, cx| {
this.delegate
.context_store
.update(cx, |context_store, cx| {
context_store.add_text_thread(thread, true, cx)
})
.ok();
cx.spawn(async move |this, cx| {
let thread = open_thread_task.await?;
this.update(cx, |this, cx| {
this.delegate
.context_store
.update(cx, |context_store, cx| {
context_store.add_thread(thread, true, cx)
})
})
.detach_and_log_err(cx);
}
}
.ok();
})
})
.detach_and_log_err(cx);
}
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
@@ -227,20 +168,13 @@ impl PickerDelegate for ThreadContextPickerDelegate {
}
pub fn render_thread_context_entry(
entry: &ThreadContextEntry,
thread: &ThreadContextEntry,
context_store: WeakEntity<ContextStore>,
cx: &mut App,
) -> Div {
let is_added = match entry {
ThreadContextEntry::Thread { id, .. } => context_store
.upgrade()
.map_or(false, |ctx_store| ctx_store.read(cx).includes_thread(&id)),
ThreadContextEntry::Context { path, .. } => {
context_store.upgrade().map_or(false, |ctx_store| {
ctx_store.read(cx).includes_text_thread(path)
})
}
};
let added = context_store.upgrade().map_or(false, |ctx_store| {
ctx_store.read(cx).includes_thread(&thread.id).is_some()
});
h_flex()
.gap_1p5()
@@ -255,9 +189,9 @@ pub fn render_thread_context_entry(
.size(IconSize::XSmall)
.color(Color::Muted),
)
.child(Label::new(entry.title().clone()).truncate()),
.child(Label::new(thread.summary.clone()).truncate()),
)
.when(is_added, |el| {
.when(added, |el| {
el.child(
h_flex()
.gap_1()
@@ -277,54 +211,28 @@ pub struct ThreadMatch {
pub is_recent: bool,
}
pub fn unordered_thread_entries(
thread_store: Entity<ThreadStore>,
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 text_threads = text_thread_store
.read(cx)
.unordered_contexts()
.map(|context| {
(
context.mtime.to_utc(),
ThreadContextEntry::Context {
path: context.path.clone(),
title: context.title.clone().into(),
},
)
});
threads.chain(text_threads)
}
pub(crate) fn search_threads(
query: String,
cancellation_flag: Arc<AtomicBool>,
thread_store: Entity<ThreadStore>,
text_thread_store: Entity<TextThreadStore>,
cx: &mut App,
) -> Task<Vec<ThreadMatch>> {
let mut threads =
unordered_thread_entries(thread_store, text_thread_store, cx).collect::<Vec<_>>();
threads.sort_unstable_by_key(|(updated_at, _)| std::cmp::Reverse(*updated_at));
let threads = thread_store
.read(cx)
.threads()
.into_iter()
.map(|thread| ThreadContextEntry {
id: thread.id,
summary: thread.summary,
})
.collect::<Vec<_>>();
let executor = cx.background_executor().clone();
cx.background_spawn(async move {
if query.is_empty() {
threads
.into_iter()
.map(|(_, thread)| ThreadMatch {
.map(|thread| ThreadMatch {
thread,
is_recent: false,
})
@@ -333,7 +241,7 @@ pub(crate) fn search_threads(
let candidates = threads
.iter()
.enumerate()
.map(|(id, (_, thread))| StringMatchCandidate::new(id, &thread.title()))
.map(|(id, thread)| StringMatchCandidate::new(id, &thread.summary))
.collect::<Vec<_>>();
let matches = fuzzy::match_strings(
&candidates,
@@ -348,7 +256,7 @@ pub(crate) fn search_threads(
matches
.into_iter()
.map(|mat| ThreadMatch {
thread: threads[mat.candidate_id].1.clone(),
thread: threads[mat.candidate_id].clone(),
is_recent: false,
})
.collect()

View File

@@ -1,140 +0,0 @@
use std::sync::Arc;
use anyhow::Context as _;
use context_server::ContextServerId;
use extension::{ContextServerConfiguration, ExtensionManifest};
use gpui::Task;
use language::LanguageRegistry;
use project::context_server_store::registry::ContextServerDescriptorRegistry;
use ui::prelude::*;
use util::ResultExt;
use workspace::Workspace;
use crate::agent_configuration::ConfigureContextServerModal;
pub(crate) fn init(language_registry: Arc<LanguageRegistry>, cx: &mut App) {
cx.observe_new(move |_: &mut Workspace, window, cx| {
let Some(window) = window else {
return;
};
if let Some(extension_events) = extension::ExtensionEvents::try_global(cx).as_ref() {
cx.subscribe_in(extension_events, window, {
let language_registry = language_registry.clone();
move |workspace, _, event, window, cx| match event {
extension::Event::ExtensionInstalled(manifest) => {
show_configure_mcp_modal(
language_registry.clone(),
manifest,
workspace,
window,
cx,
);
}
extension::Event::ConfigureExtensionRequested(manifest) => {
if !manifest.context_servers.is_empty() {
show_configure_mcp_modal(
language_registry.clone(),
manifest,
workspace,
window,
cx,
);
}
}
_ => {}
}
})
.detach();
} else {
log::info!(
"No extension events global found. Skipping context server configuration wizard"
);
}
})
.detach();
}
pub enum Configuration {
NotAvailable(ContextServerId, Option<SharedString>),
Required(
ContextServerId,
Option<SharedString>,
ContextServerConfiguration,
),
}
fn show_configure_mcp_modal(
language_registry: Arc<LanguageRegistry>,
manifest: &Arc<ExtensionManifest>,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<'_, Workspace>,
) {
let context_server_store = workspace.project().read(cx).context_server_store();
let repository: Option<SharedString> = manifest.repository.as_ref().map(|s| s.clone().into());
let registry = ContextServerDescriptorRegistry::default_global(cx).read(cx);
let worktree_store = workspace.project().read(cx).worktree_store();
let configuration_tasks = manifest
.context_servers
.keys()
.cloned()
.map({
|key| {
let Some(descriptor) = registry.context_server_descriptor(&key) else {
return Task::ready(Configuration::NotAvailable(
ContextServerId(key),
repository.clone(),
));
};
cx.spawn({
let repository_url = repository.clone();
let worktree_store = worktree_store.clone();
async move |_, cx| {
let configuration = descriptor
.configuration(worktree_store.clone(), &cx)
.await
.context("Failed to resolve context server configuration")
.log_err()
.flatten();
match configuration {
Some(config) => Configuration::Required(
ContextServerId(key),
repository_url,
config,
),
None => {
Configuration::NotAvailable(ContextServerId(key), repository_url)
}
}
}
})
}
})
.collect::<Vec<_>>();
let jsonc_language = language_registry.language_for_name("jsonc");
cx.spawn_in(window, async move |this, cx| {
let configurations = futures::future::join_all(configuration_tasks).await;
let jsonc_language = jsonc_language.await.ok();
this.update_in(cx, |this, window, cx| {
let workspace = cx.entity().downgrade();
this.toggle_modal(window, cx, |window, cx| {
ConfigureContextServerModal::new(
configurations.into_iter(),
context_server_store,
jsonc_language,
language_registry,
workspace,
window,
cx,
)
});
})
})
.detach();
}

File diff suppressed because it is too large Load Diff

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