Compare commits

..

39 Commits

Author SHA1 Message Date
Michael Sloan
0c5b550cbe Enable screenshare on windows 2025-04-04 15:18:45 -06:00
Michael Sloan
eb12a126b8 Fix build on windows 2025-04-04 15:17:00 -06:00
Michael Sloan
a431430be2 Add install of libxcb-devel for dnf / yum 2025-04-04 14:05:45 -06:00
Michael Sloan
a428365d18 Fix build on windows 2025-04-04 14:02:32 -06:00
Michael Sloan
dcd396b7c5 Merge branch 'main' into linux-screenshare 2025-04-04 13:33:53 -06:00
Michael Sloan
327154a39a Have livekit_client enable x11 and wayland features
Without this, `cargo check -p livekit_client` would fail on linux due to `PlatformScreenCaptureFrame` being `()`.
2025-04-04 13:28:40 -06:00
Michael Sloan
b8a8124ecf Disable screen capture on windows since it doesn't work yet
A revert of this change will be on the `windows-screenshare` branch
2025-04-04 12:42:44 -06:00
Michael Sloan
79bc5fff3f Attempt to fix build on mac 2025-04-02 01:22:41 -06:00
Junkui Zhang
63b382e840 Bump scap 2025-04-02 15:19:42 +08:00
Junkui Zhang
80a4a0d38f Fix windwos 2025-04-02 15:15:33 +08:00
Michael Sloan
919e04acad Fix build of remote-server 2025-04-02 01:14:28 -06:00
Michael Sloan
566c6248dd Only include scap dep on linux if x11 or wayland gpui feature is used 2025-04-02 01:05:14 -06:00
Junkui Zhang
2ce0c9092c Fix build on Windows 2025-04-02 14:23:44 +08:00
Michael Sloan
f13820b024 Add libxcb deps to script/linux 2025-04-02 00:15:24 -06:00
Michael Sloan
5caa16cd13 Merge branch 'main' into linux-screenshare 2025-04-01 23:00:09 -06:00
Michael Sloan
699af33d4e Remove dbus / pipewire from deps installation, only needed for wayland 2025-04-01 22:40:13 -06:00
Michael Sloan
6c7d95d82e Update scap with wayland feature to allow omitting dbus / pipewire deps
Rationale is that:

* It's been tricky to get scap with wayland support passing CI
* The deps for wayland support aren't needed for this initial version that only supports X11
2025-04-01 22:37:09 -06:00
Junkui Zhang
3d5b43b40b Try fix windows 2025-04-01 17:12:42 +08:00
Michael Sloan
a77e05eb54 Attempt to fix build on windows and mac 2025-04-01 01:34:43 -06:00
Michael Sloan
e820a54771 Add pipewire dev packages to script/linux 2025-04-01 00:55:54 -06:00
Michael Sloan
aa87b19311 Workaround Box<dyn ScreenCaptureSource> not being Send
Having `trait ScreenCaptureSource: Send` and `trait ScreenCaptureStream: Send worked for linux compilation, but `MacScreenCaptureSource` does not implement `Send`: https://github.com/zed-industries/zed/actions/runs/14187088998/job/39744280748?pr=27807#step:4:145

An alternative might be to have `unsafe impl Send` for `MacScreenCaptureSource` and `MacScreenCaptureStream`. However, I'm guessing this won't work at all due to Cocoa requiring its use be from the UI thread.
2025-04-01 00:05:09 -06:00
Michael Sloan
3a9df20364 Clippy 2025-03-31 23:15:37 -06:00
Michael Sloan
f2f9a41ee2 Add dbus dev packages to script/linux 2025-03-31 22:17:23 -06:00
Michael Sloan
9b0b532937 Bump scap fork version to add support for freebsd 2025-03-31 14:24:00 -06:00
Michael Sloan
cea539a792 Merge branch 'main' into linux-screenshare 2025-03-31 14:21:24 -06:00
Michael Sloan
adb73a43eb For now only use scap for X11 screen capture
* Wayland screen capture doesn't work for me yet

* Windows support untested and our scap branch probably doesn't yet build due to changes to error handling

Co-authored-by: Conrad <conrad@zed.dev>
2025-03-31 14:13:42 -06:00
Michael Sloan
46560f3a99 Remove commented out publish_local_wav_track 2025-03-31 14:09:20 -06:00
Michael Sloan
aa4d74fd72 Improve scap error handling 2025-03-31 13:37:04 -06:00
Michael Sloan
7c6ec72ae7 Misc polish 2025-03-31 10:36:24 -06:00
Michael Sloan
393ec764ae Make scap dep conditional 2025-03-31 10:34:17 -06:00
Michael Sloan
c90777e9b7 Fix screen capture post refactor 2025-03-31 10:26:29 -06:00
Michael Sloan
35df7b508e Improve error handling + remove some dbg! 2025-03-31 10:26:25 -06:00
Michael Sloan
280dfa7721 Bump scap dep to remove a debug println 2025-03-31 10:02:34 -06:00
Michael Sloan
1a29dff7ca Enable screen share on windows (staff only for now as untested) 2025-03-31 09:46:40 -06:00
Michael Sloan
040e0784f3 Further cleanup 2025-03-30 16:21:19 -06:00
Michael Sloan
8fa47f7569 Merge branch 'main' into linux-screenshare 2025-03-29 12:37:29 -06:00
Michael Sloan
379f3f0abb Cleanup progress 2025-03-29 12:36:59 -06:00
Michael Sloan
6ab270b37c Update scap deps to not duplicate windows and sysinfo deps 2025-03-28 20:50:02 -06:00
Michael Sloan
83ad00be8f WIP addition of Linux screen sharing support via scap library
Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
2025-03-28 18:11:23 -06:00
321 changed files with 10256 additions and 16159 deletions

View File

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

View File

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

View File

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

View File

@@ -1,56 +0,0 @@
name: Bug Report (Other)
description: |
Something else is broken in Zed (exclude crashing).
type: "Bug"
body:
- type: textarea
attributes:
label: Summary
description: Provide a one sentence summary and detailed reproduction steps
value: |
<!-- Begin your issue with a one sentence summary -->
SUMMARY_SENTENCE_HERE
### Description
<!-- Describe with sufficient detail to reproduce from a clean Zed install.
- Any code must be sufficient to reproduce (include context!)
- Code must as text, not just as a screenshot.
- Issues with insufficient detail may be summarily closed.
-->
Steps to reproduce:
1.
2.
3.
4.
Expected Behavior:
Actual Behavior:
<!-- Before Submitting, did you:
1. Include settings.json, keymap.json, .editorconfig if relevant?
2. Check your Zed.log for relevant errors? (please include!)
3. Click Preview to ensure everything looks right?
4. Hide videos, large images and logs in ``` inside collapsible blocks:
<details><summary>click to expand</summary>
```json
```
</details>
-->
validations:
required: true
- type: textarea
id: environment
attributes:
label: Zed Version and System Specs
description: |
Open Zed, from the command palette select "zed: Copy System Specs Into Clipboard"
placeholder: |
Output of "zed: Copy System Specs Into Clipboard"
validations:
required: true

57
.github/ISSUE_TEMPLATE/1_bug_report.yml vendored Normal file
View File

@@ -0,0 +1,57 @@
name: Bug Report
description: |
Something is broken in Zed (exclude crashing).
type: "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
<!-- Be verbose: Include all steps necessary to reproduce from a clean Zed installation. -->
<!-- Code snippets are better than images, a repository link that reproduces the issue is ideal. -->
Steps to trigger the problem:
1.
2.
3.
4.
Actual Behavior:
Expected Behavior:
<!--
Is there anything additional necessary to reproduce this issue?
- settings.json, keymap.json, .editorconfig etc?
- Does it happen intermittently or only with specific projects / file types?
- Have you found a workaround?
Did you check your Zed.log to see if there is any relevant details there?
- When including large items (videos, screenshots, logs, configs) please wrap with:
<details><summary>See inside for XXXXYYY</summary>
```shell
code
```
</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

