Compare commits
65 Commits
quickfix
...
project-en
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8001cb9037 | ||
|
|
18144e7d63 | ||
|
|
f2bb495bff | ||
|
|
1106ba81ff | ||
|
|
3a9fe61516 | ||
|
|
28cca2527c | ||
|
|
3fde499eee | ||
|
|
f176715f1e | ||
|
|
4a302a28b7 | ||
|
|
3154b530a0 | ||
|
|
959d5f52a4 | ||
|
|
2771623bac | ||
|
|
920487706d | ||
|
|
3d5035d8a6 | ||
|
|
d047801295 | ||
|
|
e7d1d731f4 | ||
|
|
149db5cad4 | ||
|
|
992d791852 | ||
|
|
18bf07822e | ||
|
|
3ba619db5c | ||
|
|
3559464840 | ||
|
|
32a03ee58a | ||
|
|
912ce5dc1a | ||
|
|
97e8f499fd | ||
|
|
736d5e6da1 | ||
|
|
68023fdc44 | ||
|
|
253ef7ef44 | ||
|
|
687cc5d661 | ||
|
|
290d249e38 | ||
|
|
4922a000aa | ||
|
|
96c9824997 | ||
|
|
a83151730b | ||
|
|
99335128eb | ||
|
|
e8457656c6 | ||
|
|
b037c913e5 | ||
|
|
aca7f54773 | ||
|
|
10a3ad078c | ||
|
|
c4bbdd03c5 | ||
|
|
27a47233c9 | ||
|
|
0b8c14f0d6 | ||
|
|
800f40524b | ||
|
|
1f4a2d50a0 | ||
|
|
c706acbfcb | ||
|
|
3f19ae1689 | ||
|
|
4744c4ff7e | ||
|
|
aa168c696b | ||
|
|
ce2918ef46 | ||
|
|
3cfaadd2af | ||
|
|
b69219e9e0 | ||
|
|
0ae3518b1c | ||
|
|
73964353a4 | ||
|
|
bf6e7cb6ee | ||
|
|
36c4f6082c | ||
|
|
3d032bcf2c | ||
|
|
3e28fa2cc4 | ||
|
|
c42442974d | ||
|
|
f23b972203 | ||
|
|
07d9cd7e88 | ||
|
|
a48238701d | ||
|
|
d17d747c62 | ||
|
|
bc08df2dfd | ||
|
|
0f4b734d91 | ||
|
|
6df29d3279 | ||
|
|
647cca8c8d | ||
|
|
1f81674927 |
36
.github/ISSUE_TEMPLATE/01_bug_agent.yml
vendored
@@ -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
|
||||
@@ -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
|
||||
35
.github/ISSUE_TEMPLATE/03_bug_git.yml
vendored
@@ -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
|
||||
56
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
@@ -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
@@ -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
|
||||
@@ -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
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -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
|
||||
|
||||
2
.github/actions/run_tests_windows/action.yml
vendored
@@ -23,4 +23,4 @@ runs:
|
||||
- name: Run tests
|
||||
shell: pwsh
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: cargo nextest run --workspace --no-fail-fast --config='profile.dev.debug="limited"'
|
||||
run: cargo nextest run --workspace --no-fail-fast
|
||||
|
||||
53
.github/workflows/ci.yml
vendored
@@ -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
|
||||
}
|
||||
|
||||
@@ -708,51 +706,6 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
nix-build:
|
||||
timeout-minutes: 60
|
||||
name: Nix Build
|
||||
continue-on-error: true
|
||||
if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
system:
|
||||
- os: x86 Linux
|
||||
runner: buildjet-16vcpu-ubuntu-2204
|
||||
install_nix: true
|
||||
- os: arm Mac
|
||||
runner: [macOS, ARM64, test]
|
||||
install_nix: false
|
||||
runs-on: ${{ matrix.system.runner }}
|
||||
env:
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||
GIT_LFS_SKIP_SMUDGE: 1 # breaks the livekit rust sdk examples which we don't actually depend on
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
- name: Set path
|
||||
if: ${{ ! matrix.system.install_nix }}
|
||||
run: |
|
||||
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
|
||||
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
|
||||
|
||||
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
|
||||
if: ${{ matrix.system.install_nix }}
|
||||
with:
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: zed-industries
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
skipPush: true
|
||||
- run: nix build .#debug
|
||||
- name: Limit /nix/store to 50GB
|
||||
run: "[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d"
|
||||
|
||||
auto-release-preview:
|
||||
name: Auto release preview
|
||||
if: |
|
||||
|
||||
3
.github/workflows/release_nightly.yml
vendored
@@ -216,8 +216,7 @@ jobs:
|
||||
name: zed-industries
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
- run: nix build
|
||||
- name: Limit /nix/store to 50GB
|
||||
run: '[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d'
|
||||
- run: nix-collect-garbage -d
|
||||
|
||||
update-nightly-tag:
|
||||
name: Update nightly tag
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
[
|
||||
{
|
||||
"label": "Debug Zed with LLDB",
|
||||
"adapter": "LLDB",
|
||||
"adapter": "lldb",
|
||||
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||
"request": "launch",
|
||||
"cwd": "$ZED_WORKTREE_ROOT"
|
||||
},
|
||||
{
|
||||
"label": "Debug Zed with GDB",
|
||||
"adapter": "GDB",
|
||||
"adapter": "gdb",
|
||||
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||
"request": "launch",
|
||||
"cwd": "$ZED_WORKTREE_ROOT",
|
||||
|
||||
448
Cargo.lock
generated
@@ -52,7 +52,6 @@ dependencies = [
|
||||
name = "agent"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent_rules",
|
||||
"anyhow",
|
||||
"assistant_context_editor",
|
||||
"assistant_settings",
|
||||
@@ -120,62 +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 = "agent_rules"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"fs",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"prompt_store",
|
||||
"util",
|
||||
"workspace-hack",
|
||||
"worktree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
@@ -631,6 +580,43 @@ dependencies = [
|
||||
"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]]
|
||||
name = "assistant_settings"
|
||||
version = "0.1.0"
|
||||
@@ -761,8 +747,7 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"language_model",
|
||||
"log",
|
||||
"lsp-types",
|
||||
"lsp",
|
||||
"open",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
@@ -770,7 +755,6 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"ui",
|
||||
"unindent",
|
||||
"util",
|
||||
@@ -930,6 +914,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"
|
||||
@@ -1099,6 +1095,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"
|
||||
@@ -1112,10 +1120,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",
|
||||
@@ -1123,10 +1133,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]]
|
||||
@@ -2832,6 +2839,7 @@ name = "client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-native-tls",
|
||||
"async-recursion 0.3.2",
|
||||
"async-tungstenite",
|
||||
"chrono",
|
||||
@@ -2842,7 +2850,6 @@ dependencies = [
|
||||
"feature_flags",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"gpui_tokio",
|
||||
"http_client",
|
||||
"http_client_tls",
|
||||
"log",
|
||||
@@ -2864,7 +2871,6 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tiny_http",
|
||||
"tokio",
|
||||
"tokio-socks",
|
||||
"url",
|
||||
"util",
|
||||
@@ -3347,15 +3353,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"
|
||||
@@ -3459,19 +3456,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics-helmer-fork"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics-types 0.1.3",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics-types"
|
||||
version = "0.1.3"
|
||||
@@ -4054,7 +4038,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",
|
||||
@@ -4447,12 +4431,6 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
@@ -4488,32 +4466,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"
|
||||
@@ -6149,7 +6101,6 @@ dependencies = [
|
||||
"refineable",
|
||||
"reqwest_client",
|
||||
"resvg",
|
||||
"scap",
|
||||
"schemars",
|
||||
"seahash",
|
||||
"semantic_version",
|
||||
@@ -7482,7 +7433,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",
|
||||
@@ -7497,7 +7449,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",
|
||||
@@ -8173,7 +8126,6 @@ dependencies = [
|
||||
"objc",
|
||||
"parking_lot",
|
||||
"postage",
|
||||
"scap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
@@ -8390,7 +8342,6 @@ dependencies = [
|
||||
"node_runtime",
|
||||
"pulldown-cmark 0.12.2",
|
||||
"settings",
|
||||
"sum_tree",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
@@ -8711,6 +8662,7 @@ dependencies = [
|
||||
"collections",
|
||||
"ctor",
|
||||
"env_logger 0.11.8",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"itertools 0.14.0",
|
||||
@@ -8804,7 +8756,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",
|
||||
@@ -9185,18 +9138,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
||||
dependencies = [
|
||||
"malloc_buf",
|
||||
"objc_exception",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc-foundation"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
|
||||
dependencies = [
|
||||
"block",
|
||||
"objc",
|
||||
"objc_id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9401,24 +9342,6 @@ dependencies = [
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_exception"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_id"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
|
||||
dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
@@ -9583,6 +9506,15 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "300.4.2+3.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "168ce4e058f975fe43e89d9ccf78ca668601887ae736090aacc23ae353c298e2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.107"
|
||||
@@ -9591,21 +9523,11 @@ checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "optfield"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa59f025cde9c698fcb4fcb3533db4621795374065bee908215263488f2d2a1d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
@@ -10025,7 +9947,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",
|
||||
@@ -10062,7 +9984,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",
|
||||
@@ -10081,7 +10003,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",
|
||||
@@ -10096,7 +10018,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",
|
||||
@@ -10112,7 +10034,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",
|
||||
@@ -10121,7 +10043,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",
|
||||
@@ -10134,7 +10056,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",
|
||||
@@ -10152,7 +10074,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",
|
||||
@@ -10165,7 +10087,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",
|
||||
@@ -10178,7 +10100,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",
|
||||
@@ -10191,7 +10113,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",
|
||||
@@ -10204,7 +10126,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",
|
||||
@@ -10217,7 +10139,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",
|
||||
@@ -10230,7 +10152,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",
|
||||
@@ -10242,7 +10164,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",
|
||||
@@ -10263,7 +10185,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",
|
||||
@@ -10281,7 +10203,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",
|
||||
@@ -10298,7 +10220,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",
|
||||
@@ -10312,7 +10234,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",
|
||||
@@ -10327,7 +10249,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",
|
||||
@@ -10339,7 +10261,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",
|
||||
@@ -10351,7 +10273,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",
|
||||
@@ -10364,7 +10286,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",
|
||||
@@ -10376,13 +10298,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",
|
||||
@@ -10392,7 +10314,7 @@ dependencies = [
|
||||
"pet-python-utils",
|
||||
"pet-virtualenv",
|
||||
"regex",
|
||||
"winreg 0.55.0",
|
||||
"winreg 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11226,15 +11148,6 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.32.0"
|
||||
@@ -12130,7 +12043,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",
|
||||
@@ -12462,27 +12376,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scap"
|
||||
version = "0.0.8"
|
||||
source = "git+https://github.com/zed-industries/scap?rev=08f0a01417505cc0990b9931a37e5120db92e0d0#08f0a01417505cc0990b9931a37e5120db92e0d0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cocoa 0.25.0",
|
||||
"core-graphics-helmer-fork",
|
||||
"log",
|
||||
"objc",
|
||||
"rand 0.8.5",
|
||||
"screencapturekit",
|
||||
"screencapturekit-sys",
|
||||
"sysinfo",
|
||||
"tao-core-video-sys",
|
||||
"windows 0.61.1",
|
||||
"windows-capture",
|
||||
"x11",
|
||||
"xcb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.27"
|
||||
@@ -12549,29 +12442,6 @@ version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f6280af86e5f559536da57a45ebc84948833b3bee313a7dd25232e09c878a52"
|
||||
|
||||
[[package]]
|
||||
name = "screencapturekit"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e"
|
||||
dependencies = [
|
||||
"screencapturekit-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "screencapturekit-sys"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60"
|
||||
dependencies = [
|
||||
"block",
|
||||
"dispatch",
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
"objc_id",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scrypt"
|
||||
version = "0.11.0"
|
||||
@@ -14134,18 +14004,6 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb"
|
||||
|
||||
[[package]]
|
||||
name = "tao-core-video-sys"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
@@ -14191,14 +14049,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",
|
||||
@@ -14688,9 +14544,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",
|
||||
@@ -15359,6 +15215,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"
|
||||
@@ -15413,7 +15287,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"component",
|
||||
"documented",
|
||||
"gpui",
|
||||
"icons",
|
||||
"itertools 0.14.0",
|
||||
@@ -16800,20 +16673,6 @@ dependencies = [
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-capture"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6001b777f61cafce437201de46a019ed7f4afed3b669f02e5ce4e0759164cb3e"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"ctrlc",
|
||||
"parking_lot",
|
||||
"rayon",
|
||||
"thiserror 1.0.69",
|
||||
"windows 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.2.0"
|
||||
@@ -17263,16 +17122,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"
|
||||
@@ -17679,7 +17528,6 @@ dependencies = [
|
||||
"indexmap",
|
||||
"inout",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.13.0",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"libsqlite3-sys",
|
||||
@@ -17724,6 +17572,7 @@ dependencies = [
|
||||
"scopeguard",
|
||||
"sea-orm",
|
||||
"sea-query-binder",
|
||||
"security-framework 2.11.1",
|
||||
"security-framework 3.2.0",
|
||||
"security-framework-sys",
|
||||
"semver",
|
||||
@@ -17754,7 +17603,6 @@ dependencies = [
|
||||
"toml_edit",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tungstenite 0.26.2",
|
||||
"unicode-properties",
|
||||
"url",
|
||||
"uuid",
|
||||
@@ -17828,16 +17676,6 @@ dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11"
|
||||
version = "2.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11-clipboard"
|
||||
version = "0.9.3"
|
||||
@@ -17876,18 +17714,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcb"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1e2f212bb1a92cd8caac8051b829a6582ede155ccb60b5d5908b81b100952be"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
"quick-xml 0.30.0",
|
||||
"x11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcursor"
|
||||
version = "0.3.8"
|
||||
@@ -18108,7 +17934,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.183.0"
|
||||
version = "0.181.4"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"agent",
|
||||
|
||||
39
Cargo.toml
@@ -3,13 +3,12 @@ resolver = "2"
|
||||
members = [
|
||||
"crates/activity_indicator",
|
||||
"crates/agent",
|
||||
"crates/agent_rules",
|
||||
"crates/anthropic",
|
||||
"crates/askpass",
|
||||
"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",
|
||||
@@ -210,14 +209,13 @@ edition = "2024"
|
||||
|
||||
activity_indicator = { path = "crates/activity_indicator" }
|
||||
agent = { path = "crates/agent" }
|
||||
agent_rules = { path = "crates/agent_rules" }
|
||||
ai = { path = "crates/ai" }
|
||||
anthropic = { path = "crates/anthropic" }
|
||||
askpass = { path = "crates/askpass" }
|
||||
assets = { path = "crates/assets" }
|
||||
assistant = { path = "crates/assistant" }
|
||||
assistant_context_editor = { path = "crates/assistant_context_editor" }
|
||||
assistant_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" }
|
||||
@@ -296,7 +294,6 @@ livekit_api = { path = "crates/livekit_api" }
|
||||
livekit_client = { path = "crates/livekit_client" }
|
||||
lmstudio = { path = "crates/lmstudio" }
|
||||
lsp = { path = "crates/lsp" }
|
||||
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "1fff0dd12e2071c5667327394cfec163d2a466ab" }
|
||||
markdown = { path = "crates/markdown" }
|
||||
markdown_preview = { path = "crates/markdown_preview" }
|
||||
media = { path = "crates/media" }
|
||||
@@ -399,7 +396,7 @@ 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"] }
|
||||
@@ -428,7 +425,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"
|
||||
@@ -456,8 +453,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"
|
||||
@@ -466,22 +463,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"
|
||||
@@ -504,7 +500,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"
|
||||
@@ -512,7 +508,6 @@ 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 }
|
||||
schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
|
||||
semver = "1.0"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
@@ -552,7 +547,7 @@ time = { version = "0.3", features = [
|
||||
tiny_http = "0.8"
|
||||
toml = "0.8"
|
||||
tokio = { version = "1" }
|
||||
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
|
||||
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"]}
|
||||
tower-http = "0.4.4"
|
||||
tree-sitter = { version = "0.25.3", features = ["wasm"] }
|
||||
tree-sitter-bash = "0.23"
|
||||
@@ -664,6 +659,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" }
|
||||
|
||||
@@ -672,6 +668,7 @@ workspace-hack = { path = "tooling/workspace-hack" }
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
debug = "limited"
|
||||
codegen-units = 16
|
||||
|
||||
[profile.dev.package]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
|
||||
FROM rust:1.86-bookworm as builder
|
||||
FROM rust:1.81-bookworm as builder
|
||||
WORKDIR app
|
||||
COPY . .
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-down-right-icon lucide-arrow-down-right"><path d="m7 7 10 10"/><path d="M17 7v10H7"/></svg>
|
||||
|
Before Width: | Height: | Size: 300 B |
@@ -1 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-up-right-icon lucide-arrow-up-right"><path d="M7 7h10v10"/><path d="M7 17 17 7"/></svg>
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 2H6.5C6.5 1.86739 6.44732 1.74021 6.35355 1.64645C6.25979 1.55268 6.13261 1.5 6 1.5V2ZM2 1.5C1.72386 1.5 1.5 1.72386 1.5 2C1.5 2.27614 1.72386 2.5 2 2.5L2 1.5ZM5.5 6C5.5 6.27614 5.72386 6.5 6 6.5C6.27614 6.5 6.5 6.27614 6.5 6H5.5ZM1.64645 5.64645C1.45118 5.84171 1.45118 6.15829 1.64645 6.35355C1.84171 6.54882 2.15829 6.54882 2.35355 6.35355L1.64645 5.64645ZM6 1.5H2L2 2.5H6V1.5ZM5.5 2V6H6.5V2H5.5ZM5.64645 1.64645L1.64645 5.64645L2.35355 6.35355L6.35355 2.35355L5.64645 1.64645Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 296 B After Width: | Height: | Size: 608 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bug-off-icon lucide-bug-off"><path d="M15 7.13V6a3 3 0 0 0-5.14-2.1L8 2"/><path d="M14.12 3.88 16 2"/><path d="M22 13h-4v-2a4 4 0 0 0-4-4h-1.3"/><path d="M20.97 5c0 2.1-1.6 3.8-3.5 4"/><path d="m2 2 20 20"/><path d="M7.7 7.7A4 4 0 0 0 6 11v3a6 6 0 0 0 11.13 3.13"/><path d="M12 20v-8"/><path d="M6 13H2"/><path d="M3 21c0-2.1 1.7-3.9 3.8-4"/></svg>
|
||||
|
Before Width: | Height: | Size: 551 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-off-icon lucide-circle-off"><path d="m2 2 20 20"/><path d="M8.35 2.69A10 10 0 0 1 21.3 15.65"/><path d="M19.08 19.08A10 10 0 1 1 4.92 4.92"/></svg>
|
||||
|
Before Width: | Height: | Size: 357 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.1331 11.3776C10.2754 10.6665 10.1331 9.78593 11.1998 8.53327C11.82 7.80489 12.2664 6.96894 12.2664 6.04456C12.2664 4.91305 11.8169 3.82788 11.0168 3.02778C10.2167 2.22769 9.13152 1.7782 8.00001 1.7782C6.8685 1.7782 5.78334 2.22769 4.98324 3.02778C4.18314 3.82788 3.73364 4.91305 3.73364 6.04456C3.73364 6.75562 3.87586 7.6089 4.80024 8.53327C5.86683 9.80679 5.72462 10.6665 5.86683 11.3776M10.1331 11.3776V12.8821C10.1331 13.622 9.53341 14.2218 8.79353 14.2218H7.2065C6.46662 14.2218 5.86683 13.622 5.86683 12.8821V11.3776M10.1331 11.3776H5.86683" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 751 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-power-icon lucide-power"><path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.77.04"/></svg>
|
||||
|
Before Width: | Height: | Size: 294 B |
@@ -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 |
@@ -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 |
@@ -150,7 +150,7 @@
|
||||
"context": "AgentDiff",
|
||||
"bindings": {
|
||||
"ctrl-y": "agent::Keep",
|
||||
"ctrl-n": "agent::Reject"
|
||||
"ctrl-k ctrl-r": "agent::Reject"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -311,7 +311,6 @@
|
||||
"ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
|
||||
"ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||
"ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||
"ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes",
|
||||
"back": "pane::GoBack",
|
||||
"ctrl-alt--": "pane::GoBack",
|
||||
"ctrl-alt-_": "pane::GoForward",
|
||||
@@ -482,8 +481,6 @@
|
||||
"alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
|
||||
// also possible to spawn tasks by name:
|
||||
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
|
||||
// or by tag:
|
||||
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -532,7 +529,6 @@
|
||||
"context": "Editor && showing_completions",
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCompletion",
|
||||
"shift-enter": "editor::ConfirmCompletionReplace",
|
||||
"tab": "editor::ComposeCompletion"
|
||||
}
|
||||
},
|
||||
@@ -625,6 +621,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 +632,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,
|
||||
|
||||
@@ -242,7 +242,7 @@
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-y": "agent::Keep",
|
||||
"cmd-n": "agent::Reject"
|
||||
"cmd-alt-z": "agent::Reject"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -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,24 +338,6 @@
|
||||
"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": {
|
||||
@@ -455,8 +430,7 @@
|
||||
"cmd-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
|
||||
"cmd-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
|
||||
"cmd-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||
"cmd-k w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||
"cmd-k cmd-w": "workspace::CloseAllItemsAndPanes",
|
||||
"cmd-k cmd-w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||
"cmd-f": "project_search::ToggleFocus",
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-g": "search::SelectPreviousMatch",
|
||||
@@ -525,7 +499,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",
|
||||
@@ -633,8 +607,6 @@
|
||||
"ctrl-alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
|
||||
// also possible to spawn tasks by name:
|
||||
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
|
||||
// or by tag:
|
||||
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
|
||||
}
|
||||
},
|
||||
// Bindings from Sublime Text
|
||||
@@ -681,7 +653,6 @@
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCompletion",
|
||||
"shift-enter": "editor::ConfirmCompletionReplace",
|
||||
"tab": "editor::ComposeCompletion"
|
||||
}
|
||||
},
|
||||
@@ -1004,8 +975,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",
|
||||
|
||||
@@ -58,8 +58,7 @@
|
||||
"ctrl-shift-home": "editor::SelectToBeginning",
|
||||
"ctrl-shift-end": "editor::SelectToEnd",
|
||||
"ctrl-f8": "editor::ToggleBreakpoint",
|
||||
"ctrl-shift-f8": "editor::EditLogBreakpoint",
|
||||
"ctrl-shift-u": "editor::ToggleCase"
|
||||
"ctrl-shift-f8": "editor::EditLogBreakpoint"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -55,8 +55,7 @@
|
||||
"cmd-shift-home": "editor::SelectToBeginning",
|
||||
"cmd-shift-end": "editor::SelectToEnd",
|
||||
"ctrl-f8": "editor::ToggleBreakpoint",
|
||||
"ctrl-shift-f8": "editor::EditLogBreakpoint",
|
||||
"cmd-shift-u": "editor::ToggleCase"
|
||||
"ctrl-shift-f8": "editor::EditLogBreakpoint"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
@@ -341,106 +335,27 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == helix_normal && !menu",
|
||||
"context": "vim_mode == helix_normal",
|
||||
"bindings": {
|
||||
"escape": "editor::Cancel",
|
||||
"ctrl-[": "editor::Cancel",
|
||||
":": "command_palette::Toggle",
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"y": "editor::Copy",
|
||||
"shift-y": "vim::YankLine",
|
||||
"i": "vim::InsertBefore",
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
"a": "vim::InsertAfter",
|
||||
"shift-a": "vim::InsertEndOfLine",
|
||||
"o": "vim::InsertLineBelow",
|
||||
"shift-o": "vim::InsertLineAbove",
|
||||
"~": "vim::ChangeCase",
|
||||
"ctrl-a": "vim::Increment",
|
||||
"ctrl-x": "vim::Decrement",
|
||||
"p": "vim::Paste",
|
||||
"shift-p": ["vim::Paste", { "before": true }],
|
||||
"u": "vim::Undo",
|
||||
"ctrl-r": "vim::Redo",
|
||||
"r": "vim::PushReplace",
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
">": "vim::Indent",
|
||||
"<": "vim::Outdent",
|
||||
"=": "vim::AutoIndent",
|
||||
"g u": "vim::PushLowercase",
|
||||
"g shift-u": "vim::PushUppercase",
|
||||
"g ~": "vim::PushOppositeCase",
|
||||
"\"": "vim::PushRegister",
|
||||
"g q": "vim::PushRewrap",
|
||||
"g w": "vim::PushRewrap",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePreviousItem",
|
||||
"insert": "vim::InsertBefore",
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
"] x": "editor::SelectSmallerSyntaxNode",
|
||||
"] d": "editor::GoToDiagnostic",
|
||||
"[ d": "editor::GoToPreviousDiagnostic",
|
||||
"] c": "editor::GoToHunk",
|
||||
"[ c": "editor::GoToPreviousHunk",
|
||||
// Goto mode
|
||||
"g n": "pane::ActivateNextItem",
|
||||
"g p": "pane::ActivatePreviousItem",
|
||||
// "tab": "pane::ActivateNextItem",
|
||||
// "shift-tab": "pane::ActivatePrevItem",
|
||||
"shift-h": "pane::ActivatePreviousItem",
|
||||
"shift-l": "pane::ActivateNextItem",
|
||||
"g l": "vim::EndOfLine",
|
||||
"g h": "vim::StartOfLine",
|
||||
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
|
||||
"g e": "vim::EndOfDocument",
|
||||
"g y": "editor::GoToTypeDefinition",
|
||||
"g r": "editor::FindAllReferences", // zed specific
|
||||
"g t": "vim::WindowTop",
|
||||
"g c": "vim::WindowMiddle",
|
||||
"g b": "vim::WindowBottom",
|
||||
|
||||
"x": "editor::SelectLine",
|
||||
"shift-x": "editor::SelectLine",
|
||||
// Window mode
|
||||
"space w h": "workspace::ActivatePaneLeft",
|
||||
"space w l": "workspace::ActivatePaneRight",
|
||||
"space w k": "workspace::ActivatePaneUp",
|
||||
"space w j": "workspace::ActivatePaneDown",
|
||||
"space w q": "pane::CloseActiveItem",
|
||||
"space w s": "pane::SplitRight",
|
||||
"space w r": "pane::SplitRight",
|
||||
"space w v": "pane::SplitDown",
|
||||
"space w d": "pane::SplitDown",
|
||||
// Space mode
|
||||
"space f": "file_finder::Toggle",
|
||||
"space k": "editor::Hover",
|
||||
"space s": "outline::Toggle",
|
||||
"space shift-s": "project_symbols::Toggle",
|
||||
"space d": "editor::GoToDiagnostic",
|
||||
"space r": "editor::Rename",
|
||||
"space a": "editor::ToggleCodeActions",
|
||||
"space h": "editor::SelectAllMatches",
|
||||
"space c": "editor::ToggleComments",
|
||||
"space y": "editor::Copy",
|
||||
"space p": "editor::Paste",
|
||||
// Match mode
|
||||
"m m": "vim::Matching",
|
||||
"m i w": ["workspace::SendKeystrokes", "v i w"],
|
||||
"shift-u": "editor::Redo",
|
||||
"ctrl-c": "editor::ToggleComments",
|
||||
"d": "vim::HelixDelete",
|
||||
"c": "vim::Substitute",
|
||||
"shift-c": "editor::AddSelectionBelow"
|
||||
"w": "vim::NextWordStart",
|
||||
"e": "vim::NextWordEnd",
|
||||
"b": "vim::PreviousWordStart",
|
||||
|
||||
"h": "vim::Left",
|
||||
"j": "vim::Down",
|
||||
"k": "vim::Up",
|
||||
"l": "vim::Right"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ShowWordCompletions",
|
||||
"ctrl-n": "editor::ShowWordCompletions"
|
||||
"ctrl-p": "editor::ShowCompletions",
|
||||
"ctrl-n": "editor::ShowCompletions"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,19 +6,9 @@ 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 user’s 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.
|
||||
@@ -44,106 +34,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}}
|
||||
@@ -155,7 +45,7 @@ There are rules that apply to these root directories:
|
||||
{{#each worktrees}}
|
||||
{{#if rules_file}}
|
||||
|
||||
`{{root_name}}/{{rules_file.path_in_worktree}}`:
|
||||
`{{root_name}}/{{rules_file.rel_path}}`:
|
||||
|
||||
``````
|
||||
{{{rules_file.text}}}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
In your response, and also when thinking, make sure to remember and follow my instructions about how to format code blocks (and don't ever mention that you are remembering it, just follow the instructions).
|
||||
8
assets/prompts/project_slash_command.hbs
Normal 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}}}
|
||||
@@ -658,7 +658,6 @@
|
||||
"tools": {
|
||||
"bash": true,
|
||||
"batch_tool": true,
|
||||
"code_actions": true,
|
||||
"code_symbols": true,
|
||||
"copy_path": false,
|
||||
"create_file": true,
|
||||
@@ -672,7 +671,6 @@
|
||||
"path_search": true,
|
||||
"read_file": true,
|
||||
"regex_search": true,
|
||||
"rename": true,
|
||||
"symbol_info": true,
|
||||
"thinking": true
|
||||
}
|
||||
@@ -1138,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
|
||||
@@ -1203,27 +1200,7 @@
|
||||
// When set to 0, waits indefinitely.
|
||||
//
|
||||
// Default: 0
|
||||
"lsp_fetch_timeout_ms": 0,
|
||||
// Controls what range to replace when accepting LSP completions.
|
||||
//
|
||||
// When LSP servers give an `InsertReplaceEdit` completion, they provides two ranges: `insert` and `replace`. Usually, `insert`
|
||||
// contains the word prefix before your cursor and `replace` contains the whole word.
|
||||
//
|
||||
// Effectively, this setting just changes whether Zed will use the received range for `insert` or `replace`, so the results may
|
||||
// differ depending on the underlying LSP server.
|
||||
//
|
||||
// Possible values:
|
||||
// 1. "insert"
|
||||
// Replaces text before the cursor, using the `insert` range described in the LSP specification.
|
||||
// 2. "replace"
|
||||
// Replaces text before and after the cursor, using the `replace` range described in the LSP specification.
|
||||
// 3. "replace_subsequence"
|
||||
// Behaves like `"replace"` if the text that would be replaced is a subsequence of the completion text,
|
||||
// and like `"insert"` otherwise.
|
||||
// 4. "replace_suffix"
|
||||
// Behaves like `"replace"` if the text after the cursor is a suffix of the completion, and like
|
||||
// `"insert"` otherwise.
|
||||
"lsp_insert_mode": "replace_suffix"
|
||||
"lsp_fetch_timeout_ms": 0
|
||||
},
|
||||
// Different settings for specific languages.
|
||||
"languages": {
|
||||
@@ -1459,8 +1436,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": {
|
||||
|
||||
@@ -43,8 +43,6 @@
|
||||
// "args": ["--login"]
|
||||
// }
|
||||
// }
|
||||
"shell": "system",
|
||||
// Represents the tags for inline runnable indicators, or spawning multiple tasks at once.
|
||||
"tags": []
|
||||
"shell": "system"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -19,7 +19,6 @@ test-support = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
agent_rules.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_context_editor.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
@@ -85,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
|
||||
|
||||
@@ -792,11 +792,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>> {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -178,7 +178,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>,
|
||||
@@ -227,14 +226,14 @@ impl AssistantPanel {
|
||||
) -> Self {
|
||||
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let project = workspace.project();
|
||||
let project = workspace.project().clone();
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
let workspace = workspace.weak_handle();
|
||||
let weak_self = cx.entity().downgrade();
|
||||
|
||||
let message_editor_context_store = cx.new(|_cx| {
|
||||
crate::context_store::ContextStore::new(
|
||||
project.downgrade(),
|
||||
workspace.clone(),
|
||||
Some(thread_store.downgrade()),
|
||||
)
|
||||
});
|
||||
@@ -293,7 +292,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,12 +337,11 @@ 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(
|
||||
self.project.downgrade(),
|
||||
self.workspace.clone(),
|
||||
Some(self.thread_store.downgrade()),
|
||||
)
|
||||
});
|
||||
@@ -403,7 +400,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 +450,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 +487,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,11 +509,10 @@ 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.project.downgrade(),
|
||||
this.workspace.clone(),
|
||||
Some(this.thread_store.downgrade()),
|
||||
)
|
||||
});
|
||||
@@ -552,17 +543,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,
|
||||
@@ -582,7 +562,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)
|
||||
@@ -710,29 +690,6 @@ impl AssistantPanel {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for AssistantPanel {
|
||||
@@ -855,11 +812,9 @@ impl AssistantPanel {
|
||||
if is_empty {
|
||||
Label::new(Thread::DEFAULT_SUMMARY.clone())
|
||||
.truncate()
|
||||
.ml_2()
|
||||
.into_any_element()
|
||||
} else if summary.is_none() {
|
||||
Label::new(LOADING_SUMMARY_PLACEHOLDER)
|
||||
.ml_2()
|
||||
.truncate()
|
||||
.into_any_element()
|
||||
} else {
|
||||
@@ -875,7 +830,7 @@ impl AssistantPanel {
|
||||
})
|
||||
.unwrap_or_else(|| SharedString::from(LOADING_SUMMARY_PLACEHOLDER));
|
||||
|
||||
Label::new(title).ml_2().truncate().into_any_element()
|
||||
Label::new(title).truncate().into_any_element()
|
||||
}
|
||||
ActiveView::History => Label::new("History").truncate().into_any_element(),
|
||||
ActiveView::Configuration => Label::new("Settings").truncate().into_any_element(),
|
||||
@@ -884,6 +839,7 @@ impl AssistantPanel {
|
||||
h_flex()
|
||||
.key_context("TitleEditor")
|
||||
.id("TitleEditor")
|
||||
.pl_2()
|
||||
.flex_grow()
|
||||
.w_full()
|
||||
.max_w_full()
|
||||
@@ -902,39 +858,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(
|
||||
div().pl_1().child(
|
||||
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))
|
||||
@@ -945,13 +874,7 @@ impl AssistantPanel {
|
||||
.bg(cx.theme().colors().tab_bar_background)
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.children(go_back_button)
|
||||
.child(self.render_title_view(window, cx)),
|
||||
)
|
||||
.child(self.render_title_view(window, cx))
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
@@ -1046,27 +969,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(
|
||||
@@ -1083,7 +985,13 @@ impl AssistantPanel {
|
||||
cx,
|
||||
|menu, _window, _cx| {
|
||||
menu.action(
|
||||
"New Text Thread",
|
||||
"New Thread",
|
||||
Box::new(NewThread {
|
||||
from_thread_id: None,
|
||||
}),
|
||||
)
|
||||
.action(
|
||||
"New Prompt Editor",
|
||||
NewPromptEditor.boxed_clone(),
|
||||
)
|
||||
.when(!is_empty, |menu| {
|
||||
@@ -1095,6 +1003,7 @@ impl AssistantPanel {
|
||||
)
|
||||
})
|
||||
.separator()
|
||||
.action("History", OpenHistory.boxed_clone())
|
||||
.action("Settings", OpenConfiguration.boxed_clone())
|
||||
},
|
||||
))
|
||||
@@ -1591,7 +1500,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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
@@ -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(),
|
||||
@@ -867,7 +862,7 @@ mod tests {
|
||||
.expect("Opened test file wasn't an editor")
|
||||
});
|
||||
|
||||
let context_store = cx.new(|_| ContextStore::new(project.downgrade(), None));
|
||||
let context_store = cx.new(|_| ContextStore::new(workspace.downgrade(), None));
|
||||
|
||||
let editor_entity = editor.downgrade();
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
|
||||
@@ -8,10 +8,11 @@ use futures::future::join_all;
|
||||
use futures::{self, Future, FutureExt, future};
|
||||
use gpui::{App, AppContext as _, Context, Entity, SharedString, Task, WeakEntity};
|
||||
use language::{Buffer, File};
|
||||
use project::{Project, ProjectItem, ProjectPath, Worktree};
|
||||
use project::{ProjectItem, ProjectPath, Worktree};
|
||||
use rope::Rope;
|
||||
use text::{Anchor, BufferId, OffsetRangeExt};
|
||||
use util::{ResultExt as _, maybe};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::ThreadStore;
|
||||
use crate::context::{
|
||||
@@ -22,7 +23,7 @@ use crate::context_strip::SuggestedContext;
|
||||
use crate::thread::{Thread, ThreadId};
|
||||
|
||||
pub struct ContextStore {
|
||||
project: WeakEntity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
context: Vec<AssistantContext>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
// TODO: If an EntityId is used for all context types (like BufferId), can remove ContextId.
|
||||
@@ -39,11 +40,11 @@ pub struct ContextStore {
|
||||
|
||||
impl ContextStore {
|
||||
pub fn new(
|
||||
project: WeakEntity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
project,
|
||||
workspace,
|
||||
thread_store,
|
||||
context: Vec::new(),
|
||||
next_context_id: ContextId(0),
|
||||
@@ -80,7 +81,12 @@ impl ContextStore {
|
||||
remove_if_exists: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(project) = self.project.upgrade() else {
|
||||
let workspace = self.workspace.clone();
|
||||
|
||||
let Some(project) = workspace
|
||||
.upgrade()
|
||||
.map(|workspace| workspace.read(cx).project().clone())
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("failed to read project")));
|
||||
};
|
||||
|
||||
@@ -155,7 +161,11 @@ impl ContextStore {
|
||||
remove_if_exists: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(project) = self.project.upgrade() else {
|
||||
let workspace = self.workspace.clone();
|
||||
let Some(project) = workspace
|
||||
.upgrade()
|
||||
.map(|workspace| workspace.read(cx).project().clone())
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("failed to read project")));
|
||||
};
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ 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::*;
|
||||
@@ -262,7 +262,13 @@ impl InlineAssistant {
|
||||
}
|
||||
InlineAssistTarget::Terminal(active_terminal) => {
|
||||
TerminalInlineAssistant::update_global(cx, |assistant, cx| {
|
||||
assistant.assist(&active_terminal, cx.entity(), thread_store, window, cx)
|
||||
assistant.assist(
|
||||
&active_terminal,
|
||||
cx.entity().downgrade(),
|
||||
thread_store,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
};
|
||||
@@ -316,13 +322,6 @@ impl InlineAssistant {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let Some(project) = workspace
|
||||
.upgrade()
|
||||
.map(|workspace| workspace.read(cx).project().downgrade())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (snapshot, initial_selections) = editor.update(cx, |editor, cx| {
|
||||
(
|
||||
editor.snapshot(window, cx),
|
||||
@@ -403,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,
|
||||
@@ -426,7 +425,7 @@ impl InlineAssistant {
|
||||
for range in codegen_ranges {
|
||||
let assist_id = self.next_assist_id.post_inc();
|
||||
let context_store =
|
||||
cx.new(|_cx| ContextStore::new(project.clone(), thread_store.clone()));
|
||||
cx.new(|_cx| ContextStore::new(workspace.clone(), thread_store.clone()));
|
||||
let codegen = cx.new(|cx| {
|
||||
BufferCodegen::new(
|
||||
editor.read(cx).buffer().clone(),
|
||||
@@ -520,7 +519,7 @@ impl InlineAssistant {
|
||||
initial_prompt: String,
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
focus: bool,
|
||||
workspace: Entity<Workspace>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
@@ -538,8 +537,8 @@ impl InlineAssistant {
|
||||
range.end = range.end.bias_right(&snapshot);
|
||||
}
|
||||
|
||||
let project = workspace.read(cx).project().downgrade();
|
||||
let context_store = cx.new(|_cx| ContextStore::new(project, thread_store.clone()));
|
||||
let context_store =
|
||||
cx.new(|_cx| ContextStore::new(workspace.clone(), thread_store.clone()));
|
||||
|
||||
let codegen = cx.new(|cx| {
|
||||
BufferCodegen::new(
|
||||
@@ -563,7 +562,7 @@ impl InlineAssistant {
|
||||
codegen.clone(),
|
||||
self.fs.clone(),
|
||||
context_store,
|
||||
workspace.downgrade(),
|
||||
workspace.clone(),
|
||||
thread_store,
|
||||
window,
|
||||
cx,
|
||||
@@ -590,7 +589,7 @@ impl InlineAssistant {
|
||||
end_block_id,
|
||||
range,
|
||||
codegen.clone(),
|
||||
workspace.downgrade(),
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
@@ -988,7 +987,7 @@ impl InlineAssistant {
|
||||
.map(|language| language.name())
|
||||
});
|
||||
report_assistant_event(
|
||||
AssistantEventData {
|
||||
AssistantEvent {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
message_id,
|
||||
@@ -1780,7 +1779,6 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
||||
let workspace = self.workspace.clone();
|
||||
let thread_store = self.thread_store.clone();
|
||||
window.spawn(cx, async move |cx| {
|
||||
let workspace = workspace.upgrade().context("workspace was released")?;
|
||||
let editor = editor.upgrade().context("editor was released")?;
|
||||
let range = editor
|
||||
.update(cx, |editor, cx| {
|
||||
|
||||
@@ -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();
|
||||
@@ -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",
|
||||
))
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
@@ -66,7 +66,7 @@ impl TerminalInlineAssistant {
|
||||
pub fn assist(
|
||||
&mut self,
|
||||
terminal_view: &Entity<TerminalView>,
|
||||
workspace: Entity<Workspace>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
@@ -75,8 +75,8 @@ impl TerminalInlineAssistant {
|
||||
let assist_id = self.next_assist_id.post_inc();
|
||||
let prompt_buffer =
|
||||
cx.new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(String::new(), cx)), cx));
|
||||
let project = workspace.read(cx).project().downgrade();
|
||||
let context_store = cx.new(|_cx| ContextStore::new(project, thread_store.clone()));
|
||||
let context_store =
|
||||
cx.new(|_cx| ContextStore::new(workspace.clone(), thread_store.clone()));
|
||||
let codegen = cx.new(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
|
||||
|
||||
let prompt_editor = cx.new(|cx| {
|
||||
@@ -87,7 +87,7 @@ impl TerminalInlineAssistant {
|
||||
codegen,
|
||||
self.fs.clone(),
|
||||
context_store.clone(),
|
||||
workspace.downgrade(),
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
window,
|
||||
cx,
|
||||
@@ -106,7 +106,7 @@ impl TerminalInlineAssistant {
|
||||
assist_id,
|
||||
terminal_view,
|
||||
prompt_editor,
|
||||
workspace.downgrade(),
|
||||
workspace.clone(),
|
||||
context_store,
|
||||
window,
|
||||
cx,
|
||||
@@ -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(),
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::io::Write;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent_rules::load_worktree_rules_file;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use assistant_tool::{ActionLog, Tool, ToolWorkingSet};
|
||||
@@ -22,11 +21,13 @@ use language_model::{
|
||||
};
|
||||
use project::git_store::{GitStore, GitStoreCheckpoint, RepositoryState};
|
||||
use project::{Project, Worktree};
|
||||
use prompt_store::{AssistantSystemPromptContext, PromptBuilder, WorktreeInfoForSystemPrompt};
|
||||
use prompt_store::{
|
||||
AssistantSystemPromptContext, PromptBuilder, RulesFile, WorktreeInfoForSystemPrompt,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use util::{ResultExt as _, TryFutureExt as _, post_inc};
|
||||
use util::{ResultExt as _, TryFutureExt as _, maybe, post_inc};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::context::{AssistantContext, ContextId, format_context_as_string};
|
||||
@@ -470,11 +471,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| {
|
||||
@@ -505,11 +506,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(),
|
||||
@@ -521,7 +522,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();
|
||||
@@ -532,7 +533,7 @@ impl Thread {
|
||||
}
|
||||
|
||||
git_store
|
||||
.update(cx, |store, cx| {
|
||||
.read_with(cx, |store, cx| {
|
||||
store.delete_checkpoint(final_checkpoint, cx)
|
||||
})?
|
||||
.detach();
|
||||
@@ -853,36 +854,67 @@ impl Thread {
|
||||
let root_name = worktree.root_name().into();
|
||||
let abs_path = worktree.abs_path();
|
||||
|
||||
let rules_task = load_worktree_rules_file(fs, worktree, cx);
|
||||
let Some(rules_task) = rules_task else {
|
||||
return Task::ready((
|
||||
// Note that Cline supports `.clinerules` being a directory, but that is not currently
|
||||
// supported. This doesn't seem to occur often in GitHub repositories.
|
||||
const RULES_FILE_NAMES: [&'static str; 6] = [
|
||||
".rules",
|
||||
".cursorrules",
|
||||
".windsurfrules",
|
||||
".clinerules",
|
||||
".github/copilot-instructions.md",
|
||||
"CLAUDE.md",
|
||||
];
|
||||
let selected_rules_file = RULES_FILE_NAMES
|
||||
.into_iter()
|
||||
.filter_map(|name| {
|
||||
worktree
|
||||
.entry_for_path(name)
|
||||
.filter(|entry| entry.is_file())
|
||||
.map(|entry| (entry.path.clone(), worktree.absolutize(&entry.path)))
|
||||
})
|
||||
.next();
|
||||
|
||||
if let Some((rel_rules_path, abs_rules_path)) = selected_rules_file {
|
||||
cx.spawn(async move |_| {
|
||||
let rules_file_result = maybe!(async move {
|
||||
let abs_rules_path = abs_rules_path?;
|
||||
let text = fs.load(&abs_rules_path).await.with_context(|| {
|
||||
format!("Failed to load assistant rules file {:?}", abs_rules_path)
|
||||
})?;
|
||||
anyhow::Ok(RulesFile {
|
||||
rel_path: rel_rules_path,
|
||||
abs_path: abs_rules_path.into(),
|
||||
text: text.trim().to_string(),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
let (rules_file, rules_file_error) = match rules_file_result {
|
||||
Ok(rules_file) => (Some(rules_file), None),
|
||||
Err(err) => (
|
||||
None,
|
||||
Some(ThreadError::Message {
|
||||
header: "Error loading rules file".into(),
|
||||
message: format!("{err}").into(),
|
||||
}),
|
||||
),
|
||||
};
|
||||
let worktree_info = WorktreeInfoForSystemPrompt {
|
||||
root_name,
|
||||
abs_path,
|
||||
rules_file,
|
||||
};
|
||||
(worktree_info, rules_file_error)
|
||||
})
|
||||
} else {
|
||||
Task::ready((
|
||||
WorktreeInfoForSystemPrompt {
|
||||
root_name,
|
||||
abs_path,
|
||||
rules_file: None,
|
||||
},
|
||||
None,
|
||||
));
|
||||
};
|
||||
|
||||
cx.spawn(async move |_| {
|
||||
let (rules_file, rules_file_error) = match rules_task.await {
|
||||
Ok(rules_file) => (Some(rules_file), None),
|
||||
Err(err) => (
|
||||
None,
|
||||
Some(ThreadError::Message {
|
||||
header: "Error loading rules file".into(),
|
||||
message: format!("{err}").into(),
|
||||
}),
|
||||
),
|
||||
};
|
||||
let worktree_info = WorktreeInfoForSystemPrompt {
|
||||
root_name,
|
||||
abs_path,
|
||||
rules_file,
|
||||
};
|
||||
(worktree_info, rules_file_error)
|
||||
})
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_to_model(
|
||||
@@ -997,21 +1029,6 @@ impl Thread {
|
||||
|
||||
self.attached_tracked_files_state(&mut request.messages, cx);
|
||||
|
||||
// Add reminder to the last user message about
|
||||
// easily-forgotten aspects of the system prompt.
|
||||
if let Some(last_user_message) = request
|
||||
.messages
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find(|msg| msg.role == Role::User)
|
||||
{
|
||||
last_user_message
|
||||
.content
|
||||
.push(MessageContent::Text(system_prompt_reminder(
|
||||
&self.prompt_builder,
|
||||
)));
|
||||
}
|
||||
|
||||
request
|
||||
}
|
||||
|
||||
@@ -1397,7 +1414,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(
|
||||
@@ -1470,7 +1487,6 @@ impl Thread {
|
||||
tool_use_id.clone(),
|
||||
tool_name,
|
||||
output,
|
||||
cx,
|
||||
);
|
||||
|
||||
cx.emit(ThreadEvent::ToolFinished {
|
||||
@@ -1634,10 +1650,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,
|
||||
@@ -1815,7 +1831,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,
|
||||
@@ -1825,12 +1841,6 @@ impl Thread {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn system_prompt_reminder(prompt_builder: &prompt_store::PromptBuilder) -> String {
|
||||
prompt_builder
|
||||
.generate_assistant_system_prompt_reminder()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ThreadError {
|
||||
PaymentRequired,
|
||||
@@ -1900,7 +1910,7 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
|
||||
let (_workspace, _thread_store, thread, context_store, prompt_builder) =
|
||||
let (_workspace, _thread_store, thread, context_store) =
|
||||
setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
add_file_to_context(&project, &context_store, "test/code.rs", cx)
|
||||
@@ -1954,14 +1964,8 @@ fn main() {{
|
||||
});
|
||||
|
||||
assert_eq!(request.messages.len(), 1);
|
||||
let actual_message = request.messages[0].string_contents();
|
||||
let expected_content = format!(
|
||||
"{}Please explain this code{}",
|
||||
expected_context,
|
||||
system_prompt_reminder(&prompt_builder)
|
||||
);
|
||||
|
||||
assert_eq!(actual_message, expected_content);
|
||||
let expected_full_message = format!("{}Please explain this code", expected_context);
|
||||
assert_eq!(request.messages[0].string_contents(), expected_full_message);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -1978,7 +1982,7 @@ fn main() {{
|
||||
)
|
||||
.await;
|
||||
|
||||
let (_, _thread_store, thread, context_store, _prompt_builder) =
|
||||
let (_, _thread_store, thread, context_store) =
|
||||
setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Open files individually
|
||||
@@ -2078,7 +2082,7 @@ fn main() {{
|
||||
)
|
||||
.await;
|
||||
|
||||
let (_, _thread_store, thread, _context_store, prompt_builder) =
|
||||
let (_, _thread_store, thread, _context_store) =
|
||||
setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Insert user message without any context (empty context vector)
|
||||
@@ -2104,14 +2108,11 @@ fn main() {{
|
||||
});
|
||||
|
||||
assert_eq!(request.messages.len(), 1);
|
||||
let actual_message = request.messages[0].string_contents();
|
||||
let expected_content = format!(
|
||||
"What is the best way to learn Rust?{}",
|
||||
system_prompt_reminder(&prompt_builder)
|
||||
assert_eq!(
|
||||
request.messages[0].string_contents(),
|
||||
"What is the best way to learn Rust?"
|
||||
);
|
||||
|
||||
assert_eq!(actual_message, expected_content);
|
||||
|
||||
// Add second message, also without context
|
||||
let message2_id = thread.update(cx, |thread, cx| {
|
||||
thread.insert_user_message("Are there any good books?", vec![], None, cx)
|
||||
@@ -2127,17 +2128,14 @@ fn main() {{
|
||||
});
|
||||
|
||||
assert_eq!(request.messages.len(), 2);
|
||||
// First message should be the system prompt
|
||||
assert_eq!(request.messages[0].role, Role::User);
|
||||
|
||||
// Second message should be the user message with prompt reminder
|
||||
let actual_message = request.messages[1].string_contents();
|
||||
let expected_content = format!(
|
||||
"Are there any good books?{}",
|
||||
system_prompt_reminder(&prompt_builder)
|
||||
assert_eq!(
|
||||
request.messages[0].string_contents(),
|
||||
"What is the best way to learn Rust?"
|
||||
);
|
||||
assert_eq!(
|
||||
request.messages[1].string_contents(),
|
||||
"Are there any good books?"
|
||||
);
|
||||
|
||||
assert_eq!(actual_message, expected_content);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -2150,7 +2148,7 @@ fn main() {{
|
||||
)
|
||||
.await;
|
||||
|
||||
let (_workspace, _thread_store, thread, context_store, prompt_builder) =
|
||||
let (_workspace, _thread_store, thread, context_store) =
|
||||
setup_test_environment(cx, project.clone()).await;
|
||||
|
||||
// Open buffer and add it to context
|
||||
@@ -2210,14 +2208,11 @@ fn main() {{
|
||||
// The last message should be the stale buffer notification
|
||||
assert_eq!(last_message.role, Role::User);
|
||||
|
||||
let actual_message = last_message.string_contents();
|
||||
let expected_content = format!(
|
||||
"These files changed since last read:\n- code.rs\n{}",
|
||||
system_prompt_reminder(&prompt_builder)
|
||||
);
|
||||
|
||||
// Check the exact content of the message
|
||||
let expected_content = "These files changed since last read:\n- code.rs\n";
|
||||
assert_eq!(
|
||||
actual_message, expected_content,
|
||||
last_message.string_contents(),
|
||||
expected_content,
|
||||
"Last message should be exactly the stale buffer notification"
|
||||
);
|
||||
}
|
||||
@@ -2255,27 +2250,24 @@ fn main() {{
|
||||
Entity<ThreadStore>,
|
||||
Entity<Thread>,
|
||||
Entity<ContextStore>,
|
||||
Arc<PromptBuilder>,
|
||||
) {
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
|
||||
let thread_store = cx.update(|_, cx| {
|
||||
ThreadStore::new(project.clone(), Arc::default(), prompt_builder.clone(), cx).unwrap()
|
||||
ThreadStore::new(
|
||||
project.clone(),
|
||||
Arc::default(),
|
||||
Arc::new(PromptBuilder::new(None).unwrap()),
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
|
||||
let context_store = cx.new(|_cx| ContextStore::new(project.downgrade(), None));
|
||||
let context_store = cx.new(|_cx| ContextStore::new(workspace.downgrade(), None));
|
||||
|
||||
(
|
||||
workspace,
|
||||
thread_store,
|
||||
thread,
|
||||
context_store,
|
||||
prompt_builder,
|
||||
)
|
||||
(workspace, thread_store, thread, context_store)
|
||||
}
|
||||
|
||||
async fn add_file_to_context(
|
||||
|
||||
@@ -431,6 +431,17 @@ impl RenderOnce for PastThread {
|
||||
.end_slot(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Label::new("Thread")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.size(px(3.))
|
||||
.rounded_full()
|
||||
.bg(cx.theme().colors().text_disabled),
|
||||
)
|
||||
.child(
|
||||
Label::new(thread_timestamp)
|
||||
.color(Color::Muted)
|
||||
@@ -441,7 +452,12 @@ impl RenderOnce for PastThread {
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
|
||||
Tooltip::for_action(
|
||||
"Delete Thread",
|
||||
&RemoveSelectedThread,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click({
|
||||
let assistant_panel = self.assistant_panel.clone();
|
||||
@@ -522,6 +538,17 @@ impl RenderOnce for PastContext {
|
||||
.end_slot(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Label::new("Prompt Editor")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.size(px(3.))
|
||||
.rounded_full()
|
||||
.bg(cx.theme().colors().text_disabled),
|
||||
)
|
||||
.child(
|
||||
Label::new(context_timestamp)
|
||||
.color(Color::Muted)
|
||||
@@ -531,9 +558,7 @@ impl RenderOnce for PastContext {
|
||||
IconButton::new("delete", IconName::TrashAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
|
||||
})
|
||||
.tooltip(Tooltip::text("Delete Prompt Editor"))
|
||||
.on_click({
|
||||
let assistant_panel = self.assistant_panel.clone();
|
||||
let path = self.context.path.clone();
|
||||
|
||||
@@ -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;
|
||||
@@ -201,7 +200,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,34 +331,9 @@ impl ToolUseState {
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_name: Arc<str>,
|
||||
output: Result<String>,
|
||||
cx: &App,
|
||||
) -> Option<PendingToolUse> {
|
||||
telemetry::event!("Agent Tool Finished", tool_name, success = output.is_ok());
|
||||
|
||||
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 {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
[package]
|
||||
name = "agent_rules"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/agent_rules.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
fs.workspace = true
|
||||
gpui.workspace = true
|
||||
prompt_store.workspace = true
|
||||
util.workspace = true
|
||||
worktree.workspace = true
|
||||
workspace-hack = { version = "0.1", path = "../../tooling/workspace-hack" }
|
||||
|
||||
[dev-dependencies]
|
||||
indoc.workspace = true
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,51 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use fs::Fs;
|
||||
use gpui::{App, AppContext, Task};
|
||||
use prompt_store::SystemPromptRulesFile;
|
||||
use util::maybe;
|
||||
use worktree::Worktree;
|
||||
|
||||
const RULES_FILE_NAMES: [&'static str; 6] = [
|
||||
".rules",
|
||||
".cursorrules",
|
||||
".windsurfrules",
|
||||
".clinerules",
|
||||
".github/copilot-instructions.md",
|
||||
"CLAUDE.md",
|
||||
];
|
||||
|
||||
pub fn load_worktree_rules_file(
|
||||
fs: Arc<dyn Fs>,
|
||||
worktree: &Worktree,
|
||||
cx: &App,
|
||||
) -> Option<Task<Result<SystemPromptRulesFile>>> {
|
||||
let selected_rules_file = RULES_FILE_NAMES
|
||||
.into_iter()
|
||||
.filter_map(|name| {
|
||||
worktree
|
||||
.entry_for_path(name)
|
||||
.filter(|entry| entry.is_file())
|
||||
.map(|entry| (entry.path.clone(), worktree.absolutize(&entry.path)))
|
||||
})
|
||||
.next();
|
||||
|
||||
// Note that Cline supports `.clinerules` being a directory, but that is not currently
|
||||
// supported. This doesn't seem to occur often in GitHub repositories.
|
||||
selected_rules_file.map(|(path_in_worktree, abs_path)| {
|
||||
let fs = fs.clone();
|
||||
cx.background_spawn(maybe!(async move {
|
||||
let abs_path = abs_path?;
|
||||
let text = fs
|
||||
.load(&abs_path)
|
||||
.await
|
||||
.with_context(|| format!("Failed to load assistant rules file {:?}", abs_path))?;
|
||||
anyhow::Ok(SystemPromptRulesFile {
|
||||
path_in_worktree,
|
||||
abs_path: abs_path.into(),
|
||||
text: text.trim().to_string(),
|
||||
})
|
||||
}))
|
||||
})
|
||||
}
|
||||
@@ -321,54 +321,38 @@ pub async fn stream_completion(
|
||||
.map(|output| output.0)
|
||||
}
|
||||
|
||||
/// An individual rate limit.
|
||||
#[derive(Debug)]
|
||||
pub struct RateLimit {
|
||||
pub limit: usize,
|
||||
pub remaining: usize,
|
||||
pub reset: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl RateLimit {
|
||||
fn from_headers(resource: &str, headers: &HeaderMap<HeaderValue>) -> Result<Self> {
|
||||
let limit =
|
||||
get_header(&format!("anthropic-ratelimit-{resource}-limit"), headers)?.parse()?;
|
||||
let remaining = get_header(
|
||||
&format!("anthropic-ratelimit-{resource}-remaining"),
|
||||
headers,
|
||||
)?
|
||||
.parse()?;
|
||||
let reset = DateTime::parse_from_rfc3339(get_header(
|
||||
&format!("anthropic-ratelimit-{resource}-reset"),
|
||||
headers,
|
||||
)?)?
|
||||
.to_utc();
|
||||
|
||||
Ok(Self {
|
||||
limit,
|
||||
remaining,
|
||||
reset,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://docs.anthropic.com/en/api/rate-limits#response-headers>
|
||||
#[derive(Debug)]
|
||||
pub struct RateLimitInfo {
|
||||
pub requests: Option<RateLimit>,
|
||||
pub tokens: Option<RateLimit>,
|
||||
pub input_tokens: Option<RateLimit>,
|
||||
pub output_tokens: Option<RateLimit>,
|
||||
pub requests_limit: usize,
|
||||
pub requests_remaining: usize,
|
||||
pub requests_reset: DateTime<Utc>,
|
||||
pub tokens_limit: usize,
|
||||
pub tokens_remaining: usize,
|
||||
pub tokens_reset: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl RateLimitInfo {
|
||||
fn from_headers(headers: &HeaderMap<HeaderValue>) -> Self {
|
||||
Self {
|
||||
requests: RateLimit::from_headers("requests", headers).log_err(),
|
||||
tokens: RateLimit::from_headers("tokens", headers).log_err(),
|
||||
input_tokens: RateLimit::from_headers("input-tokens", headers).log_err(),
|
||||
output_tokens: RateLimit::from_headers("output-tokens", headers).log_err(),
|
||||
}
|
||||
fn from_headers(headers: &HeaderMap<HeaderValue>) -> Result<Self> {
|
||||
let tokens_limit = get_header("anthropic-ratelimit-tokens-limit", headers)?.parse()?;
|
||||
let requests_limit = get_header("anthropic-ratelimit-requests-limit", headers)?.parse()?;
|
||||
let tokens_remaining =
|
||||
get_header("anthropic-ratelimit-tokens-remaining", headers)?.parse()?;
|
||||
let requests_remaining =
|
||||
get_header("anthropic-ratelimit-requests-remaining", headers)?.parse()?;
|
||||
let requests_reset = get_header("anthropic-ratelimit-requests-reset", headers)?;
|
||||
let tokens_reset = get_header("anthropic-ratelimit-tokens-reset", headers)?;
|
||||
let requests_reset = DateTime::parse_from_rfc3339(requests_reset)?.to_utc();
|
||||
let tokens_reset = DateTime::parse_from_rfc3339(tokens_reset)?.to_utc();
|
||||
|
||||
Ok(Self {
|
||||
requests_limit,
|
||||
tokens_limit,
|
||||
requests_remaining,
|
||||
tokens_remaining,
|
||||
requests_reset,
|
||||
tokens_reset,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,7 +418,7 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
}
|
||||
})
|
||||
.boxed();
|
||||
Ok((stream, Some(rate_limits)))
|
||||
Ok((stream, rate_limits.log_err()))
|
||||
} else {
|
||||
let mut body = Vec::new();
|
||||
response
|
||||
|
||||
@@ -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,
|
||||
@@ -892,7 +892,7 @@ impl InlineAssistant {
|
||||
.map(|language| language.name())
|
||||
});
|
||||
report_assistant_event(
|
||||
AssistantEventData {
|
||||
AssistantEvent {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
message_id,
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2793,7 +2793,7 @@ fn render_thought_process_fold_icon_button(
|
||||
let button = match status {
|
||||
ThoughtProcessStatus::Pending => button
|
||||
.child(
|
||||
Icon::new(IconName::LightBulb)
|
||||
Icon::new(IconName::Brain)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
@@ -2808,7 +2808,7 @@ fn render_thought_process_fold_icon_button(
|
||||
),
|
||||
ThoughtProcessStatus::Completed => button
|
||||
.style(ButtonStyle::Filled)
|
||||
.child(Icon::new(IconName::LightBulb).size(IconSize::Small))
|
||||
.child(Icon::new(IconName::Brain).size(IconSize::Small))
|
||||
.child(Label::new("Thought Process").single_line()),
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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]
|
||||
68
crates/assistant_eval/README.md
Normal 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.
|
||||
@@ -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][×tamp] = serde_json::to_value(&result)?;
|
||||
eval_data[exercise_name][×tamp][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)
|
||||
@@ -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}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
37
crates/assistant_eval/src/judge.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
210
crates/assistant_eval/src/templates_eval.rs
Normal 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
|
||||
"#,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -23,18 +23,16 @@ http_client.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
log.workspace = true
|
||||
lsp-types.workspace = true
|
||||
open = { workspace = true }
|
||||
lsp.workspace = true
|
||||
project.workspace = true
|
||||
regex.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
strum.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
worktree.workspace = true
|
||||
open = { workspace = true }
|
||||
workspace-hack.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
mod bash_tool;
|
||||
mod batch_tool;
|
||||
mod code_action_tool;
|
||||
mod code_symbol_iter;
|
||||
mod code_symbols_tool;
|
||||
mod copy_path_tool;
|
||||
mod create_directory_tool;
|
||||
@@ -14,10 +14,8 @@ mod move_path_tool;
|
||||
mod now_tool;
|
||||
mod open_tool;
|
||||
mod path_search_tool;
|
||||
mod quickfix_tool;
|
||||
mod read_file_tool;
|
||||
mod regex_search_tool;
|
||||
mod rename_tool;
|
||||
mod replace;
|
||||
mod schema;
|
||||
mod symbol_info_tool;
|
||||
@@ -33,7 +31,6 @@ use move_path_tool::MovePathTool;
|
||||
|
||||
use crate::bash_tool::BashTool;
|
||||
use crate::batch_tool::BatchTool;
|
||||
use crate::code_action_tool::CodeActionTool;
|
||||
use crate::code_symbols_tool::CodeSymbolsTool;
|
||||
use crate::create_directory_tool::CreateDirectoryTool;
|
||||
use crate::create_file_tool::CreateFileTool;
|
||||
@@ -45,10 +42,8 @@ use crate::list_directory_tool::ListDirectoryTool;
|
||||
use crate::now_tool::NowTool;
|
||||
use crate::open_tool::OpenTool;
|
||||
use crate::path_search_tool::PathSearchTool;
|
||||
use crate::quickfix_tool::QuickfixTool;
|
||||
use crate::read_file_tool::ReadFileTool;
|
||||
use crate::regex_search_tool::RegexSearchTool;
|
||||
use crate::rename_tool::RenameTool;
|
||||
use crate::symbol_info_tool::SymbolInfoTool;
|
||||
use crate::thinking_tool::ThinkingTool;
|
||||
|
||||
@@ -64,7 +59,6 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
|
||||
registry.register_tool(DeletePathTool);
|
||||
registry.register_tool(FindReplaceFileTool);
|
||||
registry.register_tool(SymbolInfoTool);
|
||||
registry.register_tool(CodeActionTool);
|
||||
registry.register_tool(MovePathTool);
|
||||
registry.register_tool(DiagnosticsTool);
|
||||
registry.register_tool(ListDirectoryTool);
|
||||
@@ -72,10 +66,8 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
|
||||
registry.register_tool(OpenTool);
|
||||
registry.register_tool(CodeSymbolsTool);
|
||||
registry.register_tool(PathSearchTool);
|
||||
registry.register_tool(QuickfixTool);
|
||||
registry.register_tool(ReadFileTool);
|
||||
registry.register_tool(RegexSearchTool);
|
||||
registry.register_tool(RenameTool);
|
||||
registry.register_tool(ThinkingTool);
|
||||
registry.register_tool(FetchTool::new(http_client));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
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};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
@@ -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,184 +121,33 @@ impl Tool for BashTool {
|
||||
worktree.read(cx).abs_path()
|
||||
};
|
||||
|
||||
cx.background_spawn(run_command_limited(working_dir, input.command))
|
||||
}
|
||||
}
|
||||
|
||||
const LIMIT: usize = 16 * 1024;
|
||||
|
||||
async fn run_command_limited(working_dir: Arc<Path>, command: String) -> Result<String> {
|
||||
// Add 2>&1 to merge stderr into stdout for proper interleaving.
|
||||
let command = format!("({}) 2>&1", command);
|
||||
|
||||
let mut cmd = new_smol_command("bash")
|
||||
.arg("-c")
|
||||
.arg(&command)
|
||||
.current_dir(working_dir)
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.context("Failed to execute bash command")?;
|
||||
|
||||
// Capture stdout with a limit
|
||||
let stdout = cmd.stdout.take().unwrap();
|
||||
let mut reader = BufReader::new(stdout);
|
||||
|
||||
// Read one more byte to determine whether the output was truncated
|
||||
let mut buffer = vec![0; LIMIT + 1];
|
||||
let mut bytes_read = 0;
|
||||
|
||||
// Read until we reach the limit
|
||||
loop {
|
||||
let read = reader.read(&mut buffer[bytes_read..]).await?;
|
||||
if read == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
bytes_read += read;
|
||||
if bytes_read > LIMIT {
|
||||
bytes_read = LIMIT + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Repeatedly fill the output reader's buffer without copying it.
|
||||
loop {
|
||||
let skipped_bytes = reader.fill_buf().await?;
|
||||
if skipped_bytes.is_empty() {
|
||||
break;
|
||||
}
|
||||
let skipped_bytes_len = skipped_bytes.len();
|
||||
reader.consume_unpin(skipped_bytes_len);
|
||||
}
|
||||
|
||||
let output_bytes = &buffer[..bytes_read.min(LIMIT)];
|
||||
|
||||
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 until_last_line = &output_bytes[..last_line_ix.unwrap_or(output_bytes.len())];
|
||||
let output_string = String::from_utf8_lossy(until_last_line);
|
||||
|
||||
format!(
|
||||
"Command output too long. The first {} bytes:\n\n{}",
|
||||
output_string.len(),
|
||||
output_block(&output_string),
|
||||
)
|
||||
} else {
|
||||
output_block(&String::from_utf8_lossy(&output_bytes))
|
||||
};
|
||||
|
||||
let output_with_status = if status.success() {
|
||||
if output_string.is_empty() {
|
||||
"Command executed successfully.".to_string()
|
||||
} else {
|
||||
output_string.to_string()
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
"Command failed with exit code {}\n\n{}",
|
||||
status.code().unwrap_or(-1),
|
||||
output_string,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(output_with_status)
|
||||
}
|
||||
|
||||
fn output_block(output: &str) -> String {
|
||||
format!(
|
||||
"```\n{}{}```",
|
||||
output,
|
||||
if output.ends_with('\n') { "" } else { "\n" }
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(not(windows))]
|
||||
mod tests {
|
||||
use gpui::TestAppContext;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_run_command_simple(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
|
||||
let result =
|
||||
run_command_limited(Path::new(".").into(), "echo 'Hello, World!'".to_string()).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), "```\nHello, World!\n```");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_interleaved_stdout_stderr(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
|
||||
let command =
|
||||
"echo 'stdout 1' && echo 'stderr 1' >&2 && echo 'stdout 2' && echo 'stderr 2' >&2";
|
||||
let result = run_command_limited(Path::new(".").into(), command.to_string()).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
result.unwrap(),
|
||||
"```\nstdout 1\nstderr 1\nstdout 2\nstderr 2\n```"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multiple_output_reads(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
|
||||
// Command with multiple outputs that might require multiple reads
|
||||
let result = run_command_limited(
|
||||
Path::new(".").into(),
|
||||
"echo '1'; sleep 0.01; echo '2'; sleep 0.01; echo '3'".to_string(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), "```\n1\n2\n3\n```");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_output_truncation_single_line(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
|
||||
let cmd = format!("echo '{}';", "X".repeat(LIMIT * 2));
|
||||
|
||||
let result = run_command_limited(Path::new(".").into(), cmd).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
|
||||
let content_start = output.find("```\n").map(|i| i + 4).unwrap_or(0);
|
||||
let content_end = output.rfind("\n```").unwrap_or(output.len());
|
||||
let content_length = content_end - content_start;
|
||||
|
||||
// Output should be exactly the limit
|
||||
assert_eq!(content_length, LIMIT);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_output_truncation_multiline(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
|
||||
let cmd = format!("echo '{}'; ", "X".repeat(120)).repeat(160);
|
||||
let result = run_command_limited(Path::new(".").into(), cmd).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
|
||||
assert!(output.starts_with("Command output too long. The first 16334 bytes:\n\n"));
|
||||
|
||||
let content_start = output.find("```\n").map(|i| i + 4).unwrap_or(0);
|
||||
let content_end = output.rfind("\n```").unwrap_or(output.len());
|
||||
let content_length = content_end - content_start;
|
||||
|
||||
assert!(content_length <= LIMIT);
|
||||
cx.spawn(async move |_| {
|
||||
// Add 2>&1 to merge stderr into stdout for proper interleaving.
|
||||
let command = format!("({}) 2>&1", input.command);
|
||||
|
||||
let output = new_smol_command("bash")
|
||||
.arg("-c")
|
||||
.arg(&command)
|
||||
.current_dir(working_dir)
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to execute bash command")?;
|
||||
|
||||
let output_string = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
|
||||
if output.status.success() {
|
||||
if output_string.is_empty() {
|
||||
Ok("Command executed successfully.".to_string())
|
||||
} else {
|
||||
Ok(output_string)
|
||||
}
|
||||
} else {
|
||||
Ok(format!(
|
||||
"Command failed with exit code {}\n{}",
|
||||
output.status.code().unwrap_or(-1),
|
||||
&output_string
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,389 +0,0 @@
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language::{self, Anchor, Buffer, ToPointUtf16};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use project::{self, LspAction, Project};
|
||||
use regex::Regex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use ui::IconName;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct CodeActionToolInput {
|
||||
/// The relative path to the file containing the text range.
|
||||
///
|
||||
/// WARNING: you MUST start this path with one of the project's root directories.
|
||||
pub path: String,
|
||||
|
||||
/// The specific code action to execute.
|
||||
///
|
||||
/// If this field is provided, the tool will execute the specified action.
|
||||
/// If omitted, the tool will list all available code actions for the text range.
|
||||
///
|
||||
/// Here are some actions that are commonly supported (but may not be for this particular
|
||||
/// text range; you can omit this field to list all the actions, if you want to know
|
||||
/// what your options are, or you can just try an action and if it fails I'll tell you
|
||||
/// what the available actions were instead):
|
||||
/// - "quickfix.all" - applies all available quick fixes in the range
|
||||
/// - "source.organizeImports" - sorts and cleans up import statements
|
||||
/// - "source.fixAll" - applies all available auto fixes
|
||||
/// - "refactor.extract" - extracts selected code into a new function or variable
|
||||
/// - "refactor.inline" - inlines a variable by replacing references with its value
|
||||
/// - "refactor.rewrite" - general code rewriting operations
|
||||
/// - "source.addMissingImports" - adds imports for references that lack them
|
||||
/// - "source.removeUnusedImports" - removes imports that aren't being used
|
||||
/// - "source.implementInterface" - generates methods required by an interface/trait
|
||||
/// - "source.generateAccessors" - creates getter/setter methods
|
||||
/// - "source.convertToAsyncFunction" - converts callback-style code to async/await
|
||||
///
|
||||
/// Also, there is a special case: if you specify exactly "textDocument/rename" as the action,
|
||||
/// then this will rename the symbol to whatever string you specified for the `arguments` field.
|
||||
pub action: Option<String>,
|
||||
|
||||
/// Optional arguments to pass to the code action.
|
||||
///
|
||||
/// For rename operations (when action="textDocument/rename"), this should contain the new name.
|
||||
/// For other code actions, these arguments may be passed to the language server.
|
||||
pub arguments: Option<serde_json::Value>,
|
||||
|
||||
/// The text that comes immediately before the text range in the file.
|
||||
pub context_before_range: String,
|
||||
|
||||
/// The text range. This text must appear in the file right between `context_before_range`
|
||||
/// and `context_after_range`.
|
||||
///
|
||||
/// The file must contain exactly one occurrence of `context_before_range` followed by
|
||||
/// `text_range` followed by `context_after_range`. If the file contains zero occurrences,
|
||||
/// or if it contains more than one occurrence, the tool will fail, so it is absolutely
|
||||
/// critical that you verify ahead of time that the string is unique. You can search
|
||||
/// the file's contents to verify this ahead of time.
|
||||
///
|
||||
/// To make the string more likely to be unique, include a minimum of 1 line of context
|
||||
/// before the text range, as well as a minimum of 1 line of context after the text range.
|
||||
/// If these lines of context are not enough to obtain a string that appears only once
|
||||
/// in the file, then double the number of context lines until the string becomes unique.
|
||||
/// (Start with 1 line before and 1 line after though, because too much context is
|
||||
/// needlessly costly.)
|
||||
///
|
||||
/// Do not alter the context lines of code in any way, and make sure to preserve all
|
||||
/// whitespace and indentation for all lines of code. The combined string must be exactly
|
||||
/// as it appears in the file, or else this tool call will fail.
|
||||
pub text_range: String,
|
||||
|
||||
/// The text that comes immediately after the text range in the file.
|
||||
pub context_after_range: String,
|
||||
}
|
||||
|
||||
pub struct CodeActionTool;
|
||||
|
||||
impl Tool for CodeActionTool {
|
||||
fn name(&self) -> String {
|
||||
"code_actions".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _input: &serde_json::Value, _cx: &App) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
include_str!("./code_action_tool/description.md").into()
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::Wand
|
||||
}
|
||||
|
||||
fn input_schema(
|
||||
&self,
|
||||
_format: language_model::LanguageModelToolSchemaFormat,
|
||||
) -> serde_json::Value {
|
||||
let schema = schemars::schema_for!(CodeActionToolInput);
|
||||
serde_json::to_value(&schema).unwrap()
|
||||
}
|
||||
|
||||
fn ui_text(&self, input: &serde_json::Value) -> String {
|
||||
match serde_json::from_value::<CodeActionToolInput>(input.clone()) {
|
||||
Ok(input) => {
|
||||
if let Some(action) = &input.action {
|
||||
if action == "textDocument/rename" {
|
||||
let new_name = match &input.arguments {
|
||||
Some(serde_json::Value::String(new_name)) => new_name.clone(),
|
||||
Some(value) => {
|
||||
if let Ok(new_name) =
|
||||
serde_json::from_value::<String>(value.clone())
|
||||
{
|
||||
new_name
|
||||
} else {
|
||||
"invalid name".to_string()
|
||||
}
|
||||
}
|
||||
None => "missing name".to_string(),
|
||||
};
|
||||
format!("Rename '{}' to '{}'", input.text_range, new_name)
|
||||
} else {
|
||||
format!(
|
||||
"Execute code action '{}' for '{}'",
|
||||
action, input.text_range
|
||||
)
|
||||
}
|
||||
} else {
|
||||
format!("List available code actions for '{}'", input.text_range)
|
||||
}
|
||||
}
|
||||
Err(_) => "Perform code action".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
input: serde_json::Value,
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
let input = match serde_json::from_value::<CodeActionToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
};
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let buffer = {
|
||||
let project_path = project.read_with(cx, |project, cx| {
|
||||
project
|
||||
.find_project_path(&input.path, cx)
|
||||
.context("Path not found in project")
|
||||
})??;
|
||||
|
||||
project.update(cx, |project, cx| project.open_buffer(project_path, cx))?.await?
|
||||
};
|
||||
|
||||
action_log.update(cx, |action_log, cx| {
|
||||
action_log.buffer_read(buffer.clone(), cx);
|
||||
})?;
|
||||
|
||||
let range = {
|
||||
let Some(range) = buffer.read_with(cx, |buffer, _cx| {
|
||||
find_text_range(&buffer, &input.context_before_range, &input.text_range, &input.context_after_range)
|
||||
})? else {
|
||||
return Err(anyhow!(
|
||||
"Failed to locate the text specified by context_before_range, text_range, and context_after_range. Make sure context_before_range and context_after_range each match exactly once in the file."
|
||||
));
|
||||
};
|
||||
|
||||
range
|
||||
};
|
||||
|
||||
if let Some(action_type) = &input.action {
|
||||
// Special-case the `rename` operation
|
||||
let response = if action_type == "textDocument/rename" {
|
||||
let Some(new_name) = input.arguments.and_then(|args| serde_json::from_value::<String>(args).ok()) else {
|
||||
return Err(anyhow!("For rename operations, 'arguments' must be a string containing the new name"));
|
||||
};
|
||||
|
||||
let position = buffer.read_with(cx, |buffer, _| {
|
||||
range.start.to_point_utf16(&buffer.snapshot())
|
||||
})?;
|
||||
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.perform_rename(buffer.clone(), position, new_name.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
format!("Renamed '{}' to '{}'", input.text_range, new_name)
|
||||
} else {
|
||||
// Get code actions for the range
|
||||
let actions = project
|
||||
.update(cx, |project, cx| {
|
||||
project.code_actions(&buffer, range.clone(), None, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
if actions.is_empty() {
|
||||
return Err(anyhow!("No code actions available for this range"));
|
||||
}
|
||||
|
||||
// Find all matching actions
|
||||
let regex = match Regex::new(action_type) {
|
||||
Ok(regex) => regex,
|
||||
Err(err) => return Err(anyhow!("Invalid regex pattern: {}", err)),
|
||||
};
|
||||
let mut matching_actions = actions
|
||||
.into_iter()
|
||||
.filter(|action| { regex.is_match(action.lsp_action.title()) });
|
||||
|
||||
let Some(action) = matching_actions.next() else {
|
||||
return Err(anyhow!("No code actions match the pattern: {}", action_type));
|
||||
};
|
||||
|
||||
// There should have been exactly one matching action.
|
||||
if let Some(second) = matching_actions.next() {
|
||||
let mut all_matches = vec![action, second];
|
||||
|
||||
all_matches.extend(matching_actions);
|
||||
|
||||
return Err(anyhow!(
|
||||
"Pattern '{}' matches multiple code actions: {}",
|
||||
action_type,
|
||||
all_matches.into_iter().map(|action| action.lsp_action.title().to_string()).collect::<Vec<_>>().join(", ")
|
||||
));
|
||||
}
|
||||
|
||||
let title = action.lsp_action.title().to_string();
|
||||
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.apply_code_action(buffer.clone(), action, true, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
format!("Completed code action: {}", title)
|
||||
};
|
||||
|
||||
project
|
||||
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))?
|
||||
.await?;
|
||||
|
||||
action_log.update(cx, |log, cx| {
|
||||
log.buffer_edited(buffer.clone(), cx)
|
||||
})?;
|
||||
|
||||
Ok(response)
|
||||
} else {
|
||||
// No action specified, so list the available ones.
|
||||
let (position_start, position_end) = buffer.read_with(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
(
|
||||
range.start.to_point_utf16(&snapshot),
|
||||
range.end.to_point_utf16(&snapshot)
|
||||
)
|
||||
})?;
|
||||
|
||||
// Convert position to display coordinates (1-based)
|
||||
let position_start_display = language::Point {
|
||||
row: position_start.row + 1,
|
||||
column: position_start.column + 1,
|
||||
};
|
||||
|
||||
let position_end_display = language::Point {
|
||||
row: position_end.row + 1,
|
||||
column: position_end.column + 1,
|
||||
};
|
||||
|
||||
// Get code actions for the range
|
||||
let actions = project
|
||||
.update(cx, |project, cx| {
|
||||
project.code_actions(&buffer, range.clone(), None, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let mut response = format!(
|
||||
"Available code actions for text range '{}' at position {}:{} to {}:{} (UTF-16 coordinates):\n\n",
|
||||
input.text_range,
|
||||
position_start_display.row, position_start_display.column,
|
||||
position_end_display.row, position_end_display.column
|
||||
);
|
||||
|
||||
if actions.is_empty() {
|
||||
response.push_str("No code actions available for this range.");
|
||||
} else {
|
||||
for (i, action) in actions.iter().enumerate() {
|
||||
let title = match &action.lsp_action {
|
||||
LspAction::Action(code_action) => code_action.title.as_str(),
|
||||
LspAction::Command(command) => command.title.as_str(),
|
||||
LspAction::CodeLens(code_lens) => {
|
||||
if let Some(cmd) = &code_lens.command {
|
||||
cmd.title.as_str()
|
||||
} else {
|
||||
"Unknown code lens"
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let kind = match &action.lsp_action {
|
||||
LspAction::Action(code_action) => {
|
||||
if let Some(kind) = &code_action.kind {
|
||||
kind.as_str()
|
||||
} else {
|
||||
"unknown"
|
||||
}
|
||||
},
|
||||
LspAction::Command(_) => "command",
|
||||
LspAction::CodeLens(_) => "code_lens",
|
||||
};
|
||||
|
||||
response.push_str(&format!("{}. {title} ({kind})\n", i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the range of the text in the buffer, if it appears between context_before_range
|
||||
/// and context_after_range, and if that combined string has one unique result in the buffer.
|
||||
///
|
||||
/// If an exact match fails, it tries adding a newline to the end of context_before_range and
|
||||
/// to the beginning of context_after_range to accommodate line-based context matching.
|
||||
fn find_text_range(
|
||||
buffer: &Buffer,
|
||||
context_before_range: &str,
|
||||
text_range: &str,
|
||||
context_after_range: &str,
|
||||
) -> Option<Range<Anchor>> {
|
||||
let snapshot = buffer.snapshot();
|
||||
let text = snapshot.text();
|
||||
|
||||
// First try with exact match
|
||||
let search_string = format!("{context_before_range}{text_range}{context_after_range}");
|
||||
let mut positions = text.match_indices(&search_string);
|
||||
let position_result = positions.next();
|
||||
|
||||
if let Some(position) = position_result {
|
||||
// Check if the matched string is unique
|
||||
if positions.next().is_none() {
|
||||
let range_start = position.0 + context_before_range.len();
|
||||
let range_end = range_start + text_range.len();
|
||||
let range_start_anchor = snapshot.anchor_before(snapshot.offset_to_point(range_start));
|
||||
let range_end_anchor = snapshot.anchor_before(snapshot.offset_to_point(range_end));
|
||||
|
||||
return Some(range_start_anchor..range_end_anchor);
|
||||
}
|
||||
}
|
||||
|
||||
// If exact match fails or is not unique, try with line-based context
|
||||
// Add a newline to the end of before context and beginning of after context
|
||||
let line_based_before = if context_before_range.ends_with('\n') {
|
||||
context_before_range.to_string()
|
||||
} else {
|
||||
format!("{context_before_range}\n")
|
||||
};
|
||||
|
||||
let line_based_after = if context_after_range.starts_with('\n') {
|
||||
context_after_range.to_string()
|
||||
} else {
|
||||
format!("\n{context_after_range}")
|
||||
};
|
||||
|
||||
let line_search_string = format!("{line_based_before}{text_range}{line_based_after}");
|
||||
let mut line_positions = text.match_indices(&line_search_string);
|
||||
let line_position = line_positions.next()?;
|
||||
|
||||
// The line-based search string must also appear exactly once
|
||||
if line_positions.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let line_range_start = line_position.0 + line_based_before.len();
|
||||
let line_range_end = line_range_start + text_range.len();
|
||||
let line_range_start_anchor =
|
||||
snapshot.anchor_before(snapshot.offset_to_point(line_range_start));
|
||||
let line_range_end_anchor = snapshot.anchor_before(snapshot.offset_to_point(line_range_end));
|
||||
|
||||
Some(line_range_start_anchor..line_range_end_anchor)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
A tool for applying code actions to specific sections of your code. It uses language servers to provide refactoring capabilities similar to what you'd find in an IDE.
|
||||
|
||||
This tool can:
|
||||
- List all available code actions for a selected text range
|
||||
- Execute a specific code action on that range
|
||||
- Rename symbols across your codebase. This tool is the preferred way to rename things, and you should always prefer to rename code symbols using this tool rather than using textual find/replace when both are available.
|
||||
|
||||
Use this tool when you want to:
|
||||
- Discover what code actions are available for a piece of code
|
||||
- Apply automatic fixes and code transformations
|
||||
- Rename variables, functions, or other symbols consistently throughout your project
|
||||
- Clean up imports, implement interfaces, or perform other language-specific operations
|
||||
|
||||
- If unsure what actions are available, call the tool without specifying an action to get a list
|
||||
- For common operations, you can directly specify actions like "quickfix.all" or "source.organizeImports"
|
||||
- For renaming, use the special "textDocument/rename" action and provide the new name in the arguments field
|
||||
- Be specific with your text range and context to ensure the tool identifies the correct code location
|
||||
|
||||
The tool will automatically save any changes it makes to your files.
|
||||
88
crates/assistant_tools/src/code_symbol_iter.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -11,58 +11,32 @@ use std::{fmt::Write, path::Path, sync::Arc};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, JsonSchema)]
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DiagnosticsToolInput {
|
||||
/// The specific paths to get detailed diagnostics for (including individual line numbers).
|
||||
/// The path to get diagnostics for. If not provided, returns a project-wide summary.
|
||||
///
|
||||
/// Regardless of whether any paths are specified here, a count of the total number of warnings
|
||||
/// and errors in the project will be reported, so providing paths here gets you strictly
|
||||
/// more information.
|
||||
///
|
||||
/// These paths should never be absolute, and the first component
|
||||
/// of each path should always be a root directory in a project.
|
||||
/// This path should never be absolute, and the first component
|
||||
/// of the path should always be a root directory in a project.
|
||||
///
|
||||
/// <example>
|
||||
/// If the project has the following root directories:
|
||||
///
|
||||
/// - lorem
|
||||
/// - ipsum
|
||||
/// - amet
|
||||
///
|
||||
/// If you want detailed diagnostics with line numbers for `dolor.txt` in `ipsum` and `consectetur.txt` in `amet`, you should use:
|
||||
///
|
||||
/// "paths": ["ipsum/dolor.txt", "amet/consectetur.txt"]
|
||||
/// If you wanna access diagnostics for `dolor.txt` in `ipsum`, you should use the path `ipsum/dolor.txt`.
|
||||
/// </example>
|
||||
#[serde(deserialize_with = "deserialize_path")]
|
||||
#[serde(default)]
|
||||
pub paths: Vec<String>,
|
||||
|
||||
/// Which severity levels to show. Default is all.
|
||||
/// To show only errors and warnings, you should use:
|
||||
///
|
||||
/// "severity": ["error", "warning"]
|
||||
#[serde(default)]
|
||||
pub severity: Vec<Severity>,
|
||||
pub path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Copy, Clone, strum::Display, Hash,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Severity {
|
||||
Error,
|
||||
Warning,
|
||||
Information,
|
||||
Hint,
|
||||
}
|
||||
|
||||
fn deserialize_path<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
|
||||
fn deserialize_path<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let paths = Vec::<String>::deserialize(deserializer)?;
|
||||
// The model passes an empty string for some paths
|
||||
Ok(paths.into_iter().filter(|s| !s.is_empty()).collect())
|
||||
let opt = Option::<String>::deserialize(deserializer)?;
|
||||
// The model passes an empty string sometimes
|
||||
Ok(opt.filter(|s| !s.is_empty()))
|
||||
}
|
||||
|
||||
pub struct DiagnosticsTool;
|
||||
@@ -72,7 +46,7 @@ impl Tool for DiagnosticsTool {
|
||||
"diagnostics".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -89,21 +63,17 @@ impl Tool for DiagnosticsTool {
|
||||
}
|
||||
|
||||
fn ui_text(&self, input: &serde_json::Value) -> String {
|
||||
serde_json::from_value::<DiagnosticsToolInput>(input.clone())
|
||||
if let Some(path) = serde_json::from_value::<DiagnosticsToolInput>(input.clone())
|
||||
.ok()
|
||||
.and_then(|input| {
|
||||
input.paths.first().map(|first_path| {
|
||||
if input.paths.len() > 1 {
|
||||
format!("Check diagnostics for {} paths", input.paths.len())
|
||||
} else {
|
||||
format!(
|
||||
"Check diagnostics for {}",
|
||||
MarkdownString::inline_code(first_path)
|
||||
)
|
||||
}
|
||||
})
|
||||
.and_then(|input| match input.path {
|
||||
Some(path) if !path.is_empty() => Some(MarkdownString::inline_code(&path)),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_else(|| "Check project diagnostics".to_string())
|
||||
{
|
||||
format!("Check diagnostics for {path}")
|
||||
} else {
|
||||
"Check project diagnostics".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn run(
|
||||
@@ -114,20 +84,62 @@ impl Tool for DiagnosticsTool {
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
let input = serde_json::from_value::<DiagnosticsToolInput>(input).unwrap_or_default();
|
||||
let severity_filter = input.severity;
|
||||
let mut summary_output = String::new();
|
||||
let mut has_diagnostics = false;
|
||||
|
||||
// Always report the global diagnostics summary.
|
||||
match serde_json::from_value::<DiagnosticsToolInput>(input)
|
||||
.ok()
|
||||
.and_then(|input| input.path)
|
||||
{
|
||||
let project = project.read(cx);
|
||||
Some(path) if !path.is_empty() => {
|
||||
let Some(project_path) = project.read(cx).find_project_path(&path, cx) else {
|
||||
return Task::ready(Err(anyhow!("Could not find path {path} in project",)));
|
||||
};
|
||||
|
||||
let buffer =
|
||||
project.update(cx, |project, cx| project.open_buffer(project_path, cx));
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let mut output = String::new();
|
||||
let buffer = buffer.await?;
|
||||
let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
|
||||
|
||||
for (_, group) in snapshot.diagnostic_groups(None) {
|
||||
let entry = &group.entries[group.primary_ix];
|
||||
let range = entry.range.to_point(&snapshot);
|
||||
let severity = match entry.diagnostic.severity {
|
||||
DiagnosticSeverity::ERROR => "error",
|
||||
DiagnosticSeverity::WARNING => "warning",
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
writeln!(
|
||||
output,
|
||||
"{} at line {}: {}",
|
||||
severity,
|
||||
range.start.row + 1,
|
||||
entry.diagnostic.message
|
||||
)?;
|
||||
}
|
||||
|
||||
if output.is_empty() {
|
||||
Ok("File doesn't have errors or warnings!".to_string())
|
||||
} else {
|
||||
Ok(output)
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
let project = project.read(cx);
|
||||
let mut output = String::new();
|
||||
let mut has_diagnostics = false;
|
||||
|
||||
for (project_path, _, summary) in project.diagnostic_summaries(true, cx) {
|
||||
if summary.error_count > 0 || summary.warning_count > 0 {
|
||||
let Some(worktree) = project.worktree_for_id(project_path.worktree_id, cx)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for (project_path, _, summary) in project.diagnostic_summaries(true, cx) {
|
||||
if summary.error_count > 0 || summary.warning_count > 0 {
|
||||
if let Some(worktree) = project.worktree_for_id(project_path.worktree_id, cx) {
|
||||
has_diagnostics = true;
|
||||
summary_output.push_str(&format!(
|
||||
output.push_str(&format!(
|
||||
"{}: {} error(s), {} warning(s)\n",
|
||||
Path::new(worktree.read(cx).root_name())
|
||||
.join(project_path.path)
|
||||
@@ -137,113 +149,17 @@ impl Tool for DiagnosticsTool {
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action_log.update(cx, |action_log, _cx| {
|
||||
action_log.checked_project_diagnostics();
|
||||
});
|
||||
}
|
||||
|
||||
if input.paths.is_empty() {
|
||||
// If no paths specified, just return the summary
|
||||
if has_diagnostics {
|
||||
Task::ready(Ok(summary_output))
|
||||
} else {
|
||||
Task::ready(Ok("No errors or warnings found.".to_string()))
|
||||
}
|
||||
} else {
|
||||
let buffer_tasks = input
|
||||
.paths
|
||||
.into_iter()
|
||||
.filter_map(|path| {
|
||||
project
|
||||
.read(cx)
|
||||
.find_project_path(&path, cx)
|
||||
.map(|project_path| {
|
||||
(
|
||||
project_path.clone(),
|
||||
project.update(cx, |project, cx| {
|
||||
project.open_buffer(project_path, cx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let mut output = String::new();
|
||||
|
||||
output.push_str("# Project Summary\n\n");
|
||||
action_log.update(cx, |action_log, _cx| {
|
||||
action_log.checked_project_diagnostics();
|
||||
});
|
||||
|
||||
if has_diagnostics {
|
||||
output.push_str(&summary_output);
|
||||
output.push_str("\n");
|
||||
Task::ready(Ok(output))
|
||||
} else {
|
||||
output.push_str("No errors or warnings found in the project.\n");
|
||||
Task::ready(Ok("No errors or warnings found in the project.".to_string()))
|
||||
}
|
||||
|
||||
output.push('\n');
|
||||
|
||||
let mut header_printed = false;
|
||||
|
||||
for (project_path, buffer_task) in buffer_tasks {
|
||||
let mut path_printed = false;
|
||||
let buffer = buffer_task.await?;
|
||||
let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
|
||||
|
||||
for (_, group) in snapshot.diagnostic_groups(None) {
|
||||
let entry = &group.entries[group.primary_ix];
|
||||
let range = entry.range.to_point(&snapshot);
|
||||
|
||||
if let Ok(severity) = Severity::try_from(&entry.diagnostic.severity) {
|
||||
if severity_filter.is_empty() || severity_filter.contains(&severity) {
|
||||
if !header_printed {
|
||||
output.push_str("# Per-Path Diagnostics\n\n");
|
||||
header_printed = true;
|
||||
}
|
||||
|
||||
if !path_printed {
|
||||
writeln!(output, "## {}", project_path.path.display())?;
|
||||
path_printed = true;
|
||||
}
|
||||
|
||||
writeln!(
|
||||
output,
|
||||
"\n### {severity} at line {}\n{}",
|
||||
range.start.row + 1,
|
||||
entry.diagnostic.message
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !header_printed {
|
||||
output.push_str("No specific diagnostics found for the requested paths.");
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&DiagnosticSeverity> for Severity {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(
|
||||
value: &DiagnosticSeverity,
|
||||
) -> Result<Self, <Severity as TryFrom<&DiagnosticSeverity>>::Error> {
|
||||
if *value == DiagnosticSeverity::ERROR {
|
||||
Ok(Self::Error)
|
||||
} else if *value == DiagnosticSeverity::WARNING {
|
||||
Ok(Self::Warning)
|
||||
} else if *value == DiagnosticSeverity::INFORMATION {
|
||||
Ok(Self::Information)
|
||||
} else if *value == DiagnosticSeverity::HINT {
|
||||
Ok(Self::Hint)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -63,16 +63,6 @@ pub struct FindReplaceFileToolInput {
|
||||
/// even one character in this string is different in any way from how it appears
|
||||
/// in the file, then the tool call will fail.
|
||||
///
|
||||
/// If you get an error that the `find` string was not found, this means that either
|
||||
/// you made a mistake, or that the file has changed since you last looked at it.
|
||||
/// Either way, when this happens, you should retry doing this tool call until it
|
||||
/// succeeds, up to 3 times. Each time you retry, you should take another look at
|
||||
/// the exact text of the file in question, to make sure that you are searching for
|
||||
/// exactly the right string. Regardless of whether it was because you made a mistake
|
||||
/// or because the file changed since you last looked at it, you should be extra
|
||||
/// careful when retrying in this way. It's a bad experience for the user if
|
||||
/// this `find` string isn't found, so be super careful to get it exactly right!
|
||||
///
|
||||
/// <example>
|
||||
/// If a file contains this code:
|
||||
///
|
||||
@@ -139,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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
Find one unique part of a file in the project and replace that text with new text.
|
||||
|
||||
This tool is the preferred way to make edits to files *except* when making a rename. When making a rename specifically, the rename tool must always be used instead.
|
||||
This tool is the preferred way to make edits to files. If you have multiple edits to make, including edits across multiple files, then make a plan to respond with a single message containing multiple calls to this tool - one call for each find/replace operation.
|
||||
|
||||
If you have multiple edits to make, including edits across multiple files, then make a plan to respond with a single message containing a batch of calls to this tool - one call for each find/replace operation.
|
||||
|
||||
You should only use this tool when you want to edit a subset of a file's contents, but not the entire file. You should not use this tool when you want to replace the entire contents of a file with completely different contents. You also should not use this tool when you want to move or rename a file. You absolutely must NEVER use this tool to create new files from scratch. If you ever consider using this tool to create a new file from scratch, for any reason, instead you must reconsider and choose a different approach.
|
||||
You should use this tool when you want to edit a subset of a file's contents, but not the entire file. You should not use this tool when you want to replace the entire contents of a file with completely different contents. You also should not use this tool when you want to move or rename a file. You absolutely must NEVER use this tool to create new files from scratch. If you ever consider using this tool to create a new file from scratch, for any reason, instead you must reconsider and choose a different approach.
|
||||
|
||||
DO NOT call this tool until the code to be edited appears in the conversation! You must use another tool to read the file's contents into the conversation, or ask the user to add it to context first.
|
||||
|
||||
Never call this tool with identical "find" and "replace" strings. Instead, stop and think about what you actually want to do.
|
||||
|
||||
REMEMBER: You can use this tool after you just used the `create_file` tool. It's better to edit the file you just created than to recreate a new file from scratch.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,361 +0,0 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use gpui::AppContext;
|
||||
use gpui::{App, Entity, Task};
|
||||
use language::{DiagnosticSeverity, OffsetRangeExt};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use lsp_types::CodeActionKind;
|
||||
use project::LspAction;
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::f32::consts::E;
|
||||
use std::{fmt::Write, path::Path, sync::Arc};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct QuickfixToolInput {
|
||||
/// The path to get diagnostics for and apply quickfixes. If not provided, checks the entire project.
|
||||
///
|
||||
/// This path should never be absolute, and the first component
|
||||
/// of the path should always be a root directory in a project.
|
||||
///
|
||||
/// <example>
|
||||
/// If the project has the following root directories:
|
||||
///
|
||||
/// - lorem
|
||||
/// - ipsum
|
||||
///
|
||||
/// If you want to apply quickfixes for `dolor.txt` in `ipsum`, you should use the path `ipsum/dolor.txt`.
|
||||
/// </example>
|
||||
pub path: Option<String>,
|
||||
}
|
||||
|
||||
pub struct QuickfixTool;
|
||||
|
||||
impl Tool for QuickfixTool {
|
||||
fn name(&self) -> String {
|
||||
"quickfix".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
include_str!("./quickfix_tool/description.md").into()
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::Check
|
||||
}
|
||||
|
||||
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> serde_json::Value {
|
||||
json_schema_for::<QuickfixToolInput>(format)
|
||||
}
|
||||
|
||||
fn ui_text(&self, input: &serde_json::Value) -> String {
|
||||
if let Some(path) = serde_json::from_value::<QuickfixToolInput>(input.clone())
|
||||
.ok()
|
||||
.and_then(|input| match input.path {
|
||||
Some(path) if !path.is_empty() => Some(MarkdownString::inline_code(&path)),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
format!("Apply quickfixes for {path}")
|
||||
} else {
|
||||
"Apply project-wide quickfixes".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
input: serde_json::Value,
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
match serde_json::from_value::<QuickfixToolInput>(input)
|
||||
.ok()
|
||||
.and_then(|input| input.path)
|
||||
{
|
||||
Some(path) if !path.is_empty() => {
|
||||
let Some(project_path) = project.read(cx).find_project_path(&path, cx) else {
|
||||
return Task::ready(Err(anyhow!("Could not find path {path} in project")));
|
||||
};
|
||||
|
||||
let buffer =
|
||||
project.update(cx, |project, cx| project.open_buffer(project_path, cx));
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let mut output = String::new();
|
||||
let mut fixes_applied = 0;
|
||||
let buffer = buffer.await?;
|
||||
let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
|
||||
let mut unfixed = Vec::new();
|
||||
|
||||
// Collect diagnostics to apply quickfixes for
|
||||
for entry in snapshot.diagnostic_groups(None).into_iter().flat_map(
|
||||
|(_, group)| group.entries.into_iter()
|
||||
) {
|
||||
let range = entry.range.to_point(&snapshot);
|
||||
let actions = project
|
||||
.update(cx, |project, cx| {
|
||||
project.code_actions(
|
||||
&buffer,
|
||||
range.start..range.end,
|
||||
None,
|
||||
cx
|
||||
)
|
||||
})?.await?;
|
||||
|
||||
let quickfixes = actions.into_iter().filter(|action| {
|
||||
if let LspAction::Action(code_action) = &action.lsp_action {
|
||||
code_action.kind == Some(CodeActionKind::QUICKFIX)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
match quickfixes.first() {
|
||||
Some(default_quickfix) => {
|
||||
// If there's a quickfix marked as preferred, use that.
|
||||
// Otherwise fall back on the first available quickfix in the list.
|
||||
let preferred_action = quickfixes.iter().find(|action| {
|
||||
if let LspAction::Action(code_action) = &action.lsp_action {
|
||||
code_action.is_preferred.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}).unwrap_or(default_quickfix);
|
||||
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.apply_code_action(buffer.clone(), preferred_action.clone(), true, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
log::info!("Applied quickfix: {}", preferred_action.lsp_action.title());
|
||||
|
||||
fixes_applied += 1;
|
||||
}
|
||||
None => {
|
||||
unfixed.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the buffer after applying fixes
|
||||
if fixes_applied > 0 {
|
||||
project
|
||||
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))?
|
||||
.await?;
|
||||
|
||||
action_log.update(cx, |log, cx| {
|
||||
log.buffer_edited(buffer.clone(), cx)
|
||||
})?;
|
||||
}
|
||||
|
||||
// Generate summary
|
||||
if output.is_empty() {
|
||||
Ok("No issues found in the file!".to_string())
|
||||
} else {
|
||||
writeln!(output, "\nSummary: Applied {} quickfixes. Remaining issues: {} - use the diagnostics tool to see them.",
|
||||
fixes_applied, unfixed.len())?;
|
||||
Ok(output)
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
todo!("Share code with the other branch.");
|
||||
let mut output = String::new();
|
||||
let mut files_with_diagnostics = Vec::new();
|
||||
|
||||
// Collect all files with diagnostics for processing
|
||||
{
|
||||
let project_ref = project.read(cx);
|
||||
for (project_path, _, summary) in project_ref.diagnostic_summaries(true, cx) {
|
||||
if summary.error_count > 0 || summary.warning_count > 0 {
|
||||
if let Some(worktree) =
|
||||
project_ref.worktree_for_id(project_path.worktree_id, cx)
|
||||
{
|
||||
let path_str = Path::new(worktree.read(cx).root_name())
|
||||
.join(project_path.path)
|
||||
.display()
|
||||
.to_string();
|
||||
|
||||
files_with_diagnostics.push(path_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a task to process all files with diagnostics
|
||||
let project = project.clone();
|
||||
let action_log = action_log.clone();
|
||||
let files_to_process = files_with_diagnostics.clone();
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let mut total_fixes_applied = 0;
|
||||
let mut total_errors_unfixed = 0;
|
||||
let mut total_warnings_unfixed = 0;
|
||||
|
||||
// Process each file with diagnostics
|
||||
for file_path in files_to_process {
|
||||
writeln!(output, "Processing {}...", file_path)?;
|
||||
|
||||
let Ok(Some(project_path)) = project.read_with(cx, |project, cx| project.find_project_path(&file_path, cx)) else {
|
||||
writeln!(output, " Could not resolve project path for {}", file_path)?;
|
||||
continue;
|
||||
};
|
||||
|
||||
let buffer_open_result = project.update(cx, |project, cx|
|
||||
project.open_buffer(project_path, cx));
|
||||
|
||||
let Ok(buffer_handle) = buffer_open_result else {
|
||||
writeln!(output, " Failed to open buffer for {}", file_path)?;
|
||||
continue;
|
||||
};
|
||||
|
||||
let buffer = match buffer_handle.await {
|
||||
Ok(buffer) => buffer,
|
||||
Err(err) => {
|
||||
writeln!(output, " Failed to load buffer for {}: {}", file_path, err)?;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let mut file_fixes_applied = 0;
|
||||
let mut file_errors_unfixed = 0;
|
||||
let mut file_warnings_unfixed = 0;
|
||||
let mut needs_save = false;
|
||||
|
||||
let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
|
||||
|
||||
// Process diagnostics for this file
|
||||
for (_, group) in snapshot.diagnostic_groups(None) {
|
||||
let entry = &group.entries[group.primary_ix];
|
||||
let range = entry.range.to_point(&snapshot);
|
||||
let severity = entry.diagnostic.severity;
|
||||
|
||||
// Skip informational and hint diagnostics
|
||||
if severity != DiagnosticSeverity::ERROR &&
|
||||
severity != DiagnosticSeverity::WARNING {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get code actions (quickfixes) for this diagnostic
|
||||
let actions = project
|
||||
.update(cx, |project, cx| {
|
||||
project.code_actions(
|
||||
&buffer,
|
||||
range.start..range.end,
|
||||
None,
|
||||
cx
|
||||
)
|
||||
})?;
|
||||
|
||||
let actions_result = actions.await;
|
||||
|
||||
match actions_result {
|
||||
Ok(actions) => {
|
||||
// Find quickfix actions
|
||||
let quickfixes = actions.into_iter().filter(|action| {
|
||||
if let LspAction::Action(code_action) = &action.lsp_action {
|
||||
if let Some(kind) = &code_action.kind {
|
||||
kind.as_str().starts_with("quickfix")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
if !quickfixes.is_empty() {
|
||||
// Find the preferred quickfix (marked as isPreferred or the first one)
|
||||
let preferred_action = quickfixes.iter().find(|action| {
|
||||
if let LspAction::Action(code_action) = &action.lsp_action {
|
||||
code_action.is_preferred.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}).unwrap_or(&quickfixes[0]);
|
||||
|
||||
// Apply the quickfix
|
||||
let title = preferred_action.lsp_action.title().to_string();
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.apply_code_action(buffer.clone(), preferred_action.clone(), true, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
writeln!(output, " Applied quickfix: {title}")?;
|
||||
file_fixes_applied += 1;
|
||||
needs_save = true;
|
||||
} else {
|
||||
// Track unfixed diagnostics
|
||||
match severity {
|
||||
DiagnosticSeverity::ERROR => file_errors_unfixed += 1,
|
||||
DiagnosticSeverity::WARNING => file_warnings_unfixed += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
// Track unfixed diagnostics
|
||||
match severity {
|
||||
DiagnosticSeverity::ERROR => file_errors_unfixed += 1,
|
||||
DiagnosticSeverity::WARNING => file_warnings_unfixed += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the buffer after applying fixes
|
||||
if needs_save {
|
||||
project
|
||||
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))?
|
||||
.await?;
|
||||
|
||||
action_log.update(cx, |log, cx| {
|
||||
log.buffer_edited(buffer.clone(), cx)
|
||||
})?;
|
||||
}
|
||||
|
||||
// Update totals
|
||||
total_fixes_applied += file_fixes_applied;
|
||||
total_errors_unfixed += file_errors_unfixed;
|
||||
total_warnings_unfixed += file_warnings_unfixed;
|
||||
|
||||
// File summary
|
||||
if file_fixes_applied > 0 || file_errors_unfixed > 0 || file_warnings_unfixed > 0 {
|
||||
writeln!(output, " {} quickfixes applied. Remaining: {} errors, {} warnings\n",
|
||||
file_fixes_applied, file_errors_unfixed, file_warnings_unfixed)?;
|
||||
} else {
|
||||
writeln!(output, " No issues fixed or found\n")?;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark that we've checked diagnostics
|
||||
action_log.update(cx, |action_log, _cx| {
|
||||
action_log.checked_project_diagnostics();
|
||||
})?;
|
||||
|
||||
// Generate overall summary
|
||||
if files_with_diagnostics.is_empty() {
|
||||
Ok("No issues found in the project!".to_string())
|
||||
} else {
|
||||
writeln!(output, "\nProject-wide summary: Applied {} quickfixes. Remaining issues: {} errors, {} warnings.",
|
||||
total_fixes_applied, total_errors_unfixed, total_warnings_unfixed)?;
|
||||
Ok(output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
Get errors and warnings for the project or a specific file and apply quickfixes automatically where possible.
|
||||
|
||||
This tool can be invoked to find diagnostics in your code and automatically apply available quickfixes to resolve them. Quickfixes are code edits suggested by language servers that can automatically fix common issues.
|
||||
|
||||
When a path is provided, it checks that specific file for diagnostics and applies quickfixes.
|
||||
When no path is provided, it finds diagnostics project-wide and applies quickfixes where possible.
|
||||
|
||||
<example>
|
||||
To automatically fix issues in a specific file:
|
||||
{
|
||||
"path": "src/main.rs"
|
||||
}
|
||||
|
||||
To find and fix issues across the entire project:
|
||||
{}
|
||||
</example>
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{code_symbols_tool::file_outline, schema::json_schema_for};
|
||||
use crate::code_symbols_tool::file_outline;
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use gpui::{App, Entity, Task};
|
||||
@@ -15,7 +16,7 @@ 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;
|
||||
const MAX_FILE_SIZE_TO_READ: usize = 4096;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ReadFileToolInput {
|
||||
@@ -51,7 +52,7 @@ impl Tool for ReadFileTool {
|
||||
"read_file".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language::{self, Buffer, ToPointUtf16};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use ui::IconName;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct RenameToolInput {
|
||||
/// The relative path to the file containing the symbol to rename.
|
||||
///
|
||||
/// WARNING: you MUST start this path with one of the project's root directories.
|
||||
pub path: String,
|
||||
|
||||
/// The new name to give to the symbol.
|
||||
pub new_name: String,
|
||||
|
||||
/// The text that comes immediately before the symbol in the file.
|
||||
pub context_before_symbol: String,
|
||||
|
||||
/// The symbol to rename. This text must appear in the file right between
|
||||
/// `context_before_symbol` and `context_after_symbol`.
|
||||
///
|
||||
/// The file must contain exactly one occurrence of `context_before_symbol` followed by
|
||||
/// `symbol` followed by `context_after_symbol`. If the file contains zero occurrences,
|
||||
/// or if it contains more than one occurrence, the tool will fail, so it is absolutely
|
||||
/// critical that you verify ahead of time that the string is unique. You can search
|
||||
/// the file's contents to verify this ahead of time.
|
||||
///
|
||||
/// To make the string more likely to be unique, include a minimum of 1 line of context
|
||||
/// before the symbol, as well as a minimum of 1 line of context after the symbol.
|
||||
/// If these lines of context are not enough to obtain a string that appears only once
|
||||
/// in the file, then double the number of context lines until the string becomes unique.
|
||||
/// (Start with 1 line before and 1 line after though, because too much context is
|
||||
/// needlessly costly.)
|
||||
///
|
||||
/// Do not alter the context lines of code in any way, and make sure to preserve all
|
||||
/// whitespace and indentation for all lines of code. The combined string must be exactly
|
||||
/// as it appears in the file, or else this tool call will fail.
|
||||
pub symbol: String,
|
||||
|
||||
/// The text that comes immediately after the symbol in the file.
|
||||
pub context_after_symbol: String,
|
||||
}
|
||||
|
||||
pub struct RenameTool;
|
||||
|
||||
impl Tool for RenameTool {
|
||||
fn name(&self) -> String {
|
||||
"rename".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _input: &serde_json::Value, _cx: &App) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
include_str!("./rename_tool/description.md").into()
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::Pencil
|
||||
}
|
||||
|
||||
fn input_schema(
|
||||
&self,
|
||||
_format: language_model::LanguageModelToolSchemaFormat,
|
||||
) -> serde_json::Value {
|
||||
let schema = schemars::schema_for!(RenameToolInput);
|
||||
serde_json::to_value(&schema).unwrap()
|
||||
}
|
||||
|
||||
fn ui_text(&self, input: &serde_json::Value) -> String {
|
||||
match serde_json::from_value::<RenameToolInput>(input.clone()) {
|
||||
Ok(input) => {
|
||||
format!("Rename '{}' to '{}'", input.symbol, input.new_name)
|
||||
}
|
||||
Err(_) => "Rename symbol".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
input: serde_json::Value,
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
let input = match serde_json::from_value::<RenameToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
};
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let buffer = {
|
||||
let project_path = project.read_with(cx, |project, cx| {
|
||||
project
|
||||
.find_project_path(&input.path, cx)
|
||||
.context("Path not found in project")
|
||||
})??;
|
||||
|
||||
project.update(cx, |project, cx| project.open_buffer(project_path, cx))?.await?
|
||||
};
|
||||
|
||||
action_log.update(cx, |action_log, cx| {
|
||||
action_log.buffer_read(buffer.clone(), cx);
|
||||
})?;
|
||||
|
||||
let position = {
|
||||
let Some(position) = buffer.read_with(cx, |buffer, _cx| {
|
||||
find_symbol_position(&buffer, &input.context_before_symbol, &input.symbol, &input.context_after_symbol)
|
||||
})? else {
|
||||
return Err(anyhow!(
|
||||
"Failed to locate the symbol specified by context_before_symbol, symbol, and context_after_symbol. Make sure context_before_symbol and context_after_symbol each match exactly once in the file."
|
||||
));
|
||||
};
|
||||
|
||||
buffer.read_with(cx, |buffer, _| {
|
||||
position.to_point_utf16(&buffer.snapshot())
|
||||
})?
|
||||
};
|
||||
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.perform_rename(buffer.clone(), position, input.new_name.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
project
|
||||
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))?
|
||||
.await?;
|
||||
|
||||
action_log.update(cx, |log, cx| {
|
||||
log.buffer_edited(buffer.clone(), cx)
|
||||
})?;
|
||||
|
||||
Ok(format!("Renamed '{}' to '{}'", input.symbol, input.new_name))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the position of the symbol in the buffer, if it appears between context_before_symbol
|
||||
/// and context_after_symbol, and if that combined string has one unique result in the buffer.
|
||||
///
|
||||
/// If an exact match fails, it tries adding a newline to the end of context_before_symbol and
|
||||
/// to the beginning of context_after_symbol to accommodate line-based context matching.
|
||||
fn find_symbol_position(
|
||||
buffer: &Buffer,
|
||||
context_before_symbol: &str,
|
||||
symbol: &str,
|
||||
context_after_symbol: &str,
|
||||
) -> Option<language::Anchor> {
|
||||
let snapshot = buffer.snapshot();
|
||||
let text = snapshot.text();
|
||||
|
||||
// First try with exact match
|
||||
let search_string = format!("{context_before_symbol}{symbol}{context_after_symbol}");
|
||||
let mut positions = text.match_indices(&search_string);
|
||||
let position_result = positions.next();
|
||||
|
||||
if let Some(position) = position_result {
|
||||
// Check if the matched string is unique
|
||||
if positions.next().is_none() {
|
||||
let symbol_start = position.0 + context_before_symbol.len();
|
||||
let symbol_start_anchor =
|
||||
snapshot.anchor_before(snapshot.offset_to_point(symbol_start));
|
||||
|
||||
return Some(symbol_start_anchor);
|
||||
}
|
||||
}
|
||||
|
||||
// If exact match fails or is not unique, try with line-based context
|
||||
// Add a newline to the end of before context and beginning of after context
|
||||
let line_based_before = if context_before_symbol.ends_with('\n') {
|
||||
context_before_symbol.to_string()
|
||||
} else {
|
||||
format!("{context_before_symbol}\n")
|
||||
};
|
||||
|
||||
let line_based_after = if context_after_symbol.starts_with('\n') {
|
||||
context_after_symbol.to_string()
|
||||
} else {
|
||||
format!("\n{context_after_symbol}")
|
||||
};
|
||||
|
||||
let line_search_string = format!("{line_based_before}{symbol}{line_based_after}");
|
||||
let mut line_positions = text.match_indices(&line_search_string);
|
||||
let line_position = line_positions.next()?;
|
||||
|
||||
// The line-based search string must also appear exactly once
|
||||
if line_positions.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let line_symbol_start = line_position.0 + line_based_before.len();
|
||||
let line_symbol_start_anchor =
|
||||
snapshot.anchor_before(snapshot.offset_to_point(line_symbol_start));
|
||||
|
||||
Some(line_symbol_start_anchor)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
Renames a symbol across your codebase using the language server's semantic knowledge.
|
||||
|
||||
This tool performs a rename refactoring operation on a specified symbol. It uses the project's language server to analyze the code and perform the rename correctly across all files where the symbol is referenced.
|
||||
|
||||
Unlike a simple find and replace, this tool understands the semantic meaning of the code, so it only renames the specific symbol you specify and not unrelated text that happens to have the same name.
|
||||
|
||||
Examples of symbols you can rename:
|
||||
- Variables
|
||||
- Functions
|
||||
- Classes/structs
|
||||
- Fields/properties
|
||||
- Methods
|
||||
- Interfaces/traits
|
||||
|
||||
The language server handles updating all references to the renamed symbol throughout the codebase.
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ impl Tool for ThinkingTool {
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::LightBulb
|
||||
IconName::Brain
|
||||
}
|
||||
|
||||
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> serde_json::Value {
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||