@@ -5,12 +5,10 @@ body:
- type: textarea
attributes:
label: Summary
description: Summarize the issue with detailed reproduction steps
description: Describe the bug with a one line summary, and provide detailed reproduction steps
value: |
<!-- Begin your issue with a one sentence summary -->
SUMMARY_SENTENCE_HERE
<!-- Please insert a one line summary of the issue below -->
### Description
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
Steps to trigger the problem:
1.
@@ -18,6 +16,7 @@ body:
3.
Actual Behavior:
Expected Behavior:
validations:
@@ -41,11 +40,10 @@ body:
value: |
<details><summary>Zed.log</summary>
<!-- Paste your log inside the code block. -->
```log
<!-- Click below this line and paste or drag-and-drop your log-->
```
</details>
```
<!-- Click above this line and paste or drag-and-drop your log--></details>
validations:
required: false

View File

@@ -4,6 +4,9 @@ contact_links:
- name: Feature Request
url: https://github.com/zed-industries/zed/discussions/new/choose
about: To request a feature, open a new Discussion in one of the appropriate Discussion categories
- name: "Zed Discord"
- name: Zed Discussion Forum
url: https://github.com/zed-industries/zed/discussions
about: A community discussion forum
- name: "Zed Discord: #Support Channel"
url: https://zed.dev/community-links
about: Real-time discussion and user support

View File

@@ -114,9 +114,7 @@ jobs:
timeout-minutes: 60
name: Check workspace-hack crate
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-8vcpu-ubuntu-2204
steps:
@@ -133,13 +131,13 @@ jobs:
- name: Check workspace-hack Cargo.toml is up-to-date
run: |
cargo hakari generate --diff || {
echo "To fix, run script/update-workspace-hack or script/update-workspace-hack.ps1";
echo "To fix, run script/update-workspace-hack";
false
}
- name: Check all crates depend on workspace-hack
run: |
cargo hakari manage-deps --dry-run || {
echo "To fix, run script/update-workspace-hack or script/update-workspace-hack.ps1"
echo "To fix, run script/update-workspace-hack"
false
}

352
Cargo.lock generated
View File

@@ -119,48 +119,12 @@ dependencies = [
"ui_input",
"util",
"uuid",
"vim_mode_setting",
"workspace",
"workspace-hack",
"zed_actions",
]
[[package]]
name = "agent_eval"
version = "0.1.0"
dependencies = [
"agent",
"anyhow",
"assistant_tool",
"assistant_tools",
"clap",
"client",
"collections",
"context_server",
"dap",
"env_logger 0.11.8",
"fs",
"futures 0.3.31",
"gpui",
"gpui_tokio",
"language",
"language_model",
"language_models",
"node_runtime",
"project",
"prompt_store",
"release_channel",
"reqwest_client",
"serde",
"serde_json",
"serde_json_lenient",
"settings",
"smol",
"tempfile",
"util",
"walkdir",
"workspace-hack",
]
[[package]]
name = "ahash"
version = "0.7.8"
@@ -574,7 +538,6 @@ dependencies = [
"collections",
"context_server",
"editor",
"feature_flags",
"fs",
"futures 0.3.31",
"fuzzy",
@@ -613,7 +576,43 @@ dependencies = [
"uuid",
"workspace",
"workspace-hack",
"zed_actions",
]
[[package]]
name = "assistant_eval"
version = "0.1.0"
dependencies = [
"agent",
"anyhow",
"assistant_tool",
"assistant_tools",
"clap",
"client",
"collections",
"context_server",
"dap",
"env_logger 0.11.8",
"fs",
"futures 0.3.31",
"gpui",
"gpui_tokio",
"language",
"language_model",
"language_models",
"node_runtime",
"project",
"prompt_store",
"release_channel",
"reqwest_client",
"serde",
"serde_json",
"serde_json_lenient",
"settings",
"smol",
"tempfile",
"util",
"walkdir",
"workspace-hack",
]
[[package]]
@@ -746,6 +745,7 @@ dependencies = [
"itertools 0.14.0",
"language",
"language_model",
"lsp",
"open",
"project",
"rand 0.8.5",
@@ -912,6 +912,18 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "async-native-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec"
dependencies = [
"futures-util",
"native-tls",
"thiserror 1.0.69",
"url",
]
[[package]]
name = "async-net"
version = "2.0.0"
@@ -1081,6 +1093,18 @@ version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-tls"
version = "0.13.0"
source = "git+https://github.com/zed-industries/async-tls?rev=1e759a4b5e370f87dc15e40756ac4f8815b61d9d#1e759a4b5e370f87dc15e40756ac4f8815b61d9d"
dependencies = [
"futures-core",
"futures-io",
"rustls 0.23.25",
"rustls-pemfile 2.2.0",
"webpki-roots",
]
[[package]]
name = "async-trait"
version = "0.1.88"
@@ -1094,10 +1118,12 @@ dependencies = [
[[package]]
name = "async-tungstenite"
version = "0.29.1"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef0f7efedeac57d9b26170f72965ecfd31473ca52ca7a64e925b0b6f5f079886"
checksum = "1c348fb0b6d132c596eca3dcd941df48fb597aafcb07a738ec41c004b087dc99"
dependencies = [
"async-std",
"async-tls",
"atomic-waker",
"futures-core",
"futures-io",
@@ -1105,10 +1131,7 @@ dependencies = [
"futures-util",
"log",
"pin-project-lite",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.2",
"tungstenite 0.26.2",
"tungstenite 0.24.0",
]
[[package]]
@@ -2118,9 +2141,9 @@ dependencies = [
[[package]]
name = "blake3"
version = "1.8.1"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389a099b34312839e16420d499a9cad9650541715937ffbdd40d36f49e77eeb3"
checksum = "34a796731680be7931955498a16a10b2270c7762963d5d570fdbfe02dcbf314f"
dependencies = [
"arrayref",
"arrayvec",
@@ -2432,9 +2455,9 @@ dependencies = [
[[package]]
name = "cap-fs-ext"
version = "3.4.3"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6323b9baffb4d6d9c65bfef3129db62b1391f7fb953fe67c0d7cb0804feb77b"
checksum = "7f78efdd7378980d79c0f36b519e51191742d2c9f91ffa5e228fba9f3806d2e1"
dependencies = [
"cap-primitives",
"cap-std",
@@ -2444,9 +2467,9 @@ dependencies = [
[[package]]
name = "cap-net-ext"
version = "3.4.3"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66022e5e076ea27041a05ebd4349022e2133e6f4977974dffd54ceb7b982e871"
checksum = "4ac68674a6042af2bcee1adad9f6abd432642cf03444ce3a5b36c3f39f23baf8"
dependencies = [
"cap-primitives",
"cap-std",
@@ -2456,9 +2479,9 @@ dependencies = [
[[package]]
name = "cap-primitives"
version = "3.4.3"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50ad0183a9659850877cefe8f5b87d564b2dd1fe78b18945813687f29c0a6878"
checksum = "8fc15faeed2223d8b8e8cc1857f5861935a06d06713c4ac106b722ae9ce3c369"
dependencies = [
"ambient-authority",
"fs-set-times",
@@ -2473,9 +2496,9 @@ dependencies = [
[[package]]
name = "cap-rand"
version = "3.4.3"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab78a9f6301e70c0fe5df7328adbcb9228277fdb7bab36f312fc072f505e38c2"
checksum = "dea13372b49df066d1ae654e5c6e41799c1efd9f6b36794b921e877ea4037977"
dependencies = [
"ambient-authority",
"rand 0.8.5",
@@ -2483,9 +2506,9 @@ dependencies = [
[[package]]
name = "cap-std"
version = "3.4.3"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c41814365b796ed12688026cb90a1e03236a84ccf009628f9c43c8aa3af250a"
checksum = "c3dbd3e8e8d093d6ccb4b512264869e1281cdb032f7940bd50b2894f96f25609"
dependencies = [
"cap-primitives",
"io-extras",
@@ -2495,9 +2518,9 @@ dependencies = [
[[package]]
name = "cap-time-ext"
version = "3.4.3"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb57b71bb69b97c638ec38b477e9b33fa1c1cff0e437dd55d15c117cf17ed5dc"
checksum = "bd736b20fc033f564a1995fb82fc349146de43aabba19c7368b4cb17d8f9ea53"
dependencies = [
"ambient-authority",
"cap-primitives",
@@ -2575,9 +2598,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.18"
version = "1.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
dependencies = [
"jobserver",
"libc",
@@ -2814,6 +2837,7 @@ name = "client"
version = "0.1.0"
dependencies = [
"anyhow",
"async-native-tls",
"async-recursion 0.3.2",
"async-tungstenite",
"chrono",
@@ -2824,7 +2848,6 @@ dependencies = [
"feature_flags",
"futures 0.3.31",
"gpui",
"gpui_tokio",
"http_client",
"http_client_tls",
"log",
@@ -2846,7 +2869,6 @@ dependencies = [
"thiserror 2.0.12",
"time",
"tiny_http",
"tokio",
"tokio-socks",
"url",
"util",
@@ -3329,15 +3351,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "convert_case"
version = "0.8.0"
@@ -3926,9 +3939,9 @@ checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d"
[[package]]
name = "ctrlc"
version = "3.4.6"
version = "3.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c"
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
dependencies = [
"nix",
"windows-sys 0.59.0",
@@ -4036,7 +4049,7 @@ dependencies = [
[[package]]
name = "dap-types"
version = "0.0.1"
source = "git+https://github.com/zed-industries/dap-types?rev=be69a016ba710191b9fdded28c8b042af4b617f7#be69a016ba710191b9fdded28c8b042af4b617f7"
source = "git+https://github.com/zed-industries/dap-types?rev=bfd4af0#bfd4af084bbaa5f344e6925370d7642e41d0b5b8"
dependencies = [
"schemars",
"serde",
@@ -4470,32 +4483,6 @@ dependencies = [
"workspace-hack",
]
[[package]]
name = "documented"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6db32f0995bc4553d2de888999075acd0dbeef75ba923503f6a724263dc6f3"
dependencies = [
"documented-macros",
"phf",
"thiserror 1.0.69",
]
[[package]]
name = "documented-macros"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a394bb35929b58f9a5fd418f7c6b17a4b616efcc1e53e6995ca123948f87e5fa"
dependencies = [
"convert_case 0.6.0",
"itertools 0.13.0",
"optfield",
"proc-macro2",
"quote",
"strum",
"syn 2.0.100",
]
[[package]]
name = "dotenvy"
version = "0.15.7"
@@ -4837,9 +4824,9 @@ dependencies = [
[[package]]
name = "errno"
version = "0.3.11"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.59.0",
@@ -5284,9 +5271,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flate2"
version = "1.1.1"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -7464,7 +7451,8 @@ dependencies = [
[[package]]
name = "jupyter-protocol"
version = "0.6.0"
source = "git+https://github.com/ConradIrwin/runtimed?rev=7130c804216b6914355d15d0b91ea91f6babd734#7130c804216b6914355d15d0b91ea91f6babd734"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9ae6296f9476658b3550293c113996daf75fa542cd8d078abb4c60207bded14"
dependencies = [
"anyhow",
"async-trait",
@@ -7479,7 +7467,8 @@ dependencies = [
[[package]]
name = "jupyter-websocket-client"
version = "0.9.0"
source = "git+https://github.com/ConradIrwin/runtimed?rev=7130c804216b6914355d15d0b91ea91f6babd734#7130c804216b6914355d15d0b91ea91f6babd734"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49c1ba895c5271ff8dcae51c347fd3588905ba0025a57e20955fd231fe1228cc"
dependencies = [
"anyhow",
"async-trait",
@@ -7658,6 +7647,7 @@ dependencies = [
"picker",
"proto",
"ui",
"workspace",
"workspace-hack",
"zed_actions",
]
@@ -7921,9 +7911,9 @@ checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]]
name = "libmimalloc-sys"
version = "0.1.41"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b20daca3a4ac14dbdc753c5e90fc7b490a48a9131daed3c9a9ced7b2defd37b"
checksum = "07d0e07885d6a754b9c7993f2625187ad694ee985d60f23355ff0e7077261502"
dependencies = [
"cc",
"libc",
@@ -8372,7 +8362,6 @@ dependencies = [
"node_runtime",
"pulldown-cmark 0.12.2",
"settings",
"sum_tree",
"theme",
"ui",
"util",
@@ -8594,9 +8583,9 @@ dependencies = [
[[package]]
name = "mimalloc"
version = "0.1.45"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03cb1f88093fe50061ca1195d336ffec131347c7b833db31f9ab62a2d1b7925f"
checksum = "99585191385958383e13f6b822e6b6d8d9cf928e7d286ceb092da92b43c87bc1"
dependencies = [
"libmimalloc-sys",
]
@@ -8625,9 +8614,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.7"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [
"adler2",
"simd-adler32",
@@ -8786,7 +8775,8 @@ dependencies = [
[[package]]
name = "nbformat"
version = "0.10.0"
source = "git+https://github.com/ConradIrwin/runtimed?rev=7130c804216b6914355d15d0b91ea91f6babd734#7130c804216b6914355d15d0b91ea91f6babd734"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "244c1673f02b4d5f3c51b6f8ed28d57182cb166a50a6dbf651a3d53e23dc81c0"
dependencies = [
"anyhow",
"chrono",
@@ -9535,9 +9525,9 @@ dependencies = [
[[package]]
name = "openssl"
version = "0.10.72"
version = "0.10.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd"
dependencies = [
"bitflags 2.9.0",
"cfg-if",
@@ -9566,26 +9556,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.107"
name = "openssl-src"
version = "300.4.2+3.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07"
checksum = "168ce4e058f975fe43e89d9ccf78ca668601887ae736090aacc23ae353c298e2"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "optfield"
version = "0.3.0"
name = "openssl-sys"
version = "0.9.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa59f025cde9c698fcb4fcb3533db4621795374065bee908215263488f2d2a1d"
checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
[[package]]
@@ -10007,7 +9996,7 @@ dependencies = [
[[package]]
name = "pet"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"clap",
"env_logger 0.10.2",
@@ -10044,7 +10033,7 @@ dependencies = [
[[package]]
name = "pet-conda"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -10063,7 +10052,7 @@ dependencies = [
[[package]]
name = "pet-core"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"clap",
"lazy_static",
@@ -10078,7 +10067,7 @@ dependencies = [
[[package]]
name = "pet-env-var-path"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"lazy_static",
"log",
@@ -10094,7 +10083,7 @@ dependencies = [
[[package]]
name = "pet-fs"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10103,7 +10092,7 @@ dependencies = [
[[package]]
name = "pet-global-virtualenvs"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10116,7 +10105,7 @@ dependencies = [
[[package]]
name = "pet-homebrew"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"lazy_static",
"log",
@@ -10134,7 +10123,7 @@ dependencies = [
[[package]]
name = "pet-jsonrpc"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"env_logger 0.10.2",
"log",
@@ -10147,7 +10136,7 @@ dependencies = [
[[package]]
name = "pet-linux-global-python"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10160,7 +10149,7 @@ dependencies = [
[[package]]
name = "pet-mac-commandlinetools"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10173,7 +10162,7 @@ dependencies = [
[[package]]
name = "pet-mac-python-org"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10186,7 +10175,7 @@ dependencies = [
[[package]]
name = "pet-mac-xcode"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10199,7 +10188,7 @@ dependencies = [
[[package]]
name = "pet-pipenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10212,7 +10201,7 @@ dependencies = [
[[package]]
name = "pet-pixi"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10224,7 +10213,7 @@ dependencies = [
[[package]]
name = "pet-poetry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"base64 0.22.1",
"lazy_static",
@@ -10245,7 +10234,7 @@ dependencies = [
[[package]]
name = "pet-pyenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"lazy_static",
"log",
@@ -10263,7 +10252,7 @@ dependencies = [
[[package]]
name = "pet-python-utils"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -10280,7 +10269,7 @@ dependencies = [
[[package]]
name = "pet-reporter"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"env_logger 0.10.2",
"log",
@@ -10294,7 +10283,7 @@ dependencies = [
[[package]]
name = "pet-telemetry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -10309,7 +10298,7 @@ dependencies = [
[[package]]
name = "pet-venv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10321,7 +10310,7 @@ dependencies = [
[[package]]
name = "pet-virtualenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10333,7 +10322,7 @@ dependencies = [
[[package]]
name = "pet-virtualenvwrapper"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10346,7 +10335,7 @@ dependencies = [
[[package]]
name = "pet-windows-registry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"lazy_static",
"log",
@@ -10358,13 +10347,13 @@ dependencies = [
"pet-virtualenv",
"pet-windows-store",
"regex",
"winreg 0.55.0",
"winreg 0.52.0",
]
[[package]]
name = "pet-windows-store"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
dependencies = [
"lazy_static",
"log",
@@ -10374,7 +10363,7 @@ dependencies = [
"pet-python-utils",
"pet-virtualenv",
"regex",
"winreg 0.55.0",
"winreg 0.52.0",
]
[[package]]
@@ -12112,7 +12101,8 @@ dependencies = [
[[package]]
name = "runtimelib"
version = "0.25.0"
source = "git+https://github.com/ConradIrwin/runtimed?rev=7130c804216b6914355d15d0b91ea91f6babd734#7130c804216b6914355d15d0b91ea91f6babd734"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9af6ed9fd10d7ee940676945510c197c2a472806bb652096a713985c44ffd643"
dependencies = [
"anyhow",
"async-dispatcher",
@@ -12219,7 +12209,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags 2.9.0",
"errno 0.3.11",
"errno 0.3.10",
"itoa",
"libc",
"linux-raw-sys 0.4.15",
@@ -12234,7 +12224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [
"bitflags 2.9.0",
"errno 0.3.11",
"errno 0.3.10",
"libc",
"linux-raw-sys 0.9.3",
"windows-sys 0.59.0",
@@ -12246,7 +12236,7 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a25c3aad9fc1424eb82c88087789a7d938e1829724f3e4043163baf0d13cfc12"
dependencies = [
"errno 0.3.11",
"errno 0.3.10",
"libc",
"rustix 0.38.44",
]
@@ -12447,7 +12437,7 @@ dependencies = [
[[package]]
name = "scap"
version = "0.0.8"
source = "git+https://github.com/zed-industries/scap?rev=08f0a01417505cc0990b9931a37e5120db92e0d0#08f0a01417505cc0990b9931a37e5120db92e0d0"
source = "git+https://github.com/zed-industries/scap?rev=5715067104794aa356977c543e2f3e95c6183044#5715067104794aa356977c543e2f3e95c6183044"
dependencies = [
"anyhow",
"cocoa 0.25.0",
@@ -13912,9 +13902,9 @@ dependencies = [
[[package]]
name = "swash"
version = "0.2.2"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fae9a562c7b46107d9c78cd78b75bbe1e991c16734c0aee8ff0ee711fb8b620a"
checksum = "13d5bbc2aa266907ed8ee977c9c9e16363cc2b001266104e13397b57f1d15f71"
dependencies = [
"skrifa",
"yazi",
@@ -14173,14 +14163,12 @@ name = "tasks_ui"
version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"debugger_ui",
"editor",
"feature_flags",
"file_icons",
"fuzzy",
"gpui",
"itertools 0.14.0",
"language",
"menu",
"picker",
@@ -14670,9 +14658,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.44.2"
version = "1.44.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
dependencies = [
"backtrace",
"bytes 1.10.1",
@@ -15341,6 +15329,24 @@ dependencies = [
"utf-8",
]
[[package]]
name = "tungstenite"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
dependencies = [
"byteorder",
"bytes 1.10.1",
"data-encoding",
"http 1.3.1",
"httparse",
"log",
"rand 0.8.5",
"sha1",
"thiserror 1.0.69",
"utf-8",
]
[[package]]
name = "tungstenite"
version = "0.26.2"
@@ -15395,7 +15401,6 @@ version = "0.1.0"
dependencies = [
"chrono",
"component",
"documented",
"gpui",
"icons",
"itertools 0.14.0",
@@ -17245,16 +17250,6 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "winreg"
version = "0.55.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97"
dependencies = [
"cfg-if",
"windows-sys 0.59.0",
]
[[package]]
name = "winresource"
version = "0.1.20"
@@ -17661,7 +17656,6 @@ dependencies = [
"indexmap",
"inout",
"itertools 0.12.1",
"itertools 0.13.0",
"lazy_static",
"libc",
"libsqlite3-sys",
@@ -17706,6 +17700,7 @@ dependencies = [
"scopeguard",
"sea-orm",
"sea-query-binder",
"security-framework 2.11.1",
"security-framework 3.2.0",
"security-framework-sys",
"semver",
@@ -17736,7 +17731,6 @@ dependencies = [
"toml_edit",
"tracing",
"tracing-core",
"tungstenite 0.26.2",
"unicode-properties",
"url",
"uuid",

View File

@@ -8,7 +8,7 @@ members = [
"crates/assets",
"crates/assistant",
"crates/assistant_context_editor",
"crates/agent_eval",
"crates/assistant_eval",
"crates/assistant_settings",
"crates/assistant_slash_command",
"crates/assistant_slash_commands",
@@ -215,7 +215,7 @@ askpass = { path = "crates/askpass" }
assets = { path = "crates/assets" }
assistant = { path = "crates/assistant" }
assistant_context_editor = { path = "crates/assistant_context_editor" }
assistant_eval = { path = "crates/agent_eval" }
assistant_eval = { path = "crates/assistant_eval" }
assistant_settings = { path = "crates/assistant_settings" }
assistant_slash_command = { path = "crates/assistant_slash_command" }
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
@@ -396,14 +396,18 @@ async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "8
async-recursion = "1.0.0"
async-tar = "0.5.0"
async-trait = "0.1"
async-tungstenite = "0.29.1"
async-tungstenite = "0.28"
async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
aws-config = { version = "1.6.1", features = ["behavior-version-latest"] }
aws-credential-types = { version = "1.2.2", features = ["hardcoded-credentials"] }
aws-sdk-bedrockruntime = { version = "1.80.0", features = ["behavior-version-latest"] }
aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
aws-config = { version = "1.5.16", features = ["behavior-version-latest"] }
aws-credential-types = { version = "1.2.1", features = [
"hardcoded-credentials",
] }
aws-sdk-bedrockruntime = { version = "1.73.0", features = [
"behavior-version-latest",
] }
aws-smithy-runtime-api = { version = "1.7.3", features = ["http-1x", "client"] }
aws-smithy-types = { version = "1.2.13", features = ["http-body-1-x"] }
base64 = "0.22"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
@@ -425,7 +429,7 @@ core-foundation = "0.10.0"
core-foundation-sys = "0.8.6"
ctor = "0.4.0"
dashmap = "6.0"
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "be69a016ba710191b9fdded28c8b042af4b617f7" }
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "bfd4af0" }
derive_more = "0.99.17"
dirs = "4.0"
ec4rs = "1.1"
@@ -453,8 +457,8 @@ indoc = "2"
inventory = "0.3.19"
itertools = "0.14.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" }
jupyter-protocol = { version = "0.6.0" }
jupyter-websocket-client = { version = "0.9.0" }
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
@@ -463,22 +467,21 @@ log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0"
mlua = { version = "0.10", features = ["lua54", "vendored", "async", "send"] }
nanoid = "0.4"
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
nbformat = { version = "0.10.0" }
nix = "0.29"
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"
pathdiff = "0.2"
pet = { 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" }
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
proc-macro2 = "1.0.93"
@@ -501,7 +504,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
"stream",
] }
rsa = "0.9.6"
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
runtimelib = { version = "0.25.0", default-features = false, features = [
"async-dispatcher-runtime",
] }
rustc-demangle = "0.1.23"
@@ -509,7 +512,7 @@ rust-embed = { version = "8.4", features = ["include-exclude"] }
rustc-hash = "2.1.0"
rustls = { version = "0.23.22" }
rustls-platform-verifier = "0.5.0"
scap = { git = "https://github.com/zed-industries/scap", rev = "08f0a01417505cc0990b9931a37e5120db92e0d0", default-features = false }
scap = { git = "https://github.com/zed-industries/scap", rev = "5715067104794aa356977c543e2f3e95c6183044", default-features = false }
schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] }
@@ -661,6 +664,7 @@ features = [
# TODO livekit https://github.com/RustAudio/cpal/pull/891
[patch.crates-io]
cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" }
real-async-tls = { git = "https://github.com/zed-industries/async-tls", rev = "1e759a4b5e370f87dc15e40756ac4f8815b61d9d", package = "async-tls" }
notify = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }

View File

@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.09666 3.02263C3.0567 3.00312 3.01178 2.9961 2.96778 3.0025C2.92377 3.00889 2.88271 3.02839 2.84995 3.05847C2.8172 3.08854 2.79426 3.12778 2.78413 3.17108C2.77401 3.21439 2.77716 3.25973 2.79319 3.30121L4.05638 6.69C4.13088 6.89005 4.13088 7.11022 4.05638 7.31027L2.79363 10.6991C2.77769 10.7405 2.77457 10.7858 2.78469 10.829C2.79481 10.8722 2.8177 10.9114 2.85038 10.9414C2.88306 10.9715 2.92402 10.991 2.96794 10.9975C3.01186 11.0039 3.05671 10.997 3.09666 10.9776L11.0943 7.20097C11.1324 7.18297 11.1645 7.15455 11.187 7.11899C11.2096 7.08344 11.2215 7.04222 11.2215 7.00014C11.2215 6.95805 11.2096 6.91683 11.187 6.88128C11.1645 6.84573 11.1324 6.8173 11.0943 6.79931L3.09666 3.02263Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.11255 7.00014H11.2216" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1014 B

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 9.8V4.2C4 4.08954 4.08954 4 4.2 4H9.8C9.91046 4 10 4.08954 10 4.2V9.8C10 9.91046 9.91046 10 9.8 10H4.2C4.08954 10 4 9.91046 4 9.8Z" fill="#C56757" stroke="#C56757" stroke-width="1.25" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 325 B

View File

@@ -532,7 +532,6 @@
"context": "Editor && showing_completions",
"bindings": {
"enter": "editor::ConfirmCompletion",
"shift-enter": "editor::ConfirmCompletionReplace",
"tab": "editor::ComposeCompletion"
}
},
@@ -625,6 +624,7 @@
"context": "AgentPanel",
"bindings": {
"ctrl-n": "agent::NewThread",
"new": "agent::NewThread",
"ctrl-alt-n": "agent::NewPromptEditor",
"ctrl-shift-h": "agent::OpenHistory",
"ctrl-alt-c": "agent::OpenConfiguration",
@@ -635,13 +635,6 @@
"ctrl-alt-e": "agent::RemoveAllContext"
}
},
{
"context": "AgentPanel > Markdown",
"bindings": {
"copy": "markdown::CopyAsMarkdown",
"ctrl-c": "markdown::CopyAsMarkdown"
}
},
{
"context": "AgentPanel && prompt_editor",
"use_key_equivalents": true,

View File

@@ -291,13 +291,6 @@
"cmd-alt-e": "agent::RemoveAllContext"
}
},
{
"context": "AgentPanel > Markdown",
"use_key_equivalents": true,
"bindings": {
"cmd-c": "markdown::CopyAsMarkdown"
}
},
{
"context": "AgentPanel && prompt_editor",
"use_key_equivalents": true,
@@ -345,28 +338,10 @@
"enter": "agent::AcceptSuggestedContext"
}
},
{
"context": "AgentConfiguration",
"bindings": {
"ctrl--": "pane::GoBack"
}
},
{
"context": "ThreadHistory",
"bindings": {
"ctrl--": "pane::GoBack"
}
},
{
"context": "ThreadHistory",
"bindings": {
"ctrl--": "pane::GoBack"
}
},
{
"context": "ThreadHistory > Editor",
"bindings": {
"shift-backspace": "agent::RemoveSelectedThread"
"backspace": "agent::RemoveSelectedThread"
}
},
{
@@ -525,7 +500,7 @@
"cmd-k cmd-9": ["editor::FoldAtLevel", 9],
"cmd-k cmd-0": "editor::FoldAll",
"cmd-k cmd-j": "editor::UnfoldAll",
// Using `ctrl-space` / `ctrl-shift-space` in Zed requires disabling the macOS global shortcut.
// Using `ctrl-space` in Zed requires disabling the macOS global shortcut.
// System Preferences->Keyboard->Keyboard Shortcuts->Input Sources->Select the previous input source (uncheck)
"ctrl-space": "editor::ShowCompletions",
"ctrl-shift-space": "editor::ShowWordCompletions",
@@ -681,7 +656,6 @@
"use_key_equivalents": true,
"bindings": {
"enter": "editor::ConfirmCompletion",
"shift-enter": "editor::ConfirmCompletionReplace",
"tab": "editor::ComposeCompletion"
}
},
@@ -1004,8 +978,6 @@
"cmd-home": "terminal::ScrollToTop",
"shift-end": "terminal::ScrollToBottom",
"cmd-end": "terminal::ScrollToBottom",
// 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-k up": "pane::SplitUp",
"ctrl-k down": "pane::SplitDown",

View File

@@ -44,12 +44,6 @@
"[ /": "vim::PreviousComment",
"] *": "vim::NextComment",
"] /": "vim::NextComment",
"[ -": "vim::PreviousLesserIndent",
"[ +": "vim::PreviousGreaterIndent",
"[ =": "vim::PreviousSameIndent",
"] -": "vim::NextLesserIndent",
"] +": "vim::NextGreaterIndent",
"] =": "vim::NextSameIndent",
// Word motions
"w": "vim::NextWordStart",
"e": "vim::NextWordEnd",

View File

@@ -6,26 +6,15 @@ You are an AI assistant integrated into a code editor. You have the programming
It will be up to you to decide which of these you are doing based on what the user has told you. When unclear, ask clarifying questions to understand the user's intent before proceeding.
You should only perform actions that modify the user's system if explicitly requested by the user:
- If the user asks a question about how to accomplish a task, provide guidance or information, and use read-only tools (e.g., search) to assist. You may suggest potential actions, but do not directly modify the user's system without explicit instruction.
- If the user asks a question about how to accomplish a task, provide guidance or information, and use read-only tools (e.g., search) to assist. You may suggest potential actions, but do not directly modify the users system without explicit instruction.
- If the user clearly requests that you perform an action, carry out the action directly without explaining why you are doing so.
When answering questions, it's okay to give incomplete examples containing comments about what would go there in a real version. When being asked to directly perform tasks on the code base, you must ALWAYS make fully working code. You may never "simplify" the code by omitting or deleting functionality you know the user has requested, and you must NEVER write comments like "in a full version, this would..." - instead, you must actually implement the real version. Don't be lazy!
Note that project files are automatically backed up. The user can always get them back later if anything goes wrong, so there's
no need to create backup files (e.g. `.bak` files) because these files will just take up unnecessary space on the user's disk.
When attempting to resolve issues around failing tests, never simply remove the failing tests. Unless the user explicitly asks you to remove tests, ALWAYS attempt to fix the code causing the tests to fail.
Ignore "TODO"-type comments unless they're relevant to the user's explicit request or the user specifically asks you to address them. It is, however, okay to include them in codebase summaries.
<style>
Editing code:
- Make sure to take previous edits into account.
- The edits you perform might lead to errors or warnings. At the end of your changes, check whether you introduced any problems, and fix them before providing a summary of the changes you made.
- You may only attempt to fix these up to 3 times. If you have tried 3 times to fix them, and there are still problems remaining, you must not continue trying to fix them, and must instead tell the user that there are problems remaining - and ask if the user would like you to attempt to solve them further.
- Do not fix errors unrelated to your changes unless the user explicitly asks you to do so.
- Prefer to move files over recreating them. The move can be followed by minor edits if required.
- If you seem to be stuck, never go back and "simplify the implementation" by deleting the parts of the implementation you're stuck on and replacing them with comments. If you ever feel the urge to do this, instead immediately stop whatever you're doing (even if the code is in a broken state), report that you are stuck, explain what you're stuck on, and ask the user how to proceed.
Tool use:
- Make sure to adhere to the tools schema.
@@ -44,106 +33,6 @@ Responding:
For example, don't say "Now I'm going to check diagnostics to see if there are any warnings or errors," followed by running a tool which checks diagnostics and reports warnings or errors; instead, just request the tool call without saying anything.
- All tool results are provided to you automatically, so DO NOT thank the user when this happens.
Whenever you mention a code block, you MUST use ONLY the following format:
```language 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.
</style>
The user has opened a project that contains the following root directories/files. Whenever you specify a path in the project, it must be a relative path which begins with one of these root directories/files:
{{#each worktrees}}

View File

@@ -0,0 +1,8 @@
A software developer is asking a question about their project. The source files in their project have been indexed into a database of semantic text embeddings.
Your task is to generate a list of 4 diverse search queries that can be run on this embedding database, in order to retrieve a list of code snippets
that are relevant to the developer's question. Redundant search queries will be heavily penalized, so only include another query if it's sufficiently
distinct from previous ones.
Here is the question that's been asked, together with context that the developer has added manually:
{{{context_buffer}}}

View File

@@ -1136,8 +1136,7 @@
"code_actions_on_format": {},
// Settings related to running tasks.
"tasks": {
"variables": {},
"enabled": true
"variables": {}
},
// An object whose keys are language names, and whose values
// are arrays of filenames or extensions of files that should
@@ -1457,8 +1456,6 @@
"lsp": {
// Specify the LSP name as a key here.
// "rust-analyzer": {
// // A special flag for rust-analyzer integration, to use server-provided tasks
// enable_lsp_tasks": true,
// // These initialization options are merged into Zed's defaults
// "initialization_options": {
// "check": {

View File

@@ -87,9 +87,9 @@
"terminal.ansi.blue": "#83a598ff",
"terminal.ansi.bright_blue": "#414f4aff",
"terminal.ansi.dim_blue": "#c0d2cbff",
"terminal.ansi.magenta": "#d3869bff",
"terminal.ansi.bright_magenta": "#8e5868ff",
"terminal.ansi.dim_magenta": "#ff9ebbff",
"terminal.ansi.magenta": "#a89984ff",
"terminal.ansi.bright_magenta": "#514a41ff",
"terminal.ansi.dim_magenta": "#d2cabfff",
"terminal.ansi.cyan": "#8ec07cff",
"terminal.ansi.bright_cyan": "#45603eff",
"terminal.ansi.dim_cyan": "#c7dfbdff",
@@ -472,9 +472,9 @@
"terminal.ansi.blue": "#83a598ff",
"terminal.ansi.bright_blue": "#414f4aff",
"terminal.ansi.dim_blue": "#c0d2cbff",
"terminal.ansi.magenta": "#d3869bff",
"terminal.ansi.bright_magenta": "#8e5868ff",
"terminal.ansi.dim_magenta": "#ff9ebbff",
"terminal.ansi.magenta": "#a89984ff",
"terminal.ansi.bright_magenta": "#514a41ff",
"terminal.ansi.dim_magenta": "#d2cabfff",
"terminal.ansi.cyan": "#8ec07cff",
"terminal.ansi.bright_cyan": "#45603eff",
"terminal.ansi.dim_cyan": "#c7dfbdff",
@@ -857,9 +857,9 @@
"terminal.ansi.blue": "#83a598ff",
"terminal.ansi.bright_blue": "#414f4aff",
"terminal.ansi.dim_blue": "#c0d2cbff",
"terminal.ansi.magenta": "#d3869bff",
"terminal.ansi.bright_magenta": "#8e5868ff",
"terminal.ansi.dim_magenta": "#ff9ebbff",
"terminal.ansi.magenta": "#a89984ff",
"terminal.ansi.bright_magenta": "#514a41ff",
"terminal.ansi.dim_magenta": "#d2cabfff",
"terminal.ansi.cyan": "#8ec07cff",
"terminal.ansi.bright_cyan": "#45603eff",
"terminal.ansi.dim_cyan": "#c7dfbdff",
@@ -1242,9 +1242,9 @@
"terminal.ansi.blue": "#0b6678ff",
"terminal.ansi.bright_blue": "#8fb0baff",
"terminal.ansi.dim_blue": "#14333bff",
"terminal.ansi.magenta": "#8f3e71ff",
"terminal.ansi.bright_magenta": "#c76da0ff",
"terminal.ansi.dim_magenta": "#5c2848ff",
"terminal.ansi.magenta": "#7c6f64ff",
"terminal.ansi.bright_magenta": "#bcb5afff",
"terminal.ansi.dim_magenta": "#3e3833ff",
"terminal.ansi.cyan": "#437b59ff",
"terminal.ansi.bright_cyan": "#9fbca8ff",
"terminal.ansi.dim_cyan": "#253e2eff",
@@ -1627,9 +1627,9 @@
"terminal.ansi.blue": "#0b6678ff",
"terminal.ansi.bright_blue": "#8fb0baff",
"terminal.ansi.dim_blue": "#14333bff",
"terminal.ansi.magenta": "#8f3e71ff",
"terminal.ansi.bright_magenta": "#c76da0ff",
"terminal.ansi.dim_magenta": "#5c2848ff",
"terminal.ansi.magenta": "#7c6f64ff",
"terminal.ansi.bright_magenta": "#bcb5afff",
"terminal.ansi.dim_magenta": "#3e3833ff",
"terminal.ansi.cyan": "#437b59ff",
"terminal.ansi.bright_cyan": "#9fbca8ff",
"terminal.ansi.dim_cyan": "#253e2eff",
@@ -2012,9 +2012,9 @@
"terminal.ansi.blue": "#0b6678ff",
"terminal.ansi.bright_blue": "#8fb0baff",
"terminal.ansi.dim_blue": "#14333bff",
"terminal.ansi.magenta": "#8f3e71ff",
"terminal.ansi.bright_magenta": "#c76da0ff",
"terminal.ansi.dim_magenta": "#5c2848ff",
"terminal.ansi.magenta": "#7c6f64ff",
"terminal.ansi.bright_magenta": "#bcb5afff",
"terminal.ansi.dim_magenta": "#3e3833ff",
"terminal.ansi.cyan": "#437b59ff",
"terminal.ansi.bright_cyan": "#9fbca8ff",
"terminal.ansi.dim_cyan": "#253e2eff",

View File

@@ -11,22 +11,13 @@ use language::{BinaryStatus, LanguageRegistry, LanguageServerId};
use project::{
EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project,
ProjectEnvironmentEvent,
git_store::{GitStoreEvent, Repository},
};
use smallvec::SmallVec;
use std::{
cmp::Reverse,
fmt::Write,
path::Path,
sync::Arc,
time::{Duration, Instant},
};
use std::{cmp::Reverse, fmt::Write, path::Path, sync::Arc, time::Duration};
use ui::{ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
use util::truncate_and_trailoff;
use workspace::{StatusItemView, Workspace, item::ItemHandle};
const GIT_OPERATION_DELAY: Duration = Duration::from_millis(0);
actions!(activity_indicator, [ShowErrorMessage]);
pub enum Event {
@@ -114,15 +105,6 @@ impl ActivityIndicator {
)
.detach();
cx.subscribe(
&project.read(cx).git_store().clone(),
|_, _, event: &GitStoreEvent, cx| match event {
project::git_store::GitStoreEvent::JobsUpdated => cx.notify(),
_ => {}
},
)
.detach();
if let Some(auto_updater) = auto_updater.as_ref() {
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
}
@@ -303,34 +285,6 @@ impl ActivityIndicator {
});
}
let current_job = self
.project
.read(cx)
.active_repository(cx)
.map(|r| r.read(cx))
.and_then(Repository::current_job);
// Show any long-running git command
if let Some(job_info) = current_job {
if Instant::now() - job_info.start >= GIT_OPERATION_DELAY {
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: job_info.message.into(),
on_click: None,
});
}
}
// Show any language server installation info.
let mut downloading = SmallVec::<[_; 3]>::new();
let mut checking_for_update = SmallVec::<[_; 3]>::new();

View File

@@ -84,6 +84,7 @@ ui.workspace = true
ui_input.workspace = true
util.workspace = true
uuid.workspace = true
vim_mode_setting.workspace = true
workspace.workspace = true
zed_actions.workspace = true
workspace-hack.workspace = true

File diff suppressed because it is too large Load Diff

View File

@@ -45,28 +45,23 @@ impl AgentDiff {
window: &mut Window,
cx: &mut App,
) -> Result<Entity<Self>> {
workspace.update(cx, |workspace, cx| {
Self::deploy_in_workspace(thread, workspace, window, cx)
})
}
pub fn deploy_in_workspace(
thread: Entity<Thread>,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Entity<Self> {
let existing_diff = workspace
.items_of_type::<AgentDiff>(cx)
.find(|diff| diff.read(cx).thread == thread);
let existing_diff = workspace.update(cx, |workspace, cx| {
workspace
.items_of_type::<AgentDiff>(cx)
.find(|diff| diff.read(cx).thread == thread)
})?;
if let Some(existing_diff) = existing_diff {
workspace.activate_item(&existing_diff, true, true, window, cx);
existing_diff
workspace.update(cx, |workspace, cx| {
workspace.activate_item(&existing_diff, true, true, window, cx);
})?;
Ok(existing_diff)
} else {
let agent_diff =
cx.new(|cx| AgentDiff::new(thread.clone(), workspace.weak_handle(), window, cx));
workspace.add_item_to_center(Box::new(agent_diff.clone()), window, cx);
agent_diff
cx.new(|cx| AgentDiff::new(thread.clone(), workspace.clone(), window, cx));
workspace.update(cx, |workspace, cx| {
workspace.add_item_to_center(Box::new(agent_diff.clone()), window, cx);
})?;
Ok(agent_diff)
}
}
@@ -792,11 +787,15 @@ impl editor::Addon for AgentDiffAddon {
pub struct AgentDiffToolbar {
agent_diff: Option<WeakEntity<AgentDiff>>,
_workspace: WeakEntity<Workspace>,
}
impl AgentDiffToolbar {
pub fn new() -> Self {
Self { agent_diff: None }
pub fn new(workspace: &Workspace, _: &mut Context<Self>) -> Self {
Self {
agent_diff: None,
_workspace: workspace.weak_handle(),
}
}
fn agent_diff(&self, _: &App) -> Option<Entity<AgentDiff>> {

View File

@@ -51,6 +51,7 @@ actions!(
ToggleProfileSelector,
RemoveAllContext,
OpenHistory,
OpenConfiguration,
AddContextServer,
RemoveSelectedThread,
Chat,

View File

@@ -409,7 +409,6 @@ impl Render for AssistantConfiguration {
v_flex()
.id("assistant-configuration")
.key_context("AgentConfiguration")
.track_focus(&self.focus_handle(cx))
.bg(cx.theme().colors().panel_background)
.size_full()

View File

@@ -17,7 +17,7 @@ use fs::Fs;
use gpui::{
Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, Corner, Entity,
EventEmitter, FocusHandle, Focusable, FontWeight, KeyContext, Pixels, Subscription, Task,
UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
UpdateGlobal, WeakEntity, action_with_deprecated_aliases, prelude::*, pulsating_between,
};
use language::LanguageRegistry;
use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};
@@ -33,8 +33,7 @@ use ui::{
use util::ResultExt as _;
use workspace::Workspace;
use workspace::dock::{DockPosition, Panel, PanelEvent};
use zed_actions::agent::OpenConfiguration;
use zed_actions::assistant::{OpenPromptLibrary, ToggleFocus};
use zed_actions::assistant::ToggleFocus;
use crate::active_thread::ActiveThread;
use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
@@ -45,9 +44,15 @@ use crate::thread_history::{PastContext, PastThread, ThreadHistory};
use crate::thread_store::ThreadStore;
use crate::{
AgentDiff, InlineAssistant, NewPromptEditor, NewThread, OpenActiveThreadAsMarkdown,
OpenAgentDiff, OpenHistory, ThreadEvent, ToggleContextPicker,
OpenAgentDiff, OpenConfiguration, OpenHistory, ThreadEvent, ToggleContextPicker,
};
action_with_deprecated_aliases!(
assistant,
OpenPromptLibrary,
["assistant::DeployPromptLibrary"]
);
pub fn init(cx: &mut App) {
cx.observe_new(
|workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
@@ -87,8 +92,9 @@ pub fn init(cx: &mut App) {
.register_action(|workspace, _: &OpenAgentDiff, window, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(window, cx);
let thread = panel.read(cx).thread.read(cx).thread().clone();
AgentDiff::deploy_in_workspace(thread, workspace, window, cx);
panel.update(cx, |panel, cx| {
panel.open_agent_diff(&OpenAgentDiff, window, cx);
});
}
});
},
@@ -170,7 +176,6 @@ pub struct AssistantPanel {
language_registry: Arc<LanguageRegistry>,
thread_store: Entity<ThreadStore>,
thread: Entity<ActiveThread>,
_thread_subscription: Subscription,
message_editor: Entity<MessageEditor>,
context_store: Entity<assistant_context_editor::ContextStore>,
context_editor: Option<Entity<ContextEditor>>,
@@ -178,7 +183,6 @@ pub struct AssistantPanel {
configuration_subscription: Option<Subscription>,
local_timezone: UtcOffset,
active_view: ActiveView,
previous_view: Option<ActiveView>,
history_store: Entity<HistoryStore>,
history: Entity<ThreadHistory>,
assistant_dropdown_menu_handle: PopoverMenuHandle<ContextMenu>,
@@ -254,15 +258,7 @@ impl AssistantPanel {
let history_store =
cx.new(|cx| HistoryStore::new(thread_store.clone(), context_store.clone(), cx));
cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
let active_view = ActiveView::thread(thread.clone(), window, cx);
let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
if let ThreadEvent::MessageAdded(_) = &event {
// needed to leave empty state
cx.notify();
}
});
let thread = cx.new(|cx| {
ActiveThread::new(
thread.clone(),
@@ -283,7 +279,6 @@ impl AssistantPanel {
language_registry,
thread_store: thread_store.clone(),
thread,
_thread_subscription: thread_subscription,
message_editor,
context_store,
context_editor: None,
@@ -293,7 +288,6 @@ impl AssistantPanel {
chrono::Local::now().offset().local_minus_utc(),
)
.unwrap(),
previous_view: None,
history_store: history_store.clone(),
history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)),
assistant_dropdown_menu_handle: PopoverMenuHandle::default(),
@@ -339,8 +333,7 @@ impl AssistantPanel {
.thread_store
.update(cx, |this, cx| this.create_thread(cx));
let thread_view = ActiveView::thread(thread.clone(), window, cx);
self.set_active_view(thread_view, window, cx);
self.active_view = ActiveView::thread(thread.clone(), window, cx);
let message_editor_context_store = cx.new(|_cx| {
crate::context_store::ContextStore::new(
@@ -380,14 +373,6 @@ impl AssistantPanel {
cx,
)
});
self._thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
if let ThreadEvent::MessageAdded(_) = &event {
// needed to leave empty state
cx.notify();
}
});
self.message_editor = cx.new(|cx| {
MessageEditor::new(
self.fs.clone(),
@@ -403,7 +388,7 @@ impl AssistantPanel {
}
fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.set_active_view(ActiveView::PromptEditor, window, cx);
self.active_view = ActiveView::PromptEditor;
let context = self
.context_store
@@ -453,16 +438,11 @@ impl AssistantPanel {
}
fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if matches!(self.active_view, ActiveView::History) {
if let Some(previous_view) = self.previous_view.take() {
self.set_active_view(previous_view, window, cx);
}
} else {
self.thread_store
.update(cx, |thread_store, cx| thread_store.reload(cx))
.detach_and_log_err(cx);
self.set_active_view(ActiveView::History, window, cx);
}
self.thread_store
.update(cx, |thread_store, cx| thread_store.reload(cx))
.detach_and_log_err(cx);
self.active_view = ActiveView::History;
self.history.focus_handle(cx).focus(window);
cx.notify();
}
@@ -495,7 +475,7 @@ impl AssistantPanel {
cx,
)
});
this.set_active_view(ActiveView::PromptEditor, window, cx);
this.active_view = ActiveView::PromptEditor;
this.context_editor = Some(editor);
anyhow::Ok(())
@@ -517,8 +497,7 @@ impl AssistantPanel {
cx.spawn_in(window, async move |this, cx| {
let thread = open_thread_task.await?;
this.update_in(cx, |this, window, cx| {
let thread_view = ActiveView::thread(thread.clone(), window, cx);
this.set_active_view(thread_view, window, cx);
this.active_view = ActiveView::thread(thread.clone(), window, cx);
let message_editor_context_store = cx.new(|_cx| {
crate::context_store::ContextStore::new(
this.workspace.clone(),
@@ -552,17 +531,6 @@ impl AssistantPanel {
})
}
pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
match self.active_view {
ActiveView::Configuration | ActiveView::History => {
self.active_view =
ActiveView::thread(self.thread.read(cx).thread().clone(), window, cx);
cx.notify();
}
_ => {}
}
}
pub fn open_agent_diff(
&mut self,
_: &OpenAgentDiff,
@@ -570,11 +538,7 @@ impl AssistantPanel {
cx: &mut Context<Self>,
) {
let thread = self.thread.read(cx).thread().clone();
self.workspace
.update(cx, |workspace, cx| {
AgentDiff::deploy_in_workspace(thread, workspace, window, cx)
})
.log_err();
AgentDiff::deploy(thread, self.workspace.clone(), window, cx).log_err();
}
pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@@ -582,7 +546,7 @@ impl AssistantPanel {
let tools = self.thread_store.read(cx).tools();
let fs = self.fs.clone();
self.set_active_view(ActiveView::Configuration, window, cx);
self.active_view = ActiveView::Configuration;
self.configuration =
Some(cx.new(|cx| {
AssistantConfiguration::new(fs, context_server_manager, tools, window, cx)
@@ -689,49 +653,20 @@ impl AssistantPanel {
self.thread.read(cx).thread().clone()
}
pub(crate) fn delete_thread(
&mut self,
thread_id: &ThreadId,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut Context<Self>) {
self.thread_store
.update(cx, |this, cx| this.delete_thread(thread_id, cx))
.detach_and_log_err(cx);
}
pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
self.context_editor.clone()
}
pub(crate) fn delete_context(
&mut self,
path: PathBuf,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
pub(crate) fn delete_context(&mut self, path: PathBuf, cx: &mut Context<Self>) {
self.context_store
.update(cx, |this, cx| this.delete_local_context(path, cx))
}
fn set_active_view(
&mut self,
new_view: ActiveView,
window: &mut Window,
cx: &mut Context<Self>,
) {
let current_is_history = matches!(self.active_view, ActiveView::History);
let new_is_history = matches!(new_view, ActiveView::History);
if current_is_history && !new_is_history {
self.active_view = new_view;
} else if !current_is_history && new_is_history {
self.previous_view = Some(std::mem::replace(&mut self.active_view, new_view));
} else {
if !new_is_history {
self.previous_view = None;
}
self.active_view = new_view;
}
self.focus_handle(cx).focus(window);
.detach_and_log_err(cx);
}
}
@@ -882,6 +817,7 @@ impl AssistantPanel {
h_flex()
.key_context("TitleEditor")
.id("TitleEditor")
.pl_2()
.flex_grow()
.w_full()
.max_w_full()
@@ -900,37 +836,12 @@ impl AssistantPanel {
let is_empty = active_thread.is_empty();
let focus_handle = self.focus_handle(cx);
let is_history = matches!(self.active_view, ActiveView::History);
let show_token_count = match &self.active_view {
ActiveView::Thread { .. } => !is_empty,
ActiveView::PromptEditor => self.context_editor.is_some(),
_ => false,
};
let go_back_button = match &self.active_view {
ActiveView::History | ActiveView::Configuration => Some(
IconButton::new("go-back", IconName::ArrowLeft)
.icon_size(IconSize::Small)
.on_click(cx.listener(|this, _, window, cx| {
this.go_back(&workspace::GoBack, window, cx);
}))
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Go Back",
&workspace::GoBack,
&focus_handle,
window,
cx,
)
}
}),
),
_ => None,
};
h_flex()
.id("assistant-toolbar")
.h(Tab::container_height(cx))
@@ -941,14 +852,7 @@ impl AssistantPanel {
.bg(cx.theme().colors().tab_bar_background)
.border_b_1()
.border_color(cx.theme().colors().border)
.child(
h_flex()
.w_full()
.pl_2()
.gap_2()
.children(go_back_button)
.child(self.render_title_view(window, cx)),
)
.child(self.render_title_view(window, cx))
.child(
h_flex()
.h_full()
@@ -1043,27 +947,6 @@ impl AssistantPanel {
);
}),
)
.child(
IconButton::new("open-history", IconName::HistoryRerun)
.icon_size(IconSize::Small)
.toggle_state(is_history)
.selected_icon_color(Color::Accent)
.tooltip({
let focus_handle = self.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"History",
&OpenHistory,
&focus_handle,
window,
cx,
)
}
})
.on_click(move |_event, window, cx| {
window.dispatch_action(OpenHistory.boxed_clone(), cx);
}),
)
.child(
PopoverMenu::new("assistant-menu")
.trigger_with_tooltip(
@@ -1080,6 +963,12 @@ impl AssistantPanel {
cx,
|menu, _window, _cx| {
menu.action(
"New Thread",
Box::new(NewThread {
from_thread_id: None,
}),
)
.action(
"New Prompt Editor",
NewPromptEditor.boxed_clone(),
)
@@ -1092,6 +981,7 @@ impl AssistantPanel {
)
})
.separator()
.action("History", OpenHistory.boxed_clone())
.action("Settings", OpenConfiguration.boxed_clone())
},
))
@@ -1588,7 +1478,6 @@ impl Render for AssistantPanel {
.on_action(cx.listener(Self::open_active_thread_as_markdown))
.on_action(cx.listener(Self::deploy_prompt_library))
.on_action(cx.listener(Self::open_agent_diff))
.on_action(cx.listener(Self::go_back))
.child(self.render_toolbar(window, cx))
.map(|parent| match self.active_view {
ActiveView::Thread { .. } => parent

View File

@@ -28,7 +28,7 @@ use std::{
time::Instant,
};
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
pub struct BufferCodegen {
alternatives: Vec<Entity<CodegenAlternative>>,
@@ -601,7 +601,7 @@ impl CodegenAlternative {
let error_message = result.as_ref().err().map(|error| error.to_string());
report_assistant_event(
AssistantEventData {
AssistantEvent {
conversation_id: None,
message_id,
kind: AssistantKind::Inline,

View File

@@ -13,8 +13,7 @@ use editor::display_map::{Crease, FoldId};
use editor::{Anchor, AnchorRangeExt as _, Editor, ExcerptId, FoldPlaceholder, ToOffset};
use file_context_picker::render_file_context_entry;
use gpui::{
App, DismissEvent, Empty, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task,
WeakEntity,
App, DismissEvent, Empty, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity,
};
use multi_buffer::MultiBufferRow;
use project::{Entry, ProjectPath};
@@ -106,7 +105,6 @@ pub(super) struct ContextPicker {
context_store: WeakEntity<ContextStore>,
thread_store: Option<WeakEntity<ThreadStore>>,
confirm_behavior: ConfirmBehavior,
_subscriptions: Vec<Subscription>,
}
impl ContextPicker {
@@ -118,22 +116,6 @@ impl ContextPicker {
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let subscriptions = context_store
.upgrade()
.map(|context_store| {
cx.observe(&context_store, |this, _, cx| this.notify_current_picker(cx))
})
.into_iter()
.chain(
thread_store
.as_ref()
.and_then(|thread_store| thread_store.upgrade())
.map(|thread_store| {
cx.observe(&thread_store, |this, _, cx| this.notify_current_picker(cx))
}),
)
.collect::<Vec<Subscription>>();
ContextPicker {
mode: ContextPickerState::Default(ContextMenu::build(
window,
@@ -144,7 +126,6 @@ impl ContextPicker {
context_store,
thread_store,
confirm_behavior,
_subscriptions: subscriptions,
}
}
@@ -389,16 +370,6 @@ impl ContextPicker {
recent_context_picker_entries(context_store, self.thread_store.clone(), workspace, cx)
}
fn notify_current_picker(&mut self, cx: &mut Context<Self>) {
match &self.mode {
ContextPickerState::Default(entity) => entity.update(cx, |_, cx| cx.notify()),
ContextPickerState::File(entity) => entity.update(cx, |_, cx| cx.notify()),
ContextPickerState::Symbol(entity) => entity.update(cx, |_, cx| cx.notify()),
ContextPickerState::Fetch(entity) => entity.update(cx, |_, cx| cx.notify()),
ContextPickerState::Thread(entity) => entity.update(cx, |_, cx| cx.notify()),
}
}
}
impl EventEmitter<DismissEvent> for ContextPicker {}

View File

@@ -106,13 +106,12 @@ impl ContextPickerCompletionProvider {
.iter()
.map(|mode| {
Completion {
replace_range: source_range.clone(),
old_range: source_range.clone(),
new_text: format!("@{} ", mode.mention_prefix()),
label: CodeLabel::plain(mode.label().to_string(), None),
icon_path: Some(mode.icon().path().into()),
documentation: None,
source: project::CompletionSource::Custom,
insert_text_mode: None,
// This ensures that when a user accepts this completion, the
// completion menu will still be shown after "@category " is
// inserted
@@ -160,11 +159,10 @@ impl ContextPickerCompletionProvider {
let new_text = MentionLink::for_thread(&thread_entry);
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
old_range: source_range.clone(),
new_text,
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(
@@ -205,13 +203,12 @@ impl ContextPickerCompletionProvider {
let new_text = MentionLink::for_fetch(&url_to_fetch);
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
old_range: source_range.clone(),
new_text,
label: CodeLabel::plain(url_to_fetch.to_string(), None),
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(IconName::Globe.path().into()),
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
IconName::Globe.path().into(),
url_to_fetch.clone(),
@@ -235,8 +232,8 @@ impl ContextPickerCompletionProvider {
url_to_fetch.to_string(),
))
.await?;
context_store.update(cx, |context_store, cx| {
context_store.add_fetched_url(url_to_fetch.to_string(), content, cx)
context_store.update(cx, |context_store, _| {
context_store.add_fetched_url(url_to_fetch.to_string(), content)
})
})
.detach_and_log_err(cx);
@@ -287,13 +284,12 @@ impl ContextPickerCompletionProvider {
let new_text = MentionLink::for_file(&file_name, &full_path);
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
old_range: source_range.clone(),
new_text,
label,
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(completion_icon_path),
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
crease_icon_path,
file_name,
@@ -350,13 +346,12 @@ impl ContextPickerCompletionProvider {
let new_text = MentionLink::for_symbol(&symbol.name, &full_path);
let new_text_len = new_text.len();
Some(Completion {
replace_range: source_range.clone(),
old_range: source_range.clone(),
new_text,
label,
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(IconName::Code.path().into()),
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
IconName::Code.path().into(),
symbol.name.clone().into(),

View File

@@ -213,8 +213,8 @@ impl PickerDelegate for FetchContextPickerDelegate {
this.update_in(cx, |this, window, cx| {
this.delegate
.context_store
.update(cx, |context_store, cx| {
context_store.add_fetched_url(url, text, cx)
.update(cx, |context_store, _cx| {
context_store.add_fetched_url(url, text);
})?;
match confirm_behavior {

View File

@@ -98,11 +98,11 @@ impl ContextStore {
let buffer = open_buffer_task.await?;
let buffer_id = this.update(cx, |_, cx| buffer.read(cx).remote_id())?;
let already_included = this.update(cx, |this, cx| {
let already_included = this.update(cx, |this, _cx| {
match this.will_include_buffer(buffer_id, &project_path.path) {
Some(FileInclusion::Direct(context_id)) => {
if remove_if_exists {
this.remove_context(context_id, cx);
this.remove_context(context_id);
}
true
}
@@ -120,8 +120,8 @@ impl ContextStore {
let text = text_task.await;
this.update(cx, |this, cx| {
this.insert_file(make_context_buffer(buffer_info, text), cx);
this.update(cx, |this, _cx| {
this.insert_file(make_context_buffer(buffer_info, text));
})?;
anyhow::Ok(())
@@ -139,20 +139,19 @@ impl ContextStore {
let text = text_task.await;
this.update(cx, |this, cx| {
this.insert_file(make_context_buffer(buffer_info, text), cx)
this.update(cx, |this, _cx| {
this.insert_file(make_context_buffer(buffer_info, text))
})?;
anyhow::Ok(())
})
}
fn insert_file(&mut self, context_buffer: ContextBuffer, cx: &mut Context<Self>) {
fn insert_file(&mut self, context_buffer: ContextBuffer) {
let id = self.next_context_id.post_inc();
self.files.insert(context_buffer.id, id);
self.context
.push(AssistantContext::File(FileContext { id, context_buffer }));
cx.notify();
}
pub fn add_directory(
@@ -172,7 +171,7 @@ impl ContextStore {
let already_included = match self.includes_directory(&project_path.path) {
Some(FileInclusion::Direct(context_id)) => {
if remove_if_exists {
self.remove_context(context_id, cx);
self.remove_context(context_id);
}
true
}
@@ -239,20 +238,15 @@ impl ContextStore {
));
}
this.update(cx, |this, cx| {
this.insert_directory(project_path, context_buffers, cx);
this.update(cx, |this, _| {
this.insert_directory(project_path, context_buffers);
})?;
anyhow::Ok(())
})
}
fn insert_directory(
&mut self,
project_path: ProjectPath,
context_buffers: Vec<ContextBuffer>,
cx: &mut Context<Self>,
) {
fn insert_directory(&mut self, project_path: ProjectPath, context_buffers: Vec<ContextBuffer>) {
let id = self.next_context_id.post_inc();
self.directories.insert(project_path.path.to_path_buf(), id);
@@ -262,7 +256,6 @@ impl ContextStore {
project_path,
context_buffers,
}));
cx.notify();
}
pub fn add_symbol(
@@ -293,7 +286,7 @@ impl ContextStore {
if let Some(id) = matching_symbol_id {
if remove_if_exists {
self.remove_context(id, cx);
self.remove_context(id);
}
return Task::ready(Ok(false));
}
@@ -308,24 +301,21 @@ impl ContextStore {
cx.spawn(async move |this, cx| {
let content = collect_content_task.await;
this.update(cx, |this, cx| {
this.insert_symbol(
make_context_symbol(
buffer_info,
project_path,
symbol_name,
symbol_range,
symbol_enclosing_range,
content,
),
cx,
)
this.update(cx, |this, _cx| {
this.insert_symbol(make_context_symbol(
buffer_info,
project_path,
symbol_name,
symbol_range,
symbol_enclosing_range,
content,
))
})?;
anyhow::Ok(true)
})
}
fn insert_symbol(&mut self, context_symbol: ContextSymbol, cx: &mut Context<Self>) {
fn insert_symbol(&mut self, context_symbol: ContextSymbol) {
let id = self.next_context_id.post_inc();
self.symbols.insert(context_symbol.id.clone(), id);
self.symbols_by_path
@@ -338,7 +328,6 @@ impl ContextStore {
id,
context_symbol,
}));
cx.notify();
}
pub fn add_thread(
@@ -349,7 +338,7 @@ impl ContextStore {
) {
if let Some(context_id) = self.includes_thread(&thread.read(cx).id()) {
if remove_if_exists {
self.remove_context(context_id, cx);
self.remove_context(context_id);
}
} else {
self.insert_thread(thread, cx);
@@ -364,14 +353,14 @@ impl ContextStore {
})
}
fn insert_thread(&mut self, thread: Entity<Thread>, cx: &mut Context<Self>) {
fn insert_thread(&mut self, thread: Entity<Thread>, cx: &mut App) {
if let Some(summary_task) =
thread.update(cx, |thread, cx| thread.generate_detailed_summary(cx))
{
let thread = thread.clone();
let thread_store = self.thread_store.clone();
self.thread_summary_tasks.push(cx.spawn(async move |_, cx| {
self.thread_summary_tasks.push(cx.spawn(async move |cx| {
summary_task.await;
if let Some(thread_store) = thread_store {
@@ -393,26 +382,15 @@ impl ContextStore {
self.threads.insert(thread.read(cx).id().clone(), id);
self.context
.push(AssistantContext::Thread(ThreadContext { id, thread, text }));
cx.notify();
}
pub fn add_fetched_url(
&mut self,
url: String,
text: impl Into<SharedString>,
cx: &mut Context<ContextStore>,
) {
pub fn add_fetched_url(&mut self, url: String, text: impl Into<SharedString>) {
if self.includes_url(&url).is_none() {
self.insert_fetched_url(url, text, cx);
self.insert_fetched_url(url, text);
}
}
fn insert_fetched_url(
&mut self,
url: String,
text: impl Into<SharedString>,
cx: &mut Context<ContextStore>,
) {
fn insert_fetched_url(&mut self, url: String, text: impl Into<SharedString>) {
let id = self.next_context_id.post_inc();
self.fetched_urls.insert(url.clone(), id);
@@ -422,7 +400,6 @@ impl ContextStore {
url: url.into(),
text: text.into(),
}));
cx.notify();
}
pub fn accept_suggested_context(
@@ -449,7 +426,7 @@ impl ContextStore {
Task::ready(Ok(()))
}
pub fn remove_context(&mut self, id: ContextId, cx: &mut Context<Self>) {
pub fn remove_context(&mut self, id: ContextId) {
let Some(ix) = self.context.iter().position(|context| context.id() == id) else {
return;
};
@@ -481,8 +458,6 @@ impl ContextStore {
self.threads.retain(|_, context_id| *context_id != id);
}
}
cx.notify();
}
/// Returns whether the buffer is already included directly in the context, or if it will be

View File

@@ -59,7 +59,6 @@ impl ContextStrip {
let focus_handle = cx.focus_handle();
let subscriptions = vec![
cx.observe(&context_store, |_, _, cx| cx.notify()),
cx.subscribe_in(&context_picker, window, Self::handle_context_picker_event),
cx.on_focus(&focus_handle, window, Self::handle_focus),
cx.on_blur(&focus_handle, window, Self::handle_blur),
@@ -291,9 +290,9 @@ impl ContextStrip {
if let Some(index) = self.focused_index {
let mut is_empty = false;
self.context_store.update(cx, |this, cx| {
self.context_store.update(cx, |this, _cx| {
if let Some(item) = this.context().get(index) {
this.remove_context(item.id(), cx);
this.remove_context(item.id());
}
is_empty = this.context().is_empty();
@@ -476,8 +475,8 @@ impl Render for ContextStrip {
Some({
let context_store = self.context_store.clone();
Rc::new(cx.listener(move |_this, _event, _window, cx| {
context_store.update(cx, |this, cx| {
this.remove_context(id, cx);
context_store.update(cx, |this, _cx| {
this.remove_context(id);
});
cx.notify();
}))

View File

@@ -31,14 +31,14 @@ use project::LspAction;
use project::{CodeAction, ProjectTransaction};
use prompt_store::PromptBuilder;
use settings::{Settings, SettingsStore};
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
use text::{OffsetRangeExt, ToPoint as _};
use ui::prelude::*;
use util::RangeExt;
use util::ResultExt;
use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId};
use zed_actions::agent::OpenConfiguration;
use workspace::{ItemHandle, Toast, Workspace, notifications::NotificationId};
use workspace::{ShowConfiguration, dock::Panel};
use crate::AssistantPanel;
use crate::buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent};
@@ -295,7 +295,7 @@ impl InlineAssistant {
if let Some(answer) = answer {
if answer == 0 {
cx.update(|window, cx| {
window.dispatch_action(Box::new(OpenConfiguration), cx)
window.dispatch_action(Box::new(ShowConfiguration), cx)
})
.ok();
}
@@ -402,7 +402,7 @@ impl InlineAssistant {
codegen_ranges.push(anchor_range);
if let Some(model) = LanguageModelRegistry::read_global(cx).inline_assistant_model() {
self.telemetry.report_assistant_event(AssistantEventData {
self.telemetry.report_assistant_event(AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
phase: AssistantPhase::Invoked,
@@ -621,14 +621,14 @@ impl InlineAssistant {
BlockProperties {
style: BlockStyle::Sticky,
placement: BlockPlacement::Above(range.start),
height: Some(prompt_editor_height),
height: prompt_editor_height,
render: build_assist_editor_renderer(prompt_editor),
priority: 0,
},
BlockProperties {
style: BlockStyle::Sticky,
placement: BlockPlacement::Below(range.end),
height: None,
height: 0,
render: Arc::new(|cx| {
v_flex()
.h_full()
@@ -987,7 +987,7 @@ impl InlineAssistant {
.map(|language| language.name())
});
report_assistant_event(
AssistantEventData {
AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
message_id,
@@ -1392,7 +1392,7 @@ impl InlineAssistant {
deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1);
new_blocks.push(BlockProperties {
placement: BlockPlacement::Above(new_row),
height: Some(height),
height,
style: BlockStyle::Flex,
render: Arc::new(move |cx| {
div()

View File

@@ -8,7 +8,7 @@ use file_icons::FileIcons;
use fs::Fs;
use gpui::{
Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription, TextStyle,
WeakEntity, linear_color_stop, linear_gradient, point, pulsating_between,
WeakEntity, linear_color_stop, linear_gradient, point,
};
use language::Buffer;
use language_model::{ConfiguredModel, LanguageModelRegistry};
@@ -18,8 +18,12 @@ use project::Project;
use settings::Settings;
use std::time::Duration;
use theme::ThemeSettings;
use ui::{Disclosure, KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
use ui::{
ButtonLike, Disclosure, KeyBinding, PlatformStyle, PopoverMenu, PopoverMenuHandle, Tooltip,
prelude::*,
};
use util::ResultExt as _;
use vim_mode_setting::VimModeSetting;
use workspace::Workspace;
use crate::assistant_model_selector::AssistantModelSelector;
@@ -222,8 +226,7 @@ impl MessageEditor {
let thread = self.thread.clone();
let context_store = self.context_store.clone();
let git_store = self.project.read(cx).git_store().clone();
let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
let checkpoint = self.project.read(cx).git_store().read(cx).checkpoint(cx);
cx.spawn(async move |this, cx| {
let checkpoint = checkpoint.await.ok();
@@ -237,14 +240,14 @@ impl MessageEditor {
cx.emit(ThreadEvent::ShowError(load_error));
}
})
.log_err();
.ok();
thread
.update(cx, |thread, cx| {
let context = context_store.read(cx).context().clone();
thread.insert_user_message(user_message, context, checkpoint, cx);
})
.log_err();
.ok();
if let Some(wait_for_summaries) = context_store
.update(cx, |context_store, cx| context_store.wait_for_summaries(cx))
@@ -254,7 +257,7 @@ impl MessageEditor {
this.waiting_for_summaries_to_send = true;
cx.notify();
})
.log_err();
.ok();
wait_for_summaries.await;
@@ -262,7 +265,7 @@ impl MessageEditor {
this.waiting_for_summaries_to_send = false;
cx.notify();
})
.log_err();
.ok();
}
// Send to model after summaries are done
@@ -270,7 +273,7 @@ impl MessageEditor {
.update(cx, |thread, cx| {
thread.send_to_model(model, request_kind, cx);
})
.log_err();
.ok();
})
.detach();
}
@@ -351,7 +354,24 @@ impl Render for MessageEditor {
let total_token_usage = thread.total_token_usage(cx);
let is_model_selected = self.is_model_selected(cx);
let is_editor_empty = self.is_editor_empty(cx);
let is_edit_changes_expanded = self.edits_expanded;
let needs_confirmation =
thread.has_pending_tool_uses() && thread.tools_needing_confirmation().next().is_some();
let submit_label_color = if is_editor_empty {
Color::Muted
} else {
Color::Default
};
let vim_mode_enabled = VimModeSetting::get_global(cx).0;
let platform = PlatformStyle::platform();
let linux = platform == PlatformStyle::Linux;
let windows = platform == PlatformStyle::Windows;
let button_width = if linux || windows || vim_mode_enabled {
px(82.)
} else {
px(64.)
};
let action_log = self.thread.read(cx).action_log();
let changed_buffers = action_log.read(cx).changed_buffers(cx);
@@ -399,6 +419,70 @@ impl Render for MessageEditor {
),
)
})
.when(is_generating, |parent| {
let focus_handle = self.editor.focus_handle(cx).clone();
parent.child(
h_flex().py_3().w_full().justify_center().child(
h_flex()
.flex_none()
.pl_2()
.pr_1()
.py_1()
.bg(editor_bg_color)
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_lg()
.shadow_md()
.gap_1()
.child(
Icon::new(IconName::ArrowCircle)
.size(IconSize::XSmall)
.color(Color::Muted)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(2)).repeat(),
|icon, delta| {
icon.transform(gpui::Transformation::rotate(
gpui::percentage(delta),
))
},
),
)
.child({
Label::new(if needs_confirmation {
"Waiting for confirmation…"
} else {
"Generating…"
})
.size(LabelSize::XSmall)
.color(Color::Muted)
})
.child(ui::Divider::vertical())
.child(
Button::new("cancel-generation", "Cancel")
.label_size(LabelSize::XSmall)
.key_binding(
KeyBinding::for_action_in(
&editor::actions::Cancel,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(move |_event, window, cx| {
focus_handle.dispatch_action(
&editor::actions::Cancel,
window,
cx,
);
}),
),
),
)
})
.when(changed_buffers_count > 0, |parent| {
parent.child(
v_flex()
@@ -417,12 +501,12 @@ impl Render for MessageEditor {
.child(
h_flex()
.id("edits-container")
.cursor_pointer()
.p_1p5()
.justify_between()
.when(is_edit_changes_expanded, |this| {
.when(self.edits_expanded, |this| {
this.border_b_1().border_color(border_color)
})
.cursor_pointer()
.on_click(cx.listener(|this, _, window, cx| {
this.handle_review_click(window, cx)
}))
@@ -432,7 +516,7 @@ impl Render for MessageEditor {
.child(
Disclosure::new(
"edits-disclosure",
is_edit_changes_expanded,
self.edits_expanded,
)
.on_click(
cx.listener(|this, _ev, _window, cx| {
@@ -482,9 +566,9 @@ impl Render for MessageEditor {
})),
),
)
.when(is_edit_changes_expanded, |parent| {
.when(self.edits_expanded, |parent| {
parent.child(
v_flex().children(
v_flex().bg(cx.theme().colors().editor_background).children(
changed_buffers.into_iter().enumerate().flat_map(
|(index, (buffer, _diff))| {
let file = buffer.read(cx).file()?;
@@ -498,7 +582,7 @@ impl Render for MessageEditor {
} else {
Some(
Label::new(format!(
"/{}{}",
"{}{}",
parent_str,
std::path::MAIN_SEPARATOR_STR
))
@@ -526,105 +610,70 @@ impl Render for MessageEditor {
.size(IconSize::Small)
});
let hover_color = cx.theme()
.colors()
.element_background
.blend(cx.theme().colors().editor_foreground.opacity(0.025));
let overlay_gradient = linear_gradient(
90.,
linear_color_stop(
editor_bg_color,
1.,
),
linear_color_stop(
editor_bg_color
.opacity(0.2),
0.,
),
);
let overlay_gradient_hover = linear_gradient(
90.,
linear_color_stop(
hover_color,
1.,
),
linear_color_stop(
hover_color
.opacity(0.2),
0.,
),
);
let element = h_flex()
.group("edited-code")
.id(("file-container", index))
.cursor_pointer()
let element = div()
.relative()
.py_1()
.pl_2()
.pr_1()
.gap_2()
.justify_between()
.bg(cx.theme().colors().editor_background)
.hover(|style| style.bg(hover_color))
.px_2()
.when(index + 1 < changed_buffers_count, |parent| {
parent.border_color(border_color).border_b_1()
})
.child(
h_flex()
.id("file-name")
.pr_8()
.gap_1p5()
.max_w_full()
.overflow_x_scroll()
.child(file_icon)
.gap_2()
.justify_between()
.child(
h_flex()
.gap_0p5()
.children(name_label)
.children(parent_label)
) // TODO: show lines changed
.child(
Label::new("+")
.color(Color::Created),
.id(("file-container", index))
.pr_8()
.gap_1p5()
.max_w_full()
.overflow_x_scroll()
.cursor_pointer()
.on_click({
let buffer = buffer.clone();
cx.listener(move |this, _, window, cx| {
this.handle_file_click(buffer.clone(), window, cx);
})
})
.tooltip(
Tooltip::text(format!("Review {}", path.display()))
)
.child(file_icon)
.child(
h_flex()
.children(parent_label)
.children(name_label),
) // TODO: show lines changed
.child(
Label::new("+")
.color(Color::Created),
)
.child(
Label::new("-")
.color(Color::Deleted),
),
)
.child(
Label::new("-")
.color(Color::Deleted),
div()
.h_full()
.absolute()
.w_8()
.bottom_0()
.right_0()
.bg(linear_gradient(
90.,
linear_color_stop(
editor_bg_color,
1.,
),
linear_color_stop(
editor_bg_color
.opacity(0.2),
0.,
),
)),
),
)
.child(
div().visible_on_hover("edited-code").child(
Button::new("review", "Review")
.label_size(LabelSize::Small)
.on_click({
let buffer = buffer.clone();
cx.listener(move |this, _, window, cx| {
this.handle_file_click(buffer.clone(), window, cx);
})
})
)
)
.child(
div()
.id("gradient-overlay")
.absolute()
.h_5_6()
.w_12()
.bottom_0()
.right(px(52.))
.bg(overlay_gradient)
.group_hover("edited-code", |style| style.bg(overlay_gradient_hover))
,
)
.on_click({
let buffer = buffer.clone();
cx.listener(move |this, _, window, cx| {
this.handle_file_click(buffer.clone(), window, cx);
})
});
);
Some(element)
},
@@ -684,6 +733,7 @@ impl Render for MessageEditor {
..Default::default()
},
).into_any()
})
.child(
PopoverMenu::new("inline-context-picker")
@@ -691,6 +741,7 @@ impl Render for MessageEditor {
inline_context_picker.update(cx, |this, cx| {
this.init(window, cx);
});
Some(inline_context_picker.clone())
})
.attach(gpui::Corner::TopLeft)
@@ -707,72 +758,60 @@ impl Render for MessageEditor {
.justify_between()
.child(h_flex().gap_2().child(self.profile_selector.clone()))
.child(
h_flex().gap_1().child(self.model_selector.clone())
.map(|parent| {
if is_generating {
parent.child(
IconButton::new("stop-generation", IconName::StopFilled)
.icon_color(Color::Error)
.style(ButtonStyle::Tinted(ui::TintColor::Error))
.tooltip(move |window, cx| {
Tooltip::for_action(
"Stop Generation",
&editor::actions::Cancel,
window,
cx,
)
})
.on_click(move |_event, window, cx| {
focus_handle.dispatch_action(
&editor::actions::Cancel,
window,
cx,
);
})
.with_animation(
"pulsating-label",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(pulsating_between(0.4, 1.0)),
|icon_button, delta| icon_button.alpha(delta),
),
)
} else {
parent.child(
IconButton::new("send-message", IconName::Send)
.icon_color(Color::Accent)
.style(ButtonStyle::Filled)
.disabled(
is_editor_empty
|| !is_model_selected
|| self.waiting_for_summaries_to_send
h_flex().gap_1().child(self.model_selector.clone()).child(
ButtonLike::new("submit-message")
.width(button_width.into())
.style(ButtonStyle::Filled)
.disabled(
is_editor_empty
|| !is_model_selected
|| is_generating
|| self.waiting_for_summaries_to_send
)
.child(
h_flex()
.w_full()
.justify_between()
.child(
Label::new("Submit")
.size(LabelSize::Small)
.color(submit_label_color),
)
.children(
KeyBinding::for_action_in(
&Chat,
&focus_handle,
window,
cx,
)
.on_click(move |_event, window, cx| {
focus_handle.dispatch_action(&Chat, window, cx);
})
.when(!is_editor_empty && is_model_selected, |button| {
button.tooltip(move |window, cx| {
Tooltip::for_action(
"Send",
&Chat,
window,
cx,
)
})
})
.when(is_editor_empty, |button| {
button.tooltip(Tooltip::text(
"Type a message to submit",
))
})
.when(!is_model_selected, |button| {
button.tooltip(Tooltip::text(
"Select a model to continue",
))
})
)
}
})
.map(|binding| {
binding
.when(vim_mode_enabled, |kb| {
kb.size(rems_from_px(12.))
})
.into_any_element()
}),
),
)
.on_click(move |_event, window, cx| {
focus_handle.dispatch_action(&Chat, window, cx);
})
.when(is_editor_empty, |button| {
button.tooltip(Tooltip::text(
"Type a message to submit",
))
})
.when(is_generating, |button| {
button.tooltip(Tooltip::text(
"Cancel to submit a new message",
))
})
.when(!is_model_selected, |button| {
button.tooltip(Tooltip::text(
"Select a model to continue",
))
}),
),
),
),
)

View File

@@ -6,7 +6,7 @@ use language_model::{
ConfiguredModel, LanguageModelRegistry, LanguageModelRequest, report_assistant_event,
};
use std::{sync::Arc, time::Instant};
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal::Terminal;
pub struct TerminalCodegen {
@@ -79,7 +79,7 @@ impl TerminalCodegen {
let error_message = result.as_ref().err().map(|error| error.to_string());
report_assistant_event(
AssistantEventData {
AssistantEvent {
conversation_id: None,
kind: AssistantKind::InlineTerminal,
message_id,

View File

@@ -18,7 +18,7 @@ use language_model::{
};
use prompt_store::PromptBuilder;
use std::sync::Arc;
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal_view::TerminalView;
use ui::prelude::*;
use util::ResultExt;
@@ -292,7 +292,7 @@ impl TerminalInlineAssistant {
let codegen = assist.codegen.read(cx);
let executor = cx.background_executor().clone();
report_assistant_event(
AssistantEventData {
AssistantEvent {
conversation_id: None,
kind: AssistantKind::InlineTerminal,
message_id: codegen.message_id.clone(),

View File

@@ -391,20 +391,17 @@ impl Thread {
self.summary.clone().unwrap_or(Self::DEFAULT_SUMMARY)
}
pub fn set_summary(&mut self, new_summary: impl Into<SharedString>, cx: &mut Context<Self>) {
let Some(current_summary) = &self.summary else {
// Don't allow setting summary until generated
return;
pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut Context<Self>) {
let summary = summary.into();
let old_summary = self.summary_or_default();
self.summary = if summary.is_empty() {
Some(Self::DEFAULT_SUMMARY)
} else {
Some(summary)
};
let mut new_summary = new_summary.into();
if new_summary.is_empty() {
new_summary = Self::DEFAULT_SUMMARY;
}
if current_summary != &new_summary {
self.summary = Some(new_summary);
if Some(old_summary) != self.summary {
cx.emit(ThreadEvent::SummaryChanged);
}
}
@@ -471,11 +468,11 @@ impl Thread {
cx.emit(ThreadEvent::CheckpointChanged);
cx.notify();
let git_store = self.project().read(cx).git_store().clone();
let restore = git_store.update(cx, |git_store, cx| {
git_store.restore_checkpoint(checkpoint.git_checkpoint.clone(), cx)
});
let project = self.project.read(cx);
let restore = project
.git_store()
.read(cx)
.restore_checkpoint(checkpoint.git_checkpoint.clone(), cx);
cx.spawn(async move |this, cx| {
let result = restore.await;
this.update(cx, |this, cx| {
@@ -506,11 +503,11 @@ impl Thread {
};
let git_store = self.project.read(cx).git_store().clone();
let final_checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
let final_checkpoint = git_store.read(cx).checkpoint(cx);
cx.spawn(async move |this, cx| match final_checkpoint.await {
Ok(final_checkpoint) => {
let equal = git_store
.update(cx, |store, cx| {
.read_with(cx, |store, cx| {
store.compare_checkpoints(
pending_checkpoint.git_checkpoint.clone(),
final_checkpoint.clone(),
@@ -522,7 +519,7 @@ impl Thread {
if equal {
git_store
.update(cx, |store, cx| {
.read_with(cx, |store, cx| {
store.delete_checkpoint(pending_checkpoint.git_checkpoint, cx)
})?
.detach();
@@ -533,7 +530,7 @@ impl Thread {
}
git_store
.update(cx, |store, cx| {
.read_with(cx, |store, cx| {
store.delete_checkpoint(final_checkpoint, cx)
})?
.detach();
@@ -1414,7 +1411,7 @@ impl Thread {
for tool_use in pending_tool_uses.iter() {
if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
if tool.needs_confirmation(&tool_use.input, cx)
if tool.needs_confirmation()
&& !AssistantSettings::get_global(cx).always_allow_tool_actions
{
self.tool_use.confirm_tool_use(
@@ -1487,7 +1484,6 @@ impl Thread {
tool_use_id.clone(),
tool_name,
output,
cx,
);
cx.emit(ThreadEvent::ToolFinished {
@@ -1651,10 +1647,10 @@ impl Thread {
.ok()
.flatten()
.map(|repo| {
repo.update(cx, |repo, _| {
repo.read_with(cx, |repo, _| {
let current_branch =
repo.branch.as_ref().map(|branch| branch.name.to_string());
repo.send_job(None, |state, _| async move {
repo.send_job(|state, _| async move {
let RepositoryState::Local { backend, .. } = state else {
return GitState {
remote_url: None,
@@ -1832,7 +1828,7 @@ impl Thread {
));
self.tool_use
.insert_tool_output(tool_use_id.clone(), tool_name, err, cx);
.insert_tool_output(tool_use_id.clone(), tool_name, err);
cx.emit(ThreadEvent::ToolFinished {
tool_use_id,

View File

@@ -17,7 +17,6 @@ use crate::{AssistantPanel, RemoveSelectedThread};
pub struct ThreadHistory {
assistant_panel: WeakEntity<AssistantPanel>,
history_store: Entity<HistoryStore>,
scroll_handle: UniformListScrollHandle,
selected_index: usize,
search_query: SharedString,
@@ -41,26 +40,33 @@ impl ThreadHistory {
editor
});
let search_editor_subscription =
cx.subscribe(&search_editor, |this, search_editor, event, cx| {
let search_editor_subscription = cx.subscribe_in(
&search_editor,
window,
|this, search_editor, event, window, cx| {
if let EditorEvent::BufferEdited = event {
let query = search_editor.read(cx).text(cx);
this.search_query = query.into();
this.update_search(cx);
this.update_search(window, cx);
}
});
},
);
let entries: Arc<Vec<_>> = history_store
.update(cx, |store, cx| store.entries(cx))
.into();
let history_store_subscription = cx.observe(&history_store, |this, _, cx| {
this.update_all_entries(cx);
});
let history_store_subscription =
cx.observe_in(&history_store, window, |this, history_store, window, cx| {
this.all_entries = history_store
.update(cx, |store, cx| store.entries(cx))
.into();
this.matches.clear();
this.update_search(window, cx);
});
Self {
assistant_panel,
history_store,
scroll_handle: UniformListScrollHandle::default(),
selected_index: 0,
search_query: SharedString::new_static(""),
@@ -72,16 +78,7 @@ impl ThreadHistory {
}
}
fn update_all_entries(&mut self, cx: &mut Context<Self>) {
self.all_entries = self
.history_store
.update(cx, |store, cx| store.entries(cx))
.into();
self.matches.clear();
self.update_search(cx);
}
fn update_search(&mut self, cx: &mut Context<Self>) {
fn update_search(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self._search_task.take();
if self.has_search_query() {
@@ -225,15 +222,17 @@ impl ThreadHistory {
let task_result = match entry {
HistoryEntry::Thread(thread) => self
.assistant_panel
.update(cx, move |this, cx| this.open_thread(&thread.id, window, cx)),
HistoryEntry::Context(context) => {
self.assistant_panel.update(cx, move |this, cx| {
.update(cx, move |this, cx| this.open_thread(&thread.id, window, cx))
.ok(),
HistoryEntry::Context(context) => self
.assistant_panel
.update(cx, move |this, cx| {
this.open_saved_prompt_editor(context.path.clone(), window, cx)
})
}
.ok(),
};
if let Some(task) = task_result.log_err() {
if let Some(task) = task_result {
task.detach_and_log_err(cx);
};
@@ -244,22 +243,28 @@ impl ThreadHistory {
fn remove_selected_thread(
&mut self,
_: &RemoveSelectedThread,
_window: &mut Window,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(entry) = self.get_match(self.selected_index) {
let task_result = match entry {
HistoryEntry::Thread(thread) => self
.assistant_panel
.update(cx, |this, cx| this.delete_thread(&thread.id, cx)),
HistoryEntry::Context(context) => self
.assistant_panel
.update(cx, |this, cx| this.delete_context(context.path.clone(), cx)),
};
match entry {
HistoryEntry::Thread(thread) => {
self.assistant_panel
.update(cx, |this, cx| {
this.delete_thread(&thread.id, cx);
})
.ok();
}
HistoryEntry::Context(context) => {
self.assistant_panel
.update(cx, |this, cx| {
this.delete_context(context.path.clone(), cx);
})
.ok();
}
}
if let Some(task) = task_result.log_err() {
task.detach_and_log_err(cx);
};
self.update_search(window, cx);
cx.notify();
}
@@ -451,21 +456,14 @@ impl RenderOnce for PastThread {
IconButton::new("delete", IconName::TrashAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.tooltip(move |window, cx| {
Tooltip::for_action(
"Delete Thread",
&RemoveSelectedThread,
window,
cx,
)
})
.tooltip(Tooltip::text("Delete Thread"))
.on_click({
let assistant_panel = self.assistant_panel.clone();
let id = self.thread.id.clone();
move |_event, _window, cx| {
assistant_panel
.update(cx, |this, cx| {
this.delete_thread(&id, cx).detach_and_log_err(cx);
this.delete_thread(&id, cx);
})
.ok();
}
@@ -558,22 +556,14 @@ impl RenderOnce for PastContext {
IconButton::new("delete", IconName::TrashAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.tooltip(move |window, cx| {
Tooltip::for_action(
"Delete Prompt Editor",
&RemoveSelectedThread,
window,
cx,
)
})
.tooltip(Tooltip::text("Delete Prompt Editor"))
.on_click({
let assistant_panel = self.assistant_panel.clone();
let path = self.context.path.clone();
move |_event, _window, cx| {
assistant_panel
.update(cx, |this, cx| {
this.delete_context(path.clone(), cx)
.detach_and_log_err(cx);
this.delete_context(path.clone(), cx);
})
.ok();
}

View File

@@ -174,9 +174,8 @@ impl ThreadStore {
let database = database_future.await.map_err(|err| anyhow!(err))?;
database.delete_thread(id.clone()).await?;
this.update(cx, |this, cx| {
this.threads.retain(|thread| thread.id != id);
cx.notify();
this.update(cx, |this, _cx| {
this.threads.retain(|thread| thread.id != id)
})
})
}

View File

@@ -7,11 +7,10 @@ use futures::FutureExt as _;
use futures::future::Shared;
use gpui::{App, SharedString, Task};
use language_model::{
LanguageModelRegistry, LanguageModelRequestMessage, LanguageModelToolResult,
LanguageModelToolUse, LanguageModelToolUseId, MessageContent, Role,
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
LanguageModelToolUseId, MessageContent, Role,
};
use ui::IconName;
use util::truncate_lines_to_byte_limit;
use crate::thread::MessageId;
use crate::thread_store::SerializedMessage;
@@ -36,18 +35,6 @@ pub enum ToolUseStatus {
Error(SharedString),
}
impl ToolUseStatus {
pub fn text(&self) -> SharedString {
match self {
ToolUseStatus::NeedsConfirmation => "".into(),
ToolUseStatus::Pending => "".into(),
ToolUseStatus::Running => "".into(),
ToolUseStatus::Finished(out) => out.clone(),
ToolUseStatus::Error(out) => out.clone(),
}
}
}
pub struct ToolUseState {
tools: Arc<ToolWorkingSet>,
tool_uses_by_assistant_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
@@ -201,7 +188,7 @@ impl ToolUseState {
let (icon, needs_confirmation) = if let Some(tool) = self.tools.tool(&tool_use.name, cx)
{
(tool.icon(), tool.needs_confirmation(&tool_use.input, cx))
(tool.icon(), tool.needs_confirmation())
} else {
(IconName::Cog, false)
};
@@ -332,32 +319,9 @@ impl ToolUseState {
tool_use_id: LanguageModelToolUseId,
tool_name: Arc<str>,
output: Result<String>,
cx: &App,
) -> Option<PendingToolUse> {
match output {
Ok(tool_result) => {
let model_registry = LanguageModelRegistry::read_global(cx);
const BYTES_PER_TOKEN_ESTIMATE: usize = 3;
// Protect from clearly large output
let tool_output_limit = model_registry
.default_model()
.map(|model| model.model.max_token_count() * BYTES_PER_TOKEN_ESTIMATE)
.unwrap_or(usize::MAX);
let tool_result = if tool_result.len() <= tool_output_limit {
tool_result
} else {
let truncated = truncate_lines_to_byte_limit(&tool_result, tool_output_limit);
format!(
"Tool result too long. The first {} bytes:\n\n{}",
truncated.len(),
truncated
)
};
self.tool_results.insert(
tool_use_id.clone(),
LanguageModelToolResult {

View File

@@ -37,11 +37,11 @@ use ui::{ContextMenu, PopoverMenu, Tooltip, prelude::*};
use util::{ResultExt, maybe};
use workspace::DraggedTab;
use workspace::{
DraggedSelection, Pane, ToggleZoom, Workspace,
DraggedSelection, Pane, ShowConfiguration, ToggleZoom, Workspace,
dock::{DockPosition, Panel, PanelEvent},
pane,
};
use zed_actions::assistant::{InlineAssist, OpenPromptLibrary, ShowConfiguration, ToggleFocus};
use zed_actions::assistant::{InlineAssist, OpenPromptLibrary, ToggleFocus};
pub fn init(cx: &mut App) {
workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
@@ -54,15 +54,7 @@ pub fn init(cx: &mut App) {
.register_action(ContextEditor::insert_dragged_files)
.register_action(AssistantPanel::show_configuration)
.register_action(AssistantPanel::create_new_context)
.register_action(AssistantPanel::restart_context_servers)
.register_action(|workspace, _: &OpenPromptLibrary, window, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| {
panel.deploy_prompt_library(&OpenPromptLibrary, window, cx)
});
}
});
.register_action(AssistantPanel::restart_context_servers);
},
)
.detach();

View File

@@ -57,7 +57,7 @@ use std::{
time::{Duration, Instant},
};
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal_view::terminal_panel::TerminalPanel;
use text::{OffsetRangeExt, ToPoint as _};
use theme::ThemeSettings;
@@ -315,7 +315,7 @@ impl InlineAssistant {
if let Some(ConfiguredModel { model, .. }) =
LanguageModelRegistry::read_global(cx).default_model()
{
self.telemetry.report_assistant_event(AssistantEventData {
self.telemetry.report_assistant_event(AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
phase: AssistantPhase::Invoked,
@@ -527,14 +527,14 @@ impl InlineAssistant {
BlockProperties {
style: BlockStyle::Sticky,
placement: BlockPlacement::Above(range.start),
height: Some(prompt_editor_height),
height: prompt_editor_height,
render: build_assist_editor_renderer(prompt_editor),
priority: 0,
},
BlockProperties {
style: BlockStyle::Sticky,
placement: BlockPlacement::Below(range.end),
height: None,
height: 0,
render: Arc::new(|cx| {
v_flex()
.h_full()
@@ -892,7 +892,7 @@ impl InlineAssistant {
.map(|language| language.name())
});
report_assistant_event(
AssistantEventData {
AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
message_id,
@@ -1301,7 +1301,7 @@ impl InlineAssistant {
deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1);
new_blocks.push(BlockProperties {
placement: BlockPlacement::Above(new_row),
height: Some(height),
height,
style: BlockStyle::Flex,
render: Arc::new(move |cx| {
div()
@@ -3148,7 +3148,7 @@ impl CodegenAlternative {
let error_message = result.as_ref().err().map(|error| error.to_string());
report_assistant_event(
AssistantEventData {
AssistantEvent {
conversation_id: None,
message_id,
kind: AssistantKind::Inline,

View File

@@ -27,7 +27,7 @@ use std::{
sync::Arc,
time::{Duration, Instant},
};
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal::Terminal;
use terminal_view::TerminalView;
use theme::ThemeSettings;
@@ -324,7 +324,7 @@ impl TerminalInlineAssistant {
let codegen = assist.codegen.read(cx);
let executor = cx.background_executor().clone();
report_assistant_event(
AssistantEventData {
AssistantEvent {
conversation_id: None,
kind: AssistantKind::InlineTerminal,
message_id: codegen.message_id.clone(),
@@ -1183,7 +1183,7 @@ impl Codegen {
let error_message = result.as_ref().err().map(|error| error.to_string());
report_assistant_event(
AssistantEventData {
AssistantEvent {
conversation_id: None,
kind: AssistantKind::InlineTerminal,
message_id,

View File

@@ -22,7 +22,6 @@ clock.workspace = true
collections.workspace = true
context_server.workspace = true
editor.workspace = true
feature_flags.workspace = true
fs.workspace = true
futures.workspace = true
fuzzy.workspace = true
@@ -54,9 +53,8 @@ theme.workspace = true
ui.workspace = true
util.workspace = true
uuid.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true
workspace-hack.workspace = true
[dev-dependencies]
language_model = { workspace = true, features = ["test-support"] }

View File

@@ -40,7 +40,7 @@ use std::{
sync::Arc,
time::{Duration, Instant},
};
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use text::{BufferSnapshot, ToPoint};
use ui::IconName;
use util::{ResultExt, TryFutureExt, post_inc};
@@ -2498,7 +2498,7 @@ impl AssistantContext {
.language()
.map(|language| language.name());
report_assistant_event(
AssistantEventData {
AssistantEvent {
conversation_id: Some(this.id.0.clone()),
kind: AssistantKind::Panel,
phase: AssistantPhase::Response,

View File

@@ -18,7 +18,6 @@ use editor::{
scroll::Autoscroll,
};
use editor::{FoldPlaceholder, display_map::CreaseId};
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt as _};
use fs::Fs;
use futures::FutureExt;
use gpui::{
@@ -57,7 +56,8 @@ use ui::{
use util::{ResultExt, maybe};
use workspace::searchable::{Direction, SearchableItemHandle};
use workspace::{
Save, Toast, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
Save, ShowConfiguration, Toast, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
Workspace,
item::{self, FollowableItem, Item, ItemHandle},
notifications::NotificationId,
pane::{self, SaveIntent},
@@ -1562,7 +1562,7 @@ impl ContextEditor {
})
};
let create_block_properties = |message: &Message| BlockProperties {
height: Some(2),
height: 2,
style: BlockStyle::Sticky,
placement: BlockPlacement::Above(
buffer
@@ -2111,7 +2111,7 @@ impl ContextEditor {
let image = render_image.clone();
anchor.is_valid(&buffer).then(|| BlockProperties {
placement: BlockPlacement::Above(anchor),
height: Some(MAX_HEIGHT_IN_LINES),
height: MAX_HEIGHT_IN_LINES,
style: BlockStyle::Sticky,
render: Arc::new(move |cx| {
let image_size = size_for_image(
@@ -2359,19 +2359,7 @@ impl ContextEditor {
.on_click({
let focus_handle = self.focus_handle(cx).clone();
move |_event, window, cx| {
if cx.has_flag::<Assistant2FeatureFlag>() {
focus_handle.dispatch_action(
&zed_actions::agent::OpenConfiguration,
window,
cx,
);
} else {
focus_handle.dispatch_action(
&zed_actions::assistant::ShowConfiguration,
window,
cx,
);
};
focus_handle.dispatch_action(&ShowConfiguration, window, cx);
}
}),
)

View File

@@ -120,14 +120,13 @@ impl SlashCommandCompletionProvider {
) as Arc<_>
});
Some(project::Completion {
replace_range: name_range.clone(),
old_range: name_range.clone(),
documentation: Some(CompletionDocumentation::SingleLine(
command.description().into(),
)),
new_text,
label: command.label(cx),
icon_path: None,
insert_text_mode: None,
confirm,
source: CompletionSource::Custom,
})
@@ -219,7 +218,7 @@ impl SlashCommandCompletionProvider {
}
project::Completion {
replace_range: if new_argument.replace_previous_arguments {
old_range: if new_argument.replace_previous_arguments {
argument_range.clone()
} else {
last_argument_range.clone()
@@ -229,7 +228,6 @@ impl SlashCommandCompletionProvider {
new_text,
documentation: None,
confirm,
insert_text_mode: None,
source: CompletionSource::Custom,
}
})

View File

@@ -1,5 +1,5 @@
[package]
name = "agent_eval"
name = "assistant_eval"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
@@ -9,7 +9,7 @@ license = "GPL-3.0-or-later"
workspace = true
[[bin]]
name = "agent_eval"
name = "assistant_eval"
path = "src/main.rs"
[dependencies]

View File

@@ -0,0 +1,68 @@
# Tool Evals
A framework for evaluating and benchmarking the agent panel generations.
## Overview
Tool Evals provides a headless environment for running assistants evaluations on code repositories. It automates the process of:
1. Setting up test code and repositories
2. Sending prompts to language models
3. Allowing the assistant to use tools to modify code
4. Collecting metrics on performance and tool usage
5. Evaluating results against known good solutions
## How It Works
The system consists of several key components:
- **Eval**: Loads exercises from the zed-ace-framework repository, creates temporary repos, and executes evaluations
- **HeadlessAssistant**: Provides a headless environment for running the AI assistant
- **Judge**: Evaluates AI-generated solutions against reference implementations and assigns scores
- **Templates**: Defines evaluation frameworks for different tasks (Project Creation, Code Modification, Conversational Guidance)
## Setup Requirements
### Prerequisites
- Rust and Cargo
- Git
- Python (for report generation)
- Network access to clone repositories
- Appropriate API keys for language models and git services (Anthropic, GitHub, etc.)
### Environment Variables
Ensure you have the required API keys set, either from a dev run of Zed or via these environment variables:
- `ZED_ANTHROPIC_API_KEY` for Claude models
- `ZED_GITHUB_API_KEY` for GitHub API (or similar)
## Usage
### Running Evaluations
```bash
# Run all tests
cargo run -p assistant_eval -- --all
# Run only specific languages
cargo run -p assistant_eval -- --all --languages python,rust
# Limit concurrent evaluations
cargo run -p assistant_eval -- --all --concurrency 5
# Limit number of exercises per language
cargo run -p assistant_eval -- --all --max-exercises-per-language 3
```
### Evaluation Template Types
The system supports three types of evaluation templates:
1. **ProjectCreation**: Tests the model's ability to create new implementations from scratch
2. **CodeModification**: Tests the model's ability to modify existing code to meet new requirements
3. **ConversationalGuidance**: Tests the model's ability to provide guidance without writing code
### Support Repo
The [zed-industries/zed-ace-framework](https://github.com/zed-industries/zed-ace-framework) contains the analytics and reporting scripts.

View File

@@ -1,6 +1,6 @@
use crate::git_commands::{run_git, setup_temp_repo};
use crate::headless_assistant::{HeadlessAppState, HeadlessAssistant};
use crate::{get_exercise_language, get_exercise_name};
use crate::{get_exercise_language, get_exercise_name, templates_eval::Template};
use agent::RequestKind;
use anyhow::{Result, anyhow};
use collections::HashMap;
@@ -18,6 +18,8 @@ use std::{
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct EvalResult {
pub exercise_name: String,
pub template_name: String,
pub score: String,
pub diff: String,
pub assistant_response: String,
pub elapsed_time_ms: u128,
@@ -27,6 +29,7 @@ pub struct EvalResult {
pub output_tokens: usize,
pub total_tokens: usize,
pub tool_use_counts: usize,
pub judge_model_name: String, // Added field for judge model name
}
pub struct EvalOutput {
@@ -248,6 +251,29 @@ pub async fn read_instructions(exercise_path: &Path) -> Result<String> {
Ok(instructions)
}
pub async fn read_example_solution(exercise_path: &Path, language: &str) -> Result<String> {
// Map the language to the file extension
let language_extension = match language {
"python" => "py",
"go" => "go",
"rust" => "rs",
"typescript" => "ts",
"javascript" => "js",
"ruby" => "rb",
"php" => "php",
"bash" => "sh",
"multi" => "diff",
"internal" => "diff",
_ => return Err(anyhow!("Unsupported language: {}", language)),
};
let example_path = exercise_path
.join(".meta")
.join(format!("example.{}", language_extension));
println!("Reading example solution from: {}", example_path.display());
let example = smol::unblock(move || std::fs::read_to_string(&example_path)).await?;
Ok(example)
}
pub async fn save_eval_results(exercise_path: &Path, results: Vec<EvalResult>) -> Result<()> {
let eval_dir = exercise_path.join("evaluation");
fs::create_dir_all(&eval_dir)?;
@@ -285,8 +311,12 @@ pub async fn save_eval_results(exercise_path: &Path, results: Vec<EvalResult>) -
// Group the new results by test name (exercise name)
for result in results {
let exercise_name = &result.exercise_name;
let template_name = &result.template_name;
println!("Adding result: exercise={}", exercise_name);
println!(
"Adding result: exercise={}, template={}",
exercise_name, template_name
);
// Ensure the exercise entry exists
if eval_data.get(exercise_name).is_none() {
@@ -299,7 +329,7 @@ pub async fn save_eval_results(exercise_path: &Path, results: Vec<EvalResult>) -
}
// Add this result under the timestamp with template name as key
eval_data[exercise_name][&timestamp] = serde_json::to_value(&result)?;
eval_data[exercise_name][&timestamp][template_name] = serde_json::to_value(&result)?;
}
// Write back to file with pretty formatting
@@ -314,7 +344,9 @@ pub async fn save_eval_results(exercise_path: &Path, results: Vec<EvalResult>) -
pub async fn run_exercise_eval(
exercise_path: PathBuf,
template: Template,
model: Arc<dyn LanguageModel>,
judge_model: Arc<dyn LanguageModel>,
app_state: Arc<HeadlessAppState>,
base_sha: String,
_framework_path: PathBuf,
@@ -327,15 +359,68 @@ pub async fn run_exercise_eval(
"\n\nWhen writing the code for this prompt, use {} to achieve the goal.",
language
));
let example_solution = read_example_solution(&exercise_path, &language).await?;
println!("Running evaluation for exercise: {}", exercise_name);
println!(
"Running evaluation for exercise: {} with template: {}",
exercise_name, template.name
);
// Create temporary directory with exercise files
let temp_dir = setup_temp_repo(&exercise_path, &base_sha).await?;
let temp_path = temp_dir.path().to_path_buf();
if template.name == "ProjectCreation" {
for entry in fs::read_dir(&temp_path)? {
let entry = entry?;
let path = entry.path();
// Skip directories that start with dot (like .docs, .meta, .git)
if path.is_dir()
&& path
.file_name()
.and_then(|name| name.to_str())
.map(|name| name.starts_with("."))
.unwrap_or(false)
{
continue;
}
// Delete regular files
if path.is_file() {
println!(" Deleting file: {}", path.display());
fs::remove_file(path)?;
}
}
// Commit the deletion so it shows up in the diff
run_git(&temp_path, &["add", "."]).await?;
run_git(
&temp_path,
&["commit", "-m", "Remove root files for clean slate"],
)
.await?;
}
let local_commit_sha = run_git(&temp_path, &["rev-parse", "HEAD"]).await?;
// Prepare prompt based on template
let prompt = match template.name {
"ProjectCreation" => format!(
"I need to create a new implementation for this exercise. Please create all the necessary files in the best location.\n\n{}",
instructions
),
"CodeModification" => format!(
"I need help updating my code to meet these requirements. Please modify the appropriate files:\n\n{}",
instructions
),
"ConversationalGuidance" => format!(
"I'm trying to solve this coding exercise but I'm not sure where to start. Can you help me understand the requirements and guide me through the solution process without writing code for me?\n\n{}",
instructions
),
_ => instructions.clone(),
};
let start_time = SystemTime::now();
// Create a basic eval struct to work with the existing system
@@ -345,7 +430,7 @@ pub async fn run_exercise_eval(
url: format!("file://{}", temp_path.display()),
base_sha: local_commit_sha, // Use the local commit SHA instead of the framework base SHA
},
user_prompt: instructions.clone(),
user_prompt: prompt,
};
// Run the evaluation
@@ -356,6 +441,79 @@ pub async fn run_exercise_eval(
// Get diff from git
let diff = eval_output.diff.clone();
// For project creation template, we need to compare with reference implementation
let judge_output = if template.name == "ProjectCreation" {
let project_judge_prompt = template
.content
.replace(
"<!-- ```requirements go here``` -->",
&format!("```\n{}\n```", instructions),
)
.replace(
"<!-- ```reference code goes here``` -->",
&format!("```{}\n{}\n```", language, example_solution),
)
.replace(
"<!-- ```git diff goes here``` -->",
&format!("```\n{}\n```", diff),
);
// Use the run_with_prompt method which we'll add to judge.rs
let judge = crate::judge::Judge {
original_diff: None,
original_message: Some(project_judge_prompt),
model: judge_model.clone(),
};
cx.update(|cx| judge.run_with_prompt(cx))?.await?
} else if template.name == "CodeModification" {
// For CodeModification, we'll compare the example solution with the LLM-generated solution
let code_judge_prompt = template
.content
.replace(
"<!-- ```reference code goes here``` -->",
&format!("```{}\n{}\n```", language, example_solution),
)
.replace(
"<!-- ```git diff goes here``` -->",
&format!("```\n{}\n```", diff),
);
// Use the run_with_prompt method
let judge = crate::judge::Judge {
original_diff: None,
original_message: Some(code_judge_prompt),
model: judge_model.clone(),
};
cx.update(|cx| judge.run_with_prompt(cx))?.await?
} else {
// Conversational template
let conv_judge_prompt = template
.content
.replace(
"<!-- ```query goes here``` -->",
&format!("```\n{}\n```", instructions),
)
.replace(
"<!-- ```transcript goes here``` -->",
&format!("```\n{}\n```", eval_output.last_message),
)
.replace(
"<!-- ```git diff goes here``` -->",
&format!("```\n{}\n```", diff),
);
// Use the run_with_prompt method for consistency
let judge = crate::judge::Judge {
original_diff: None,
original_message: Some(conv_judge_prompt),
model: judge_model.clone(),
};
cx.update(|cx| judge.run_with_prompt(cx))?.await?
};
let elapsed_time = start_time.elapsed()?;
// Calculate total tokens as the sum of input and output tokens
@@ -364,9 +522,14 @@ pub async fn run_exercise_eval(
let tool_use_counts = eval_output.tool_use_counts.values().sum::<u32>();
let total_tokens = input_tokens + output_tokens;
// Get judge model name
let judge_model_name = judge_model.id().0.to_string();
// Save results to evaluation directory
let result = EvalResult {
exercise_name: exercise_name.clone(),
template_name: template.name.to_string(),
score: judge_output.trim().to_string(),
diff,
assistant_response: eval_output.last_message.clone(),
elapsed_time_ms: elapsed_time.as_millis(),
@@ -378,6 +541,7 @@ pub async fn run_exercise_eval(
output_tokens: output_tokens.try_into().unwrap(),
total_tokens: total_tokens.try_into().unwrap(),
tool_use_counts: tool_use_counts.try_into().unwrap(),
judge_model_name, // Add judge model name to result
};
Ok(result)

View File

@@ -4,10 +4,12 @@ use assistant_tool::ToolWorkingSet;
use client::{Client, UserStore};
use collections::HashMap;
use dap::DapRegistry;
use gpui::{App, Entity, SemanticVersion, Subscription, Task, prelude::*};
use futures::StreamExt;
use gpui::{App, AsyncApp, Entity, SemanticVersion, Subscription, Task, prelude::*};
use language::LanguageRegistry;
use language_model::{
AuthenticateError, LanguageModel, LanguageModelProviderId, LanguageModelRegistry,
LanguageModelRequest,
};
use node_runtime::NodeRuntime;
use project::{Project, RealFs};
@@ -244,3 +246,34 @@ pub fn authenticate_model_provider(
let model_provider = model_registry.provider(&provider_id).unwrap();
model_provider.authenticate(cx)
}
pub async fn send_language_model_request(
model: Arc<dyn LanguageModel>,
request: LanguageModelRequest,
cx: &mut AsyncApp,
) -> anyhow::Result<String> {
match model.stream_completion_text(request, &cx).await {
Ok(mut stream) => {
let mut full_response = String::new();
// Process the response stream
while let Some(chunk_result) = stream.stream.next().await {
match chunk_result {
Ok(chunk_str) => {
full_response.push_str(&chunk_str);
}
Err(err) => {
return Err(anyhow!(
"Error receiving response from language model: {err}"
));
}
}
}
Ok(full_response)
}
Err(err) => Err(anyhow!(
"Failed to get response from language model. Error was: {err}"
)),
}
}

View File

@@ -0,0 +1,37 @@
use crate::headless_assistant::send_language_model_request;
use anyhow::anyhow;
use gpui::{App, Task};
use language_model::{
LanguageModel, LanguageModelRequest, LanguageModelRequestMessage, MessageContent, Role,
};
use std::sync::Arc;
pub struct Judge {
#[allow(dead_code)]
pub original_diff: Option<String>,
pub original_message: Option<String>,
pub model: Arc<dyn LanguageModel>,
}
impl Judge {
pub fn run_with_prompt(&self, cx: &mut App) -> Task<anyhow::Result<String>> {
let Some(prompt) = self.original_message.as_ref() else {
return Task::ready(Err(anyhow!("No prompt provided in original_message")));
};
let request = LanguageModelRequest {
messages: vec![LanguageModelRequestMessage {
role: Role::User,
content: vec![MessageContent::Text(prompt.clone())],
cache: false,
}],
temperature: Some(0.0),
tools: Vec::new(),
stop: Vec::new(),
};
let model = self.model.clone();
let request = request.clone();
cx.spawn(async move |cx| send_language_model_request(model, request, cx).await)
}
}

View File

@@ -2,6 +2,8 @@ mod eval;
mod get_exercise;
mod git_commands;
mod headless_assistant;
mod judge;
mod templates_eval;
use clap::Parser;
use eval::{run_exercise_eval, save_eval_results};
@@ -13,10 +15,11 @@ use headless_assistant::{authenticate_model_provider, find_model};
use language_model::LanguageModelRegistry;
use reqwest_client::ReqwestClient;
use std::{path::PathBuf, sync::Arc};
use templates_eval::all_templates;
#[derive(Parser, Debug)]
#[command(
name = "agent_eval",
name = "assistant_eval",
disable_version_flag = true,
before_help = "Tool eval runner"
)]
@@ -34,17 +37,24 @@ struct Args {
/// Name of the model (default: "claude-3-7-sonnet-latest")
#[arg(long, default_value = "claude-3-7-sonnet-latest")]
model_name: String,
/// Name of the editor model (default: value of `--model_name`).
/// Name of the judge model (default: value of `--model_name`).
#[arg(long)]
editor_model_name: Option<String>,
judge_model_name: Option<String>,
/// Number of evaluations to run concurrently (default: 3)
#[arg(short, long, default_value = "5")]
#[arg(short, long, default_value = "3")]
concurrency: usize,
/// Maximum number of exercises to evaluate per language
#[arg(long)]
max_exercises_per_language: Option<usize>,
}
// First, let's define the order in which templates should be executed
const TEMPLATE_EXECUTION_ORDER: [&str; 3] = [
"ProjectCreation",
"CodeModification",
"ConversationalGuidance",
];
fn main() {
env_logger::init();
let args = Args::parse();
@@ -66,7 +76,7 @@ fn main() {
let app_state = headless_assistant::init(cx);
let model = find_model(&args.model_name, cx).unwrap();
let editor_model = if let Some(model_name) = &args.editor_model_name {
let judge_model = if let Some(model_name) = &args.judge_model_name {
find_model(model_name, cx).unwrap()
} else {
model.clone()
@@ -77,7 +87,7 @@ fn main() {
});
let model_provider_id = model.provider_id();
let editor_model_provider_id = editor_model.provider_id();
let judge_model_provider_id = judge_model.provider_id();
let framework_path_clone = framework_path.clone();
let languages_clone = languages.clone();
@@ -90,17 +100,15 @@ fn main() {
.unwrap()
.await
.unwrap();
cx.update(|cx| authenticate_model_provider(editor_model_provider_id.clone(), cx))
cx.update(|cx| authenticate_model_provider(judge_model_provider_id.clone(), cx))
.unwrap()
.await
.unwrap();
println!("framework path: {}", framework_path_clone.display());
// Read base SHA from setup.json
let base_sha = read_base_sha(&framework_path_clone).await.unwrap();
println!("base sha: {}", base_sha);
// Find all exercises for the specified languages
let all_exercises = find_exercises(
&framework_path_clone,
&languages_clone
@@ -132,12 +140,23 @@ fn main() {
println!("Will run {} exercises", exercises_to_run.len());
// Get all templates and sort them according to the execution order
let mut templates = all_templates();
templates.sort_by_key(|template| {
TEMPLATE_EXECUTION_ORDER
.iter()
.position(|&name| name == template.name)
.unwrap_or(usize::MAX)
});
// Create exercise eval tasks - each exercise is a single task that will run templates sequentially
let exercise_tasks: Vec<_> = exercises_to_run
.into_iter()
.map(|exercise_path| {
let exercise_name = get_exercise_name(&exercise_path);
let templates_clone = templates.clone();
let model_clone = model.clone();
let judge_model_clone = judge_model.clone();
let app_state_clone = app_state.clone();
let base_sha_clone = base_sha.clone();
let framework_path_clone = framework_path_clone.clone();
@@ -147,22 +166,56 @@ fn main() {
println!("Processing exercise: {}", exercise_name);
let mut exercise_results = Vec::new();
match run_exercise_eval(
exercise_path.clone(),
model_clone.clone(),
app_state_clone.clone(),
base_sha_clone.clone(),
framework_path_clone.clone(),
cx_clone.clone(),
)
.await
{
Ok(result) => {
println!("Completed {}", exercise_name);
exercise_results.push(result);
}
// Determine the language for this exercise
let language = match get_exercise_language(&exercise_path) {
Ok(lang) => lang,
Err(err) => {
println!("Error running {}: {}", exercise_name, err);
println!(
"Error determining language for {}: {}",
exercise_name, err
);
return exercise_results;
}
};
// Run each template sequentially for this exercise
for template in templates_clone {
// For "multi" or "internal" language, only run the CodeModification template
if (language == "multi" || language == "internal")
&& template.name != "CodeModification"
{
println!(
"Skipping {} template for {} language",
template.name, language
);
continue;
}
match run_exercise_eval(
exercise_path.clone(),
template.clone(),
model_clone.clone(),
judge_model_clone.clone(),
app_state_clone.clone(),
base_sha_clone.clone(),
framework_path_clone.clone(),
cx_clone.clone(),
)
.await
{
Ok(result) => {
println!(
"Completed {} with template {} - score: {}",
exercise_name, template.name, result.score
);
exercise_results.push(result);
}
Err(err) => {
println!(
"Error running {} with template {}: {}",
exercise_name, template.name, err
);
}
}
}

View File

@@ -0,0 +1,210 @@
#[derive(Clone, Debug)]
pub struct Template {
pub name: &'static str,
pub content: &'static str,
}
pub fn all_templates() -> Vec<Template> {
vec![
Template {
name: "ProjectCreation",
content: r#"
# Project Creation Evaluation Template
## Instructions
Evaluate how well the AI assistant created a new implementation from scratch. Score it between 0.0 and 1.0 based on quality and fulfillment of requirements.
- 1.0 = Perfect implementation that creates all necessary files with correct functionality.
- 0.0 = Completely fails to create working files or meet requirements.
Note: A git diff output is required. If no code changes are provided (i.e., no git diff output), the score must be 0.0.
## Evaluation Criteria
Please consider the following aspects in order of importance:
1. **File Creation (25%)**
- Did the assistant create all necessary files?
- Are the files appropriately named and organized?
- Did the assistant create a complete solution without missing components?
2. **Functional Correctness (40%)**
- Does the implementation fulfill all specified requirements?
- Does it handle edge cases properly?
- Is it free of logical errors and bugs?
- Do all components work together as expected?
3. **Code Quality (20%)**
- Is the code well-structured, readable and well-documented?
- Does it follow language-specific best practices?
- Is there proper error handling?
- Are naming conventions clear and consistent?
4. **Architecture Design (15%)**
- Is the code modular and extensible?
- Is there proper separation of concerns?
- Are appropriate design patterns used?
- Is the overall architecture appropriate for the requirements?
## Input
Requirements:
<!-- ```requirements go here``` -->
Reference Implementation:
<!-- ```reference code goes here``` -->
AI-Generated Implementation (git diff output):
<!-- ```git diff goes here``` -->
## Output Format
THE ONLY OUTPUT SHOULD BE A SCORE BETWEEN 0.0 AND 1.0.
EXAMPLE ONE:
0.92
EXAMPLE TWO:
0.85
EXAMPLE THREE:
0.78
"#,
},
Template {
name: "CodeModification",
content: r#"
# Code Modification Evaluation Template
## Instructions
Evaluate how well the AI assistant modified existing code to meet requirements. Score between 0.0 and 1.0 based on quality and appropriateness of changes.
- 1.0 = Perfect modifications that correctly implement all requirements.
- 0.0 = Failed to make appropriate changes or introduced serious errors.
## Evaluation Criteria
Please consider the following aspects in order of importance:
1. **Functional Correctness (50%)**
- Do the modifications correctly implement the requirements?
- Did the assistant modify the right files and code sections?
- Are the changes free of bugs and logical errors?
- Do the modifications maintain compatibility with existing code?
2. **Modification Approach (25%)**
- Are the changes minimal and focused on what needs to be changed?
- Did the assistant avoid unnecessary modifications?
- Are the changes integrated seamlessly with the existing codebase?
- Did the assistant preserve the original code style and patterns?
3. **Code Quality (15%)**
- Are the modifications well-structured and documented?
- Do they follow the same conventions as the original code?
- Is there proper error handling in the modified code?
- Are the changes readable and maintainable?
4. **Solution Completeness (10%)**
- Do the modifications completely address all requirements?
- Are there any missing changes or overlooked requirements?
- Did the assistant consider all necessary edge cases?
## Input
Original:
<!-- ```reference code goes here``` -->
New (git diff output):
<!-- ```git diff goes here``` -->
## Output Format
THE ONLY OUTPUT SHOULD BE A SCORE BETWEEN 0.0 AND 1.0.
EXAMPLE ONE:
0.92
EXAMPLE TWO:
0.85
EXAMPLE THREE:
0.78
"#,
},
Template {
name: "ConversationalGuidance",
content: r#"
# Conversational Guidance Evaluation Template
## Instructions
Evaluate the quality of the AI assistant's conversational guidance and score it between 0.0 and 1.0.
- 1.0 = Perfect guidance with ideal information gathering, clarification, and advice without writing code.
- 0.0 = Completely unhelpful, inappropriate guidance, or wrote code when it should not have.
## Evaluation Criteria
ABSOLUTE REQUIREMENT:
- The assistant should NOT generate complete code solutions in conversation mode.
- If the git diff shows the assistant wrote complete code, the score should be significantly reduced.
1. **Information Gathering Effectiveness (30%)**
- Did the assistant ask relevant and precise questions?
- Did it efficiently narrow down the problem scope?
- Did it avoid unnecessary or redundant questions?
- Was questioning appropriately paced and contextual?
2. **Conceptual Guidance (30%)**
- Did the assistant provide high-level approaches and strategies?
- Did it explain relevant concepts and algorithms?
- Did it offer planning advice without implementing the solution?
- Did it suggest a structured approach to solving the problem?
3. **Educational Value (20%)**
- Did the assistant help the user understand the problem better?
- Did it provide explanations that would help the user learn?
- Did it guide without simply giving away answers?
- Did it encourage the user to think through parts of the problem?
4. **Conversation Quality (20%)**
- Was the conversation logically structured and easy to follow?
- Did the assistant maintain appropriate context throughout?
- Was the interaction helpful without being condescending?
- Did the conversation reach a satisfactory conclusion with clear next steps?
## Input
Initial Query:
<!-- ```query goes here``` -->
Conversation Transcript:
<!-- ```transcript goes here``` -->
Git Diff:
<!-- ```git diff goes here``` -->
## Output Format
THE ONLY OUTPUT SHOULD BE A SCORE BETWEEN 0.0 AND 1.0.
EXAMPLE ONE:
0.92
EXAMPLE TWO:
0.85
EXAMPLE THREE:
0.78
"#,
},
]
}

View File

@@ -48,7 +48,7 @@ pub trait Tool: 'static + Send + Sync {
/// Returns true iff the tool needs the users's confirmation
/// before having permission to run.
fn needs_confirmation(&self, input: &serde_json::Value, cx: &App) -> bool;
fn needs_confirmation(&self) -> bool;
/// Returns the JSON schema that describes the tool's input.
fn input_schema(&self, _: LanguageModelToolSchemaFormat) -> serde_json::Value {

View File

@@ -23,6 +23,7 @@ http_client.workspace = true
itertools.workspace = true
language.workspace = true
language_model.workspace = true
lsp.workspace = true
project.workspace = true
regex.workspace = true
schemars.workspace = true

View File

@@ -1,5 +1,6 @@
mod bash_tool;
mod batch_tool;
mod code_symbol_iter;
mod code_symbols_tool;
mod copy_path_tool;
mod create_directory_tool;

View File

@@ -1,8 +1,6 @@
use crate::schema::json_schema_for;
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use futures::io::BufReader;
use futures::{AsyncBufReadExt, AsyncReadExt, FutureExt};
use gpui::{App, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -16,7 +14,7 @@ use util::markdown::MarkdownString;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct BashToolInput {
/// The bash one-liner command to execute.
/// The bash command to execute as a one-liner.
command: String,
/// Working directory for the command. This must be one of the root directories of the project.
cd: String,
@@ -29,7 +27,7 @@ impl Tool for BashTool {
"bash".to_string()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
true
}
@@ -123,113 +121,33 @@ impl Tool for BashTool {
worktree.read(cx).abs_path()
};
cx.spawn(async move |cx| {
cx.spawn(async move |_| {
// Add 2>&1 to merge stderr into stdout for proper interleaving.
let command = format!("({}) 2>&1", input.command);
let mut cmd = new_smol_command("bash")
let output = new_smol_command("bash")
.arg("-c")
.arg(&command)
.current_dir(working_dir)
.stdout(std::process::Stdio::piped())
.spawn()
.output()
.await
.context("Failed to execute bash command")?;
// Capture stdout with a limit
let stdout = cmd.stdout.take().unwrap();
let mut reader = BufReader::new(stdout);
let output_string = String::from_utf8_lossy(&output.stdout).to_string();
const MESSAGE_1: &str = "Command output too long. The first ";
const MESSAGE_2: &str = " bytes:\n\n";
const ERR_MESSAGE_1: &str = "Command failed with exit code ";
const ERR_MESSAGE_2: &str = "\n\n";
const STDOUT_LIMIT: usize = 8192;
const LIMIT: usize = STDOUT_LIMIT
- (MESSAGE_1.len()
+ (STDOUT_LIMIT.ilog10() as usize + 1) // byte count
+ MESSAGE_2.len()
+ ERR_MESSAGE_1.len()
+ 3 // status code
+ ERR_MESSAGE_2.len());
// Read one more byte to determine whether the output was truncated
let mut buffer = vec![0; LIMIT + 1];
let bytes_read = reader.read(&mut buffer).await?;
let mut timer = cx
.background_executor()
.timer(std::time::Duration::from_secs(10))
.fuse();
// Repeatedly fill the output reader's buffer without copying it.
loop {
let mut skipped_bytes = reader.fill_buf().fuse();
futures::select! {
skipped_bytes = skipped_bytes => {
let skipped_bytes = skipped_bytes?;
if skipped_bytes.is_empty() {
break;
}
let skipped_bytes_len = skipped_bytes.len();
reader.consume_unpin(skipped_bytes_len);
timer = cx
.background_executor()
.timer(std::time::Duration::from_secs(10))
.fuse();
}
_ = timer => {
return Err(anyhow!("Command timed out. Output so far:\n{}", String::from_utf8_lossy(&buffer[..bytes_read])));
}
}
}
let output_bytes = &buffer[..bytes_read];
// Let the process continue running
let status = cmd.status().await.context("Failed to get command status")?;
let output_string = if bytes_read > LIMIT {
// Valid to find `\n` in UTF-8 since 0-127 ASCII characters are not used in
// multi-byte characters.
let last_line_ix = output_bytes.iter().rposition(|b| *b == b'\n');
let output_string = String::from_utf8_lossy(
&output_bytes[..last_line_ix.unwrap_or(output_bytes.len())],
);
format!(
"{}{}{}{}",
MESSAGE_1,
output_string.len(),
MESSAGE_2,
output_string
)
} else {
String::from_utf8_lossy(&output_bytes).into()
};
let output_with_status = if status.success() {
if output.status.success() {
if output_string.is_empty() {
"Command executed successfully.".to_string()
Ok("Command executed successfully.".to_string())
} else {
output_string.to_string()
Ok(output_string)
}
} else {
format!(
"{}{}{}{}",
ERR_MESSAGE_1,
status.code().unwrap_or(-1),
ERR_MESSAGE_2,
output_string,
)
};
debug_assert!(output_with_status.len() <= STDOUT_LIMIT);
Ok(output_with_status)
Ok(format!(
"Command failed with exit code {}\n{}",
output.status.code().unwrap_or(-1),
&output_string
))
}
})
}
}

View File

@@ -151,17 +151,8 @@ impl Tool for BatchTool {
"batch_tool".into()
}
fn needs_confirmation(&self, input: &serde_json::Value, cx: &App) -> bool {
serde_json::from_value::<BatchToolInput>(input.clone())
.map(|input| {
let working_set = ToolWorkingSet::default();
input.invocations.iter().any(|invocation| {
working_set
.tool(&invocation.name, cx)
.map_or(false, |tool| tool.needs_confirmation(&invocation.input, cx))
})
})
.unwrap_or(false)
fn needs_confirmation(&self) -> bool {
true
}
fn description(&self) -> String {

View File

@@ -0,0 +1,88 @@
use project::DocumentSymbol;
use regex::Regex;
#[derive(Debug, Clone)]
pub struct Entry {
pub name: String,
pub kind: lsp::SymbolKind,
pub depth: u32,
pub start_line: usize,
pub end_line: usize,
}
/// An iterator that filters document symbols based on a regex pattern.
/// This iterator recursively traverses the document symbol tree, incrementing depth for child symbols.
#[derive(Debug, Clone)]
pub struct CodeSymbolIterator<'a> {
symbols: &'a [DocumentSymbol],
regex: Option<Regex>,
// Stack of (symbol, depth) pairs to process
pending_symbols: Vec<(&'a DocumentSymbol, u32)>,
current_index: usize,
current_depth: u32,
}
impl<'a> CodeSymbolIterator<'a> {
pub fn new(symbols: &'a [DocumentSymbol], regex: Option<Regex>) -> Self {
Self {
symbols,
regex,
pending_symbols: Vec::new(),
current_index: 0,
current_depth: 0,
}
}
}
impl Iterator for CodeSymbolIterator<'_> {
type Item = Entry;
fn next(&mut self) -> Option<Self::Item> {
if let Some((symbol, depth)) = self.pending_symbols.pop() {
for child in symbol.children.iter().rev() {
self.pending_symbols.push((child, depth + 1));
}
return Some(Entry {
name: symbol.name.clone(),
kind: symbol.kind,
depth,
start_line: symbol.range.start.0.row as usize,
end_line: symbol.range.end.0.row as usize,
});
}
while self.current_index < self.symbols.len() {
let regex = self.regex.as_ref();
let symbol = &self.symbols[self.current_index];
self.current_index += 1;
if regex.is_none_or(|regex| regex.is_match(&symbol.name)) {
// Push in reverse order to maintain traversal order
for child in symbol.children.iter().rev() {
self.pending_symbols.push((child, self.current_depth + 1));
}
return Some(Entry {
name: symbol.name.clone(),
kind: symbol.kind,
depth: self.current_depth,
start_line: symbol.range.start.0.row as usize,
end_line: symbol.range.end.0.row as usize,
});
} else {
// Even if parent doesn't match, push children to check them later
for child in symbol.children.iter().rev() {
self.pending_symbols.push((child, self.current_depth + 1));
}
// Check if any pending children match our criteria
if let Some(result) = self.next() {
return Some(result);
}
}
}
None
}
}

View File

@@ -1,21 +1,24 @@
use std::fmt::Write;
use std::fmt::{self, Write};
use std::path::PathBuf;
use std::sync::Arc;
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use collections::IndexMap;
use gpui::{App, AsyncApp, Entity, Task};
use language::{OutlineItem, ParseStatus, Point};
use language::{CodeLabel, Language, LanguageRegistry};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::{Project, Symbol};
use lsp::SymbolKind;
use project::{DocumentSymbol, Project, Symbol};
use regex::{Regex, RegexBuilder};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use ui::IconName;
use util::markdown::MarkdownString;
use crate::code_symbol_iter::{CodeSymbolIterator, Entry};
use crate::schema::json_schema_for;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct CodeSymbolsInput {
/// The relative path of the source code file to read and get the symbols for.
@@ -79,7 +82,7 @@ impl Tool for CodeSymbolsTool {
"code_symbols".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
false
}
@@ -153,7 +156,7 @@ impl Tool for CodeSymbolsTool {
}
}
pub async fn file_outline(
async fn file_outline(
project: Entity<Project>,
path: String,
action_log: Entity<ActionLog>,
@@ -177,28 +180,24 @@ pub async fn file_outline(
action_log.buffer_read(buffer.clone(), cx);
})?;
// Wait until the buffer has been fully parsed, so that we can read its outline.
let mut parse_status = buffer.read_with(cx, |buffer, _| buffer.parse_status())?;
while parse_status
.recv()
.await
.map_or(false, |status| status != ParseStatus::Idle)
{}
let symbols = project
.update(cx, |project, cx| project.document_symbols(&buffer, cx))?
.await?;
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
let Some(outline) = snapshot.outline(None) else {
return Err(anyhow!("No outline information available for this file."));
};
if symbols.is_empty() {
return Err(
if buffer.read_with(cx, |buffer, _| buffer.snapshot().is_empty())? {
anyhow!("This file is empty.")
} else {
anyhow!("No outline information available for this file.")
},
);
}
render_outline(
outline
.items
.into_iter()
.map(|item| item.to_point(&snapshot)),
regex,
offset,
)
.await
let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned())?;
let language_registry = project.read_with(cx, |project, _| project.languages().clone())?;
render_outline(&symbols, language, language_registry, regex, offset).await
}
async fn project_symbols(
@@ -293,27 +292,61 @@ async fn project_symbols(
}
async fn render_outline(
items: impl IntoIterator<Item = OutlineItem<Point>>,
symbols: &[DocumentSymbol],
language: Option<Arc<Language>>,
registry: Arc<LanguageRegistry>,
regex: Option<Regex>,
offset: u32,
) -> Result<String> {
const RESULTS_PER_PAGE_USIZE: usize = RESULTS_PER_PAGE as usize;
let entries = CodeSymbolIterator::new(symbols, regex.clone())
.skip(offset as usize)
// Take 1 more than RESULTS_PER_PAGE so we can tell if there are more results.
.take(RESULTS_PER_PAGE_USIZE.saturating_add(1))
.collect::<Vec<Entry>>();
let has_more = entries.len() > RESULTS_PER_PAGE_USIZE;
let mut items = items.into_iter().skip(offset as usize);
// Get language-specific labels, if available
let labels = match &language {
Some(lang) => {
let entries_for_labels: Vec<(String, SymbolKind)> = entries
.iter()
.take(RESULTS_PER_PAGE_USIZE)
.map(|entry| (entry.name.clone(), entry.kind))
.collect();
let entries = items
.by_ref()
.filter(|item| {
regex
.as_ref()
.is_none_or(|regex| regex.is_match(&item.text))
})
.take(RESULTS_PER_PAGE_USIZE)
.collect::<Vec<_>>();
let has_more = items.next().is_some();
let lang_name = lang.name();
if let Some(lsp_adapter) = registry.lsp_adapters(&lang_name).first().cloned() {
lsp_adapter
.labels_for_symbols(&entries_for_labels, lang)
.await
.ok()
} else {
None
}
}
None => None,
};
let mut output = String::new();
let entries_rendered = render_entries(&mut output, entries);
let entries_rendered = match &labels {
Some(label_list) => render_entries(
&mut output,
entries
.into_iter()
.take(RESULTS_PER_PAGE_USIZE)
.zip(label_list.iter())
.map(|(entry, label)| (entry, label.as_ref())),
),
None => render_entries(
&mut output,
entries
.into_iter()
.take(RESULTS_PER_PAGE_USIZE)
.map(|entry| (entry, None)),
),
};
// Calculate pagination information
let page_start = offset + 1;
@@ -339,19 +372,31 @@ async fn render_outline(
Ok(output)
}
fn render_entries(output: &mut String, items: impl IntoIterator<Item = OutlineItem<Point>>) -> u32 {
fn render_entries<'a>(
output: &mut String,
entries: impl IntoIterator<Item = (Entry, Option<&'a CodeLabel>)>,
) -> u32 {
let mut entries_rendered = 0;
for item in items {
for (entry, label) in entries {
// Indent based on depth ("" for level 0, " " for level 1, etc.)
for _ in 0..item.depth {
output.push(' ');
for _ in 0..entry.depth {
output.push_str(" ");
}
match label {
Some(label) => {
output.push_str(label.text());
}
None => {
write_symbol_kind(output, entry.kind).ok();
output.push_str(&entry.name);
}
}
output.push_str(&item.text);
// Add position information - convert to 1-based line numbers for display
let start_line = item.range.start.row + 1;
let end_line = item.range.end.row + 1;
let start_line = entry.start_line + 1;
let end_line = entry.end_line + 1;
if start_line == end_line {
writeln!(output, " [L{}]", start_line).ok();
@@ -363,3 +408,38 @@ fn render_entries(output: &mut String, items: impl IntoIterator<Item = OutlineIt
entries_rendered
}
// We may not have a language server adapter to have language-specific
// ways to translate SymbolKnd into a string. In that situation,
// fall back on some reasonable default strings to render.
fn write_symbol_kind(buf: &mut String, kind: SymbolKind) -> Result<(), fmt::Error> {
match kind {
SymbolKind::FILE => write!(buf, "file "),
SymbolKind::MODULE => write!(buf, "module "),
SymbolKind::NAMESPACE => write!(buf, "namespace "),
SymbolKind::PACKAGE => write!(buf, "package "),
SymbolKind::CLASS => write!(buf, "class "),
SymbolKind::METHOD => write!(buf, "method "),
SymbolKind::PROPERTY => write!(buf, "property "),
SymbolKind::FIELD => write!(buf, "field "),
SymbolKind::CONSTRUCTOR => write!(buf, "constructor "),
SymbolKind::ENUM => write!(buf, "enum "),
SymbolKind::INTERFACE => write!(buf, "interface "),
SymbolKind::FUNCTION => write!(buf, "function "),
SymbolKind::VARIABLE => write!(buf, "variable "),
SymbolKind::CONSTANT => write!(buf, "constant "),
SymbolKind::STRING => write!(buf, "string "),
SymbolKind::NUMBER => write!(buf, "number "),
SymbolKind::BOOLEAN => write!(buf, "boolean "),
SymbolKind::ARRAY => write!(buf, "array "),
SymbolKind::OBJECT => write!(buf, "object "),
SymbolKind::KEY => write!(buf, "key "),
SymbolKind::NULL => write!(buf, "null "),
SymbolKind::ENUM_MEMBER => write!(buf, "enum member "),
SymbolKind::STRUCT => write!(buf, "struct "),
SymbolKind::EVENT => write!(buf, "event "),
SymbolKind::OPERATOR => write!(buf, "operator "),
SymbolKind::TYPE_PARAMETER => write!(buf, "type parameter "),
_ => Ok(()),
}
}

View File

@@ -43,7 +43,7 @@ impl Tool for CopyPathTool {
"copy_path".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
true
}

View File

@@ -33,7 +33,7 @@ impl Tool for CreateDirectoryTool {
"create_directory".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
true
}

View File

@@ -40,7 +40,7 @@ impl Tool for CreateFileTool {
"create_file".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
false
}

View File

@@ -33,7 +33,7 @@ impl Tool for DeletePathTool {
"delete_path".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
true
}

View File

@@ -46,7 +46,7 @@ impl Tool for DiagnosticsTool {
"diagnostics".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
false
}

View File

@@ -116,7 +116,7 @@ impl Tool for FetchTool {
"fetch".to_string()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
true
}

View File

@@ -129,7 +129,7 @@ impl Tool for FindReplaceFileTool {
"find_replace_file".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
false
}

View File

@@ -44,7 +44,7 @@ impl Tool for ListDirectoryTool {
"list_directory".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
false
}

View File

@@ -42,7 +42,7 @@ impl Tool for MovePathTool {
"move_path".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
true
}

View File

@@ -33,7 +33,7 @@ impl Tool for NowTool {
"now".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
false
}

View File

@@ -23,7 +23,7 @@ impl Tool for OpenTool {
"open".to_string()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
true
}

View File

@@ -41,7 +41,7 @@ impl Tool for PathSearchTool {
"path_search".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
false
}

View File

@@ -1,6 +1,7 @@
use std::path::Path;
use std::sync::Arc;
use crate::{code_symbols_tool::file_outline, schema::json_schema_for};
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use gpui::{App, Entity, Task};
@@ -12,11 +13,6 @@ use serde::{Deserialize, Serialize};
use ui::IconName;
use util::markdown::MarkdownString;
/// If the model requests to read a file whose size exceeds this, then
/// the tool will return an error along with the model's symbol outline,
/// and suggest trying again using line ranges from the outline.
const MAX_FILE_SIZE_TO_READ: usize = 16384;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ReadFileToolInput {
/// The relative path of the file to read.
@@ -30,10 +26,10 @@ pub struct ReadFileToolInput {
/// - directory1
/// - directory2
///
/// If you want to access `file.txt` in `directory1`, you should use the path `directory1/file.txt`.
/// If you want to access `file.txt` in `directory2`, you should use the path `directory2/file.txt`.
/// If you wanna access `file.txt` in `directory1`, you should use the path `directory1/file.txt`.
/// If you wanna access `file.txt` in `directory2`, you should use the path `directory2/file.txt`.
/// </example>
pub path: String,
pub path: Arc<Path>,
/// Optional line number to start reading on (1-based index)
#[serde(default)]
@@ -51,7 +47,7 @@ impl Tool for ReadFileTool {
"read_file".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
false
}
@@ -70,7 +66,7 @@ impl Tool for ReadFileTool {
fn ui_text(&self, input: &serde_json::Value) -> String {
match serde_json::from_value::<ReadFileToolInput>(input.clone()) {
Ok(input) => {
let path = MarkdownString::inline_code(&input.path);
let path = MarkdownString::inline_code(&input.path.display().to_string());
match (input.start_line, input.end_line) {
(Some(start), None) => format!("Read file {path} (from line {start})"),
(Some(start), Some(end)) => format!("Read file {path} (lines {start}-{end})"),
@@ -95,10 +91,12 @@ impl Tool for ReadFileTool {
};
let Some(project_path) = project.read(cx).find_project_path(&input.path, cx) else {
return Task::ready(Err(anyhow!("Path {} not found in project", &input.path,)));
return Task::ready(Err(anyhow!(
"Path {} not found in project",
&input.path.display()
)));
};
let file_path = input.path.clone();
cx.spawn(async move |cx| {
let buffer = cx
.update(|cx| {
@@ -106,46 +104,27 @@ impl Tool for ReadFileTool {
})?
.await?;
// Check if specific line ranges are provided
if input.start_line.is_some() || input.end_line.is_some() {
let result = buffer.read_with(cx, |buffer, _cx| {
let text = buffer.text();
let result = buffer.read_with(cx, |buffer, _cx| {
let text = buffer.text();
if input.start_line.is_some() || input.end_line.is_some() {
let start = input.start_line.unwrap_or(1);
let lines = text.split('\n').skip(start - 1);
if let Some(end) = input.end_line {
let count = end.saturating_sub(start).max(1); // Ensure at least 1 line
let count = end.saturating_sub(start);
Itertools::intersperse(lines.take(count), "\n").collect()
} else {
Itertools::intersperse(lines, "\n").collect()
}
})?;
action_log.update(cx, |log, cx| {
log.buffer_read(buffer, cx);
})?;
Ok(result)
} else {
// No line ranges specified, so check file size to see if it's too big.
let file_size = buffer.read_with(cx, |buffer, _cx| buffer.text().len())?;
if file_size <= MAX_FILE_SIZE_TO_READ {
// File is small enough, so return its contents.
let result = buffer.read_with(cx, |buffer, _cx| buffer.text())?;
action_log.update(cx, |log, cx| {
log.buffer_read(buffer, cx);
})?;
Ok(result)
} else {
// File is too big, so return an error with the outline
// and a suggestion to read again with line numbers.
let outline = file_outline(project, file_path, action_log, None, 0, cx).await?;
Ok(format!("This file was too big to read all at once. Here is an outline of its symbols:\n\n{outline}\n\nUsing the line numbers in this outline, you can call this tool again while specifying the start_line and end_line fields to see the implementations of symbols in the outline."))
text
}
}
})?;
action_log.update(cx, |log, cx| {
log.buffer_read(buffer, cx);
})?;
anyhow::Ok(result)
})
}
}

View File

@@ -1,6 +1 @@
Reads the content of the given file in the project.
If the file is too big to read all at once, and neither a start line
nor an end line was specified, then this returns an outline of the
file's symbols (with line numbers) instead of the file's contents,
so that it can be called again with line ranges.

View File

@@ -44,7 +44,7 @@ impl Tool for RegexSearchTool {
"regex_search".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
false
}

View File

@@ -72,7 +72,7 @@ impl Tool for SymbolInfoTool {
"symbol_info".into()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
false
}

View File

@@ -24,7 +24,7 @@ impl Tool for ThinkingTool {
"thinking".to_string()
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
fn needs_confirmation(&self) -> bool {
false
}

View File

@@ -1,25 +1,21 @@
mod models;
use std::collections::HashMap;
use std::pin::Pin;
use anyhow::{Error, Result, anyhow};
use anyhow::{Context, Error, Result, anyhow};
use aws_sdk_bedrockruntime as bedrock;
pub use aws_sdk_bedrockruntime as bedrock_client;
pub use aws_sdk_bedrockruntime::types::{
AutoToolChoice as BedrockAutoToolChoice, ContentBlock as BedrockInnerContent,
Tool as BedrockTool, ToolChoice as BedrockToolChoice, ToolConfiguration as BedrockToolConfig,
ToolInputSchema as BedrockToolInputSchema, ToolSpecification as BedrockToolSpec,
ContentBlock as BedrockInnerContent, SpecificToolChoice as BedrockSpecificTool,
ToolChoice as BedrockToolChoice, ToolInputSchema as BedrockToolInputSchema,
ToolSpecification as BedrockTool,
};
use aws_smithy_types::{Document, Number as AwsNumber};
pub use bedrock::operation::converse_stream::ConverseStreamInput as BedrockStreamingRequest;
pub use bedrock::types::{
ContentBlock as BedrockRequestContent, ConversationRole as BedrockRole,
ConverseOutput as BedrockResponse, ConverseStreamOutput as BedrockStreamingResponse,
ImageBlock as BedrockImageBlock, Message as BedrockMessage,
ResponseStream as BedrockResponseStream, ToolResultBlock as BedrockToolResultBlock,
ToolResultContentBlock as BedrockToolResultContentBlock,
ToolResultStatus as BedrockToolResultStatus, ToolUseBlock as BedrockToolUseBlock,
Message as BedrockMessage, ResponseStream as BedrockResponseStream,
};
use futures::stream::{self, BoxStream, Stream};
use serde::{Deserialize, Serialize};
@@ -28,6 +24,25 @@ use thiserror::Error;
pub use crate::models::*;
pub async fn complete(
client: &bedrock::Client,
request: Request,
) -> Result<BedrockResponse, BedrockError> {
let response = bedrock::Client::converse(client)
.model_id(request.model.clone())
.set_messages(request.messages.into())
.send()
.await
.context("failed to send request to Bedrock");
match response {
Ok(output) => output
.output
.ok_or_else(|| BedrockError::Other(anyhow!("no output"))),
Err(err) => Err(BedrockError::Other(err)),
}
}
pub async fn stream_completion(
client: bedrock::Client,
request: Request,
@@ -35,32 +50,11 @@ pub async fn stream_completion(
) -> Result<BoxStream<'static, Result<BedrockStreamingResponse, BedrockError>>, Error> {
handle
.spawn(async move {
let mut response = bedrock::Client::converse_stream(&client)
let response = bedrock::Client::converse_stream(&client)
.model_id(request.model.clone())
.set_messages(request.messages.into());
if let Some(Thinking::Enabled {
budget_tokens: Some(budget_tokens),
}) = request.thinking
{
response =
response.additional_model_request_fields(Document::Object(HashMap::from([(
"thinking".to_string(),
Document::from(HashMap::from([
("type".to_string(), Document::String("enabled".to_string())),
(
"budget_tokens".to_string(),
Document::Number(AwsNumber::PosInt(budget_tokens)),
),
])),
)])));
}
if request.tools.is_some() && !request.tools.as_ref().unwrap().tools.is_empty() {
response = response.set_tool_config(request.tools);
}
let response = response.send().await;
.set_messages(request.messages.into())
.send()
.await;
match response {
Ok(output) => {
@@ -71,7 +65,7 @@ pub async fn stream_completion(
>,
> = Box::pin(stream::unfold(output.stream, |mut stream| async move {
match stream.recv().await {
Ok(Some(output)) => Some(({ Ok(output) }, stream)),
Ok(Some(output)) => Some((Ok(output), stream)),
Ok(None) => None,
Err(err) => {
Some((
@@ -141,18 +135,13 @@ pub fn value_to_aws_document(value: &Value) -> Document {
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum Thinking {
Enabled { budget_tokens: Option<u64> },
}
#[derive(Debug)]
pub struct Request {
pub model: String,
pub max_tokens: u32,
pub messages: Vec<BedrockMessage>,
pub tools: Option<BedrockToolConfig>,
pub thinking: Option<Thinking>,
pub tools: Vec<BedrockTool>,
pub tool_choice: Option<BedrockToolChoice>,
pub system: Option<String>,
pub metadata: Option<Metadata>,
pub stop_sequences: Vec<String>,

View File

@@ -2,38 +2,21 @@ use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use strum::EnumIter;
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub enum BedrockModelMode {
#[default]
Default,
Thinking {
budget_tokens: Option<u64>,
},
}
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
pub enum Model {
// Anthropic models (already included)
#[default]
#[serde(rename = "claude-3-5-sonnet-v2", alias = "claude-3-5-sonnet-latest")]
Claude3_5SonnetV2,
Claude3_5Sonnet,
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
Claude3_7Sonnet,
#[serde(
rename = "claude-3-7-sonnet-thinking",
alias = "claude-3-7-sonnet-thinking-latest"
)]
Claude3_7SonnetThinking,
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
Claude3Opus,
#[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-latest")]
Claude3Sonnet,
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
Claude3_5Haiku,
Claude3_5Sonnet,
Claude3Haiku,
// Amazon Nova Models
AmazonNovaLite,
AmazonNovaMicro,
@@ -86,7 +69,7 @@ pub enum Model {
impl Model {
pub fn from_id(id: &str) -> anyhow::Result<Self> {
if id.starts_with("claude-3-5-sonnet-v2") {
Ok(Self::Claude3_5SonnetV2)
Ok(Self::Claude3_5Sonnet)
} else if id.starts_with("claude-3-opus") {
Ok(Self::Claude3Opus)
} else if id.starts_with("claude-3-sonnet") {
@@ -95,8 +78,6 @@ impl Model {
Ok(Self::Claude3_5Haiku)
} else if id.starts_with("claude-3-7-sonnet") {
Ok(Self::Claude3_7Sonnet)
} else if id.starts_with("claude-3-7-sonnet-thinking") {
Ok(Self::Claude3_7SonnetThinking)
} else {
Err(anyhow!("invalid model id"))
}
@@ -104,18 +85,14 @@ impl Model {
pub fn id(&self) -> &str {
match self {
Model::Claude3_5SonnetV2 => "anthropic.claude-3-5-sonnet-20241022-v2:0",
Model::Claude3_5Sonnet => "anthropic.claude-3-5-sonnet-20240620-v1:0",
Model::Claude3Opus => "anthropic.claude-3-opus-20240229-v1:0",
Model::Claude3Sonnet => "anthropic.claude-3-sonnet-20240229-v1:0",
Model::Claude3Haiku => "anthropic.claude-3-haiku-20240307-v1:0",
Model::Claude3_5Haiku => "anthropic.claude-3-5-haiku-20241022-v1:0",
Model::Claude3_7Sonnet | Model::Claude3_7SonnetThinking => {
"anthropic.claude-3-7-sonnet-20250219-v1:0"
}
Model::AmazonNovaLite => "amazon.nova-lite-v1:0",
Model::AmazonNovaMicro => "amazon.nova-micro-v1:0",
Model::AmazonNovaPro => "amazon.nova-pro-v1:0",
Model::Claude3_5Sonnet => "us.anthropic.claude-3-5-sonnet-20241022-v2:0",
Model::Claude3Opus => "us.anthropic.claude-3-opus-20240229-v1:0",
Model::Claude3Sonnet => "us.anthropic.claude-3-sonnet-20240229-v1:0",
Model::Claude3_5Haiku => "us.anthropic.claude-3-5-haiku-20241022-v1:0",
Model::Claude3_7Sonnet => "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
Model::AmazonNovaLite => "us.amazon.nova-lite-v1:0",
Model::AmazonNovaMicro => "us.amazon.nova-micro-v1:0",
Model::AmazonNovaPro => "us.amazon.nova-pro-v1:0",
Model::DeepSeekR1 => "us.deepseek.r1-v1:0",
Model::AI21J2GrandeInstruct => "ai21.j2-grande-instruct",
Model::AI21J2JumboInstruct => "ai21.j2-jumbo-instruct",
@@ -151,14 +128,11 @@ impl Model {
pub fn display_name(&self) -> &str {
match self {
Self::Claude3_5SonnetV2 => "Claude 3.5 Sonnet v2",
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet v2",
Self::Claude3Opus => "Claude 3 Opus",
Self::Claude3Sonnet => "Claude 3 Sonnet",
Self::Claude3Haiku => "Claude 3 Haiku",
Self::Claude3_5Haiku => "Claude 3.5 Haiku",
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
Self::Claude3_7SonnetThinking => "Claude 3.7 Sonnet Thinking",
Self::AmazonNovaLite => "Amazon Nova Lite",
Self::AmazonNovaMicro => "Amazon Nova Micro",
Self::AmazonNovaPro => "Amazon Nova Pro",
@@ -199,7 +173,7 @@ impl Model {
pub fn max_token_count(&self) -> usize {
match self {
Self::Claude3_5SonnetV2
Self::Claude3_5Sonnet
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3_5Haiku
@@ -212,8 +186,7 @@ impl Model {
pub fn max_output_tokens(&self) -> u32 {
match self {
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3_5Haiku => 4_096,
Self::Claude3_7Sonnet | Self::Claude3_7SonnetThinking => 128_000,
Self::Claude3_5SonnetV2 => 8_192,
Self::Claude3_5Sonnet => 8_192,
Self::Custom {
max_output_tokens, ..
} => max_output_tokens.unwrap_or(4_096),
@@ -223,7 +196,7 @@ impl Model {
pub fn default_temperature(&self) -> f32 {
match self {
Self::Claude3_5SonnetV2
Self::Claude3_5Sonnet
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3_5Haiku
@@ -235,253 +208,4 @@ impl Model {
_ => 1.0,
}
}
pub fn supports_tool_use(&self) -> bool {
match self {
// Anthropic Claude 3 models (all support tool use)
Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3_5Sonnet
| Self::Claude3_5SonnetV2
| Self::Claude3_7Sonnet
| Self::Claude3_7SonnetThinking
| Self::Claude3_5Haiku => true,
// Amazon Nova models (all support tool use)
Self::AmazonNovaPro | Self::AmazonNovaLite | Self::AmazonNovaMicro => true,
// AI21 Jamba 1.5 models support tool use
Self::AI21Jamba15LargeV1 | Self::AI21Jamba15MiniV1 => true,
// Cohere Command R models support tool use
Self::CohereCommandRV1 | Self::CohereCommandRPlusV1 => true,
// All other models don't support tool use
// Including Meta Llama 3.2, AI21 Jurassic, and others
_ => false,
}
}
pub fn mode(&self) -> BedrockModelMode {
match self {
Model::Claude3_7SonnetThinking => BedrockModelMode::Thinking {
budget_tokens: Some(4096),
},
_ => BedrockModelMode::Default,
}
}
pub fn cross_region_inference_id(&self, region: &str) -> Result<String, anyhow::Error> {
let region_group = if region.starts_with("us-gov-") {
"us-gov"
} else if region.starts_with("us-") {
"us"
} else if region.starts_with("eu-") {
"eu"
} else if region.starts_with("ap-") || region == "me-central-1" || region == "me-south-1" {
"apac"
} else if region.starts_with("ca-") || region.starts_with("sa-") {
// Canada and South America regions - default to US profiles
"us"
} else {
// Unknown region
return Err(anyhow!("Unsupported Region"));
};
let model_id = self.id();
match (self, region_group) {
// Custom models can't have CRI IDs
(Model::Custom { .. }, _) => Ok(self.id().into()),
// Models with US Gov only
(Model::Claude3_5Sonnet, "us-gov") | (Model::Claude3Haiku, "us-gov") => {
Ok(format!("{}.{}", region_group, model_id))
}
// Models available only in US
(Model::Claude3Opus, "us")
| (Model::Claude3_7Sonnet, "us")
| (Model::Claude3_7SonnetThinking, "us") => {
Ok(format!("{}.{}", region_group, model_id))
}
// Models available in US, EU, and APAC
(Model::Claude3_5SonnetV2, "us")
| (Model::Claude3_5SonnetV2, "apac")
| (Model::Claude3_5Sonnet, _)
| (Model::Claude3Haiku, _)
| (Model::Claude3Sonnet, _)
| (Model::AmazonNovaLite, _)
| (Model::AmazonNovaMicro, _)
| (Model::AmazonNovaPro, _) => Ok(format!("{}.{}", region_group, model_id)),
// Models with limited EU availability
(Model::MetaLlama321BInstructV1, "us")
| (Model::MetaLlama321BInstructV1, "eu")
| (Model::MetaLlama323BInstructV1, "us")
| (Model::MetaLlama323BInstructV1, "eu") => {
Ok(format!("{}.{}", region_group, model_id))
}
// US-only models (all remaining Meta models)
(Model::MetaLlama38BInstructV1, "us")
| (Model::MetaLlama370BInstructV1, "us")
| (Model::MetaLlama318BInstructV1, "us")
| (Model::MetaLlama318BInstructV1_128k, "us")
| (Model::MetaLlama3170BInstructV1, "us")
| (Model::MetaLlama3170BInstructV1_128k, "us")
| (Model::MetaLlama3211BInstructV1, "us")
| (Model::MetaLlama3290BInstructV1, "us") => {
Ok(format!("{}.{}", region_group, model_id))
}
// Any other combination is not supported
_ => Ok(self.id().into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_us_region_inference_ids() -> anyhow::Result<()> {
// Test US regions
assert_eq!(
Model::Claude3_5SonnetV2.cross_region_inference_id("us-east-1")?,
"us.anthropic.claude-3-5-sonnet-20241022-v2:0"
);
assert_eq!(
Model::Claude3_5SonnetV2.cross_region_inference_id("us-west-2")?,
"us.anthropic.claude-3-5-sonnet-20241022-v2:0"
);
assert_eq!(
Model::AmazonNovaPro.cross_region_inference_id("us-east-2")?,
"us.amazon.nova-pro-v1:0"
);
Ok(())
}
#[test]
fn test_eu_region_inference_ids() -> anyhow::Result<()> {
// Test European regions
assert_eq!(
Model::Claude3Sonnet.cross_region_inference_id("eu-west-1")?,
"eu.anthropic.claude-3-sonnet-20240229-v1:0"
);
assert_eq!(
Model::AmazonNovaMicro.cross_region_inference_id("eu-north-1")?,
"eu.amazon.nova-micro-v1:0"
);
Ok(())
}
#[test]
fn test_apac_region_inference_ids() -> anyhow::Result<()> {
// Test Asia-Pacific regions
assert_eq!(
Model::Claude3_5SonnetV2.cross_region_inference_id("ap-northeast-1")?,
"apac.anthropic.claude-3-5-sonnet-20241022-v2:0"
);
assert_eq!(
Model::AmazonNovaLite.cross_region_inference_id("ap-south-1")?,
"apac.amazon.nova-lite-v1:0"
);
Ok(())
}
#[test]
fn test_gov_region_inference_ids() -> anyhow::Result<()> {
// Test Government regions
assert_eq!(
Model::Claude3_5Sonnet.cross_region_inference_id("us-gov-east-1")?,
"us-gov.anthropic.claude-3-5-sonnet-20240620-v1:0"
);
assert_eq!(
Model::Claude3Haiku.cross_region_inference_id("us-gov-west-1")?,
"us-gov.anthropic.claude-3-haiku-20240307-v1:0"
);
Ok(())
}
#[test]
fn test_meta_models_inference_ids() -> anyhow::Result<()> {
// Test Meta models
assert_eq!(
Model::MetaLlama370BInstructV1.cross_region_inference_id("us-east-1")?,
"us.meta.llama3-70b-instruct-v1:0"
);
assert_eq!(
Model::MetaLlama321BInstructV1.cross_region_inference_id("eu-west-1")?,
"eu.meta.llama3-2-1b-instruct-v1:0"
);
Ok(())
}
#[test]
fn test_mistral_models_inference_ids() -> anyhow::Result<()> {
// Mistral models don't follow the regional prefix pattern,
// so they should return their original IDs
assert_eq!(
Model::MistralMistralLarge2402V1.cross_region_inference_id("us-east-1")?,
"mistral.mistral-large-2402-v1:0"
);
assert_eq!(
Model::MistralMixtral8x7BInstructV0.cross_region_inference_id("eu-west-1")?,
"mistral.mixtral-8x7b-instruct-v0:1"
);
Ok(())
}
#[test]
fn test_ai21_models_inference_ids() -> anyhow::Result<()> {
// AI21 models don't follow the regional prefix pattern,
// so they should return their original IDs
assert_eq!(
Model::AI21J2UltraV1.cross_region_inference_id("us-east-1")?,
"ai21.j2-ultra-v1"
);
assert_eq!(
Model::AI21JambaInstructV1.cross_region_inference_id("eu-west-1")?,
"ai21.jamba-instruct-v1:0"
);
Ok(())
}
#[test]
fn test_cohere_models_inference_ids() -> anyhow::Result<()> {
// Cohere models don't follow the regional prefix pattern,
// so they should return their original IDs
assert_eq!(
Model::CohereCommandRV1.cross_region_inference_id("us-east-1")?,
"cohere.command-r-v1:0"
);
assert_eq!(
Model::CohereCommandTextV14_4k.cross_region_inference_id("ap-southeast-1")?,
"cohere.command-text-v14:7:4k"
);
Ok(())
}
#[test]
fn test_custom_model_inference_ids() -> anyhow::Result<()> {
// Test custom models
let custom_model = Model::Custom {
name: "custom.my-model-v1:0".to_string(),
max_tokens: 100000,
display_name: Some("My Custom Model".to_string()),
max_output_tokens: Some(8192),
default_temperature: Some(0.7),
};
// Custom model should return its name unchanged
assert_eq!(
custom_model.cross_region_inference_id("us-east-1")?,
"custom.my-model-v1:0"
);
Ok(())
}
}

View File

@@ -131,9 +131,8 @@ impl Render for Breadcrumbs {
}),
),
None => element
// Match the height and padding of the `ButtonLike` in the other arm.
// Match the height of the `ButtonLike` in the other arm.
.h(rems_from_px(22.))
.pl_1()
.child(breadcrumbs_stack),
}
}

View File

@@ -944,8 +944,8 @@ impl Room {
)
})?;
if self.live_kit.as_ref().map_or(true, |kit| kit.deafened) {
if publication.is_audio() {
publication.set_enabled(false, cx);
if matches!(track, livekit_client::RemoteTrack::Audio(_)) {
track.set_enabled(false, cx);
}
}
match track {

View File

@@ -18,7 +18,7 @@ test-support = ["clock/test-support", "collections/test-support", "gpui/test-sup
[dependencies]
anyhow.workspace = true
async-recursion = "0.3"
async-tungstenite = { workspace = true, features = ["tokio", "tokio-rustls-manual-roots"] }
async-tungstenite = { workspace = true, features = ["async-std", "async-tls"] }
chrono = { workspace = true, features = ["serde"] }
clock.workspace = true
collections.workspace = true
@@ -26,7 +26,6 @@ credentials_provider.workspace = true
feature_flags.workspace = true
futures.workspace = true
gpui.workspace = true
gpui_tokio.workspace = true
http_client.workspace = true
http_client_tls.workspace = true
log.workspace = true
@@ -52,7 +51,6 @@ url.workspace = true
util.workspace = true
worktree.workspace = true
telemetry.workspace = true
tokio.workspace = true
workspace-hack.workspace = true
[dev-dependencies]
@@ -69,3 +67,4 @@ windows.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
cocoa.workspace = true
async-native-tls = { version = "0.5.0", features = ["vendored"] }

View File

@@ -20,7 +20,7 @@ use futures::{
AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt, TryFutureExt as _, TryStreamExt,
channel::oneshot, future::BoxFuture,
};
use gpui::{App, AsyncApp, Entity, Global, Task, WeakEntity, actions};
use gpui::{App, AppContext as _, AsyncApp, Entity, Global, Task, WeakEntity, actions};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use parking_lot::RwLock;
use postage::watch;
@@ -1086,7 +1086,7 @@ impl Client {
let rpc_url = self.rpc_url(http, release_channel);
let system_id = self.telemetry.system_id();
let metrics_id = self.telemetry.metrics_id();
cx.spawn(async move |cx| {
cx.background_spawn(async move {
use HttpOrHttps::*;
#[derive(Debug)]
@@ -1105,12 +1105,7 @@ impl Client {
.host_str()
.zip(rpc_url.port_or_known_default())
.ok_or_else(|| anyhow!("missing host in rpc url"))?;
let stream = {
let handle = cx.update(|cx| gpui_tokio::Tokio::handle(cx)).ok().unwrap();
let _guard = handle.enter();
connect_socks_proxy_stream(proxy.as_ref(), rpc_host).await?
};
let stream = connect_socks_proxy_stream(proxy.as_ref(), rpc_host).await?;
log::info!("connected to rpc endpoint {}", rpc_url);
@@ -1149,19 +1144,30 @@ impl Client {
request_headers.insert("x-zed-metrics-id", HeaderValue::from_str(&metrics_id)?);
}
let (stream, _) = async_tungstenite::tokio::client_async_tls_with_connector_and_config(
request,
stream,
Some(Arc::new(http_client_tls::tls_config()).into()),
None,
)
.await?;
Ok(Connection::new(
stream
.map_err(|error| anyhow!(error))
.sink_map_err(|error| anyhow!(error)),
))
match url_scheme {
Https => {
let (stream, _) =
async_tungstenite::async_tls::client_async_tls_with_connector(
request,
stream,
Some(http_client_tls::tls_config().into()),
)
.await?;
Ok(Connection::new(
stream
.map_err(|error| anyhow!(error))
.sink_map_err(|error| anyhow!(error)),
))
}
Http => {
let (stream, _) = async_tungstenite::client_async(request, stream).await?;
Ok(Connection::new(
stream
.map_err(|error| anyhow!(error))
.sink_map_err(|error| anyhow!(error)),
))
}
}
})
}
@@ -1633,7 +1639,7 @@ mod tests {
use crate::test::FakeServer;
use clock::FakeSystemClock;
use gpui::{AppContext as _, BackgroundExecutor, TestAppContext};
use gpui::{BackgroundExecutor, TestAppContext};
use http_client::FakeHttpClient;
use parking_lot::Mutex;
use proto::TypedEnvelope;

View File

@@ -1,7 +1,11 @@
//! socks proxy
use anyhow::{Result, anyhow};
use futures::io::{AsyncRead, AsyncWrite};
use http_client::Uri;
use tokio_socks::tcp::{Socks4Stream, Socks5Stream};
use tokio_socks::{
io::Compat,
tcp::{Socks4Stream, Socks5Stream},
};
pub(crate) async fn connect_socks_proxy_stream(
proxy: Option<&Uri>,
@@ -10,7 +14,7 @@ pub(crate) async fn connect_socks_proxy_stream(
let stream = match parse_socks_proxy(proxy) {
Some((socks_proxy, SocksVersion::V4)) => {
let stream = Socks4Stream::connect_with_socket(
tokio::net::TcpStream::connect(socks_proxy).await?,
Compat::new(smol::net::TcpStream::connect(socks_proxy).await?),
rpc_host,
)
.await
@@ -19,15 +23,13 @@ pub(crate) async fn connect_socks_proxy_stream(
}
Some((socks_proxy, SocksVersion::V5)) => Box::new(
Socks5Stream::connect_with_socket(
tokio::net::TcpStream::connect(socks_proxy).await?,
Compat::new(smol::net::TcpStream::connect(socks_proxy).await?),
rpc_host,
)
.await
.map_err(|err| anyhow!("error connecting to socks {}", err))?,
) as Box<dyn AsyncReadWrite>,
None => {
Box::new(tokio::net::TcpStream::connect(rpc_host).await?) as Box<dyn AsyncReadWrite>
}
None => Box::new(smol::net::TcpStream::connect(rpc_host).await?) as Box<dyn AsyncReadWrite>,
};
Ok(stream)
}
@@ -58,11 +60,5 @@ enum SocksVersion {
V5,
}
pub(crate) trait AsyncReadWrite:
tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static
{
}
impl<T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static> AsyncReadWrite
for T
{
}
pub(crate) trait AsyncReadWrite: AsyncRead + AsyncWrite + Unpin + Send + 'static {}
impl<T: AsyncRead + AsyncWrite + Unpin + Send + 'static> AsyncReadWrite for T {}

View File

@@ -17,7 +17,7 @@ use std::io::Write;
use std::sync::LazyLock;
use std::time::Instant;
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use telemetry_events::{AssistantEventData, AssistantPhase, Event, EventRequestBody, EventWrapper};
use telemetry_events::{AssistantEvent, AssistantPhase, Event, EventRequestBody, EventWrapper};
use util::{ResultExt, TryFutureExt};
use worktree::{UpdatedEntriesSet, WorktreeId};
@@ -329,7 +329,7 @@ impl Telemetry {
drop(state);
}
pub fn report_assistant_event(self: &Arc<Self>, event: AssistantEventData) {
pub fn report_assistant_event(self: &Arc<Self>, event: AssistantEvent) {
let event_type = match event.phase {
AssistantPhase::Response => "Assistant Responded",
AssistantPhase::Invoked => "Assistant Invoked",

View File

@@ -33,6 +33,7 @@ clock.workspace = true
collections.workspace = true
dashmap.workspace = true
derive_more.workspace = true
buffer_diff.workspace = true
envy = "0.4.2"
futures.workspace = true
google_ai.workspace = true
@@ -84,7 +85,6 @@ assistant_slash_command.workspace = true
assistant_tool.workspace = true
async-trait.workspace = true
audio.workspace = true
buffer_diff.workspace = true
call = { workspace = true, features = ["test-support"] }
channel.workspace = true
client = { workspace = true, features = ["test-support"] }

View File

@@ -127,7 +127,7 @@ async fn update_billing_preferences(
SnowflakeRow::new(
"Spend Limit Updated",
Some(user.metrics_id),
user.metrics_id,
user.admin,
None,
json!({

View File

@@ -149,37 +149,6 @@ pub async fn post_crash(
"crash report"
);
if let Some(kinesis_client) = app.kinesis_client.clone() {
if let Some(stream) = app.config.kinesis_stream.clone() {
let properties = json!({
"app_version": report.header.app_version,
"os_version": report.header.os_version,
"os_name": "macOS",
"bundle_id": report.header.bundle_id,
"incident_id": report.header.incident_id,
"installation_id": installation_id,
"description": description,
"backtrace": summary,
});
let row = SnowflakeRow::new(
"Crash Reported",
None,
false,
Some(installation_id),
properties,
);
let data = serde_json::to_vec(&row)?;
kinesis_client
.put_record()
.stream_name(stream)
.partition_key(row.insert_id.unwrap_or_default())
.data(data.into())
.send()
.await
.log_err();
}
}
if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
let payload = slack::WebhookBody::new(|w| {
w.add_section(|s| s.text(slack::Text::markdown(description)))
@@ -345,8 +314,6 @@ pub async fn post_panic(
.ok();
}
let backtrace = panic.backtrace.join("\n");
tracing::error!(
service = "client",
version = %panic.app_version,
@@ -355,40 +322,10 @@ pub async fn post_panic(
incident_id = %incident_id,
installation_id = %panic.installation_id.clone().unwrap_or_default(),
description = %panic.payload,
backtrace = %backtrace,
backtrace = %panic.backtrace.join("\n"),
"panic report"
);
if let Some(kinesis_client) = app.kinesis_client.clone() {
if let Some(stream) = app.config.kinesis_stream.clone() {
let properties = json!({
"app_version": panic.app_version,
"os_name": panic.os_name,
"os_version": panic.os_version,
"incident_id": incident_id,
"installation_id": panic.installation_id,
"description": panic.payload,
"backtrace": backtrace,
});
let row = SnowflakeRow::new(
"Panic Reported",
None,
false,
panic.installation_id.clone(),
properties,
);
let data = serde_json::to_vec(&row)?;
kinesis_client
.put_record()
.stream_name(stream)
.partition_key(row.insert_id.unwrap_or_default())
.data(data.into())
.send()
.await
.log_err();
}
}
let backtrace = if panic.backtrace.len() > 25 {
let total = panic.backtrace.len();
format!(
@@ -774,7 +711,7 @@ pub struct SnowflakeRow {
impl SnowflakeRow {
pub fn new(
event_type: impl Into<String>,
metrics_id: Option<Uuid>,
metrics_id: Uuid,
is_staff: bool,
system_id: Option<String>,
event_properties: serde_json::Value,
@@ -783,7 +720,7 @@ impl SnowflakeRow {
time: chrono::Utc::now(),
event_type: event_type.into(),
device_id: system_id,
user_id: metrics_id.map(|id| id.to_string()),
user_id: Some(metrics_id.to_string()),
insert_id: Some(uuid::Uuid::new_v4().to_string()),
event_properties,
user_properties: Some(json!({"is_staff": is_staff})),

View File

@@ -591,7 +591,7 @@ async fn check_usage_limit(
SnowflakeRow::new(
"Language Model Rate Limited",
Some(claims.metrics_id),
claims.metrics_id,
claims.is_staff,
claims.system_id.clone(),
json!({
@@ -719,7 +719,7 @@ impl<S> Drop for TokenCountingStream<S> {
});
SnowflakeRow::new(
"Language Model Used",
Some(claims.metrics_id),
claims.metrics_id,
claims.is_staff,
claims.system_id.clone(),
properties,

View File

@@ -318,7 +318,6 @@ impl Server {
.add_request_handler(forward_read_only_project_request::<proto::OpenUncommittedDiff>)
.add_request_handler(forward_read_only_project_request::<proto::LspExtExpandMacro>)
.add_request_handler(forward_read_only_project_request::<proto::LspExtOpenDocs>)
.add_request_handler(forward_mutating_project_request::<proto::LspExtRunnables>)
.add_request_handler(
forward_read_only_project_request::<proto::LspExtSwitchSourceHeader>,
)
@@ -4154,13 +4153,13 @@ async fn get_llm_api_token(
fn to_axum_message(message: TungsteniteMessage) -> anyhow::Result<AxumMessage> {
let message = match message {
TungsteniteMessage::Text(payload) => AxumMessage::Text(payload.as_str().to_string()),
TungsteniteMessage::Binary(payload) => AxumMessage::Binary(payload.into()),
TungsteniteMessage::Ping(payload) => AxumMessage::Ping(payload.into()),
TungsteniteMessage::Pong(payload) => AxumMessage::Pong(payload.into()),
TungsteniteMessage::Text(payload) => AxumMessage::Text(payload),
TungsteniteMessage::Binary(payload) => AxumMessage::Binary(payload),
TungsteniteMessage::Ping(payload) => AxumMessage::Ping(payload),
TungsteniteMessage::Pong(payload) => AxumMessage::Pong(payload),
TungsteniteMessage::Close(frame) => AxumMessage::Close(frame.map(|frame| AxumCloseFrame {
code: frame.code.into(),
reason: frame.reason.as_str().to_owned().into(),
reason: frame.reason,
})),
// We should never receive a frame while reading the message, according
// to the `tungstenite` maintainers:
@@ -4180,14 +4179,14 @@ fn to_axum_message(message: TungsteniteMessage) -> anyhow::Result<AxumMessage> {
fn to_tungstenite_message(message: AxumMessage) -> TungsteniteMessage {
match message {
AxumMessage::Text(payload) => TungsteniteMessage::Text(payload.into()),
AxumMessage::Binary(payload) => TungsteniteMessage::Binary(payload.into()),
AxumMessage::Ping(payload) => TungsteniteMessage::Ping(payload.into()),
AxumMessage::Pong(payload) => TungsteniteMessage::Pong(payload.into()),
AxumMessage::Text(payload) => TungsteniteMessage::Text(payload),
AxumMessage::Binary(payload) => TungsteniteMessage::Binary(payload),
AxumMessage::Ping(payload) => TungsteniteMessage::Ping(payload),
AxumMessage::Pong(payload) => TungsteniteMessage::Pong(payload),
AxumMessage::Close(frame) => {
TungsteniteMessage::Close(frame.map(|frame| TungsteniteCloseFrame {
code: frame.code.into(),
reason: frame.reason.as_ref().into(),
reason: frame.reason,
}))
}
}

View File

@@ -6866,14 +6866,10 @@ async fn test_remote_git_branches(
assert_eq!(branches_b, branches_set);
cx_b.update(|cx| {
repo_b.update(cx, |repository, _cx| {
repository.change_branch(new_branch.to_string())
})
})
.await
.unwrap()
.unwrap();
cx_b.update(|cx| repo_b.read(cx).change_branch(new_branch.to_string()))
.await
.unwrap()
.unwrap();
executor.run_until_parked();
@@ -6896,18 +6892,18 @@ async fn test_remote_git_branches(
// Also try creating a new branch
cx_b.update(|cx| {
repo_b.update(cx, |repository, _cx| {
repository.create_branch("totally-new-branch".to_string())
})
repo_b
.read(cx)
.create_branch("totally-new-branch".to_string())
})
.await
.unwrap()
.unwrap();
cx_b.update(|cx| {
repo_b.update(cx, |repository, _cx| {
repository.change_branch("totally-new-branch".to_string())
})
repo_b
.read(cx)
.change_branch("totally-new-branch".to_string())
})
.await
.unwrap()

View File

@@ -283,7 +283,7 @@ async fn test_ssh_collaboration_git_branches(
let repo_b = cx_b.update(|cx| project_b.read(cx).active_repository(cx).unwrap());
let branches_b = cx_b
.update(|cx| repo_b.update(cx, |repo_b, _cx| repo_b.branches()))
.update(|cx| repo_b.read(cx).branches())
.await
.unwrap()
.unwrap();
@@ -297,14 +297,10 @@ async fn test_ssh_collaboration_git_branches(
assert_eq!(&branches_b, &branches_set);
cx_b.update(|cx| {
repo_b.update(cx, |repo_b, _cx| {
repo_b.change_branch(new_branch.to_string())
})
})
.await
.unwrap()
.unwrap();
cx_b.update(|cx| repo_b.read(cx).change_branch(new_branch.to_string()))
.await
.unwrap()
.unwrap();
executor.run_until_parked();
@@ -329,18 +325,18 @@ async fn test_ssh_collaboration_git_branches(
// Also try creating a new branch
cx_b.update(|cx| {
repo_b.update(cx, |repo_b, _cx| {
repo_b.create_branch("totally-new-branch".to_string())
})
repo_b
.read(cx)
.create_branch("totally-new-branch".to_string())
})
.await
.unwrap()
.unwrap();
cx_b.update(|cx| {
repo_b.update(cx, |repo_b, _cx| {
repo_b.change_branch("totally-new-branch".to_string())
})
repo_b
.read(cx)
.change_branch("totally-new-branch".to_string())
})
.await
.unwrap()

View File

@@ -309,13 +309,12 @@ impl MessageEditor {
.map(|mat| {
let (new_text, label) = completion_fn(&mat);
Completion {
replace_range: range.clone(),
old_range: range.clone(),
new_text,
label,
icon_path: None,
confirm: None,
documentation: None,
insert_text_mode: None,
source: CompletionSource::Custom,
}
})

View File

@@ -35,7 +35,7 @@ use ui::{
};
use util::{ResultExt, TryFutureExt, maybe};
use workspace::{
Deafen, LeaveCall, Mute, OpenChannelNotes, ScreenShare, ShareProject, Workspace,
OpenChannelNotes, Workspace,
dock::{DockPosition, Panel, PanelEvent},
notifications::{DetachAndPromptErr, NotifyResultExt, NotifyTaskExt},
};
@@ -80,57 +80,6 @@ pub fn init(cx: &mut App) {
});
}
});
// TODO: make it possible to bind this one to a held key for push to talk?
// how to make "toggle_on_modifiers_press" contextual?
workspace.register_action(|_, _: &Mute, window, cx| {
let room = ActiveCall::global(cx).read(cx).room().cloned();
if let Some(room) = room {
window.defer(cx, move |_window, cx| {
room.update(cx, |room, cx| room.toggle_mute(cx))
});
}
});
workspace.register_action(|_, _: &Deafen, window, cx| {
let room = ActiveCall::global(cx).read(cx).room().cloned();
if let Some(room) = room {
window.defer(cx, move |_window, cx| {
room.update(cx, |room, cx| room.toggle_deafen(cx))
});
}
});
workspace.register_action(|_, _: &LeaveCall, window, cx| {
CollabPanel::leave_call(window, cx);
});
workspace.register_action(|workspace, _: &ShareProject, window, cx| {
let project = workspace.project().clone();
println!("{project:?}");
window.defer(cx, move |_window, cx| {
ActiveCall::global(cx).update(cx, move |call, cx| {
if let Some(room) = call.room() {
println!("{room:?}");
if room.read(cx).is_sharing_project() {
call.unshare_project(project, cx).ok();
} else {
call.share_project(project, cx).detach_and_log_err(cx);
}
}
});
});
});
workspace.register_action(|_, _: &ScreenShare, window, cx| {
let room = ActiveCall::global(cx).read(cx).room().cloned();
if let Some(room) = room {
window.defer(cx, move |_window, cx| {
room.update(cx, |room, cx| {
if room.is_screen_sharing() {
room.unshare_screen(cx).ok();
} else {
room.share_screen(cx).detach_and_log_err(cx);
};
});
});
}
});
})
.detach();
}

View File

@@ -3,62 +3,37 @@ use std::ops::{Deref, DerefMut};
use std::sync::LazyLock;
use collections::HashMap;
use gpui::{
AnyElement, App, IntoElement, RenderOnce, SharedString, Window, div, pattern_slash, prelude::*,
px, rems,
};
use gpui::{AnyElement, App, IntoElement, RenderOnce, SharedString, Window, div, prelude::*, px};
use linkme::distributed_slice;
use parking_lot::RwLock;
use theme::ActiveTheme;
pub trait Component {
fn scope() -> ComponentScope {
ComponentScope::None
}
fn scope() -> Option<ComponentScope>;
fn name() -> &'static str {
std::any::type_name::<Self>()
}
/// Returns a name that the component should be sorted by.
///
/// Implement this if the component should be sorted in an alternate order than its name.
///
/// Example:
///
/// For example, to group related components together when sorted:
///
/// - Button -> ButtonA
/// - IconButton -> ButtonBIcon
/// - ToggleButton -> ButtonCToggle
///
/// This naming scheme keeps these components together and allows them to /// be sorted in a logical order.
fn sort_name() -> &'static str {
Self::name()
}
fn description() -> Option<&'static str> {
None
}
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
None
}
}
pub trait ComponentPreview: Component {
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement;
}
#[distributed_slice]
pub static __ALL_COMPONENTS: [fn()] = [..];
#[distributed_slice]
pub static __ALL_PREVIEWS: [fn()] = [..];
pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> =
LazyLock::new(|| RwLock::new(ComponentRegistry::new()));
pub struct ComponentRegistry {
components: Vec<(
ComponentScope,
// name
&'static str,
// sort name
&'static str,
// description
Option<&'static str>,
)>,
previews: HashMap<&'static str, fn(&mut Window, &mut App) -> Option<AnyElement>>,
components: Vec<(Option<ComponentScope>, &'static str, Option<&'static str>)>,
previews: HashMap<&'static str, fn(&mut Window, &mut App) -> AnyElement>,
}
impl ComponentRegistry {
@@ -72,16 +47,30 @@ impl ComponentRegistry {
pub fn init() {
let component_fns: Vec<_> = __ALL_COMPONENTS.iter().cloned().collect();
let preview_fns: Vec<_> = __ALL_PREVIEWS.iter().cloned().collect();
for f in component_fns {
f();
}
for f in preview_fns {
f();
}
}
pub fn register_component<T: Component>() {
let component_data = (T::scope(), T::name(), T::sort_name(), T::description());
let mut data = COMPONENT_DATA.write();
data.components.push(component_data);
data.previews.insert(T::name(), T::preview);
let component_data = (T::scope(), T::name(), T::description());
COMPONENT_DATA.write().components.push(component_data);
}
pub fn register_preview<T: ComponentPreview>() {
let preview_data = (
T::name(),
T::preview as fn(&mut Window, &mut App) -> AnyElement,
);
COMPONENT_DATA
.write()
.previews
.insert(preview_data.0, preview_data.1);
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -91,41 +80,29 @@ pub struct ComponentId(pub &'static str);
pub struct ComponentMetadata {
id: ComponentId,
name: SharedString,
sort_name: SharedString,
scope: ComponentScope,
scope: Option<ComponentScope>,
description: Option<SharedString>,
preview: Option<fn(&mut Window, &mut App) -> Option<AnyElement>>,
preview: Option<fn(&mut Window, &mut App) -> AnyElement>,
}
impl ComponentMetadata {
pub fn id(&self) -> ComponentId {
self.id.clone()
}
pub fn name(&self) -> SharedString {
self.name.clone()
}
pub fn sort_name(&self) -> SharedString {
self.sort_name.clone()
}
pub fn scopeless_name(&self) -> SharedString {
self.name
.clone()
.split("::")
.last()
.unwrap_or(&self.name)
.to_string()
.into()
}
pub fn scope(&self) -> ComponentScope {
pub fn scope(&self) -> Option<ComponentScope> {
self.scope.clone()
}
pub fn description(&self) -> Option<SharedString> {
self.description.clone()
}
pub fn preview(&self) -> Option<fn(&mut Window, &mut App) -> Option<AnyElement>> {
pub fn preview(&self) -> Option<fn(&mut Window, &mut App) -> AnyElement> {
self.preview
}
}
@@ -136,18 +113,26 @@ impl AllComponents {
pub fn new() -> Self {
AllComponents(HashMap::default())
}
/// Returns all components with previews
pub fn all_previews(&self) -> Vec<&ComponentMetadata> {
self.0.values().filter(|c| c.preview.is_some()).collect()
}
/// Returns all components with previews sorted by name
pub fn all_previews_sorted(&self) -> Vec<ComponentMetadata> {
let mut previews: Vec<ComponentMetadata> =
self.all_previews().into_iter().cloned().collect();
previews.sort_by_key(|a| a.name());
previews
}
/// Returns all components
pub fn all(&self) -> Vec<&ComponentMetadata> {
self.0.values().collect()
}
/// Returns all components sorted by name
pub fn all_sorted(&self) -> Vec<ComponentMetadata> {
let mut components: Vec<ComponentMetadata> = self.all().into_iter().cloned().collect();
components.sort_by_key(|a| a.name());
@@ -157,6 +142,7 @@ impl AllComponents {
impl Deref for AllComponents {
type Target = HashMap<ComponentId, ComponentMetadata>;
fn deref(&self) -> &Self::Target {
&self.0
}
@@ -171,127 +157,139 @@ impl DerefMut for AllComponents {
pub fn components() -> AllComponents {
let data = COMPONENT_DATA.read();
let mut all_components = AllComponents::new();
for (scope, name, sort_name, description) in &data.components {
for (scope, name, description) in &data.components {
let preview = data.previews.get(name).cloned();
let component_name = SharedString::new_static(name);
let sort_name = SharedString::new_static(sort_name);
let id = ComponentId(name);
all_components.insert(
id.clone(),
ComponentMetadata {
id,
name: component_name,
sort_name,
scope: scope.clone(),
description: description.map(Into::into),
preview,
},
);
}
all_components
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ComponentScope {
Collaboration,
DataDisplay,
Editor,
Images,
Input,
Layout,
Loading,
Navigation,
None,
Input,
Notification,
Overlays,
Status,
Typography,
Editor,
Collaboration,
VersionControl,
Unknown(SharedString),
}
impl Display for ComponentScope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ComponentScope::Collaboration => write!(f, "Collaboration"),
ComponentScope::DataDisplay => write!(f, "Data Display"),
ComponentScope::Editor => write!(f, "Editor"),
ComponentScope::Images => write!(f, "Images & Icons"),
ComponentScope::Input => write!(f, "Forms & Input"),
ComponentScope::Layout => write!(f, "Layout & Structure"),
ComponentScope::Loading => write!(f, "Loading & Progress"),
ComponentScope::Navigation => write!(f, "Navigation"),
ComponentScope::None => write!(f, "Unsorted"),
ComponentScope::Layout => write!(f, "Layout"),
ComponentScope::Input => write!(f, "Input"),
ComponentScope::Notification => write!(f, "Notification"),
ComponentScope::Overlays => write!(f, "Overlays & Layering"),
ComponentScope::Status => write!(f, "Status"),
ComponentScope::Typography => write!(f, "Typography"),
ComponentScope::Editor => write!(f, "Editor"),
ComponentScope::Collaboration => write!(f, "Collaboration"),
ComponentScope::VersionControl => write!(f, "Version Control"),
ComponentScope::Unknown(name) => write!(f, "Unknown: {}", name),
}
}
}
impl From<&str> for ComponentScope {
fn from(value: &str) -> Self {
match value {
"Layout" => ComponentScope::Layout,
"Input" => ComponentScope::Input,
"Notification" => ComponentScope::Notification,
"Editor" => ComponentScope::Editor,
"Collaboration" => ComponentScope::Collaboration,
"Version Control" | "VersionControl" => ComponentScope::VersionControl,
_ => ComponentScope::Unknown(SharedString::new(value)),
}
}
}
impl From<String> for ComponentScope {
fn from(value: String) -> Self {
match value.as_str() {
"Layout" => ComponentScope::Layout,
"Input" => ComponentScope::Input,
"Notification" => ComponentScope::Notification,
"Editor" => ComponentScope::Editor,
"Collaboration" => ComponentScope::Collaboration,
"Version Control" | "VersionControl" => ComponentScope::VersionControl,
_ => ComponentScope::Unknown(SharedString::new(value)),
}
}
}
/// Which side of the preview to show labels on
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExampleLabelSide {
/// Left side
Left,
/// Right side
Right,
/// Top side
#[default]
Top,
/// Bottom side
Bottom,
}
/// A single example of a component.
#[derive(IntoElement)]
pub struct ComponentExample {
pub variant_name: SharedString,
pub description: Option<SharedString>,
pub element: AnyElement,
variant_name: SharedString,
element: AnyElement,
label_side: ExampleLabelSide,
grow: bool,
}
impl RenderOnce for ComponentExample {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
div()
.w_full()
.flex()
.flex_col()
.gap_3()
.child(
div()
.child(self.variant_name.clone())
.text_size(rems(1.25))
.text_color(cx.theme().colors().text),
)
.when_some(self.description, |this, description| {
this.child(
div()
.text_size(rems(0.9375))
.text_color(cx.theme().colors().text_muted)
.child(description.clone()),
)
})
.child(
div()
.flex()
.w_full()
.rounded_xl()
.min_h(px(100.))
.justify_center()
.p_8()
.border_1()
.border_color(cx.theme().colors().border)
.bg(pattern_slash(
cx.theme().colors().surface_background.opacity(0.5),
24.0,
24.0,
))
.shadow_sm()
.child(self.element),
)
let base = div().flex();
let base = match self.label_side {
ExampleLabelSide::Right => base.flex_row(),
ExampleLabelSide::Left => base.flex_row_reverse(),
ExampleLabelSide::Bottom => base.flex_col(),
ExampleLabelSide::Top => base.flex_col_reverse(),
};
base.gap_2()
.p_2()
.text_size(px(10.))
.text_color(cx.theme().colors().text_muted)
.when(self.grow, |this| this.flex_1())
.when(!self.grow, |this| this.flex_none())
.child(self.element)
.child(self.variant_name)
.into_any_element()
}
}
impl ComponentExample {
/// Create a new example with the given variant name and example value.
pub fn new(variant_name: impl Into<SharedString>, element: AnyElement) -> Self {
Self {
variant_name: variant_name.into(),
element,
description: None,
label_side: ExampleLabelSide::default(),
grow: false,
}
}
pub fn description(mut self, description: impl Into<SharedString>) -> Self {
self.description = Some(description.into());
/// Set the example to grow to fill the available horizontal space.
pub fn grow(mut self) -> Self {
self.grow = true;
self
}
}
@@ -311,7 +309,7 @@ impl RenderOnce for ComponentExampleGroup {
.flex_col()
.text_sm()
.text_color(cx.theme().colors().text_muted)
.w_full()
.when(self.grow, |this| this.w_full().flex_1())
.when_some(self.title, |this, title| {
this.gap_4().child(
div()
@@ -338,7 +336,7 @@ impl RenderOnce for ComponentExampleGroup {
.child(
div()
.flex()
.flex_col()
.when(self.vertical, |this| this.flex_col())
.items_start()
.w_full()
.gap_6()
@@ -350,6 +348,7 @@ impl RenderOnce for ComponentExampleGroup {
}
impl ComponentExampleGroup {
/// Create a new group of examples with the given title.
pub fn new(examples: Vec<ComponentExample>) -> Self {
Self {
title: None,
@@ -358,6 +357,8 @@ impl ComponentExampleGroup {
vertical: false,
}
}
/// Create a new group of examples with the given title.
pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample>) -> Self {
Self {
title: Some(title.into()),
@@ -366,16 +367,21 @@ impl ComponentExampleGroup {
vertical: false,
}
}
/// Set the group to grow to fill the available horizontal space.
pub fn grow(mut self) -> Self {
self.grow = true;
self
}
/// Lay the group out vertically.
pub fn vertical(mut self) -> Self {
self.vertical = true;
self
}
}
/// Create a single example
pub fn single_example(
variant_name: impl Into<SharedString>,
example: AnyElement,
@@ -383,10 +389,12 @@ pub fn single_example(
ComponentExample::new(variant_name, example)
}
/// Create a group of examples without a title
pub fn example_group(examples: Vec<ComponentExample>) -> ComponentExampleGroup {
ComponentExampleGroup::new(examples)
}
/// Create a group of examples with a title
pub fn example_group_with_title(
title: impl Into<SharedString>,
examples: Vec<ComponentExample>,

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