Compare commits
338 Commits
debug-shel
...
linux/keys
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8276554222 | ||
|
|
2400ed34e0 | ||
|
|
ce23637dc1 | ||
|
|
7bcd6d839f | ||
|
|
20b9989b79 | ||
|
|
3269029a3e | ||
|
|
ea0e908714 | ||
|
|
8b69ccb488 | ||
|
|
2d6d53219b | ||
|
|
de615870cc | ||
|
|
f1f9470a14 | ||
|
|
9433c381eb | ||
|
|
6ede6a1573 | ||
|
|
7e0d74db8a | ||
|
|
e019ce8260 | ||
|
|
7ab7eab54e | ||
|
|
7da2e2af50 | ||
|
|
92a986cffd | ||
|
|
8b6658c650 | ||
|
|
9bc4c0f7f5 | ||
|
|
f829a178f6 | ||
|
|
2df80de880 | ||
|
|
2976b99b14 | ||
|
|
ad3d44119a | ||
|
|
0b5a264f88 | ||
|
|
f22d06cf6e | ||
|
|
26718da9bb | ||
|
|
671a91e69d | ||
|
|
bbda5d4f78 | ||
|
|
2fb674088f | ||
|
|
5c4ea49793 | ||
|
|
21e7cc3fed | ||
|
|
f53168c56c | ||
|
|
3182f14972 | ||
|
|
eca211e0f1 | ||
|
|
d6add799dc | ||
|
|
e9649dc25c | ||
|
|
71daf47ad5 | ||
|
|
66c6f5066e | ||
|
|
be79ccde07 | ||
|
|
061ba9b6d2 | ||
|
|
1ba3c2f589 | ||
|
|
bbee877ca8 | ||
|
|
c6e697fc7f | ||
|
|
b7b83105fc | ||
|
|
7d901c5e47 | ||
|
|
1a8d1944b0 | ||
|
|
35db644beb | ||
|
|
76e52ea374 | ||
|
|
ca0f0cc8d1 | ||
|
|
a133c1311d | ||
|
|
08ffd9884a | ||
|
|
dc591fe7c7 | ||
|
|
95784d53ca | ||
|
|
9b63ba6205 | ||
|
|
862e733ef5 | ||
|
|
66dda8e368 | ||
|
|
16d02cfdb3 | ||
|
|
2c41e10c98 | ||
|
|
9ab5e78b79 | ||
|
|
e30e4381de | ||
|
|
de627ba04d | ||
|
|
80eed63255 | ||
|
|
974bc4096a | ||
|
|
642d8bb8f5 | ||
|
|
41fe2a2ab4 | ||
|
|
96ff6d86a3 | ||
|
|
6e5763215f | ||
|
|
de0e6f716c | ||
|
|
93bfae71dc | ||
|
|
171be7e009 | ||
|
|
d5cc1cbaa9 | ||
|
|
6d26f107dd | ||
|
|
e2b9dfa89c | ||
|
|
495ec7a109 | ||
|
|
b9b42bee99 | ||
|
|
a9b82e1e57 | ||
|
|
81cc1e8f75 | ||
|
|
7114a5ca99 | ||
|
|
45d200f2f8 | ||
|
|
1569b662ff | ||
|
|
4ed206b37c | ||
|
|
6daf888fdb | ||
|
|
acff48fc0d | ||
|
|
ecf4d5539e | ||
|
|
8e8a772c2d | ||
|
|
df57754baf | ||
|
|
3a247ee947 | ||
|
|
ad8b823555 | ||
|
|
139af02737 | ||
|
|
6b7c30d7ad | ||
|
|
1220049089 | ||
|
|
01bdef130b | ||
|
|
9a3720edd3 | ||
|
|
11ddecb995 | ||
|
|
684e14e55b | ||
|
|
263080c4c4 | ||
|
|
925464cfc6 | ||
|
|
bcac748c2b | ||
|
|
0ca0914cca | ||
|
|
8bd739d869 | ||
|
|
60e9ab8f93 | ||
|
|
3327f90e0f | ||
|
|
5e15c05a9d | ||
|
|
1f3575ad6e | ||
|
|
f1db3b4e1d | ||
|
|
02d0e725a8 | ||
|
|
211d6205b9 | ||
|
|
4693f16759 | ||
|
|
c0dc758f24 | ||
|
|
9b7632d5f6 | ||
|
|
877ef5e1b1 | ||
|
|
66a1c356bf | ||
|
|
a9107dfaeb | ||
|
|
d549993c73 | ||
|
|
e0c860c42a | ||
|
|
2a6ef006f4 | ||
|
|
38febed02d | ||
|
|
8cc3b094d2 | ||
|
|
d87603dd60 | ||
|
|
de9053c7ca | ||
|
|
ddf3d99265 | ||
|
|
c35af6c2e2 | ||
|
|
6cb382c49f | ||
|
|
966e75b610 | ||
|
|
861ca05fb9 | ||
|
|
f785853239 | ||
|
|
82aee6bcf7 | ||
|
|
955580dae6 | ||
|
|
c99e42a3d6 | ||
|
|
018dbfba09 | ||
|
|
30a441b714 | ||
|
|
83562fca77 | ||
|
|
6b456ede49 | ||
|
|
6efc5ecefe | ||
|
|
2246b01c4b | ||
|
|
01295aa687 | ||
|
|
66e45818af | ||
|
|
76fe33245f | ||
|
|
44d1f512f8 | ||
|
|
69fd23e947 | ||
|
|
4ad47fc9cc | ||
|
|
0555bbd0ec | ||
|
|
d3da0a809e | ||
|
|
31ec7ef2ec | ||
|
|
75928f4859 | ||
|
|
543a7b123a | ||
|
|
8fecacfbaa | ||
|
|
59cdea00c5 | ||
|
|
3d7e012e09 | ||
|
|
8ebea17a9c | ||
|
|
5253702200 | ||
|
|
ed7552d3e3 | ||
|
|
38544e514a | ||
|
|
03ca2f4d2b | ||
|
|
91bfe6f968 | ||
|
|
0ebf7f54bb | ||
|
|
7a2593e520 | ||
|
|
fcd9da6cef | ||
|
|
fcdd99e244 | ||
|
|
4e6b7ee3ea | ||
|
|
34322ef1cd | ||
|
|
cc0d8a411e | ||
|
|
12ab53b804 | ||
|
|
b46e961991 | ||
|
|
42aca4189a | ||
|
|
f34a7abf17 | ||
|
|
2bf2c5c580 | ||
|
|
a6ee4a18c4 | ||
|
|
cdb7564d89 | ||
|
|
968587a745 | ||
|
|
48c8555076 | ||
|
|
06ddc74917 | ||
|
|
5c88e9c66b | ||
|
|
2a8121fbfc | ||
|
|
6cd4dbdea1 | ||
|
|
def8bab5a8 | ||
|
|
32d058d95e | ||
|
|
77c4530e12 | ||
|
|
fbc4256732 | ||
|
|
82fac9da82 | ||
|
|
e224da8522 | ||
|
|
610f4605d8 | ||
|
|
0553dc0d49 | ||
|
|
f27b508e4b | ||
|
|
64c413b9b6 | ||
|
|
5f70a9cf59 | ||
|
|
105acacff9 | ||
|
|
aa60647fe8 | ||
|
|
4cc06bfb4e | ||
|
|
11cb9ddeb9 | ||
|
|
169620632a | ||
|
|
f9bd54b4b1 | ||
|
|
4fdda8d5a1 | ||
|
|
9dc3ac9657 | ||
|
|
6d09f3fa40 | ||
|
|
132bba8d8b | ||
|
|
903212b7f5 | ||
|
|
c15d02454e | ||
|
|
f000dfebd2 | ||
|
|
123a25c58c | ||
|
|
79f3cb1225 | ||
|
|
faca128304 | ||
|
|
3f0f316f4d | ||
|
|
9d6b2e8a32 | ||
|
|
1d74fdc59f | ||
|
|
b7bfdd3383 | ||
|
|
0e2e5b8b0d | ||
|
|
6b06685723 | ||
|
|
8d894dd1df | ||
|
|
0eee768e7b | ||
|
|
f1f19a32fb | ||
|
|
2ff155d5a2 | ||
|
|
eb74df632b | ||
|
|
0068de0386 | ||
|
|
a11647d07f | ||
|
|
274f2e90da | ||
|
|
31b7786be7 | ||
|
|
351ba5023b | ||
|
|
3041de0cdf | ||
|
|
52c42125a7 | ||
|
|
62e8f45304 | ||
|
|
0fe73a99e5 | ||
|
|
6e9c6c5684 | ||
|
|
42f788185a | ||
|
|
a5b2428897 | ||
|
|
0629804390 | ||
|
|
3151b5efc1 | ||
|
|
782fbfad90 | ||
|
|
2caa19214b | ||
|
|
bff5d85ff4 | ||
|
|
abe5d523e1 | ||
|
|
8fb3199a84 | ||
|
|
0d809c21ba | ||
|
|
93b1e95a5d | ||
|
|
49bc2e61da | ||
|
|
9a4bcd11a2 | ||
|
|
2ee5bedfa9 | ||
|
|
d497f52e17 | ||
|
|
f022a13091 | ||
|
|
c74ecb4654 | ||
|
|
7609ca7a8d | ||
|
|
32906bfa7c | ||
|
|
5fafab6e52 | ||
|
|
a2e786e0f9 | ||
|
|
b0086b472f | ||
|
|
d10cc13924 | ||
|
|
2680a78f9c | ||
|
|
197828980c | ||
|
|
7c4da37322 | ||
|
|
ce164f5e65 | ||
|
|
42c59014a9 | ||
|
|
3db452eec7 | ||
|
|
6e77e8405b | ||
|
|
465f64da7e | ||
|
|
e5a8cc7aab | ||
|
|
bdf29bf76f | ||
|
|
402c61c00d | ||
|
|
59e88ce82b | ||
|
|
22ab4c53d1 | ||
|
|
f106ea7641 | ||
|
|
e37ef2a991 | ||
|
|
1c05062482 | ||
|
|
8c04f12499 | ||
|
|
aa7ccecc49 | ||
|
|
f4aeeda2d9 | ||
|
|
ca0bd53bed | ||
|
|
ae6237178c | ||
|
|
ac3328adb6 | ||
|
|
d63909c598 | ||
|
|
c3d0230f89 | ||
|
|
bc5927d5af | ||
|
|
d2cf995e27 | ||
|
|
86161aa427 | ||
|
|
a602b4b305 | ||
|
|
047d515abf | ||
|
|
e5bcd720e1 | ||
|
|
41583fb066 | ||
|
|
521a223681 | ||
|
|
c8c6468f9c | ||
|
|
3f4098e87b | ||
|
|
1d684c8890 | ||
|
|
97c5c5a6e7 | ||
|
|
ba4fc1bcfc | ||
|
|
bbf16bda75 | ||
|
|
c56b8904cc | ||
|
|
695118d110 | ||
|
|
a675ca7a1e | ||
|
|
6e762d9c05 | ||
|
|
28380d714d | ||
|
|
f338c46bf7 | ||
|
|
5fbb7b0d40 | ||
|
|
f12b0dddf4 | ||
|
|
14bb10d783 | ||
|
|
c9ce4aec91 | ||
|
|
01dfb6fa82 | ||
|
|
f9987a1141 | ||
|
|
7432e947bc | ||
|
|
157199b65b | ||
|
|
d74f3f4ea6 | ||
|
|
9e2023bffc | ||
|
|
3ab4ad6de8 | ||
|
|
e3ce0618a3 | ||
|
|
865dd4c5fc | ||
|
|
2178f66af6 | ||
|
|
338a7395a7 | ||
|
|
4c2415b338 | ||
|
|
e6bc1308af | ||
|
|
6c46e1129d | ||
|
|
fbb5628ec6 | ||
|
|
8c9116daa5 | ||
|
|
20a3e613b8 | ||
|
|
ba1c05abf2 | ||
|
|
2823771c06 | ||
|
|
343f155ab9 | ||
|
|
2dece13d83 | ||
|
|
985dcf7523 | ||
|
|
b079871428 | ||
|
|
4983b01c89 | ||
|
|
35863c4302 | ||
|
|
a0bd25f218 | ||
|
|
8a1e795746 | ||
|
|
f4818b648e | ||
|
|
7031ed8b87 | ||
|
|
6073d2c93c | ||
|
|
00499aadd4 | ||
|
|
d1eb69c6cd | ||
|
|
40cbfb7eb2 | ||
|
|
5d0f02d356 | ||
|
|
ca8e213151 | ||
|
|
90c893747c | ||
|
|
d09c7eb317 | ||
|
|
1753432406 | ||
|
|
d9218b10ea | ||
|
|
dfdeb1bf51 | ||
|
|
b9f81c7ce7 | ||
|
|
b1450b6d71 | ||
|
|
1af9f98c1d |
@@ -19,6 +19,8 @@ rustflags = [
|
||||
"windows_slim_errors", # This cfg will reduce the size of `windows::core::Error` from 16 bytes to 4 bytes
|
||||
"-C",
|
||||
"target-feature=+crt-static", # This fixes the linking issue when compiling livekit on Windows
|
||||
"-C",
|
||||
"link-arg=-fuse-ld=lld",
|
||||
]
|
||||
|
||||
[env]
|
||||
|
||||
@@ -33,7 +33,6 @@ workspace-members = [
|
||||
"zed_emmet",
|
||||
"zed_glsl",
|
||||
"zed_html",
|
||||
"perplexity",
|
||||
"zed_proto",
|
||||
"zed_ruff",
|
||||
"slash_commands_example",
|
||||
|
||||
64
.github/actions/install_trusted_signing/action.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: "Trusted Signing on Windows"
|
||||
description: "Install trusted signing on Windows."
|
||||
|
||||
# Modified from https://github.com/Azure/trusted-signing-action
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set variables
|
||||
id: set-variables
|
||||
shell: "pwsh"
|
||||
run: |
|
||||
$defaultPath = $env:PSModulePath -split ';' | Select-Object -First 1
|
||||
"PSMODULEPATH=$defaultPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
|
||||
"TRUSTED_SIGNING_MODULE_VERSION=0.5.3" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
"BUILD_TOOLS_NUGET_VERSION=10.0.22621.3233" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
"TRUSTED_SIGNING_NUGET_VERSION=1.0.53" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
"DOTNET_SIGNCLI_NUGET_VERSION=0.9.1-beta.24469.1" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
|
||||
- name: Cache TrustedSigning PowerShell module
|
||||
id: cache-module
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-module
|
||||
with:
|
||||
path: ${{ steps.set-variables.outputs.PSMODULEPATH }}\TrustedSigning\${{ steps.set-variables.outputs.TRUSTED_SIGNING_MODULE_VERSION }}
|
||||
key: TrustedSigning-${{ steps.set-variables.outputs.TRUSTED_SIGNING_MODULE_VERSION }}
|
||||
if: ${{ inputs.cache-dependencies == 'true' }}
|
||||
|
||||
- name: Cache Microsoft.Windows.SDK.BuildTools NuGet package
|
||||
id: cache-buildtools
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-buildtools
|
||||
with:
|
||||
path: ~\AppData\Local\TrustedSigning\Microsoft.Windows.SDK.BuildTools\Microsoft.Windows.SDK.BuildTools.${{ steps.set-variables.outputs.BUILD_TOOLS_NUGET_VERSION }}
|
||||
key: Microsoft.Windows.SDK.BuildTools-${{ steps.set-variables.outputs.BUILD_TOOLS_NUGET_VERSION }}
|
||||
if: ${{ inputs.cache-dependencies == 'true' }}
|
||||
|
||||
- name: Cache Microsoft.Trusted.Signing.Client NuGet package
|
||||
id: cache-tsclient
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-tsclient
|
||||
with:
|
||||
path: ~\AppData\Local\TrustedSigning\Microsoft.Trusted.Signing.Client\Microsoft.Trusted.Signing.Client.${{ steps.set-variables.outputs.TRUSTED_SIGNING_NUGET_VERSION }}
|
||||
key: Microsoft.Trusted.Signing.Client-${{ steps.set-variables.outputs.TRUSTED_SIGNING_NUGET_VERSION }}
|
||||
if: ${{ inputs.cache-dependencies == 'true' }}
|
||||
|
||||
- name: Cache SignCli NuGet package
|
||||
id: cache-signcli
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-signcli
|
||||
with:
|
||||
path: ~\AppData\Local\TrustedSigning\sign\sign.${{ steps.set-variables.outputs.DOTNET_SIGNCLI_NUGET_VERSION }}
|
||||
key: SignCli-${{ steps.set-variables.outputs.DOTNET_SIGNCLI_NUGET_VERSION }}
|
||||
if: ${{ inputs.cache-dependencies == 'true' }}
|
||||
|
||||
- name: Install Trusted Signing module
|
||||
shell: "pwsh"
|
||||
run: |
|
||||
Install-Module -Name TrustedSigning -RequiredVersion ${{ steps.set-variables.outputs.TRUSTED_SIGNING_MODULE_VERSION }} -Force -Repository PSGallery
|
||||
if: ${{ inputs.cache-dependencies != 'true' || steps.cache-module.outputs.cache-hit != 'true' }}
|
||||
13
.github/actions/run_tests/action.yml
vendored
@@ -1,6 +1,12 @@
|
||||
name: "Run tests"
|
||||
description: "Runs the tests"
|
||||
|
||||
inputs:
|
||||
use-xvfb:
|
||||
description: "Whether to run tests with xvfb"
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
@@ -20,4 +26,9 @@ runs:
|
||||
|
||||
- name: Run tests
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: cargo nextest run --workspace --no-fail-fast
|
||||
run: |
|
||||
if [ "${{ inputs.use-xvfb }}" == "true" ]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1024x768x24 -nolisten tcp" cargo nextest run --workspace --no-fail-fast
|
||||
else
|
||||
cargo nextest run --workspace --no-fail-fast
|
||||
fi
|
||||
|
||||
98
.github/workflows/ci.yml
vendored
@@ -30,6 +30,7 @@ jobs:
|
||||
run_tests: ${{ steps.filter.outputs.run_tests }}
|
||||
run_license: ${{ steps.filter.outputs.run_license }}
|
||||
run_docs: ${{ steps.filter.outputs.run_docs }}
|
||||
run_nix: ${{ steps.filter.outputs.run_nix }}
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
@@ -64,11 +65,17 @@ jobs:
|
||||
else
|
||||
echo "run_docs=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep '^Cargo.lock') ]]; then
|
||||
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep -P '^(Cargo.lock|script/.*licenses)') ]]; then
|
||||
echo "run_license=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "run_license=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
NIX_REGEX='^(nix/|flake\.|Cargo\.|rust-toolchain.toml|\.cargo/config.toml)'
|
||||
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep "$NIX_REGEX") ]]; then
|
||||
echo "run_nix=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "run_nix=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
migration_checks:
|
||||
name: Check Postgres and Protobuf migrations, mergability
|
||||
@@ -327,6 +334,8 @@ jobs:
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
with:
|
||||
use-xvfb: true
|
||||
|
||||
- name: Build other binaries and features
|
||||
run: |
|
||||
@@ -383,7 +392,7 @@ jobs:
|
||||
|
||||
windows_tests:
|
||||
timeout-minutes: 60
|
||||
name: (Windows) Run Tests
|
||||
name: (Windows) Run Clippy and tests
|
||||
needs: [job_spec]
|
||||
if: |
|
||||
github.repository_owner == 'zed-industries' &&
|
||||
@@ -404,11 +413,10 @@ jobs:
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Setup Cargo and Rustup
|
||||
- name: Configure CI
|
||||
run: |
|
||||
mkdir -p ${{ env.CARGO_HOME }} -ErrorAction Ignore
|
||||
cp ./.cargo/ci-config.toml ${{ env.CARGO_HOME }}/config.toml
|
||||
.\script\install-rustup.ps1
|
||||
New-Item -ItemType Directory -Path "./../.cargo" -Force
|
||||
Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
|
||||
|
||||
- name: cargo clippy
|
||||
run: |
|
||||
@@ -423,18 +431,9 @@ jobs:
|
||||
- name: Limit target directory size
|
||||
run: ./script/clear-target-dir-if-larger-than.ps1 250
|
||||
|
||||
# - name: Check dev drive space
|
||||
# working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
# # `setup-dev-driver.ps1` creates a 100GB drive, with CI taking up ~45GB of the drive.
|
||||
# run: ./script/exit-ci-if-dev-drive-is-full.ps1 95
|
||||
|
||||
# Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug.
|
||||
- name: Clean CI config file
|
||||
if: always()
|
||||
run: |
|
||||
if (Test-Path "${{ env.CARGO_HOME }}/config.toml") {
|
||||
Remove-Item -Path "${{ env.CARGO_HOME }}/config.toml" -Force
|
||||
}
|
||||
run: Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
|
||||
|
||||
tests_pass:
|
||||
name: Tests Pass
|
||||
@@ -460,8 +459,10 @@ jobs:
|
||||
RET_CODE=0
|
||||
# Always check style
|
||||
[[ "${{ needs.style.result }}" != 'success' ]] && { RET_CODE=1; echo "style tests failed"; }
|
||||
[[ "${{ needs.check_docs.result }}" != 'success' ]] && { RET_CODE=1; echo "docs checks failed"; }
|
||||
|
||||
if [[ "${{ needs.job_spec.outputs.run_docs }}" == "true" ]]; then
|
||||
[[ "${{ needs.check_docs.result }}" != 'success' ]] && { RET_CODE=1; echo "docs checks failed"; }
|
||||
fi
|
||||
# Only check test jobs if they were supposed to run
|
||||
if [[ "${{ needs.job_spec.outputs.run_tests }}" == "true" ]]; then
|
||||
[[ "${{ needs.workspace_hack.result }}" != 'success' ]] && { RET_CODE=1; echo "Workspace Hack failed"; }
|
||||
@@ -744,19 +745,78 @@ jobs:
|
||||
nix-build:
|
||||
name: Build with Nix
|
||||
uses: ./.github/workflows/nix.yml
|
||||
if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix')
|
||||
needs: [job_spec]
|
||||
if: github.repository_owner == 'zed-industries' &&
|
||||
(contains(github.event.pull_request.labels.*.name, 'run-nix') ||
|
||||
needs.job_spec.outputs.run_nix == 'true')
|
||||
secrets: inherit
|
||||
with:
|
||||
flake-output: debug
|
||||
# excludes the final package to only cache dependencies
|
||||
cachix-filter: "-zed-editor-[0-9.]*-nightly"
|
||||
|
||||
bundle-windows-x64:
|
||||
timeout-minutes: 120
|
||||
name: Create a Windows installer
|
||||
runs-on: [self-hosted, Windows, X64]
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
needs: [windows_tests]
|
||||
env:
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
|
||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
|
||||
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
|
||||
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
|
||||
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||
FILE_DIGEST: SHA256
|
||||
TIMESTAMP_DIGEST: SHA256
|
||||
TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com"
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Determine version and release channel
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
||||
script/determine-release-channel.ps1
|
||||
|
||||
- name: Install trusted signing
|
||||
uses: ./.github/actions/install_trusted_signing
|
||||
|
||||
- name: Build Zed installer
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: script/bundle-windows.ps1
|
||||
|
||||
- name: Upload installer (x86_64) to Workflow - zed (run-bundling)
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
with:
|
||||
name: ZedEditorUserSetup-x64-${{ github.event.pull_request.head.sha || github.sha }}.exe
|
||||
path: ${{ env.SETUP_PATH }}
|
||||
|
||||
- name: Upload Artifacts to release
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||
# Re-enable when we are ready to publish windows preview releases
|
||||
if: false && ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) && env.RELEASE_CHANNEL == 'preview' }} # upload only preview
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
files: ${{ env.SETUP_PATH }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
auto-release-preview:
|
||||
name: Auto release preview
|
||||
if: |
|
||||
startsWith(github.ref, 'refs/tags/v')
|
||||
&& endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
|
||||
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, freebsd]
|
||||
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, bundle-windows-x64, freebsd]
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- bundle
|
||||
|
||||
12
.github/workflows/eval.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
types: [synchronize, reopened, labeled]
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -25,16 +25,6 @@ env:
|
||||
ZED_EVAL_TELEMETRY: 1
|
||||
|
||||
jobs:
|
||||
# This is a no-op job that we run to prevent GitHub from marking the workflow
|
||||
# as failed for PRs that don't have the `run-eval` label.
|
||||
noop:
|
||||
name: No-op
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
steps:
|
||||
- name: No-op
|
||||
run: echo "Nothing to do"
|
||||
|
||||
run_eval:
|
||||
timeout-minutes: 60
|
||||
name: Run Agent Eval
|
||||
|
||||
71
.github/workflows/release_nightly.yml
vendored
@@ -51,6 +51,32 @@ jobs:
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
windows-tests:
|
||||
timeout-minutes: 60
|
||||
name: Run tests on Windows
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: [self-hosted, Windows, X64]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Configure CI
|
||||
run: |
|
||||
New-Item -ItemType Directory -Path "./../.cargo" -Force
|
||||
Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests_windows
|
||||
|
||||
- name: Limit target directory size
|
||||
run: ./script/clear-target-dir-if-larger-than.ps1 1024
|
||||
|
||||
- name: Clean CI config file
|
||||
if: always()
|
||||
run: Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
|
||||
|
||||
bundle-mac:
|
||||
timeout-minutes: 60
|
||||
name: Create a macOS bundle
|
||||
@@ -213,10 +239,54 @@ jobs:
|
||||
|
||||
bundle-nix:
|
||||
name: Build and cache Nix package
|
||||
if: false
|
||||
needs: tests
|
||||
secrets: inherit
|
||||
uses: ./.github/workflows/nix.yml
|
||||
|
||||
bundle-windows-x64:
|
||||
timeout-minutes: 60
|
||||
name: Create a Windows installer
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: [self-hosted, Windows, X64]
|
||||
needs: windows-tests
|
||||
env:
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
|
||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
|
||||
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
|
||||
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
|
||||
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||
FILE_DIGEST: SHA256
|
||||
TIMESTAMP_DIGEST: SHA256
|
||||
TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com"
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Set release channel to nightly
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$version = git rev-parse --short HEAD
|
||||
Write-Host "Publishing version: $version on release channel nightly"
|
||||
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
|
||||
|
||||
- name: Install trusted signing
|
||||
uses: ./.github/actions/install_trusted_signing
|
||||
|
||||
- name: Build Zed installer
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: script/bundle-windows.ps1
|
||||
|
||||
- name: Upload Zed Nightly
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: script/upload-nightly.ps1 windows
|
||||
|
||||
update-nightly-tag:
|
||||
name: Update nightly tag
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
@@ -225,6 +295,7 @@ jobs:
|
||||
- bundle-mac
|
||||
- bundle-linux-x86
|
||||
- bundle-linux-arm
|
||||
- bundle-windows-x64
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
"build": {
|
||||
"label": "Build Zed",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build"
|
||||
]
|
||||
"args": ["build"]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -16,9 +14,7 @@
|
||||
"build": {
|
||||
"label": "Build Zed",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build"
|
||||
]
|
||||
"args": ["build"]
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
206
Cargo.lock
generated
@@ -2,6 +2,33 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "acp"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent_servers",
|
||||
"agentic-coding-protocol",
|
||||
"anyhow",
|
||||
"async-pipe",
|
||||
"buffer_diff",
|
||||
"editor",
|
||||
"env_logger 0.11.8",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"markdown",
|
||||
"project",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"tempfile",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "activity_indicator"
|
||||
version = "0.1.0"
|
||||
@@ -78,6 +105,7 @@ dependencies = [
|
||||
"language",
|
||||
"language_model",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
@@ -106,6 +134,24 @@ dependencies = [
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agent_servers"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"paths",
|
||||
"project",
|
||||
"schemars",
|
||||
"serde",
|
||||
"settings",
|
||||
"util",
|
||||
"which 6.0.3",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agent_settings"
|
||||
version = "0.1.0"
|
||||
@@ -129,8 +175,11 @@ dependencies = [
|
||||
name = "agent_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"acp",
|
||||
"agent",
|
||||
"agent_servers",
|
||||
"agent_settings",
|
||||
"agentic-coding-protocol",
|
||||
"anyhow",
|
||||
"assistant_context",
|
||||
"assistant_slash_command",
|
||||
@@ -190,6 +239,7 @@ dependencies = [
|
||||
"settings",
|
||||
"smol",
|
||||
"streaming_diff",
|
||||
"task",
|
||||
"telemetry",
|
||||
"telemetry_events",
|
||||
"terminal",
|
||||
@@ -211,6 +261,22 @@ dependencies = [
|
||||
"zed_llm_client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agentic-coding-protocol"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1ac0351749af7bf53c65042ef69fefb9351aa8b7efa0a813d6281377605c37d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"futures 0.3.31",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
@@ -537,6 +603,8 @@ dependencies = [
|
||||
"anyhow",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"net",
|
||||
"parking_lot",
|
||||
"smol",
|
||||
"tempfile",
|
||||
"util",
|
||||
@@ -1910,7 +1978,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"strum 0.27.1",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
@@ -2076,7 +2143,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-graphics"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5"
|
||||
source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"ash-window",
|
||||
@@ -2109,7 +2176,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-macros"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5"
|
||||
source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2119,7 +2186,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-util"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5"
|
||||
source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
|
||||
dependencies = [
|
||||
"blade-graphics",
|
||||
"bytemuck",
|
||||
@@ -4132,7 +4199,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dap-types"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/zed-industries/dap-types?rev=b40956a7f4d1939da67429d941389ee306a3a308#b40956a7f4d1939da67429d941389ee306a3a308"
|
||||
source = "git+https://github.com/zed-industries/dap-types?rev=7f39295b441614ca9dbf44293e53c32f666897f9#7f39295b441614ca9dbf44293e53c32f666897f9"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -4147,6 +4214,8 @@ dependencies = [
|
||||
"async-trait",
|
||||
"collections",
|
||||
"dap",
|
||||
"dotenvy",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"json_dotpath",
|
||||
@@ -4155,6 +4224,7 @@ dependencies = [
|
||||
"paths",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shlex",
|
||||
"task",
|
||||
"util",
|
||||
"workspace-hack",
|
||||
@@ -4308,6 +4378,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"alacritty_terminal",
|
||||
"anyhow",
|
||||
"bitflags 2.9.0",
|
||||
"client",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
@@ -4320,6 +4391,7 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"log",
|
||||
@@ -4340,6 +4412,7 @@ dependencies = [
|
||||
"tasks_ui",
|
||||
"telemetry",
|
||||
"terminal_view",
|
||||
"text",
|
||||
"theme",
|
||||
"tree-sitter",
|
||||
"tree-sitter-go",
|
||||
@@ -4673,12 +4746,6 @@ dependencies = [
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
@@ -4811,6 +4878,7 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"release_channel",
|
||||
"rpc",
|
||||
"schemars",
|
||||
@@ -4831,6 +4899,7 @@ dependencies = [
|
||||
"tree-sitter-python",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"tree-sitter-yaml",
|
||||
"ui",
|
||||
"unicode-script",
|
||||
"unicode-segmentation",
|
||||
@@ -5111,7 +5180,7 @@ dependencies = [
|
||||
"collections",
|
||||
"debug_adapter_extension",
|
||||
"dirs 4.0.0",
|
||||
"dotenv",
|
||||
"dotenvy",
|
||||
"env_logger 0.11.8",
|
||||
"extension",
|
||||
"fs",
|
||||
@@ -5187,6 +5256,16 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "explorer_command_injector"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"windows 0.61.1",
|
||||
"windows-core 0.61.0",
|
||||
"windows-registry 0.5.1",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exr"
|
||||
version = "1.73.0"
|
||||
@@ -8844,6 +8923,7 @@ dependencies = [
|
||||
"http_client",
|
||||
"imara-diff",
|
||||
"indoc",
|
||||
"inventory",
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"lsp",
|
||||
@@ -8942,12 +9022,15 @@ dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws_http_client",
|
||||
"bedrock",
|
||||
"chrono",
|
||||
"client",
|
||||
"collections",
|
||||
"component",
|
||||
"copilot",
|
||||
"credentials_provider",
|
||||
"deepseek",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"google_ai",
|
||||
@@ -9014,12 +9097,12 @@ dependencies = [
|
||||
"collections",
|
||||
"copilot",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"lsp",
|
||||
"picker",
|
||||
"project",
|
||||
"release_channel",
|
||||
"serde_json",
|
||||
@@ -9237,7 +9320,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libwebrtc"
|
||||
version = "0.3.10"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"jni",
|
||||
@@ -9317,7 +9400,7 @@ checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
|
||||
[[package]]
|
||||
name = "livekit"
|
||||
version = "0.7.8"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"futures-util",
|
||||
@@ -9340,7 +9423,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "livekit-api"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
@@ -9364,7 +9447,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "livekit-protocol"
|
||||
version = "0.3.9"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"livekit-runtime",
|
||||
@@ -9381,7 +9464,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "livekit-runtime"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -10226,6 +10309,18 @@ dependencies = [
|
||||
"jni-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "net"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-io",
|
||||
"smol",
|
||||
"tempfile",
|
||||
"windows 0.61.1",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.6"
|
||||
@@ -11331,14 +11426,6 @@ version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "perplexity"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"zed_extension_api 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.8.0"
|
||||
@@ -12255,6 +12342,7 @@ dependencies = [
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
"markdown",
|
||||
"node_runtime",
|
||||
"parking_lot",
|
||||
"pathdiff",
|
||||
@@ -12529,6 +12617,7 @@ dependencies = [
|
||||
"prost 0.9.0",
|
||||
"prost-build 0.9.0",
|
||||
"serde",
|
||||
"typed-path",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
@@ -13192,6 +13281,7 @@ dependencies = [
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"git",
|
||||
"git2",
|
||||
"git_hosting_providers",
|
||||
"gpui",
|
||||
"gpui_tokio",
|
||||
@@ -14049,12 +14139,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.22"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
checksum = "fe8c9d1c68d67dd9f97ecbc6f932b60eb289c5dbddd8aa1405484a8fd2fcd984"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dyn-clone",
|
||||
"indexmap",
|
||||
"ref-cast",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -14062,9 +14154,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.22"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
|
||||
checksum = "6ca9fcb757952f8e8629b9ab066fc62da523c46c2b247b1708a3be06dd82530b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -14551,28 +14643,41 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"smallvec",
|
||||
"streaming-iterator",
|
||||
"tree-sitter",
|
||||
"tree-sitter-json",
|
||||
"unindent",
|
||||
"util",
|
||||
"workspace-hack",
|
||||
"zlog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "settings_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"command_palette",
|
||||
"command_palette_hooks",
|
||||
"component",
|
||||
"db",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
"menu",
|
||||
"paths",
|
||||
"project",
|
||||
"schemars",
|
||||
"search",
|
||||
"serde",
|
||||
"settings",
|
||||
"theme",
|
||||
"tree-sitter-json",
|
||||
"tree-sitter-rust",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
@@ -15523,6 +15628,18 @@ version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
|
||||
|
||||
[[package]]
|
||||
name = "svg_preview"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"editor",
|
||||
"file_icons",
|
||||
"gpui",
|
||||
"ui",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "svgtypes"
|
||||
version = "0.15.3"
|
||||
@@ -15994,6 +16111,7 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"indexmap",
|
||||
"inventory",
|
||||
"log",
|
||||
"palette",
|
||||
"parking_lot",
|
||||
@@ -16034,7 +16152,6 @@ dependencies = [
|
||||
"indexmap",
|
||||
"log",
|
||||
"palette",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
@@ -16865,8 +16982,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tree-sitter-python"
|
||||
version = "0.23.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d065aaa27f3aaceaf60c1f0e0ac09e1cb9eb8ed28e7bcdaa52129cffc7f4b04"
|
||||
source = "git+https://github.com/zed-industries/tree-sitter-python?rev=218fcbf3fda3d029225f3dec005cb497d111b35e#218fcbf3fda3d029225f3dec005cb497d111b35e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
@@ -17005,6 +17121,12 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-path"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c462d18470a2857aa657d338af5fa67170bb48bcc80a296710ce3b0802a32566"
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.3"
|
||||
@@ -17318,6 +17440,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"rust-embed",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
@@ -18250,6 +18373,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"client",
|
||||
"feature_flags",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"http_client",
|
||||
@@ -18282,7 +18406,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "webrtc-sys"
|
||||
version = "0.3.7"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxx",
|
||||
@@ -18295,7 +18419,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "webrtc-sys-build"
|
||||
version = "0.3.6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
dependencies = [
|
||||
"fs2",
|
||||
"regex",
|
||||
@@ -18327,7 +18451,6 @@ dependencies = [
|
||||
"language",
|
||||
"picker",
|
||||
"project",
|
||||
"schemars",
|
||||
"serde",
|
||||
"settings",
|
||||
"telemetry",
|
||||
@@ -19522,6 +19645,7 @@ dependencies = [
|
||||
"rustix 1.0.7",
|
||||
"rustls 0.23.26",
|
||||
"rustls-webpki 0.103.1",
|
||||
"schemars",
|
||||
"scopeguard",
|
||||
"sea-orm",
|
||||
"sea-query-binder",
|
||||
@@ -19915,10 +20039,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.194.0"
|
||||
version = "0.196.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"agent",
|
||||
"agent_servers",
|
||||
"agent_settings",
|
||||
"agent_ui",
|
||||
"anyhow",
|
||||
@@ -20020,6 +20145,7 @@ dependencies = [
|
||||
"snippet_provider",
|
||||
"snippets_ui",
|
||||
"supermaven",
|
||||
"svg_preview",
|
||||
"sysinfo",
|
||||
"tab_switcher",
|
||||
"task",
|
||||
@@ -20112,9 +20238,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_llm_client"
|
||||
version = "0.8.4"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de7d9523255f4e00ee3d0918e5407bd252d798a4a8e71f6d37f23317a1588203"
|
||||
checksum = "6607f74dee2a18a9ce0f091844944a0e59881359ab62e0768fb0618f55d4c1dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
|
||||
38
Cargo.toml
@@ -2,9 +2,11 @@
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/activity_indicator",
|
||||
"crates/acp",
|
||||
"crates/agent_ui",
|
||||
"crates/agent",
|
||||
"crates/agent_settings",
|
||||
"crates/agent_servers",
|
||||
"crates/anthropic",
|
||||
"crates/askpass",
|
||||
"crates/assets",
|
||||
@@ -45,6 +47,7 @@ members = [
|
||||
"crates/diagnostics",
|
||||
"crates/docs_preprocessor",
|
||||
"crates/editor",
|
||||
"crates/explorer_command_injector",
|
||||
"crates/eval",
|
||||
"crates/extension",
|
||||
"crates/extension_api",
|
||||
@@ -95,9 +98,11 @@ members = [
|
||||
"crates/markdown_preview",
|
||||
"crates/media",
|
||||
"crates/menu",
|
||||
"crates/svg_preview",
|
||||
"crates/migrator",
|
||||
"crates/mistral",
|
||||
"crates/multi_buffer",
|
||||
"crates/net",
|
||||
"crates/node_runtime",
|
||||
"crates/notifications",
|
||||
"crates/ollama",
|
||||
@@ -187,7 +192,6 @@ members = [
|
||||
"extensions/emmet",
|
||||
"extensions/glsl",
|
||||
"extensions/html",
|
||||
"extensions/perplexity",
|
||||
"extensions/proto",
|
||||
"extensions/ruff",
|
||||
"extensions/slash-commands-example",
|
||||
@@ -214,10 +218,12 @@ edition = "2024"
|
||||
# Workspace member crates
|
||||
#
|
||||
|
||||
activity_indicator = { path = "crates/activity_indicator" }
|
||||
acp = { path = "crates/acp" }
|
||||
agent = { path = "crates/agent" }
|
||||
activity_indicator = { path = "crates/activity_indicator" }
|
||||
agent_ui = { path = "crates/agent_ui" }
|
||||
agent_settings = { path = "crates/agent_settings" }
|
||||
agent_servers = { path = "crates/agent_servers" }
|
||||
ai = { path = "crates/ai" }
|
||||
anthropic = { path = "crates/anthropic" }
|
||||
askpass = { path = "crates/askpass" }
|
||||
@@ -304,11 +310,13 @@ lmstudio = { path = "crates/lmstudio" }
|
||||
lsp = { path = "crates/lsp" }
|
||||
markdown = { path = "crates/markdown" }
|
||||
markdown_preview = { path = "crates/markdown_preview" }
|
||||
svg_preview = { path = "crates/svg_preview" }
|
||||
media = { path = "crates/media" }
|
||||
menu = { path = "crates/menu" }
|
||||
migrator = { path = "crates/migrator" }
|
||||
mistral = { path = "crates/mistral" }
|
||||
multi_buffer = { path = "crates/multi_buffer" }
|
||||
net = { path = "crates/net" }
|
||||
node_runtime = { path = "crates/node_runtime" }
|
||||
notifications = { path = "crates/notifications" }
|
||||
ollama = { path = "crates/ollama" }
|
||||
@@ -396,6 +404,7 @@ zlog_settings = { path = "crates/zlog_settings" }
|
||||
# External crates
|
||||
#
|
||||
|
||||
agentic-coding-protocol = "0.0.6"
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
|
||||
any_vec = "0.14"
|
||||
@@ -423,9 +432,9 @@ aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
|
||||
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
|
||||
base64 = "0.22"
|
||||
bitflags = "2.6.0"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
|
||||
blake3 = "1.5.3"
|
||||
bytes = "1.0"
|
||||
cargo_metadata = "0.19"
|
||||
@@ -442,12 +451,12 @@ core-video = { version = "0.4.3", features = ["metal"] }
|
||||
cpal = "0.16"
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
ctor = "0.4.0"
|
||||
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "b40956a7f4d1939da67429d941389ee306a3a308" }
|
||||
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "7f39295b441614ca9dbf44293e53c32f666897f9" }
|
||||
dashmap = "6.0"
|
||||
derive_more = "0.99.17"
|
||||
dirs = "4.0"
|
||||
documented = "0.9.1"
|
||||
dotenv = "0.15.0"
|
||||
dotenvy = "0.15.0"
|
||||
ec4rs = "1.1"
|
||||
emojis = "0.6.1"
|
||||
env_logger = "0.11"
|
||||
@@ -478,7 +487,7 @@ json_dotpath = "1.1"
|
||||
jsonschema = "0.30.0"
|
||||
jsonwebtoken = "9.3"
|
||||
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed" ,rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||
libc = "0.2"
|
||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||
linkify = "0.10.0"
|
||||
@@ -489,7 +498,7 @@ metal = "0.29"
|
||||
moka = { version = "0.12.10", features = ["sync"] }
|
||||
naga = { version = "25.0", features = ["wgsl-in"] }
|
||||
nanoid = "0.4"
|
||||
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
objc = "0.2"
|
||||
@@ -529,7 +538,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c77
|
||||
"stream",
|
||||
] }
|
||||
rsa = "0.9.6"
|
||||
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
|
||||
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
] }
|
||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||
@@ -538,7 +547,7 @@ rustc-hash = "2.1.0"
|
||||
rustls = { version = "0.23.26" }
|
||||
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"] }
|
||||
schemars = { version = "1.0", features = ["indexmap2"] }
|
||||
semver = "1.0"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
@@ -595,7 +604,7 @@ tree-sitter-html = "0.23"
|
||||
tree-sitter-jsdoc = "0.23"
|
||||
tree-sitter-json = "0.24"
|
||||
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
|
||||
tree-sitter-python = "0.23"
|
||||
tree-sitter-python = { git = "https://github.com/zed-industries/tree-sitter-python", rev = "218fcbf3fda3d029225f3dec005cb497d111b35e" }
|
||||
tree-sitter-regex = "0.24"
|
||||
tree-sitter-ruby = "0.23"
|
||||
tree-sitter-rust = "0.24"
|
||||
@@ -622,8 +631,10 @@ wasmtime = { version = "29", default-features = false, features = [
|
||||
] }
|
||||
wasmtime-wasi = "29"
|
||||
which = "6.0.0"
|
||||
windows-core = "0.61"
|
||||
wit-component = "0.221"
|
||||
workspace-hack = "0.1.0"
|
||||
zed_llm_client = "0.8.4"
|
||||
zed_llm_client = "= 0.8.6"
|
||||
zstd = "0.11"
|
||||
|
||||
[workspace.dependencies.async-stripe]
|
||||
@@ -658,6 +669,7 @@ features = [
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Graphics_Imaging",
|
||||
"Win32_Graphics_Imaging_D2D",
|
||||
"Win32_Networking_WinSock",
|
||||
"Win32_Security",
|
||||
"Win32_Security_Credentials",
|
||||
"Win32_Storage_FileSystem",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
|
||||
FROM rust:1.87-bookworm as builder
|
||||
FROM rust:1.88-bookworm as builder
|
||||
WORKDIR app
|
||||
COPY . .
|
||||
|
||||
|
||||
1
assets/icons/ai_gemini.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Google Gemini</title><path d="M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81"/></svg>
|
||||
|
After Width: | Height: | Size: 402 B |
1
assets/icons/arrow_down10.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-down10-icon lucide-arrow-down-1-0"><path d="m3 16 4 4 4-4"/><path d="M7 20V4"/><path d="M17 10V4h-2"/><path d="M15 10h4"/><rect x="15" y="14" width="4" height="6" ry="2"/></svg>
|
||||
|
After Width: | Height: | Size: 386 B |
3
assets/icons/bolt_filled_alt.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.98749 1.67322C7.08029 1.71878 7.15543 1.79374 7.20121 1.88643C7.24699 1.97912 7.26084 2.08434 7.24061 2.18572L6.72812 4.75007H9.28122C9.37107 4.75006 9.45903 4.77588 9.53463 4.82445C9.61022 4.87302 9.67027 4.94229 9.70761 5.02402C9.74495 5.10574 9.75801 5.19648 9.74524 5.28542C9.73247 5.37437 9.69441 5.45776 9.63559 5.52569L5.57313 10.2131C5.50536 10.2912 5.41366 10.3447 5.31233 10.3653C5.211 10.3858 5.10571 10.3723 5.01285 10.3268C4.92 10.2813 4.8448 10.2064 4.79896 10.1137C4.75311 10.021 4.7392 9.9158 4.75939 9.81439L5.27188 7.25004H2.71878C2.62893 7.25005 2.54097 7.22423 2.46537 7.17566C2.38978 7.12709 2.32973 7.05782 2.29239 6.97609C2.25505 6.89437 2.24199 6.80363 2.25476 6.71469C2.26753 6.62574 2.30559 6.54235 2.36441 6.47443L6.42687 1.78697C6.49466 1.70879 6.58641 1.65524 6.68782 1.63467C6.78923 1.61409 6.89459 1.62765 6.98749 1.67322Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clipboard"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/></svg>
|
||||
|
Before Width: | Height: | Size: 358 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0001 1.33334H4.00008C3.64646 1.33334 3.30732 1.47382 3.05727 1.72387C2.80722 1.97392 2.66675 2.31305 2.66675 2.66668V13.3333C2.66675 13.687 2.80722 14.0261 3.05727 14.2762C3.30732 14.5262 3.64646 14.6667 4.00008 14.6667H12.0001C12.3537 14.6667 12.6928 14.5262 12.9429 14.2762C13.1929 14.0261 13.3334 13.687 13.3334 13.3333V4.66668L10.0001 1.33334Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.66659 6.5L6.33325 9.83333" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.33325 6.5L9.66659 9.83333" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 804 B |
12
assets/icons/lsp_debug.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 3L7 4" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 4L10 3" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.002 6V5.51658C5.98992 5.32067 6.03266 5.12502 6.12762 4.94143C6.22259 4.75784 6.36781 4.59012 6.55453 4.44839C6.74125 4.30666 6.9656 4.19386 7.21403 4.1168C7.46246 4.03973 7.72983 4 8 4C8.27017 4 8.53754 4.03973 8.78597 4.1168C9.0344 4.19386 9.25875 4.30666 9.44547 4.44839C9.63219 4.59012 9.77741 4.75784 9.87238 4.94143C9.96734 5.12502 10.0101 5.32067 9.998 5.51658V6" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 13C6.35 13 5 11.5462 5 9.76923V8.15385C5 7.58261 5.21071 7.03477 5.58579 6.63085C5.96086 6.22692 6.46957 6 7 6H9C9.53043 6 10.0391 6.22692 10.4142 6.63085C10.7893 7.03477 11 7.58261 11 8.15385V9.76923C11 11.5462 9.65 13 8 13Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 6.16663C3.90652 6.06663 3 5.21663 3 4.16663" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 9H3" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 13C3 11.95 3.89474 11.05 5 11" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 4C13 5.05 12.0857 5.9 11 6" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 9H11" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 11C12.1053 11.05 13 11.95 13 13" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
4
assets/icons/lsp_restart.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.84265 10.7778C4.39206 11.6001 5.17295 12.241 6.08658 12.6194C7.00021 12.9978 8.00555 13.0969 8.97545 12.9039C9.94535 12.711 10.8363 12.2348 11.5355 11.5355C12.2348 10.8363 12.711 9.94535 12.9039 8.97545C13.0969 8.00555 12.9978 7.00021 12.6194 6.08658C12.241 5.17295 11.6001 4.39206 10.7778 3.84265C9.9556 3.29324 8.9889 3 8 3C6.60219 3.00526 5.26054 3.55068 4.25556 4.52222L3 5.77778" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 3V6H6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 685 B |
4
assets/icons/lsp_stop.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 5L11 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 409 B |
1
assets/icons/scroll_text.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-scroll-text-icon lucide-scroll-text"><path d="M15 12h-5"/><path d="M15 8h-5"/><path d="M19 17V5a2 2 0 0 0-2-2H4"/><path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"/></svg>
|
||||
|
After Width: | Height: | Size: 441 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-code"><path d="m13 13.5 2-2.5-2-2.5"/><path d="m21 21-4.3-4.3"/><path d="M9 8.5 7 11l2 2.5"/><circle cx="11" cy="11" r="8"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-split-icon lucide-split"><path d="M16 3h5v5"/><path d="M8 3H3v5"/><path d="M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3"/><path d="m15 9 6-6"/></svg>
|
||||
|
Before Width: | Height: | Size: 340 B After Width: | Height: | Size: 345 B |
3
assets/icons/tool_bulb.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.4174 10.2159C10.5454 9.58974 10.4174 9.57261 11.3762 8.46959C11.9337 7.82822 12.335 7.09214 12.335 6.27818C12.335 5.28184 11.9309 4.32631 11.2118 3.62179C10.4926 2.91728 9.5171 2.52148 8.50001 2.52148C7.48291 2.52148 6.50748 2.91728 5.78828 3.62179C5.06909 4.32631 4.66504 5.28184 4.66504 6.27818C4.66504 6.9043 4.79288 7.65565 5.62379 8.46959C6.58253 9.59098 6.45474 9.58974 6.58257 10.2159M10.4174 10.2159L10.4174 12.2989C10.4174 12.9504 9.87836 13.4786 9.21329 13.4786H7.78674C7.12167 13.4786 6.58253 12.9504 6.58253 12.2989L6.58257 10.2159M10.4174 10.2159H8.50001H6.58257" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 776 B |
4
assets/icons/tool_copy.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 2.5H6.5C6.22386 2.5 6 2.83579 6 3.25V4.75C6 5.16421 6.22386 5.5 6.5 5.5H9.5C9.77614 5.5 10 5.16421 10 4.75V3.25C10 2.83579 9.77614 2.5 9.5 2.5Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 3.5H11C11.2652 3.5 11.5196 3.61706 11.7071 3.82544C11.8946 4.03381 12 4.31643 12 4.61111V12.3889C12 12.6836 11.8946 12.9662 11.7071 13.1746C11.5196 13.3829 11.2652 13.5 11 13.5H5C4.73478 13.5 4.48043 13.3829 4.29289 13.1746C4.10536 12.9662 4 12.6836 4 12.3889V4.61111C4 4.31643 4.10536 4.03381 4.29289 3.82544C4.48043 3.61706 4.73478 3.5 5 3.5H6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 788 B |
5
assets/icons/tool_delete_file.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.50002 2.5H5C4.73478 2.5 4.48043 2.6159 4.29289 2.82219C4.10535 3.02848 4 3.30826 4 3.6V12.3999C4 12.6917 4.10535 12.9715 4.29289 13.1778C4.48043 13.3841 4.73478 13.5 5 13.5H11C11.2652 13.5 11.5195 13.3841 11.7071 13.1778C11.8946 12.9715 12 12.6917 12 12.3999V5.25L9.50002 2.5Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.3427 6.82379L6.65698 9.5095" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.65698 6.82379L9.3427 9.5095" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 724 B |
5
assets/icons/tool_diagnostics.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.7244 11.5299L9.01711 3.2922C8.91447 3.11109 8.76562 2.96045 8.58576 2.85564C8.4059 2.75084 8.20145 2.69562 7.99328 2.69562C7.7851 2.69562 7.58066 2.75084 7.40079 2.85564C7.22093 2.96045 7.07209 3.11109 6.96945 3.2922L2.26218 11.5299C2.15844 11.7096 2.10404 11.9135 2.1045 12.121C2.10495 12.3285 2.16026 12.5321 2.2648 12.7113C2.36934 12.8905 2.5194 13.0389 2.69978 13.1415C2.88015 13.244 3.08443 13.297 3.2919 13.2951H12.7064C12.9129 13.2949 13.1157 13.2404 13.2944 13.137C13.4731 13.0336 13.6215 12.8851 13.7247 12.7062C13.8278 12.5273 13.8821 12.3245 13.882 12.118C13.882 11.9115 13.8276 11.7087 13.7244 11.5299Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.99927 6.23425V8.58788" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.99927 10.9415H8.00492" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
3
assets/icons/tool_folder.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.4 12.5C12.6917 12.5 12.9715 12.3884 13.1778 12.1899C13.3841 11.9913 13.5 11.722 13.5 11.4412V6.14706C13.5 5.86624 13.3841 5.59693 13.1778 5.39836C12.9715 5.19979 12.6917 5.08824 12.4 5.08824H8.055C7.87103 5.08997 7.68955 5.04726 7.52717 4.96402C7.36478 4.88078 7.22668 4.75967 7.1255 4.61176L6.68 3.97647C6.57984 3.83007 6.44349 3.7099 6.28317 3.62674C6.12286 3.54358 5.94361 3.50003 5.7615 3.5H3.6C3.30826 3.5 3.02847 3.61155 2.82218 3.81012C2.61589 4.00869 2.5 4.27801 2.5 4.55882V11.4412C2.5 11.722 2.61589 11.9913 2.82218 12.1899C3.02847 12.3884 3.30826 12.5 3.6 12.5H12.4Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 778 B |
5
assets/icons/tool_hammer.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 8.5L4.94864 12.6222C4.71647 12.8544 4.40157 12.9848 4.07323 12.9848C3.74488 12.9848 3.42999 12.8544 3.19781 12.6222C2.96564 12.39 2.83521 12.0751 2.83521 11.7468C2.83521 11.4185 2.96564 11.1036 3.19781 10.8714L7.5 6.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.8352 9.98474L13.8352 6.98474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.8352 7.42495L11.7634 6.4298C11.5533 6.23484 11.4353 5.97039 11.4352 5.69462V5.08526L10.1696 3.91022C9.54495 3.33059 8.69961 3.00261 7.81649 2.99722L5.83521 2.98474L6.35041 3.41108C6.71634 3.71233 7.00935 4.08216 7.21013 4.4962C7.4109 4.91024 7.51488 5.35909 7.51521 5.81316L7.5 6.5L9 8.5L9.5 8C9.5 8 9.87337 7.79457 10.0834 7.98959L11.1552 8.98474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 988 B |
4
assets/icons/tool_notification.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 12C6.65203 12.304 6.87068 12.5565 7.13399 12.7321C7.39729 12.9076 7.69597 13 8 13C8.30403 13 8.60271 12.9076 8.86601 12.7321C9.12932 12.5565 9.34797 12.304 9.5 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3.63088 9.21874C3.56556 9.28556 3.52246 9.36865 3.50681 9.45791C3.49116 9.54718 3.50364 9.63876 3.54273 9.72152C3.58183 9.80429 3.64585 9.87467 3.72701 9.92409C3.80817 9.97352 3.90298 9.99987 3.99989 9.99994H12.0001C12.097 9.99997 12.1918 9.97372 12.273 9.92439C12.3542 9.87505 12.4183 9.80476 12.4575 9.72205C12.4967 9.63934 12.5093 9.54778 12.4938 9.45851C12.4783 9.36924 12.4353 9.2861 12.3701 9.21921C11.705 8.57941 11 7.89947 11 5.79994C11 5.05733 10.684 4.34514 10.1213 3.82004C9.55872 3.29494 8.79564 2.99994 7.99997 2.99994C7.20431 2.99994 6.44123 3.29494 5.87861 3.82004C5.31599 4.34514 4.99991 5.05733 4.99991 5.79994C4.99991 7.89947 4.2944 8.57941 3.63088 9.21874Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
4
assets/icons/tool_pencil.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5871 5.40582C12.8514 5.14152 13 4.78304 13 4.40922C13 4.03541 12.8516 3.67688 12.5873 3.41252C12.323 3.14816 11.9645 2.99962 11.5907 2.99957C11.2169 2.99953 10.8584 3.14798 10.594 3.41227L3.92098 10.0869C3.80488 10.2027 3.71903 10.3452 3.67097 10.5019L3.01047 12.678C2.99754 12.7212 2.99657 12.7672 3.00764 12.8109C3.01872 12.8547 3.04143 12.8946 3.07337 12.9265C3.1053 12.9584 3.14528 12.981 3.18905 12.992C3.23282 13.003 3.27875 13.002 3.32197 12.989L5.49849 12.329C5.65508 12.2813 5.79758 12.196 5.91349 12.0805L12.5871 5.40582Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 5L11 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 835 B |
7
assets/icons/tool_read.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 5.66667V4.33333C3 3.97971 3.14048 3.64057 3.39052 3.39052C3.64057 3.14048 3.97971 3 4.33333 3H5.66667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.3333 3H11.6666C12.0202 3 12.3593 3.14048 12.6094 3.39052C12.8594 3.64057 12.9999 3.97971 12.9999 4.33333V5.66667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.9999 10.3333V11.6666C12.9999 12.0203 12.8594 12.3594 12.6094 12.6095C12.3593 12.8595 12.0202 13 11.6666 13H10.3333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.66667 13H4.33333C3.97971 13 3.64057 12.8595 3.39052 12.6095C3.14048 12.3594 3 12.0203 3 11.6666V10.3333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.5 8H10.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
4
assets/icons/tool_regex.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.57132 13.7143C5.20251 13.7143 5.71418 13.2026 5.71418 12.5714C5.71418 11.9403 5.20251 11.4286 4.57132 11.4286C3.94014 11.4286 3.42847 11.9403 3.42847 12.5714C3.42847 13.2026 3.94014 13.7143 4.57132 13.7143Z" fill="black"/>
|
||||
<path d="M10.2856 2.85712V5.71426M10.2856 5.71426V8.5714M10.2856 5.71426H13.1428M10.2856 5.71426H7.42847M10.2856 5.71426L12.1904 3.80949M10.2856 5.71426L8.38084 7.61906M10.2856 5.71426L12.1904 7.61906M10.2856 5.71426L8.38084 3.80949" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 631 B |
4
assets/icons/tool_search.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 13L11 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.5 12C9.98528 12 12 9.98528 12 7.5C12 5.01472 9.98528 3 7.5 3C5.01472 3 3 5.01472 3 7.5C3 9.98528 5.01472 12 7.5 12Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 421 B |
5
assets/icons/tool_terminal.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.99487 8.44023L7.32821 7.10689L5.99487 5.77356" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.33838 10.2264H10.005" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.8889 3H4.11111C3.49746 3 3 3.49746 3 4.11111V11.8889C3 12.5025 3.49746 13 4.11111 13H11.8889C12.5025 13 13 12.5025 13 11.8889V4.11111C13 3.49746 12.5025 3 11.8889 3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 625 B |
5
assets/icons/tool_web.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.99993 13.4804C11.0267 13.4804 13.4803 11.0267 13.4803 7.99999C13.4803 4.97325 11.0267 2.51959 7.99993 2.51959C4.97319 2.51959 2.51953 4.97325 2.51953 7.99999C2.51953 11.0267 4.97319 13.4804 7.99993 13.4804Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 3C6.71611 4.34807 6 6.13836 6 7.99999C6 9.86163 6.71611 11.6519 8 13C9.28387 11.6519 10 9.86163 10 7.99999C10 6.13836 9.28387 4.34807 8 3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3.24121 7.04827C4.52425 8.27022 6.22817 8.95178 7.99999 8.95178C9.77182 8.95178 11.4757 8.27022 12.7588 7.04827" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 847 B |
@@ -34,7 +34,7 @@
|
||||
"ctrl-q": "zed::Quit",
|
||||
"f4": "debugger::Start",
|
||||
"shift-f5": "debugger::Stop",
|
||||
"ctrl-shift-f5": "debugger::Restart",
|
||||
"ctrl-shift-f5": "debugger::RerunSession",
|
||||
"f6": "debugger::Pause",
|
||||
"f7": "debugger::StepOver",
|
||||
"ctrl-f11": "debugger::StepInto",
|
||||
@@ -244,6 +244,7 @@
|
||||
"ctrl-alt-e": "agent::RemoveAllContext",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-enter": "agent::ContinueThread",
|
||||
"super-ctrl-b": "agent::ToggleBurnMode",
|
||||
"alt-enter": "agent::ContinueWithBurnMode"
|
||||
}
|
||||
},
|
||||
@@ -267,6 +268,14 @@
|
||||
"ctrl-alt-t": "agent::NewThread"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AgentPanel && acp_thread",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-n": "agent::NewAcpThread",
|
||||
"ctrl-alt-t": "agent::NewThread"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "MessageEditor > Editor",
|
||||
"bindings": {
|
||||
@@ -305,6 +314,15 @@
|
||||
"enter": "agent::AcceptSuggestedContext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AcpThread > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "agent::Chat",
|
||||
"up": "agent::PreviousHistoryMessage",
|
||||
"down": "agent::NextHistoryMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ThreadHistory",
|
||||
"bindings": {
|
||||
@@ -490,13 +508,27 @@
|
||||
"ctrl-k r": "editor::RevealInFileManager",
|
||||
"ctrl-k p": "editor::CopyPath",
|
||||
"ctrl-\\": "pane::SplitRight",
|
||||
"ctrl-k v": "markdown::OpenPreviewToTheSide",
|
||||
"ctrl-shift-v": "markdown::OpenPreview",
|
||||
"ctrl-alt-shift-c": "editor::DisplayCursorNames",
|
||||
"alt-.": "editor::GoToHunk",
|
||||
"alt-,": "editor::GoToPreviousHunk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == md",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-k v": "markdown::OpenPreviewToTheSide",
|
||||
"ctrl-shift-v": "markdown::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == svg",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-k v": "svg::OpenPreviewToTheSide",
|
||||
"ctrl-shift-v": "svg::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
@@ -542,6 +574,13 @@
|
||||
"ctrl-b": "workspace::ToggleLeftDock",
|
||||
"ctrl-j": "workspace::ToggleBottomDock",
|
||||
"ctrl-alt-y": "workspace::CloseAllDocks",
|
||||
"ctrl-alt-0": "workspace::ResetActiveDockSize",
|
||||
// For 0px parameter, uses UI font size value.
|
||||
"ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }],
|
||||
"ctrl-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }],
|
||||
"ctrl-alt-)": "workspace::ResetOpenDocksSize",
|
||||
"ctrl-alt-_": ["workspace::DecreaseOpenDocksSize", { "px": 0 }],
|
||||
"ctrl-alt-+": ["workspace::IncreaseOpenDocksSize", { "px": 0 }],
|
||||
"shift-find": "pane::DeploySearch",
|
||||
"ctrl-shift-f": "pane::DeploySearch",
|
||||
"ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
|
||||
@@ -583,7 +622,9 @@
|
||||
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
|
||||
// or by tag:
|
||||
// "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
|
||||
"f5": "debugger::RerunLastSession"
|
||||
"f5": "debugger::Rerun",
|
||||
"ctrl-f4": "workspace::CloseActiveDock",
|
||||
"ctrl-w": "workspace::CloseActiveDock"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -686,6 +727,13 @@
|
||||
"pagedown": "editor::ContextMenuLast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"up": "editor::SignatureHelpPrevious",
|
||||
"down": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
// Custom bindings
|
||||
{
|
||||
"bindings": {
|
||||
@@ -904,7 +952,9 @@
|
||||
"context": "BreakpointList",
|
||||
"bindings": {
|
||||
"space": "debugger::ToggleEnableBreakpoint",
|
||||
"backspace": "debugger::UnsetBreakpoint"
|
||||
"backspace": "debugger::UnsetBreakpoint",
|
||||
"left": "debugger::PreviousBreakpointProperty",
|
||||
"right": "debugger::NextBreakpointProperty"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1050,5 +1100,19 @@
|
||||
"ctrl-tab": "pane::ActivateNextItem",
|
||||
"ctrl-shift-tab": "pane::ActivatePreviousItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "MarkdownPreview",
|
||||
"bindings": {
|
||||
"pageup": "markdown::MovePageUp",
|
||||
"pagedown": "markdown::MovePageDown"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "KeymapEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-f": "search::FocusSearch"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
"bindings": {
|
||||
"f4": "debugger::Start",
|
||||
"shift-f5": "debugger::Stop",
|
||||
"shift-cmd-f5": "debugger::Restart",
|
||||
"shift-cmd-f5": "debugger::RerunSession",
|
||||
"f6": "debugger::Pause",
|
||||
"f7": "debugger::StepOver",
|
||||
"f11": "debugger::StepInto",
|
||||
"ctrl-f11": "debugger::StepInto",
|
||||
"shift-f11": "debugger::StepOut",
|
||||
"home": "menu::SelectFirst",
|
||||
"shift-pageup": "menu::SelectFirst",
|
||||
@@ -283,6 +283,7 @@
|
||||
"cmd->": "assistant::QuoteSelection",
|
||||
"cmd-alt-e": "agent::RemoveAllContext",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-ctrl-b": "agent::ToggleBurnMode",
|
||||
"cmd-shift-enter": "agent::ContinueThread",
|
||||
"alt-enter": "agent::ContinueWithBurnMode"
|
||||
}
|
||||
@@ -308,6 +309,14 @@
|
||||
"cmd-alt-t": "agent::NewThread"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AgentPanel && acp_thread",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-n": "agent::NewAcpThread",
|
||||
"cmd-alt-t": "agent::NewThread"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "MessageEditor > Editor",
|
||||
"use_key_equivalents": true,
|
||||
@@ -356,6 +365,15 @@
|
||||
"ctrl--": "pane::GoBack"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AcpThread > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "agent::Chat",
|
||||
"up": "agent::PreviousHistoryMessage",
|
||||
"down": "agent::NextHistoryMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ThreadHistory",
|
||||
"bindings": {
|
||||
@@ -544,11 +562,25 @@
|
||||
"cmd-k r": "editor::RevealInFileManager",
|
||||
"cmd-k p": "editor::CopyPath",
|
||||
"cmd-\\": "pane::SplitRight",
|
||||
"cmd-k v": "markdown::OpenPreviewToTheSide",
|
||||
"cmd-shift-v": "markdown::OpenPreview",
|
||||
"ctrl-cmd-c": "editor::DisplayCursorNames"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == md",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-k v": "markdown::OpenPreviewToTheSide",
|
||||
"cmd-shift-v": "markdown::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == svg",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-k v": "svg::OpenPreviewToTheSide",
|
||||
"cmd-shift-v": "svg::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"use_key_equivalents": true,
|
||||
@@ -587,6 +619,7 @@
|
||||
"alt-cmd-o": ["projects::OpenRecent", { "create_new_window": false }],
|
||||
"ctrl-cmd-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }],
|
||||
"ctrl-cmd-shift-o": ["projects::OpenRemote", { "from_existing_connection": true, "create_new_window": false }],
|
||||
"cmd-ctrl-b": "branches::OpenRecent",
|
||||
"ctrl-~": "workspace::NewTerminal",
|
||||
"cmd-s": "workspace::Save",
|
||||
"cmd-k s": "workspace::SaveWithoutFormat",
|
||||
@@ -604,9 +637,17 @@
|
||||
"cmd-8": ["workspace::ActivatePane", 7],
|
||||
"cmd-9": ["workspace::ActivatePane", 8],
|
||||
"cmd-b": "workspace::ToggleLeftDock",
|
||||
"cmd-alt-b": "workspace::ToggleRightDock",
|
||||
"cmd-r": "workspace::ToggleRightDock",
|
||||
"cmd-j": "workspace::ToggleBottomDock",
|
||||
"alt-cmd-y": "workspace::CloseAllDocks",
|
||||
// For 0px parameter, uses UI font size value.
|
||||
"ctrl-alt-0": "workspace::ResetActiveDockSize",
|
||||
"ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }],
|
||||
"ctrl-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }],
|
||||
"ctrl-alt-)": "workspace::ResetOpenDocksSize",
|
||||
"ctrl-alt-_": ["workspace::DecreaseOpenDocksSize", { "px": 0 }],
|
||||
"ctrl-alt-+": ["workspace::IncreaseOpenDocksSize", { "px": 0 }],
|
||||
"cmd-shift-f": "pane::DeploySearch",
|
||||
"cmd-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
|
||||
"cmd-shift-t": "pane::ReopenClosedItem",
|
||||
@@ -635,7 +676,8 @@
|
||||
"cmd-k shift-up": "workspace::SwapPaneUp",
|
||||
"cmd-k shift-down": "workspace::SwapPaneDown",
|
||||
"cmd-shift-x": "zed::Extensions",
|
||||
"f5": "debugger::RerunLastSession"
|
||||
"f5": "debugger::Rerun",
|
||||
"cmd-w": "workspace::CloseActiveDock"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -749,6 +791,13 @@
|
||||
"pagedown": "editor::ContextMenuLast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"up": "editor::SignatureHelpPrevious",
|
||||
"down": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
// Custom bindings
|
||||
{
|
||||
"use_key_equivalents": true,
|
||||
@@ -963,7 +1012,9 @@
|
||||
"context": "BreakpointList",
|
||||
"bindings": {
|
||||
"space": "debugger::ToggleEnableBreakpoint",
|
||||
"backspace": "debugger::UnsetBreakpoint"
|
||||
"backspace": "debugger::UnsetBreakpoint",
|
||||
"left": "debugger::PreviousBreakpointProperty",
|
||||
"right": "debugger::NextBreakpointProperty"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1148,5 +1199,19 @@
|
||||
"ctrl-tab": "pane::ActivateNextItem",
|
||||
"ctrl-shift-tab": "pane::ActivatePreviousItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "MarkdownPreview",
|
||||
"bindings": {
|
||||
"pageup": "markdown::MovePageUp",
|
||||
"pagedown": "markdown::MovePageDown"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "KeymapEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-f": "search::FocusSearch"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
"alt->": "editor::MoveToEnd", // end-of-buffer
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
|
||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||
"alt-^": "editor::JoinLines" // join-line
|
||||
"alt-^": "editor::JoinLines", // join-line
|
||||
"alt-q": "editor::Rewrap" // fill-paragraph
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -97,6 +98,13 @@
|
||||
"ctrl-n": "editor::ContextMenuNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
"alt->": "editor::MoveToEnd", // end-of-buffer
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
|
||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||
"alt-^": "editor::JoinLines" // join-line
|
||||
"alt-^": "editor::JoinLines", // join-line
|
||||
"alt-q": "editor::Rewrap" // fill-paragraph
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -97,6 +98,13 @@
|
||||
"ctrl-n": "editor::ContextMenuNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
|
||||
@@ -189,6 +189,8 @@
|
||||
"z shift-r": "editor::UnfoldAll",
|
||||
"z l": "vim::ColumnRight",
|
||||
"z h": "vim::ColumnLeft",
|
||||
"z shift-l": "vim::HalfPageRight",
|
||||
"z shift-h": "vim::HalfPageLeft",
|
||||
"shift-z shift-q": ["pane::CloseActiveItem", { "save_intent": "skip" }],
|
||||
"shift-z shift-z": ["pane::CloseActiveItem", { "save_intent": "save_all" }],
|
||||
// Count support
|
||||
@@ -210,43 +212,26 @@
|
||||
"ctrl-w space": "editor::OpenExcerptsSplit",
|
||||
"ctrl-w g space": "editor::OpenExcerptsSplit",
|
||||
"ctrl-6": "pane::AlternateFile",
|
||||
"ctrl-^": "pane::AlternateFile"
|
||||
"ctrl-^": "pane::AlternateFile",
|
||||
".": "vim::Repeat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == normal",
|
||||
"bindings": {
|
||||
"ctrl-[": "editor::Cancel",
|
||||
"escape": "editor::Cancel",
|
||||
":": "command_palette::Toggle",
|
||||
".": "vim::Repeat",
|
||||
"c": "vim::PushChange",
|
||||
"shift-c": "vim::ChangeToEndOfLine",
|
||||
"d": "vim::PushDelete",
|
||||
"delete": "vim::DeleteRight",
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"g shift-j": "vim::JoinLinesNoWhitespace",
|
||||
"y": "vim::PushYank",
|
||||
"shift-y": "vim::YankLine",
|
||||
"i": "vim::InsertBefore",
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
"a": "vim::InsertAfter",
|
||||
"shift-a": "vim::InsertEndOfLine",
|
||||
"x": "vim::DeleteRight",
|
||||
"shift-x": "vim::DeleteLeft",
|
||||
"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::PushIndent",
|
||||
"<": "vim::PushOutdent",
|
||||
"=": "vim::PushAutoIndent",
|
||||
@@ -256,11 +241,8 @@
|
||||
"g ~": "vim::PushOppositeCase",
|
||||
"g ?": "vim::PushRot13",
|
||||
// "g ?": "vim::PushRot47",
|
||||
"\"": "vim::PushRegister",
|
||||
"g w": "vim::PushRewrap",
|
||||
"g q": "vim::PushRewrap",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePreviousItem",
|
||||
"insert": "vim::InsertBefore",
|
||||
// tree-sitter related commands
|
||||
"[ x": "vim::SelectLargerSyntaxNode",
|
||||
@@ -327,6 +309,7 @@
|
||||
"g shift-r": ["vim::Paste", { "preserve_clipboard": true }],
|
||||
"g c": "vim::ToggleComments",
|
||||
"g q": "vim::Rewrap",
|
||||
"g w": "vim::Rewrap",
|
||||
"g ?": "vim::ConvertToRot13",
|
||||
// "g ?": "vim::ConvertToRot47",
|
||||
"\"": "vim::PushRegister",
|
||||
@@ -363,18 +346,11 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == helix_normal && !menu",
|
||||
"context": "(vim_mode == normal || vim_mode == helix_normal) && !menu",
|
||||
"bindings": {
|
||||
"escape": "editor::Cancel",
|
||||
"ctrl-[": "editor::Cancel",
|
||||
":": "command_palette::Toggle",
|
||||
"left": "vim::WrappingLeft",
|
||||
"right": "vim::WrappingRight",
|
||||
"h": "vim::WrappingLeft",
|
||||
"l": "vim::WrappingRight",
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"y": "editor::Copy",
|
||||
"shift-y": "vim::YankLine",
|
||||
"i": "vim::InsertBefore",
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
@@ -388,27 +364,40 @@
|
||||
"p": "vim::Paste",
|
||||
"shift-p": ["vim::Paste", { "before": true }],
|
||||
"u": "vim::Undo",
|
||||
"shift-u": "vim::UndoLastLine",
|
||||
"r": "vim::PushReplace",
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
"\"": "vim::PushRegister",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePreviousItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == helix_normal && !menu",
|
||||
"bindings": {
|
||||
"ctrl-[": "editor::Cancel",
|
||||
":": "command_palette::Toggle",
|
||||
"left": "vim::WrappingLeft",
|
||||
"right": "vim::WrappingRight",
|
||||
"h": "vim::WrappingLeft",
|
||||
"l": "vim::WrappingRight",
|
||||
"y": "editor::Copy",
|
||||
"alt-;": "vim::OtherEnd",
|
||||
"ctrl-r": "vim::Redo",
|
||||
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
|
||||
"t": ["vim::PushFindForward", { "before": true, "multiline": true }],
|
||||
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
|
||||
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
|
||||
"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",
|
||||
".": "vim::Repeat",
|
||||
"alt-.": "vim::RepeatFind",
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
@@ -428,7 +417,6 @@
|
||||
"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",
|
||||
@@ -477,6 +465,13 @@
|
||||
"ctrl-n": "editor::ShowWordCompletions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == replace",
|
||||
"bindings": {
|
||||
@@ -849,6 +844,25 @@
|
||||
"shift-u": "git::UnstageAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == auto_height && VimControl",
|
||||
"bindings": {
|
||||
// TODO: Implement search
|
||||
"/": null,
|
||||
"?": null,
|
||||
"#": null,
|
||||
"*": null,
|
||||
"n": null,
|
||||
"shift-n": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitCommit > Editor && VimControl && vim_mode == normal",
|
||||
"bindings": {
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"escape": "menu::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && edit_prediction",
|
||||
"bindings": {
|
||||
@@ -860,14 +874,7 @@
|
||||
{
|
||||
"context": "MessageEditor > Editor && VimControl",
|
||||
"bindings": {
|
||||
"enter": "agent::Chat",
|
||||
// TODO: Implement search
|
||||
"/": null,
|
||||
"?": null,
|
||||
"#": null,
|
||||
"*": null,
|
||||
"n": null,
|
||||
"shift-n": null
|
||||
"enter": "agent::Chat"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -228,7 +228,12 @@
|
||||
// Whether to show code action button at start of buffer line.
|
||||
"inline_code_actions": true,
|
||||
// Whether to allow drag and drop text selection in buffer.
|
||||
"drag_and_drop_selection": true,
|
||||
"drag_and_drop_selection": {
|
||||
// When true, enables drag and drop text selection in buffer.
|
||||
"enabled": true,
|
||||
// The delay in milliseconds that must elapse before drag and drop is allowed. Otherwise, a new text selection is created.
|
||||
"delay": 300
|
||||
},
|
||||
// What to do when go to definition yields no results.
|
||||
//
|
||||
// 1. Do nothing: `none`
|
||||
@@ -357,7 +362,9 @@
|
||||
// Whether to show user picture in the titlebar.
|
||||
"show_user_picture": true,
|
||||
// Whether to show the sign in button in the titlebar.
|
||||
"show_sign_in": true
|
||||
"show_sign_in": true,
|
||||
// Whether to show the menus in the titlebar.
|
||||
"show_menus": false
|
||||
},
|
||||
// Scrollbar related settings
|
||||
"scrollbar": {
|
||||
@@ -617,6 +624,8 @@
|
||||
// 3. Mark files with errors and warnings:
|
||||
// "all"
|
||||
"show_diagnostics": "all",
|
||||
// Whether to stick parent directories at top of the project panel.
|
||||
"sticky_scroll": true,
|
||||
// Settings related to indent guides in the project panel.
|
||||
"indent_guides": {
|
||||
// When to show indent guides in the project panel.
|
||||
@@ -746,8 +755,6 @@
|
||||
"default_width": 380
|
||||
},
|
||||
"agent": {
|
||||
// Version of this setting.
|
||||
"version": "2",
|
||||
// Whether the agent is enabled.
|
||||
"enabled": true,
|
||||
/// What completion mode to start new threads in, if available. Can be 'normal' or 'burn'.
|
||||
@@ -810,6 +817,7 @@
|
||||
"edit_file": true,
|
||||
"fetch": true,
|
||||
"list_directory": true,
|
||||
"project_notifications": true,
|
||||
"move_path": true,
|
||||
"now": true,
|
||||
"find_path": true,
|
||||
@@ -829,6 +837,7 @@
|
||||
"diagnostics": true,
|
||||
"fetch": true,
|
||||
"list_directory": true,
|
||||
"project_notifications": true,
|
||||
"now": true,
|
||||
"find_path": true,
|
||||
"read_file": true,
|
||||
@@ -855,7 +864,15 @@
|
||||
// its response, or needs user input.
|
||||
|
||||
// Default: false
|
||||
"play_sound_when_agent_done": false
|
||||
"play_sound_when_agent_done": false,
|
||||
/// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff.
|
||||
///
|
||||
/// Default: true
|
||||
"expand_edit_card": true,
|
||||
/// Whether to have terminal cards in the agent panel expanded, showing the whole command output.
|
||||
///
|
||||
/// Default: true
|
||||
"expand_terminal_card": true
|
||||
},
|
||||
// The settings for slash commands.
|
||||
"slash_commands": {
|
||||
@@ -1292,6 +1309,8 @@
|
||||
// Whether or not selecting text in the terminal will automatically
|
||||
// copy to the system clipboard.
|
||||
"copy_on_select": false,
|
||||
// Whether to keep the text selection after copying it to the clipboard
|
||||
"keep_selection_on_copy": false,
|
||||
// Whether to show the terminal button in the status bar
|
||||
"button": true,
|
||||
// Any key-value pairs added to this list will be added to the terminal's
|
||||
@@ -1348,7 +1367,7 @@
|
||||
// 5. Never show the scrollbar:
|
||||
// "never"
|
||||
"show": null
|
||||
}
|
||||
},
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font size.
|
||||
// "font_size": 15,
|
||||
@@ -1365,6 +1384,21 @@
|
||||
// Default: 10_000, maximum: 100_000 (all bigger values set will be treated as 100_000), 0 disables the scrolling.
|
||||
// Existing terminals will not pick up this change until they are recreated.
|
||||
// "max_scroll_history_lines": 10000,
|
||||
// The minimum APCA perceptual contrast between foreground and background colors.
|
||||
// APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x,
|
||||
// especially for dark mode. Values range from 0 to 106.
|
||||
//
|
||||
// Based on APCA Readability Criterion (ARC) Bronze Simple Mode:
|
||||
// https://readtech.org/ARC/tests/bronze-simple-mode/
|
||||
// - 0: No contrast adjustment
|
||||
// - 45: Minimum for large fluent text (36px+)
|
||||
// - 60: Minimum for other content text
|
||||
// - 75: Minimum for body text
|
||||
// - 90: Preferred for body text
|
||||
//
|
||||
// Most terminal themes have APCA values of 40-70.
|
||||
// A value of 45 preserves colorful themes while ensuring legibility.
|
||||
"minimum_contrast": 45
|
||||
},
|
||||
"code_actions_on_format": {},
|
||||
// Settings related to running tasks.
|
||||
@@ -1576,6 +1610,9 @@
|
||||
"use_on_type_format": false,
|
||||
"allow_rewrap": "anywhere",
|
||||
"soft_wrap": "editor_width",
|
||||
"completions": {
|
||||
"words": "disabled"
|
||||
},
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
@@ -1589,6 +1626,9 @@
|
||||
}
|
||||
},
|
||||
"Plain Text": {
|
||||
"completions": {
|
||||
"words": "disabled"
|
||||
},
|
||||
"allow_rewrap": "anywhere"
|
||||
},
|
||||
"Python": {
|
||||
@@ -1656,7 +1696,6 @@
|
||||
// Different settings for specific language models.
|
||||
"language_models": {
|
||||
"anthropic": {
|
||||
"version": "1",
|
||||
"api_url": "https://api.anthropic.com"
|
||||
},
|
||||
"google": {
|
||||
@@ -1666,7 +1705,6 @@
|
||||
"api_url": "http://localhost:11434"
|
||||
},
|
||||
"openai": {
|
||||
"version": "1",
|
||||
"api_url": "https://api.openai.com/v1"
|
||||
},
|
||||
"open_router": {
|
||||
@@ -1784,7 +1822,8 @@
|
||||
// `socks5h`. `http` will be used when no scheme is specified.
|
||||
//
|
||||
// By default no proxy will be used, or Zed will try get proxy settings from
|
||||
// environment variables.
|
||||
// environment variables. If certain hosts should not be proxied,
|
||||
// set the `no_proxy` environment variable and provide a comma-separated list.
|
||||
//
|
||||
// Examples:
|
||||
// - "proxy": "socks5h://localhost:10808"
|
||||
@@ -1818,6 +1857,8 @@
|
||||
"read_ssh_config": true,
|
||||
// Configures context servers for use by the agent.
|
||||
"context_servers": {},
|
||||
// Configures agent servers available in the agent panel.
|
||||
"agent_servers": {},
|
||||
"debugger": {
|
||||
"stepping_granularity": "line",
|
||||
"save_breakpoints": true,
|
||||
|
||||
@@ -3,13 +3,6 @@
|
||||
// For more documentation on how to configure debug tasks,
|
||||
// see: https://zed.dev/docs/debugger
|
||||
[
|
||||
{
|
||||
"label": "Debug active PHP file",
|
||||
"adapter": "PHP",
|
||||
"program": "$ZED_FILE",
|
||||
"request": "launch",
|
||||
"cwd": "$ZED_WORKTREE_ROOT"
|
||||
},
|
||||
{
|
||||
"label": "Debug active Python file",
|
||||
"adapter": "Debugpy",
|
||||
|
||||
46
crates/acp/Cargo.toml
Normal file
@@ -0,0 +1,46 @@
|
||||
[package]
|
||||
name = "acp"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/acp.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = ["gpui/test-support", "project/test-support"]
|
||||
gemini = []
|
||||
|
||||
[dependencies]
|
||||
agent_servers.workspace = true
|
||||
agentic-coding-protocol.workspace = true
|
||||
anyhow.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
editor.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
markdown.workspace = true
|
||||
project.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
async-pipe.workspace = true
|
||||
env_logger.workspace = true
|
||||
gpui = { workspace = true, "features" = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
project = { workspace = true, "features" = ["test-support"] }
|
||||
serde_json.workspace = true
|
||||
tempfile.workspace = true
|
||||
util.workspace = true
|
||||
settings.workspace = true
|
||||
1
crates/acp/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
1645
crates/acp/src/acp.rs
Normal file
@@ -31,7 +31,13 @@ use workspace::{StatusItemView, Workspace, item::ItemHandle};
|
||||
|
||||
const GIT_OPERATION_DELAY: Duration = Duration::from_millis(0);
|
||||
|
||||
actions!(activity_indicator, [ShowErrorMessage]);
|
||||
actions!(
|
||||
activity_indicator,
|
||||
[
|
||||
/// Displays error messages from language servers in the status bar.
|
||||
ShowErrorMessage
|
||||
]
|
||||
);
|
||||
|
||||
pub enum Event {
|
||||
ShowStatus {
|
||||
|
||||
@@ -72,6 +72,7 @@ gpui = { workspace = true, "features" = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, "features" = ["test-support"] }
|
||||
language_model = { workspace = true, "features" = ["test-support"] }
|
||||
parking_lot.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent_settings::{AgentProfileId, AgentProfileSettings, AgentSettings};
|
||||
use assistant_tool::{Tool, ToolSource, ToolWorkingSet};
|
||||
use assistant_tool::{Tool, ToolSource, ToolWorkingSet, UniqueToolName};
|
||||
use collections::IndexMap;
|
||||
use convert_case::{Case, Casing};
|
||||
use fs::Fs;
|
||||
@@ -72,7 +72,7 @@ impl AgentProfile {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn enabled_tools(&self, cx: &App) -> Vec<Arc<dyn Tool>> {
|
||||
pub fn enabled_tools(&self, cx: &App) -> Vec<(UniqueToolName, Arc<dyn Tool>)> {
|
||||
let Some(settings) = AgentSettings::get_global(cx).profiles.get(&self.id) else {
|
||||
return Vec::new();
|
||||
};
|
||||
@@ -81,7 +81,7 @@ impl AgentProfile {
|
||||
.read(cx)
|
||||
.tools(cx)
|
||||
.into_iter()
|
||||
.filter(|tool| Self::is_enabled(settings, tool.source(), tool.name()))
|
||||
.filter(|(_, tool)| Self::is_enabled(settings, tool.source(), tool.name()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -96,16 +96,11 @@ impl AgentProfile {
|
||||
fn is_enabled(settings: &AgentProfileSettings, source: ToolSource, name: String) -> bool {
|
||||
match source {
|
||||
ToolSource::Native => *settings.tools.get(name.as_str()).unwrap_or(&false),
|
||||
ToolSource::ContextServer { id } => {
|
||||
if settings.enable_all_context_servers {
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(preset) = settings.context_servers.get(id.as_ref()) else {
|
||||
return false;
|
||||
};
|
||||
*preset.tools.get(name.as_str()).unwrap_or(&false)
|
||||
}
|
||||
ToolSource::ContextServer { id } => settings
|
||||
.context_servers
|
||||
.get(id.as_ref())
|
||||
.and_then(|preset| preset.tools.get(name.as_str()).copied())
|
||||
.unwrap_or(settings.enable_all_context_servers),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +137,7 @@ mod tests {
|
||||
let mut enabled_tools = cx
|
||||
.read(|cx| profile.enabled_tools(cx))
|
||||
.into_iter()
|
||||
.map(|tool| tool.name())
|
||||
.map(|(_, tool)| tool.name())
|
||||
.collect::<Vec<_>>();
|
||||
enabled_tools.sort();
|
||||
|
||||
@@ -179,7 +174,7 @@ mod tests {
|
||||
let mut enabled_tools = cx
|
||||
.read(|cx| profile.enabled_tools(cx))
|
||||
.into_iter()
|
||||
.map(|tool| tool.name())
|
||||
.map(|(_, tool)| tool.name())
|
||||
.collect::<Vec<_>>();
|
||||
enabled_tools.sort();
|
||||
|
||||
@@ -212,7 +207,7 @@ mod tests {
|
||||
let mut enabled_tools = cx
|
||||
.read(|cx| profile.enabled_tools(cx))
|
||||
.into_iter()
|
||||
.map(|tool| tool.name())
|
||||
.map(|(_, tool)| tool.name())
|
||||
.collect::<Vec<_>>();
|
||||
enabled_tools.sort();
|
||||
|
||||
@@ -272,10 +267,10 @@ mod tests {
|
||||
}
|
||||
|
||||
fn default_tool_set(cx: &mut TestAppContext) -> Entity<ToolWorkingSet> {
|
||||
cx.new(|_| {
|
||||
cx.new(|cx| {
|
||||
let mut tool_set = ToolWorkingSet::default();
|
||||
tool_set.insert(Arc::new(FakeTool::new("enabled_mcp_tool", "mcp")));
|
||||
tool_set.insert(Arc::new(FakeTool::new("disabled_mcp_tool", "mcp")));
|
||||
tool_set.insert(Arc::new(FakeTool::new("enabled_mcp_tool", "mcp")), cx);
|
||||
tool_set.insert(Arc::new(FakeTool::new("disabled_mcp_tool", "mcp")), cx);
|
||||
tool_set
|
||||
})
|
||||
}
|
||||
|
||||
3
crates/agent/src/prompts/stale_files_prompt_header.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
[The following is an auto-generated notification; do not reply]
|
||||
|
||||
These files have changed since the last read:
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
};
|
||||
use agent_settings::{AgentProfileId, CompletionMode};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ToolId, ToolWorkingSet};
|
||||
use assistant_tool::{Tool, ToolId, ToolWorkingSet};
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::HashMap;
|
||||
use context_server::ContextServerId;
|
||||
@@ -537,8 +537,8 @@ impl ThreadStore {
|
||||
}
|
||||
ContextServerStatus::Stopped | ContextServerStatus::Error(_) => {
|
||||
if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
|
||||
tool_working_set.update(cx, |tool_working_set, _| {
|
||||
tool_working_set.remove(&tool_ids);
|
||||
tool_working_set.update(cx, |tool_working_set, cx| {
|
||||
tool_working_set.remove(&tool_ids, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -569,19 +569,17 @@ impl ThreadStore {
|
||||
.log_err()
|
||||
{
|
||||
let tool_ids = tool_working_set
|
||||
.update(cx, |tool_working_set, _| {
|
||||
response
|
||||
.tools
|
||||
.into_iter()
|
||||
.map(|tool| {
|
||||
log::info!("registering context server tool: {:?}", tool.name);
|
||||
tool_working_set.insert(Arc::new(ContextServerTool::new(
|
||||
.update(cx, |tool_working_set, cx| {
|
||||
tool_working_set.extend(
|
||||
response.tools.into_iter().map(|tool| {
|
||||
Arc::new(ContextServerTool::new(
|
||||
context_server_store.clone(),
|
||||
server.id(),
|
||||
tool,
|
||||
)))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
)) as Arc<dyn Tool>
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.log_err();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::{
|
||||
thread::{MessageId, PromptId, ThreadId},
|
||||
thread_store::SerializedMessage,
|
||||
};
|
||||
use agent_settings::CompletionMode;
|
||||
use anyhow::Result;
|
||||
use assistant_tool::{
|
||||
AnyToolCard, Tool, ToolResultContent, ToolResultOutput, ToolUseStatus, ToolWorkingSet,
|
||||
@@ -11,8 +12,9 @@ use futures::{FutureExt as _, future::Shared};
|
||||
use gpui::{App, Entity, SharedString, Task, Window};
|
||||
use icons::IconName;
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModel, LanguageModelRequest, LanguageModelToolResult,
|
||||
LanguageModelToolResultContent, LanguageModelToolUse, LanguageModelToolUseId, Role,
|
||||
ConfiguredModel, LanguageModel, LanguageModelExt, LanguageModelRequest,
|
||||
LanguageModelToolResult, LanguageModelToolResultContent, LanguageModelToolUse,
|
||||
LanguageModelToolUseId, Role,
|
||||
};
|
||||
use project::Project;
|
||||
use std::sync::Arc;
|
||||
@@ -400,6 +402,7 @@ impl ToolUseState {
|
||||
tool_name: Arc<str>,
|
||||
output: Result<ToolResultOutput>,
|
||||
configured_model: Option<&ConfiguredModel>,
|
||||
completion_mode: CompletionMode,
|
||||
) -> Option<PendingToolUse> {
|
||||
let metadata = self.tool_use_metadata_by_id.remove(&tool_use_id);
|
||||
|
||||
@@ -426,7 +429,10 @@ impl ToolUseState {
|
||||
|
||||
// Protect from overly large output
|
||||
let tool_output_limit = configured_model
|
||||
.map(|model| model.model.max_token_count() as usize * BYTES_PER_TOKEN_ESTIMATE)
|
||||
.map(|model| {
|
||||
model.model.max_token_count_for_mode(completion_mode.into()) as usize
|
||||
* BYTES_PER_TOKEN_ESTIMATE
|
||||
})
|
||||
.unwrap_or(usize::MAX);
|
||||
|
||||
let content = match tool_result {
|
||||
|
||||
27
crates/agent_servers/Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "agent_servers"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/agent_servers.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
paths.workspace = true
|
||||
project.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
util.workspace = true
|
||||
which.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
1
crates/agent_servers/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
231
crates/agent_servers/src/agent_servers.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::HashMap;
|
||||
use gpui::{App, AsyncApp, Entity, SharedString};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources, SettingsStore};
|
||||
use util::{ResultExt, paths};
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
AllAgentServersSettings::register(cx);
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug)]
|
||||
pub struct AllAgentServersSettings {
|
||||
gemini: Option<AgentServerSettings>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug)]
|
||||
pub struct AgentServerSettings {
|
||||
#[serde(flatten)]
|
||||
command: AgentServerCommand,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)]
|
||||
pub struct AgentServerCommand {
|
||||
#[serde(rename = "command")]
|
||||
pub path: PathBuf,
|
||||
#[serde(default)]
|
||||
pub args: Vec<String>,
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
pub struct Gemini;
|
||||
|
||||
pub struct AgentServerVersion {
|
||||
pub current_version: SharedString,
|
||||
pub supported: bool,
|
||||
}
|
||||
|
||||
pub trait AgentServer: Send {
|
||||
fn command(
|
||||
&self,
|
||||
project: &Entity<Project>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> impl Future<Output = Result<AgentServerCommand>>;
|
||||
|
||||
fn version(
|
||||
&self,
|
||||
command: &AgentServerCommand,
|
||||
) -> impl Future<Output = Result<AgentServerVersion>> + Send;
|
||||
}
|
||||
|
||||
const GEMINI_ACP_ARG: &str = "--acp";
|
||||
|
||||
impl AgentServer for Gemini {
|
||||
async fn command(
|
||||
&self,
|
||||
project: &Entity<Project>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<AgentServerCommand> {
|
||||
let custom_command = cx.read_global(|settings: &SettingsStore, _| {
|
||||
let settings = settings.get::<AllAgentServersSettings>(None);
|
||||
settings
|
||||
.gemini
|
||||
.as_ref()
|
||||
.map(|gemini_settings| AgentServerCommand {
|
||||
path: gemini_settings.command.path.clone(),
|
||||
args: gemini_settings
|
||||
.command
|
||||
.args
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(std::iter::once(GEMINI_ACP_ARG.into()))
|
||||
.collect(),
|
||||
env: gemini_settings.command.env.clone(),
|
||||
})
|
||||
})?;
|
||||
|
||||
if let Some(custom_command) = custom_command {
|
||||
return Ok(custom_command);
|
||||
}
|
||||
|
||||
if let Some(path) = find_bin_in_path("gemini", project, cx).await {
|
||||
return Ok(AgentServerCommand {
|
||||
path,
|
||||
args: vec![GEMINI_ACP_ARG.into()],
|
||||
env: None,
|
||||
});
|
||||
}
|
||||
|
||||
let (fs, node_runtime) = project.update(cx, |project, _| {
|
||||
(project.fs().clone(), project.node_runtime().cloned())
|
||||
})?;
|
||||
let node_runtime = node_runtime.context("gemini not found on path")?;
|
||||
|
||||
let directory = ::paths::agent_servers_dir().join("gemini");
|
||||
fs.create_dir(&directory).await?;
|
||||
node_runtime
|
||||
.npm_install_packages(&directory, &[("@google/gemini-cli", "latest")])
|
||||
.await?;
|
||||
let path = directory.join("node_modules/.bin/gemini");
|
||||
|
||||
Ok(AgentServerCommand {
|
||||
path,
|
||||
args: vec![GEMINI_ACP_ARG.into()],
|
||||
env: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn version(&self, command: &AgentServerCommand) -> Result<AgentServerVersion> {
|
||||
let version_fut = util::command::new_smol_command(&command.path)
|
||||
.args(command.args.iter())
|
||||
.arg("--version")
|
||||
.kill_on_drop(true)
|
||||
.output();
|
||||
|
||||
let help_fut = util::command::new_smol_command(&command.path)
|
||||
.args(command.args.iter())
|
||||
.arg("--help")
|
||||
.kill_on_drop(true)
|
||||
.output();
|
||||
|
||||
let (version_output, help_output) = futures::future::join(version_fut, help_fut).await;
|
||||
|
||||
let current_version = String::from_utf8(version_output?.stdout)?.into();
|
||||
let supported = String::from_utf8(help_output?.stdout)?.contains(GEMINI_ACP_ARG);
|
||||
|
||||
Ok(AgentServerVersion {
|
||||
current_version,
|
||||
supported,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn find_bin_in_path(
|
||||
bin_name: &'static str,
|
||||
project: &Entity<Project>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Option<PathBuf> {
|
||||
let (env_task, root_dir) = project
|
||||
.update(cx, |project, cx| {
|
||||
let worktree = project.visible_worktrees(cx).next();
|
||||
match worktree {
|
||||
Some(worktree) => {
|
||||
let env_task = project.environment().update(cx, |env, cx| {
|
||||
env.get_worktree_environment(worktree.clone(), cx)
|
||||
});
|
||||
|
||||
let path = worktree.read(cx).abs_path();
|
||||
(env_task, path)
|
||||
}
|
||||
None => {
|
||||
let path: Arc<Path> = paths::home_dir().as_path().into();
|
||||
let env_task = project.environment().update(cx, |env, cx| {
|
||||
env.get_directory_environment(path.clone(), cx)
|
||||
});
|
||||
(env_task, path)
|
||||
}
|
||||
}
|
||||
})
|
||||
.log_err()?;
|
||||
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
let which_result = if cfg!(windows) {
|
||||
which::which(bin_name)
|
||||
} else {
|
||||
let env = env_task.await.unwrap_or_default();
|
||||
let shell_path = env.get("PATH").cloned();
|
||||
which::which_in(bin_name, shell_path.as_ref(), root_dir.as_ref())
|
||||
};
|
||||
|
||||
if let Err(which::Error::CannotFindBinaryPath) = which_result {
|
||||
return None;
|
||||
}
|
||||
|
||||
which_result.log_err()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AgentServerCommand {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let filtered_env = self.env.as_ref().map(|env| {
|
||||
env.iter()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
k,
|
||||
if util::redact::should_redact(k) {
|
||||
"[REDACTED]"
|
||||
} else {
|
||||
v
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
f.debug_struct("AgentServerCommand")
|
||||
.field("path", &self.path)
|
||||
.field("args", &self.args)
|
||||
.field("env", &filtered_env)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl settings::Settings for AllAgentServersSettings {
|
||||
const KEY: Option<&'static str> = Some("agent_servers");
|
||||
|
||||
type FileContent = Self;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
let mut settings = AllAgentServersSettings::default();
|
||||
|
||||
for value in sources.defaults_and_customizations() {
|
||||
if value.gemini.is_some() {
|
||||
settings.gemini = value.gemini.clone();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
@@ -6,9 +6,10 @@ use anyhow::{Result, bail};
|
||||
use collections::IndexMap;
|
||||
use gpui::{App, Pixels, SharedString};
|
||||
use language_model::LanguageModel;
|
||||
use schemars::{JsonSchema, schema::Schema};
|
||||
use schemars::{JsonSchema, json_schema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub use crate::agent_profile::*;
|
||||
|
||||
@@ -49,7 +50,7 @@ pub struct AgentSettings {
|
||||
pub dock: AgentDockPosition,
|
||||
pub default_width: Pixels,
|
||||
pub default_height: Pixels,
|
||||
pub default_model: LanguageModelSelection,
|
||||
pub default_model: Option<LanguageModelSelection>,
|
||||
pub inline_assistant_model: Option<LanguageModelSelection>,
|
||||
pub commit_message_model: Option<LanguageModelSelection>,
|
||||
pub thread_summary_model: Option<LanguageModelSelection>,
|
||||
@@ -66,6 +67,8 @@ pub struct AgentSettings {
|
||||
pub model_parameters: Vec<LanguageModelParameters>,
|
||||
pub preferred_completion_mode: CompletionMode,
|
||||
pub enable_feedback: bool,
|
||||
pub expand_edit_card: bool,
|
||||
pub expand_terminal_card: bool,
|
||||
}
|
||||
|
||||
impl AgentSettings {
|
||||
@@ -211,7 +214,6 @@ impl AgentSettingsContent {
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct AgentSettingsContent {
|
||||
/// Whether the Agent is enabled.
|
||||
///
|
||||
@@ -291,6 +293,14 @@ pub struct AgentSettingsContent {
|
||||
///
|
||||
/// Default: true
|
||||
enable_feedback: Option<bool>,
|
||||
/// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff.
|
||||
///
|
||||
/// Default: true
|
||||
expand_edit_card: Option<bool>,
|
||||
/// Whether to have terminal cards in the agent panel expanded, showing the whole command output.
|
||||
///
|
||||
/// Default: true
|
||||
expand_terminal_card: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
|
||||
@@ -321,29 +331,27 @@ pub struct LanguageModelSelection {
|
||||
pub struct LanguageModelProviderSetting(pub String);
|
||||
|
||||
impl JsonSchema for LanguageModelProviderSetting {
|
||||
fn schema_name() -> String {
|
||||
fn schema_name() -> Cow<'static, str> {
|
||||
"LanguageModelProviderSetting".into()
|
||||
}
|
||||
|
||||
fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> Schema {
|
||||
schemars::schema::SchemaObject {
|
||||
enum_values: Some(vec![
|
||||
"anthropic".into(),
|
||||
"amazon-bedrock".into(),
|
||||
"google".into(),
|
||||
"lmstudio".into(),
|
||||
"ollama".into(),
|
||||
"openai".into(),
|
||||
"zed.dev".into(),
|
||||
"copilot_chat".into(),
|
||||
"deepseek".into(),
|
||||
"openrouter".into(),
|
||||
"mistral".into(),
|
||||
"vercel".into(),
|
||||
]),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
json_schema!({
|
||||
"enum": [
|
||||
"anthropic",
|
||||
"amazon-bedrock",
|
||||
"google",
|
||||
"lmstudio",
|
||||
"ollama",
|
||||
"openai",
|
||||
"zed.dev",
|
||||
"copilot_chat",
|
||||
"deepseek",
|
||||
"openrouter",
|
||||
"mistral",
|
||||
"vercel"
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,15 +367,6 @@ impl From<&str> for LanguageModelProviderSetting {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LanguageModelSelection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
provider: LanguageModelProviderSetting("openai".to_string()),
|
||||
model: "gpt-4".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct AgentProfileContent {
|
||||
pub name: Arc<str>,
|
||||
@@ -411,7 +410,10 @@ impl Settings for AgentSettings {
|
||||
&mut settings.default_height,
|
||||
value.default_height.map(Into::into),
|
||||
);
|
||||
merge(&mut settings.default_model, value.default_model.clone());
|
||||
settings.default_model = value
|
||||
.default_model
|
||||
.clone()
|
||||
.or(settings.default_model.take());
|
||||
settings.inline_assistant_model = value
|
||||
.inline_assistant_model
|
||||
.clone()
|
||||
@@ -449,6 +451,11 @@ impl Settings for AgentSettings {
|
||||
value.preferred_completion_mode,
|
||||
);
|
||||
merge(&mut settings.enable_feedback, value.enable_feedback);
|
||||
merge(&mut settings.expand_edit_card, value.expand_edit_card);
|
||||
merge(
|
||||
&mut settings.expand_terminal_card,
|
||||
value.expand_terminal_card,
|
||||
);
|
||||
|
||||
settings
|
||||
.model_parameters
|
||||
|
||||
@@ -13,14 +13,14 @@ path = "src/agent_ui.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"gpui/test-support",
|
||||
"language/test-support",
|
||||
]
|
||||
test-support = ["gpui/test-support", "language/test-support"]
|
||||
|
||||
[dependencies]
|
||||
acp.workspace = true
|
||||
agent.workspace = true
|
||||
agentic-coding-protocol.workspace = true
|
||||
agent_settings.workspace = true
|
||||
agent_servers.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_context.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
@@ -76,6 +76,7 @@ serde_json_lenient.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
streaming_diff.workspace = true
|
||||
task.workspace = true
|
||||
telemetry.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
terminal.workspace = true
|
||||
|
||||
5
crates/agent_ui/src/acp.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod completion_provider;
|
||||
mod message_history;
|
||||
mod thread_view;
|
||||
|
||||
pub use thread_view::AcpThreadView;
|
||||
574
crates/agent_ui/src/acp/completion_provider.rs
Normal file
@@ -0,0 +1,574 @@
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use editor::display_map::CreaseId;
|
||||
use editor::{CompletionProvider, Editor, ExcerptId};
|
||||
use file_icons::FileIcons;
|
||||
use gpui::{App, Entity, Task, WeakEntity};
|
||||
use language::{Buffer, CodeLabel, HighlightId};
|
||||
use lsp::CompletionContext;
|
||||
use parking_lot::Mutex;
|
||||
use project::{Completion, CompletionIntent, CompletionResponse, ProjectPath, WorktreeId};
|
||||
use rope::Point;
|
||||
use text::{Anchor, ToPoint};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context_picker::MentionLink;
|
||||
use crate::context_picker::file_context_picker::{extract_file_name_and_directory, search_files};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MentionSet {
|
||||
paths_by_crease_id: HashMap<CreaseId, ProjectPath>,
|
||||
}
|
||||
|
||||
impl MentionSet {
|
||||
pub fn insert(&mut self, crease_id: CreaseId, path: ProjectPath) {
|
||||
self.paths_by_crease_id.insert(crease_id, path);
|
||||
}
|
||||
|
||||
pub fn path_for_crease_id(&self, crease_id: CreaseId) -> Option<ProjectPath> {
|
||||
self.paths_by_crease_id.get(&crease_id).cloned()
|
||||
}
|
||||
|
||||
pub fn drain(&mut self) -> impl Iterator<Item = CreaseId> {
|
||||
self.paths_by_crease_id.drain().map(|(id, _)| id)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContextPickerCompletionProvider {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
editor: WeakEntity<Editor>,
|
||||
mention_set: Arc<Mutex<MentionSet>>,
|
||||
}
|
||||
|
||||
impl ContextPickerCompletionProvider {
|
||||
pub fn new(
|
||||
mention_set: Arc<Mutex<MentionSet>>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
editor: WeakEntity<Editor>,
|
||||
) -> Self {
|
||||
Self {
|
||||
mention_set,
|
||||
workspace,
|
||||
editor,
|
||||
}
|
||||
}
|
||||
|
||||
fn completion_for_path(
|
||||
project_path: ProjectPath,
|
||||
path_prefix: &str,
|
||||
is_recent: bool,
|
||||
is_directory: bool,
|
||||
excerpt_id: ExcerptId,
|
||||
source_range: Range<Anchor>,
|
||||
editor: Entity<Editor>,
|
||||
mention_set: Arc<Mutex<MentionSet>>,
|
||||
cx: &App,
|
||||
) -> Completion {
|
||||
let (file_name, directory) =
|
||||
extract_file_name_and_directory(&project_path.path, path_prefix);
|
||||
|
||||
let label =
|
||||
build_code_label_for_full_path(&file_name, directory.as_ref().map(|s| s.as_ref()), cx);
|
||||
let full_path = if let Some(directory) = directory {
|
||||
format!("{}{}", directory, file_name)
|
||||
} else {
|
||||
file_name.to_string()
|
||||
};
|
||||
|
||||
let crease_icon_path = if is_directory {
|
||||
FileIcons::get_folder_icon(false, cx).unwrap_or_else(|| IconName::Folder.path().into())
|
||||
} else {
|
||||
FileIcons::get_icon(Path::new(&full_path), cx)
|
||||
.unwrap_or_else(|| IconName::File.path().into())
|
||||
};
|
||||
let completion_icon_path = if is_recent {
|
||||
IconName::HistoryRerun.path().into()
|
||||
} else {
|
||||
crease_icon_path.clone()
|
||||
};
|
||||
|
||||
let new_text = format!("{} ", MentionLink::for_file(&file_name, &full_path));
|
||||
let new_text_len = new_text.len();
|
||||
Completion {
|
||||
replace_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,
|
||||
project_path,
|
||||
excerpt_id,
|
||||
source_range.start,
|
||||
new_text_len - 1,
|
||||
editor,
|
||||
mention_set,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
|
||||
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
||||
let mut label = CodeLabel::default();
|
||||
|
||||
label.push_str(&file_name, None);
|
||||
label.push_str(" ", None);
|
||||
|
||||
if let Some(directory) = directory {
|
||||
label.push_str(&directory, comment_id);
|
||||
}
|
||||
|
||||
label.filter_range = 0..label.text().len();
|
||||
|
||||
label
|
||||
}
|
||||
|
||||
impl CompletionProvider for ContextPickerCompletionProvider {
|
||||
fn completions(
|
||||
&self,
|
||||
excerpt_id: ExcerptId,
|
||||
buffer: &Entity<Buffer>,
|
||||
buffer_position: Anchor,
|
||||
_trigger: CompletionContext,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Task<Result<Vec<CompletionResponse>>> {
|
||||
let state = buffer.update(cx, |buffer, _cx| {
|
||||
let position = buffer_position.to_point(buffer);
|
||||
let line_start = Point::new(position.row, 0);
|
||||
let offset_to_line = buffer.point_to_offset(line_start);
|
||||
let mut lines = buffer.text_for_range(line_start..position).lines();
|
||||
let line = lines.next()?;
|
||||
MentionCompletion::try_parse(line, offset_to_line)
|
||||
});
|
||||
let Some(state) = state else {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
};
|
||||
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
};
|
||||
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let source_range = snapshot.anchor_before(state.source_range.start)
|
||||
..snapshot.anchor_after(state.source_range.end);
|
||||
|
||||
let editor = self.editor.clone();
|
||||
let mention_set = self.mention_set.clone();
|
||||
let MentionCompletion { argument, .. } = state;
|
||||
let query = argument.unwrap_or_else(|| "".to_string());
|
||||
|
||||
let search_task = search_files(query.clone(), Arc::<AtomicBool>::default(), &workspace, cx);
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
let matches = search_task.await;
|
||||
let Some(editor) = editor.upgrade() else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
|
||||
let completions = cx.update(|cx| {
|
||||
matches
|
||||
.into_iter()
|
||||
.map(|mat| {
|
||||
let path_match = &mat.mat;
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(path_match.worktree_id),
|
||||
path: path_match.path.clone(),
|
||||
};
|
||||
|
||||
Self::completion_for_path(
|
||||
project_path,
|
||||
&path_match.path_prefix,
|
||||
mat.is_recent,
|
||||
path_match.is_dir,
|
||||
excerpt_id,
|
||||
source_range.clone(),
|
||||
editor.clone(),
|
||||
mention_set.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})?;
|
||||
|
||||
Ok(vec![CompletionResponse {
|
||||
completions,
|
||||
// Since this does its own filtering (see `filter_completions()` returns false),
|
||||
// there is no benefit to computing whether this set of completions is incomplete.
|
||||
is_incomplete: true,
|
||||
}])
|
||||
})
|
||||
}
|
||||
|
||||
fn is_completion_trigger(
|
||||
&self,
|
||||
buffer: &Entity<language::Buffer>,
|
||||
position: language::Anchor,
|
||||
_text: &str,
|
||||
_trigger_in_words: bool,
|
||||
_menu_is_open: bool,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> bool {
|
||||
let buffer = buffer.read(cx);
|
||||
let position = position.to_point(buffer);
|
||||
let line_start = Point::new(position.row, 0);
|
||||
let offset_to_line = buffer.point_to_offset(line_start);
|
||||
let mut lines = buffer.text_for_range(line_start..position).lines();
|
||||
if let Some(line) = lines.next() {
|
||||
MentionCompletion::try_parse(line, offset_to_line)
|
||||
.map(|completion| {
|
||||
completion.source_range.start <= offset_to_line + position.column as usize
|
||||
&& completion.source_range.end >= offset_to_line + position.column as usize
|
||||
})
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_completions(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn filter_completions(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm_completion_callback(
|
||||
crease_icon_path: SharedString,
|
||||
crease_text: SharedString,
|
||||
project_path: ProjectPath,
|
||||
excerpt_id: ExcerptId,
|
||||
start: Anchor,
|
||||
content_len: usize,
|
||||
editor: Entity<Editor>,
|
||||
mention_set: Arc<Mutex<MentionSet>>,
|
||||
) -> Arc<dyn Fn(CompletionIntent, &mut Window, &mut App) -> bool + Send + Sync> {
|
||||
Arc::new(move |_, window, cx| {
|
||||
let crease_text = crease_text.clone();
|
||||
let crease_icon_path = crease_icon_path.clone();
|
||||
let editor = editor.clone();
|
||||
let project_path = project_path.clone();
|
||||
let mention_set = mention_set.clone();
|
||||
window.defer(cx, move |window, cx| {
|
||||
let crease_id = crate::context_picker::insert_crease_for_mention(
|
||||
excerpt_id,
|
||||
start,
|
||||
content_len,
|
||||
crease_text.clone(),
|
||||
crease_icon_path,
|
||||
editor.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
if let Some(crease_id) = crease_id {
|
||||
mention_set.lock().insert(crease_id, project_path);
|
||||
}
|
||||
});
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
struct MentionCompletion {
|
||||
source_range: Range<usize>,
|
||||
argument: Option<String>,
|
||||
}
|
||||
|
||||
impl MentionCompletion {
|
||||
fn try_parse(line: &str, offset_to_line: usize) -> Option<Self> {
|
||||
let last_mention_start = line.rfind('@')?;
|
||||
if last_mention_start >= line.len() {
|
||||
return Some(Self::default());
|
||||
}
|
||||
if last_mention_start > 0
|
||||
&& line
|
||||
.chars()
|
||||
.nth(last_mention_start - 1)
|
||||
.map_or(false, |c| !c.is_whitespace())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let rest_of_line = &line[last_mention_start + 1..];
|
||||
let mut argument = None;
|
||||
|
||||
let mut parts = rest_of_line.split_whitespace();
|
||||
let mut end = last_mention_start + 1;
|
||||
if let Some(argument_text) = parts.next() {
|
||||
end += argument_text.len();
|
||||
argument = Some(argument_text.to_string());
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
source_range: last_mention_start + offset_to_line..end + offset_to_line,
|
||||
argument,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::{EventEmitter, FocusHandle, Focusable, TestAppContext, VisualTestContext};
|
||||
use project::{Project, ProjectPath};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::{ops::Deref, rc::Rc};
|
||||
use util::path;
|
||||
use workspace::{AppState, Item};
|
||||
|
||||
#[test]
|
||||
fn test_mention_completion_parse() {
|
||||
assert_eq!(MentionCompletion::try_parse("Lorem Ipsum", 0), None);
|
||||
|
||||
assert_eq!(
|
||||
MentionCompletion::try_parse("Lorem @", 0),
|
||||
Some(MentionCompletion {
|
||||
source_range: 6..7,
|
||||
argument: None,
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
MentionCompletion::try_parse("Lorem @main", 0),
|
||||
Some(MentionCompletion {
|
||||
source_range: 6..11,
|
||||
argument: Some("main".to_string()),
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(MentionCompletion::try_parse("test@", 0), None);
|
||||
}
|
||||
|
||||
struct AtMentionEditor(Entity<Editor>);
|
||||
|
||||
impl Item for AtMentionEditor {
|
||||
type Event = ();
|
||||
|
||||
fn include_in_nav_history() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||
"Test".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<()> for AtMentionEditor {}
|
||||
|
||||
impl Focusable for AtMentionEditor {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.0.read(cx).focus_handle(cx).clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AtMentionEditor {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
self.0.clone().into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_context_completion_provider(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let app_state = cx.update(AppState::test);
|
||||
|
||||
cx.update(|cx| {
|
||||
language::init(cx);
|
||||
editor::init(cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
Project::init_settings(cx);
|
||||
});
|
||||
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
path!("/dir"),
|
||||
json!({
|
||||
"editor": "",
|
||||
"a": {
|
||||
"one.txt": "",
|
||||
"two.txt": "",
|
||||
"three.txt": "",
|
||||
"four.txt": ""
|
||||
},
|
||||
"b": {
|
||||
"five.txt": "",
|
||||
"six.txt": "",
|
||||
"seven.txt": "",
|
||||
"eight.txt": "",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
|
||||
let worktree = project.update(cx, |project, cx| {
|
||||
let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
|
||||
assert_eq!(worktrees.len(), 1);
|
||||
worktrees.pop().unwrap()
|
||||
});
|
||||
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
|
||||
|
||||
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||
|
||||
let paths = vec![
|
||||
path!("a/one.txt"),
|
||||
path!("a/two.txt"),
|
||||
path!("a/three.txt"),
|
||||
path!("a/four.txt"),
|
||||
path!("b/five.txt"),
|
||||
path!("b/six.txt"),
|
||||
path!("b/seven.txt"),
|
||||
path!("b/eight.txt"),
|
||||
];
|
||||
|
||||
let mut opened_editors = Vec::new();
|
||||
for path in paths {
|
||||
let buffer = workspace
|
||||
.update_in(&mut cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Path::new(path).into(),
|
||||
},
|
||||
None,
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
opened_editors.push(buffer);
|
||||
}
|
||||
|
||||
let editor = workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||
let editor = cx.new(|cx| {
|
||||
Editor::new(
|
||||
editor::EditorMode::full(),
|
||||
multi_buffer::MultiBuffer::build_simple("", cx),
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.add_item(
|
||||
Box::new(cx.new(|_| AtMentionEditor(editor.clone()))),
|
||||
true,
|
||||
true,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
editor
|
||||
});
|
||||
|
||||
let mention_set = Arc::new(Mutex::new(MentionSet::default()));
|
||||
|
||||
let editor_entity = editor.downgrade();
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
window.focus(&editor.focus_handle(cx));
|
||||
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
|
||||
mention_set.clone(),
|
||||
workspace.downgrade(),
|
||||
editor_entity,
|
||||
))));
|
||||
});
|
||||
|
||||
cx.simulate_input("Lorem ");
|
||||
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "Lorem ");
|
||||
assert!(!editor.has_visible_completions_menu());
|
||||
});
|
||||
|
||||
cx.simulate_input("@");
|
||||
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "Lorem @");
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
current_completion_labels(editor),
|
||||
&[
|
||||
"eight.txt dir/b/",
|
||||
"seven.txt dir/b/",
|
||||
"six.txt dir/b/",
|
||||
"five.txt dir/b/",
|
||||
"four.txt dir/a/",
|
||||
"three.txt dir/a/",
|
||||
"two.txt dir/a/",
|
||||
"one.txt dir/a/",
|
||||
"dir ",
|
||||
"a dir/",
|
||||
"four.txt dir/a/",
|
||||
"one.txt dir/a/",
|
||||
"three.txt dir/a/",
|
||||
"two.txt dir/a/",
|
||||
"b dir/",
|
||||
"eight.txt dir/b/",
|
||||
"five.txt dir/b/",
|
||||
"seven.txt dir/b/",
|
||||
"six.txt dir/b/",
|
||||
"editor dir/"
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
// Select and confirm "File"
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
|
||||
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
|
||||
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
|
||||
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
|
||||
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "Lorem [@four.txt](@file:dir/a/four.txt) ");
|
||||
});
|
||||
}
|
||||
|
||||
fn current_completion_labels(editor: &Editor) -> Vec<String> {
|
||||
let completions = editor.current_completions().expect("Missing completions");
|
||||
completions
|
||||
.into_iter()
|
||||
.map(|completion| completion.label.text.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub(crate) fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let store = SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
client::init_settings(cx);
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
workspace::init_settings(cx);
|
||||
editor::init_settings(cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
81
crates/agent_ui/src/acp/message_history.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
pub struct MessageHistory<T> {
|
||||
items: Vec<T>,
|
||||
current: Option<usize>,
|
||||
}
|
||||
|
||||
impl<T> MessageHistory<T> {
|
||||
pub fn new() -> Self {
|
||||
MessageHistory {
|
||||
items: Vec::new(),
|
||||
current: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, message: T) {
|
||||
self.current.take();
|
||||
self.items.push(message);
|
||||
}
|
||||
|
||||
pub fn prev(&mut self) -> Option<&T> {
|
||||
if self.items.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let new_ix = self
|
||||
.current
|
||||
.get_or_insert(self.items.len())
|
||||
.saturating_sub(1);
|
||||
|
||||
self.current = Some(new_ix);
|
||||
self.items.get(new_ix)
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Option<&T> {
|
||||
let current = self.current.as_mut()?;
|
||||
*current += 1;
|
||||
|
||||
self.items.get(*current).or_else(|| {
|
||||
self.current.take();
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_prev_next() {
|
||||
let mut history = MessageHistory::new();
|
||||
|
||||
// Test empty history
|
||||
assert_eq!(history.prev(), None);
|
||||
assert_eq!(history.next(), None);
|
||||
|
||||
// Add some messages
|
||||
history.push("first");
|
||||
history.push("second");
|
||||
history.push("third");
|
||||
|
||||
// Test prev navigation
|
||||
assert_eq!(history.prev(), Some(&"third"));
|
||||
assert_eq!(history.prev(), Some(&"second"));
|
||||
assert_eq!(history.prev(), Some(&"first"));
|
||||
assert_eq!(history.prev(), Some(&"first"));
|
||||
|
||||
assert_eq!(history.next(), Some(&"second"));
|
||||
|
||||
// Test mixed navigation
|
||||
history.push("fourth");
|
||||
assert_eq!(history.prev(), Some(&"fourth"));
|
||||
assert_eq!(history.prev(), Some(&"third"));
|
||||
assert_eq!(history.next(), Some(&"fourth"));
|
||||
assert_eq!(history.next(), None);
|
||||
|
||||
// Test that push resets navigation
|
||||
history.prev();
|
||||
history.prev();
|
||||
history.push("fifth");
|
||||
assert_eq!(history.prev(), Some(&"fifth"));
|
||||
}
|
||||
}
|
||||
1972
crates/agent_ui/src/acp/thread_view.rs
Normal file
@@ -1,9 +1,7 @@
|
||||
use crate::context_picker::{ContextPicker, MentionLink};
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::message_editor::{extract_message_creases, insert_message_creases};
|
||||
use crate::ui::{
|
||||
AddedContext, AgentNotification, AgentNotificationEvent, AnimatedLabel, ContextPill,
|
||||
};
|
||||
use crate::ui::{AddedContext, AgentNotification, AgentNotificationEvent, ContextPill};
|
||||
use crate::{AgentPanel, ModelUsageContext};
|
||||
use agent::{
|
||||
ContextStore, LastRestoreCheckpoint, MessageCrease, MessageId, MessageSegment, TextThreadStore,
|
||||
@@ -19,7 +17,7 @@ use audio::{Audio, Sound};
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::actions::{MoveUp, Paste};
|
||||
use editor::scroll::Autoscroll;
|
||||
use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer};
|
||||
use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, SelectionEffects};
|
||||
use gpui::{
|
||||
AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardEntry,
|
||||
ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla,
|
||||
@@ -47,8 +45,8 @@ use std::time::Duration;
|
||||
use text::ToPoint;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
Disclosure, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize, Tooltip,
|
||||
prelude::*,
|
||||
Banner, Disclosure, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize,
|
||||
Tooltip, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use util::markdown::MarkdownCodeBlock;
|
||||
@@ -58,6 +56,7 @@ use zed_llm_client::CompletionIntent;
|
||||
|
||||
const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container";
|
||||
const EDIT_PREVIOUS_MESSAGE_MIN_LINES: usize = 1;
|
||||
const RESPONSE_PADDING_X: Pixels = px(19.);
|
||||
|
||||
pub struct ActiveThread {
|
||||
context_store: Entity<ContextStore>,
|
||||
@@ -204,7 +203,7 @@ pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
selection_background_color: cx.theme().colors().element_selection_background,
|
||||
code_block_overflow_x_scroll: true,
|
||||
table_overflow_x_scroll: true,
|
||||
heading_level_styles: Some(HeadingLevelStyles {
|
||||
@@ -301,7 +300,7 @@ fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
selection_background_color: cx.theme().colors().element_selection_background,
|
||||
code_block_overflow_x_scroll: false,
|
||||
code_block: StyleRefinement {
|
||||
margin: EdgesRefinement::default(),
|
||||
@@ -689,9 +688,12 @@ fn open_markdown_link(
|
||||
})
|
||||
.context("Could not find matching symbol")?;
|
||||
|
||||
editor.change_selections(Some(Autoscroll::center()), window, cx, |s| {
|
||||
s.select_anchor_ranges([symbol_range.start..symbol_range.start])
|
||||
});
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::center()),
|
||||
window,
|
||||
cx,
|
||||
|s| s.select_anchor_ranges([symbol_range.start..symbol_range.start]),
|
||||
);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
})
|
||||
@@ -708,10 +710,15 @@ fn open_markdown_link(
|
||||
.downcast::<Editor>()
|
||||
.context("Item is not an editor")?;
|
||||
active_editor.update_in(cx, |editor, window, cx| {
|
||||
editor.change_selections(Some(Autoscroll::center()), window, cx, |s| {
|
||||
s.select_ranges([Point::new(line_range.start as u32, 0)
|
||||
..Point::new(line_range.start as u32, 0)])
|
||||
});
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::center()),
|
||||
window,
|
||||
cx,
|
||||
|s| {
|
||||
s.select_ranges([Point::new(line_range.start as u32, 0)
|
||||
..Point::new(line_range.start as u32, 0)])
|
||||
},
|
||||
);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
})
|
||||
@@ -1017,6 +1024,7 @@ impl ActiveThread {
|
||||
}
|
||||
}
|
||||
ThreadEvent::MessageAdded(message_id) => {
|
||||
self.clear_last_error();
|
||||
if let Some(rendered_message) = self.thread.update(cx, |thread, cx| {
|
||||
thread.message(*message_id).map(|message| {
|
||||
RenderedMessage::from_segments(
|
||||
@@ -1033,6 +1041,7 @@ impl ActiveThread {
|
||||
cx.notify();
|
||||
}
|
||||
ThreadEvent::MessageEdited(message_id) => {
|
||||
self.clear_last_error();
|
||||
if let Some(index) = self.messages.iter().position(|id| id == message_id) {
|
||||
if let Some(rendered_message) = self.thread.update(cx, |thread, cx| {
|
||||
thread.message(*message_id).map(|message| {
|
||||
@@ -1140,6 +1149,9 @@ impl ActiveThread {
|
||||
self.save_thread(cx);
|
||||
cx.notify();
|
||||
}
|
||||
ThreadEvent::RetriesFailed { message } => {
|
||||
self.show_notification(message, ui::IconName::Warning, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1449,6 +1461,7 @@ impl ActiveThread {
|
||||
&configured_model.model,
|
||||
cx,
|
||||
),
|
||||
thinking_allowed: true,
|
||||
};
|
||||
|
||||
Some(configured_model.model.count_tokens(request, cx))
|
||||
@@ -1806,7 +1819,7 @@ impl ActiveThread {
|
||||
.my_3()
|
||||
.mx_5()
|
||||
.when(is_generating_stale || message.is_hidden, |this| {
|
||||
this.child(AnimatedLabel::new("").size(LabelSize::Small))
|
||||
this.child(LoadingLabel::new("").size(LabelSize::Small))
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1835,9 +1848,10 @@ impl ActiveThread {
|
||||
.filter(|(id, _)| *id == message_id)
|
||||
.map(|(_, state)| state);
|
||||
|
||||
let colors = cx.theme().colors();
|
||||
let editor_bg_color = colors.editor_background;
|
||||
let panel_bg = colors.panel_background;
|
||||
let (editor_bg_color, panel_bg) = {
|
||||
let colors = cx.theme().colors();
|
||||
(colors.editor_background, colors.panel_background)
|
||||
};
|
||||
|
||||
let open_as_markdown = IconButton::new(("open-as-markdown", ix), IconName::DocumentText)
|
||||
.icon_size(IconSize::XSmall)
|
||||
@@ -1862,9 +1876,6 @@ impl ActiveThread {
|
||||
this.scroll_to_top(cx);
|
||||
}));
|
||||
|
||||
// For all items that should be aligned with the LLM's response.
|
||||
const RESPONSE_PADDING_X: Pixels = px(19.);
|
||||
|
||||
let show_feedback = thread.is_turn_end(ix);
|
||||
let feedback_container = h_flex()
|
||||
.group("feedback_container")
|
||||
@@ -2025,152 +2036,162 @@ impl ActiveThread {
|
||||
}
|
||||
});
|
||||
|
||||
let styled_message = match message.role {
|
||||
Role::User => v_flex()
|
||||
.id(("message-container", ix))
|
||||
.pt_2()
|
||||
.pl_2()
|
||||
.pr_2p5()
|
||||
.pb_4()
|
||||
.child(
|
||||
let styled_message = if message.ui_only {
|
||||
self.render_ui_notification(message_content, ix, cx)
|
||||
} else {
|
||||
match message.role {
|
||||
Role::User => {
|
||||
let colors = cx.theme().colors();
|
||||
v_flex()
|
||||
.id(("user-message", ix))
|
||||
.bg(editor_bg_color)
|
||||
.rounded_lg()
|
||||
.shadow_md()
|
||||
.border_1()
|
||||
.border_color(colors.border)
|
||||
.hover(|hover| hover.border_color(colors.text_accent.opacity(0.5)))
|
||||
.id(("message-container", ix))
|
||||
.pt_2()
|
||||
.pl_2()
|
||||
.pr_2p5()
|
||||
.pb_4()
|
||||
.child(
|
||||
v_flex()
|
||||
.p_2p5()
|
||||
.gap_1()
|
||||
.children(message_content)
|
||||
.when_some(editing_message_state, |this, state| {
|
||||
let focus_handle = state.editor.focus_handle(cx).clone();
|
||||
.id(("user-message", ix))
|
||||
.bg(editor_bg_color)
|
||||
.rounded_lg()
|
||||
.shadow_md()
|
||||
.border_1()
|
||||
.border_color(colors.border)
|
||||
.hover(|hover| hover.border_color(colors.text_accent.opacity(0.5)))
|
||||
.child(
|
||||
v_flex()
|
||||
.p_2p5()
|
||||
.gap_1()
|
||||
.children(message_content)
|
||||
.when_some(editing_message_state, |this, state| {
|
||||
let focus_handle = state.editor.focus_handle(cx).clone();
|
||||
|
||||
this.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.flex_wrap()
|
||||
.child(
|
||||
this.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.flex_wrap()
|
||||
.child(
|
||||
div()
|
||||
.opacity(0.8)
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::Indicator)
|
||||
.color(Color::Warning)
|
||||
div()
|
||||
.opacity(0.8)
|
||||
.child(
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::Indicator)
|
||||
.color(Color::Warning)
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Label::new("Editing will restart the thread from this point.")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Label::new("Editing will restart the thread from this point.")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
IconButton::new(
|
||||
"cancel-edit-message",
|
||||
IconName::Close,
|
||||
)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_color(Color::Error)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Cancel Edit",
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
IconButton::new(
|
||||
"cancel-edit-message",
|
||||
IconName::Close,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(Self::handle_cancel_click)),
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_color(Color::Error)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Cancel Edit",
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(Self::handle_cancel_click)),
|
||||
)
|
||||
.child(
|
||||
IconButton::new(
|
||||
"confirm-edit-message",
|
||||
IconName::Return,
|
||||
)
|
||||
.disabled(state.editor.read(cx).is_empty(cx))
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Regenerate",
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(Self::handle_regenerate_click),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
IconButton::new(
|
||||
"confirm-edit-message",
|
||||
IconName::Return,
|
||||
)
|
||||
.disabled(state.editor.read(cx).is_empty(cx))
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Regenerate",
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(Self::handle_regenerate_click),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.on_click(cx.listener({
|
||||
let message_creases = message.creases.clone();
|
||||
move |this, _, window, cx| {
|
||||
if let Some(message_text) =
|
||||
this.thread.read(cx).message(message_id).and_then(|message| {
|
||||
message.segments.first().and_then(|segment| {
|
||||
match segment {
|
||||
MessageSegment::Text(message_text) => {
|
||||
Some(Into::<Arc<str>>::into(message_text.as_str()))
|
||||
}
|
||||
_ => {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
{
|
||||
this.start_editing_message(
|
||||
message_id,
|
||||
message_text,
|
||||
&message_creases,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
})),
|
||||
)
|
||||
.on_click(cx.listener({
|
||||
let message_creases = message.creases.clone();
|
||||
move |this, _, window, cx| {
|
||||
if let Some(message_text) =
|
||||
this.thread.read(cx).message(message_id).and_then(|message| {
|
||||
message.segments.first().and_then(|segment| {
|
||||
match segment {
|
||||
MessageSegment::Text(message_text) => {
|
||||
Some(Into::<Arc<str>>::into(message_text.as_str()))
|
||||
}
|
||||
_ => {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
{
|
||||
this.start_editing_message(
|
||||
message_id,
|
||||
message_text,
|
||||
&message_creases,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
})),
|
||||
),
|
||||
Role::Assistant => v_flex()
|
||||
.id(("message-container", ix))
|
||||
.px(RESPONSE_PADDING_X)
|
||||
.gap_2()
|
||||
.children(message_content)
|
||||
.when(has_tool_uses, |parent| {
|
||||
parent.children(tool_uses.into_iter().map(|tool_use| {
|
||||
self.render_tool_use(tool_use, window, workspace.clone(), cx)
|
||||
}))
|
||||
}),
|
||||
Role::System => div().id(("message-container", ix)).py_1().px_2().child(
|
||||
v_flex()
|
||||
.bg(colors.editor_background)
|
||||
.rounded_sm()
|
||||
.child(div().p_4().children(message_content)),
|
||||
),
|
||||
}
|
||||
Role::Assistant => v_flex()
|
||||
.id(("message-container", ix))
|
||||
.px(RESPONSE_PADDING_X)
|
||||
.gap_2()
|
||||
.children(message_content)
|
||||
.when(has_tool_uses, |parent| {
|
||||
parent.children(tool_uses.into_iter().map(|tool_use| {
|
||||
self.render_tool_use(tool_use, window, workspace.clone(), cx)
|
||||
}))
|
||||
}),
|
||||
Role::System => {
|
||||
let colors = cx.theme().colors();
|
||||
div().id(("message-container", ix)).py_1().px_2().child(
|
||||
v_flex()
|
||||
.bg(colors.editor_background)
|
||||
.rounded_sm()
|
||||
.child(div().p_4().children(message_content)),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let after_editing_message = self
|
||||
@@ -2509,6 +2530,26 @@ impl ActiveThread {
|
||||
.blend(cx.theme().colors().editor_foreground.opacity(0.025))
|
||||
}
|
||||
|
||||
fn render_ui_notification(
|
||||
&self,
|
||||
message_content: impl IntoIterator<Item = impl IntoElement>,
|
||||
ix: usize,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Stateful<Div> {
|
||||
let message = div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.text_size(TextSize::XSmall.rems(cx))
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.children(message_content);
|
||||
|
||||
div()
|
||||
.id(("message-container", ix))
|
||||
.py_1()
|
||||
.px_2p5()
|
||||
.child(Banner::new().severity(ui::Severity::Warning).child(message))
|
||||
}
|
||||
|
||||
fn render_message_thinking_segment(
|
||||
&self,
|
||||
message_id: MessageId,
|
||||
@@ -2540,11 +2581,11 @@ impl ActiveThread {
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Icon::new(IconName::LightBulb)
|
||||
.size(IconSize::XSmall)
|
||||
Icon::new(IconName::ToolBulb)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(AnimatedLabel::new("Thinking").size(LabelSize::Small)),
|
||||
.child(LoadingLabel::new("Thinking").size(LabelSize::Small)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -2954,7 +2995,7 @@ impl ActiveThread {
|
||||
.overflow_x_scroll()
|
||||
.child(
|
||||
Icon::new(tool_use.icon)
|
||||
.size(IconSize::XSmall)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
@@ -3113,7 +3154,7 @@ impl ActiveThread {
|
||||
.border_color(self.tool_card_border_color(cx))
|
||||
.rounded_b_lg()
|
||||
.child(
|
||||
AnimatedLabel::new("Waiting for Confirmation").size(LabelSize::Small)
|
||||
LoadingLabel::new("Waiting for Confirmation").size(LabelSize::Small)
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -3763,9 +3804,9 @@ mod tests {
|
||||
|
||||
// Stream response to user message
|
||||
thread.update(cx, |thread, cx| {
|
||||
let request =
|
||||
thread.to_completion_request(model.clone(), CompletionIntent::UserPrompt, cx);
|
||||
thread.stream_completion(request, model, cx.active_window(), cx)
|
||||
let intent = CompletionIntent::UserPrompt;
|
||||
let request = thread.to_completion_request(model.clone(), intent, cx);
|
||||
thread.stream_completion(request, model, intent, cx.active_window(), cx)
|
||||
});
|
||||
// Follow the agent
|
||||
cx.update(|window, cx| {
|
||||
|
||||
@@ -16,7 +16,9 @@ use gpui::{
|
||||
Focusable, ScrollHandle, Subscription, Task, Transformation, WeakEntity, percentage,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||
use language_model::{
|
||||
LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,
|
||||
};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use project::{
|
||||
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
|
||||
@@ -24,8 +26,8 @@ use project::{
|
||||
};
|
||||
use settings::{Settings, update_settings_file};
|
||||
use ui::{
|
||||
ContextMenu, Disclosure, ElevationIndex, Indicator, PopoverMenu, Scrollbar, ScrollbarState,
|
||||
Switch, SwitchColor, Tooltip, prelude::*,
|
||||
ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, PopoverMenu,
|
||||
Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
@@ -86,6 +88,14 @@ impl AgentConfiguration {
|
||||
let scroll_handle = ScrollHandle::new();
|
||||
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
|
||||
|
||||
let mut expanded_provider_configurations = HashMap::default();
|
||||
if LanguageModelRegistry::read_global(cx)
|
||||
.provider(&ZED_CLOUD_PROVIDER_ID)
|
||||
.map_or(false, |cloud_provider| cloud_provider.must_accept_terms(cx))
|
||||
{
|
||||
expanded_provider_configurations.insert(ZED_CLOUD_PROVIDER_ID, true);
|
||||
}
|
||||
|
||||
let mut this = Self {
|
||||
fs,
|
||||
language_registry,
|
||||
@@ -94,7 +104,7 @@ impl AgentConfiguration {
|
||||
configuration_views_by_provider: HashMap::default(),
|
||||
context_server_store,
|
||||
expanded_context_server_tools: HashMap::default(),
|
||||
expanded_provider_configurations: HashMap::default(),
|
||||
expanded_provider_configurations,
|
||||
tools,
|
||||
_registry_subscription: registry_subscription,
|
||||
scroll_handle,
|
||||
@@ -162,19 +172,29 @@ impl AgentConfiguration {
|
||||
.unwrap_or(false);
|
||||
|
||||
v_flex()
|
||||
.py_2()
|
||||
.gap_1p5()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border.opacity(0.6))
|
||||
.when(is_expanded, |this| this.mb_2())
|
||||
.child(
|
||||
div()
|
||||
.opacity(0.6)
|
||||
.px_2()
|
||||
.child(Divider::horizontal().color(DividerColor::Border)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.map(|this| {
|
||||
if is_expanded {
|
||||
this.mt_2().mb_1()
|
||||
} else {
|
||||
this.my_2()
|
||||
}
|
||||
})
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.id(provider_id_string.clone())
|
||||
.cursor_pointer()
|
||||
.px_2()
|
||||
.py_0p5()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
@@ -237,12 +257,16 @@ impl AgentConfiguration {
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when(is_expanded, |parent| match configuration_view {
|
||||
Some(configuration_view) => parent.child(configuration_view),
|
||||
None => parent.child(Label::new(format!(
|
||||
"No configuration view for {provider_name}",
|
||||
))),
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.when(is_expanded, |parent| match configuration_view {
|
||||
Some(configuration_view) => parent.child(configuration_view),
|
||||
None => parent.child(Label::new(format!(
|
||||
"No configuration view for {provider_name}",
|
||||
))),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_provider_configuration_section(
|
||||
@@ -252,12 +276,11 @@ impl AgentConfiguration {
|
||||
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
.pr(DynamicSpacing::Base20.rems(cx))
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
.pr(DynamicSpacing::Base20.rems(cx))
|
||||
.pb_0()
|
||||
.mb_2p5()
|
||||
.gap_0p5()
|
||||
.child(Headline::new("LLM Providers"))
|
||||
@@ -266,10 +289,15 @@ impl AgentConfiguration {
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.children(
|
||||
providers
|
||||
.into_iter()
|
||||
.map(|provider| self.render_provider_configuration_block(&provider, cx)),
|
||||
.child(
|
||||
div()
|
||||
.pl(DynamicSpacing::Base08.rems(cx))
|
||||
.pr(DynamicSpacing::Base20.rems(cx))
|
||||
.children(
|
||||
providers.into_iter().map(|provider| {
|
||||
self.render_provider_configuration_block(&provider, cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -408,7 +436,7 @@ impl AgentConfiguration {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let context_server_ids = self.context_server_store.read(cx).all_server_ids().clone();
|
||||
let context_server_ids = self.context_server_store.read(cx).configured_server_ids();
|
||||
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
|
||||
@@ -180,7 +180,7 @@ impl ConfigurationSource {
|
||||
}
|
||||
|
||||
fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)>) -> String {
|
||||
let (name, path, args, env) = match existing {
|
||||
let (name, command, args, env) = match existing {
|
||||
Some((id, cmd)) => {
|
||||
let args = serde_json::to_string(&cmd.args).unwrap();
|
||||
let env = serde_json::to_string(&cmd.env.unwrap_or_default()).unwrap();
|
||||
@@ -198,14 +198,12 @@ fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)
|
||||
r#"{{
|
||||
/// The name of your MCP server
|
||||
"{name}": {{
|
||||
"command": {{
|
||||
/// The path to the executable
|
||||
"path": "{path}",
|
||||
/// The arguments to pass to the executable
|
||||
"args": {args},
|
||||
/// The environment variables to set for the executable
|
||||
"env": {env}
|
||||
}}
|
||||
/// The command which runs the MCP server
|
||||
"command": "{command}",
|
||||
/// The arguments to pass to the MCP server
|
||||
"args": {args},
|
||||
/// The environment variables to set
|
||||
"env": {env}
|
||||
}}
|
||||
}}"#
|
||||
)
|
||||
@@ -381,6 +379,14 @@ impl ConfigureContextServerModal {
|
||||
};
|
||||
|
||||
self.state = State::Waiting;
|
||||
|
||||
let existing_server = self.context_server_store.read(cx).get_running_server(&id);
|
||||
if existing_server.is_some() {
|
||||
self.context_server_store.update(cx, |store, cx| {
|
||||
store.stop_server(&id, cx).log_err();
|
||||
});
|
||||
}
|
||||
|
||||
let wait_for_context_server_task =
|
||||
wait_for_context_server(&self.context_server_store, id.clone(), cx);
|
||||
cx.spawn({
|
||||
@@ -401,13 +407,21 @@ impl ConfigureContextServerModal {
|
||||
})
|
||||
.detach();
|
||||
|
||||
// When we write the settings to the file, the context server will be restarted.
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
update_settings_file::<ProjectSettings>(fs.clone(), cx, |project_settings, _| {
|
||||
project_settings.context_servers.insert(id.0, settings);
|
||||
let settings_changed =
|
||||
ProjectSettings::get_global(cx).context_servers.get(&id.0) != Some(&settings);
|
||||
|
||||
if settings_changed {
|
||||
// When we write the settings to the file, the context server will be restarted.
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
update_settings_file::<ProjectSettings>(fs.clone(), cx, |project_settings, _| {
|
||||
project_settings.context_servers.insert(id.0, settings);
|
||||
});
|
||||
});
|
||||
});
|
||||
} else if let Some(existing_server) = existing_server {
|
||||
self.context_server_store
|
||||
.update(cx, |store, cx| store.start_server(existing_server, cx));
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, cx: &mut Context<Self>) {
|
||||
@@ -439,8 +453,7 @@ fn parse_input(text: &str) -> Result<(ContextServerId, ContextServerCommand)> {
|
||||
let object = value.as_object().context("Expected object")?;
|
||||
anyhow::ensure!(object.len() == 1, "Expected exactly one key-value pair");
|
||||
let (context_server_name, value) = object.into_iter().next().unwrap();
|
||||
let command = value.get("command").context("Expected command")?;
|
||||
let command: ContextServerCommand = serde_json::from_value(command.clone())?;
|
||||
let command: ContextServerCommand = serde_json::from_value(value.clone())?;
|
||||
Ok((ContextServerId(context_server_name.clone().into()), command))
|
||||
}
|
||||
|
||||
@@ -727,7 +740,9 @@ fn wait_for_context_server(
|
||||
});
|
||||
|
||||
cx.spawn(async move |_cx| {
|
||||
let result = rx.await.unwrap();
|
||||
let result = rx
|
||||
.await
|
||||
.map_err(|_| Arc::from("Context server store was dropped"))?;
|
||||
drop(subscription);
|
||||
result
|
||||
})
|
||||
@@ -748,7 +763,7 @@ pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
selection_background_color: colors.element_selection_background,
|
||||
link: TextStyleRefinement {
|
||||
background_color: Some(colors.editor_foreground.opacity(0.025)),
|
||||
underline: Some(UnderlineStyle {
|
||||
|
||||
@@ -5,7 +5,8 @@ use anyhow::Result;
|
||||
use buffer_diff::DiffHunkStatus;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot, ToPoint,
|
||||
Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot,
|
||||
SelectionEffects, ToPoint,
|
||||
actions::{GoToHunk, GoToPreviousHunk},
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
@@ -171,15 +172,9 @@ impl AgentDiffPane {
|
||||
|
||||
if let Some(first_hunk) = first_hunk {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
editor.change_selections(
|
||||
Some(Autoscroll::fit()),
|
||||
window,
|
||||
cx,
|
||||
|selections| {
|
||||
selections
|
||||
.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
},
|
||||
)
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +237,7 @@ impl AgentDiffPane {
|
||||
|
||||
if let Some(first_hunk) = first_hunk {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
})
|
||||
}
|
||||
@@ -416,7 +411,7 @@ fn update_editor_selection(
|
||||
};
|
||||
|
||||
if let Some(target_hunk) = target_hunk {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
let next_hunk_start = target_hunk.multi_buffer_range().start;
|
||||
selections.select_anchor_ranges([next_hunk_start..next_hunk_start]);
|
||||
})
|
||||
@@ -1380,6 +1375,7 @@ impl AgentDiff {
|
||||
| ThreadEvent::ToolConfirmationNeeded
|
||||
| ThreadEvent::ToolUseLimitReached
|
||||
| ThreadEvent::CancelEditing
|
||||
| ThreadEvent::RetriesFailed { .. }
|
||||
| ThreadEvent::ProfileChanged => {}
|
||||
}
|
||||
}
|
||||
@@ -1543,7 +1539,7 @@ impl AgentDiff {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
|
||||
editor.change_selections(
|
||||
Some(Autoscroll::center()),
|
||||
SelectionEffects::scroll(Autoscroll::center()),
|
||||
window,
|
||||
cx,
|
||||
|selections| {
|
||||
@@ -1867,7 +1863,7 @@ mod tests {
|
||||
|
||||
// Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end.
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
|
||||
});
|
||||
});
|
||||
@@ -2123,7 +2119,7 @@ mod tests {
|
||||
|
||||
// Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end.
|
||||
editor1.update_in(cx, |editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ use language_model::{ConfiguredModel, LanguageModelRegistry};
|
||||
use picker::popover_menu::PickerPopoverMenu;
|
||||
use settings::update_settings_file;
|
||||
use std::sync::Arc;
|
||||
use ui::{PopoverMenuHandle, Tooltip, prelude::*};
|
||||
use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
|
||||
|
||||
pub struct AgentModelSelector {
|
||||
selector: Entity<LanguageModelSelector>,
|
||||
@@ -94,20 +94,35 @@ impl Render for AgentModelSelector {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let model = self.selector.read(cx).delegate.active_model(cx);
|
||||
let model_name = model
|
||||
.as_ref()
|
||||
.map(|model| model.model.name().0)
|
||||
.unwrap_or_else(|| SharedString::from("No model selected"));
|
||||
let provider_icon = model
|
||||
.as_ref()
|
||||
.map(|model| model.provider.icon())
|
||||
.unwrap_or_else(|| IconName::Ai);
|
||||
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
PickerPopoverMenu::new(
|
||||
self.selector.clone(),
|
||||
Button::new("active-model", model_name)
|
||||
.label_size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.icon(IconName::ChevronDown)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon_color(Color::Muted),
|
||||
ButtonLike::new("active-model")
|
||||
.child(
|
||||
Icon::new(provider_icon)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.ml_0p5(),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
mod acp;
|
||||
mod active_thread;
|
||||
mod agent_configuration;
|
||||
mod agent_diff;
|
||||
mod agent_model_selector;
|
||||
mod agent_panel;
|
||||
mod buffer_codegen;
|
||||
mod burn_mode_tooltip;
|
||||
mod context_picker;
|
||||
mod context_server_configuration;
|
||||
mod context_strip;
|
||||
@@ -11,7 +13,6 @@ mod debug;
|
||||
mod inline_assistant;
|
||||
mod inline_prompt_editor;
|
||||
mod language_model_selector;
|
||||
mod max_mode_tooltip;
|
||||
mod message_editor;
|
||||
mod profile_selector;
|
||||
mod slash_command;
|
||||
@@ -48,57 +49,94 @@ pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
use crate::slash_command_settings::SlashCommandSettings;
|
||||
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
|
||||
pub use text_thread_editor::AgentPanelDelegate;
|
||||
pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
|
||||
pub use ui::preview::{all_agent_previews, get_agent_preview};
|
||||
|
||||
actions!(
|
||||
agent,
|
||||
[
|
||||
/// Creates a new text-based conversation thread.
|
||||
NewTextThread,
|
||||
/// Creates a new external agent conversation thread.
|
||||
NewAcpThread,
|
||||
/// Toggles the context picker interface for adding files, symbols, or other context.
|
||||
ToggleContextPicker,
|
||||
/// Toggles the navigation menu for switching between threads and views.
|
||||
ToggleNavigationMenu,
|
||||
/// Toggles the options menu for agent settings and preferences.
|
||||
ToggleOptionsMenu,
|
||||
/// Deletes the recently opened thread from history.
|
||||
DeleteRecentlyOpenThread,
|
||||
/// Toggles the profile selector for switching between agent profiles.
|
||||
ToggleProfileSelector,
|
||||
/// Removes all added context from the current conversation.
|
||||
RemoveAllContext,
|
||||
/// Expands the message editor to full size.
|
||||
ExpandMessageEditor,
|
||||
/// Opens the conversation history view.
|
||||
OpenHistory,
|
||||
/// Adds a context server to the configuration.
|
||||
AddContextServer,
|
||||
/// Removes the currently selected thread.
|
||||
RemoveSelectedThread,
|
||||
Chat,
|
||||
/// Starts a chat conversation with follow-up enabled.
|
||||
ChatWithFollow,
|
||||
/// Cycles to the next inline assist suggestion.
|
||||
CycleNextInlineAssist,
|
||||
/// Cycles to the previous inline assist suggestion.
|
||||
CyclePreviousInlineAssist,
|
||||
/// Moves focus up in the interface.
|
||||
FocusUp,
|
||||
/// Moves focus down in the interface.
|
||||
FocusDown,
|
||||
/// Moves focus left in the interface.
|
||||
FocusLeft,
|
||||
/// Moves focus right in the interface.
|
||||
FocusRight,
|
||||
/// Removes the currently focused context item.
|
||||
RemoveFocusedContext,
|
||||
/// Accepts the suggested context item.
|
||||
AcceptSuggestedContext,
|
||||
/// Opens the active thread as a markdown file.
|
||||
OpenActiveThreadAsMarkdown,
|
||||
/// Opens the agent diff view to review changes.
|
||||
OpenAgentDiff,
|
||||
/// Keeps the current suggestion or change.
|
||||
Keep,
|
||||
/// Rejects the current suggestion or change.
|
||||
Reject,
|
||||
/// Rejects all suggestions or changes.
|
||||
RejectAll,
|
||||
/// Keeps all suggestions or changes.
|
||||
KeepAll,
|
||||
/// Follows the agent's suggestions.
|
||||
Follow,
|
||||
/// Resets the trial upsell notification.
|
||||
ResetTrialUpsell,
|
||||
/// Resets the trial end upsell notification.
|
||||
ResetTrialEndUpsell,
|
||||
/// Continues the current thread.
|
||||
ContinueThread,
|
||||
/// Continues the thread with burn mode enabled.
|
||||
ContinueWithBurnMode,
|
||||
/// Toggles burn mode for faster responses.
|
||||
ToggleBurnMode,
|
||||
]
|
||||
);
|
||||
|
||||
/// Creates a new conversation thread, optionally based on an existing thread.
|
||||
#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = agent)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NewThread {
|
||||
#[serde(default)]
|
||||
from_thread_id: Option<ThreadId>,
|
||||
}
|
||||
|
||||
/// Opens the profile management interface for configuring agent tools and settings.
|
||||
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = agent)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ManageProfiles {
|
||||
#[serde(default)]
|
||||
pub customize_tools: Option<AgentProfileId>,
|
||||
@@ -157,6 +195,7 @@ pub fn init(
|
||||
agent::init(cx);
|
||||
agent_panel::init(cx);
|
||||
context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
|
||||
TextThreadEditor::init(cx);
|
||||
|
||||
register_slash_commands(cx);
|
||||
inline_assistant::init(
|
||||
@@ -208,7 +247,7 @@ fn update_active_language_model_from_settings(cx: &mut App) {
|
||||
}
|
||||
}
|
||||
|
||||
let default = to_selected_model(&settings.default_model);
|
||||
let default = settings.default_model.as_ref().map(to_selected_model);
|
||||
let inline_assistant = settings
|
||||
.inline_assistant_model
|
||||
.as_ref()
|
||||
@@ -228,7 +267,7 @@ fn update_active_language_model_from_settings(cx: &mut App) {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||
registry.select_default_model(Some(&default), cx);
|
||||
registry.select_default_model(default.as_ref(), cx);
|
||||
registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
|
||||
registry.select_commit_message_model(commit_message.as_ref(), cx);
|
||||
registry.select_thread_summary_model(thread_summary.as_ref(), cx);
|
||||
|
||||
@@ -475,6 +475,7 @@ impl CodegenAlternative {
|
||||
stop: Vec::new(),
|
||||
temperature,
|
||||
messages: vec![request_message],
|
||||
thinking_allowed: false,
|
||||
}
|
||||
}))
|
||||
}
|
||||
@@ -1094,15 +1095,9 @@ mod tests {
|
||||
};
|
||||
use language_model::{LanguageModelRegistry, TokenUsage};
|
||||
use rand::prelude::*;
|
||||
use serde::Serialize;
|
||||
use settings::SettingsStore;
|
||||
use std::{future, sync::Arc};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DummyCompletionRequest {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use gpui::{Context, FontWeight, IntoElement, Render, Window};
|
||||
use ui::{prelude::*, tooltip_container};
|
||||
|
||||
pub struct MaxModeTooltip {
|
||||
pub struct BurnModeTooltip {
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
impl MaxModeTooltip {
|
||||
impl BurnModeTooltip {
|
||||
pub fn new() -> Self {
|
||||
Self { selected: false }
|
||||
}
|
||||
@@ -16,7 +16,7 @@ impl MaxModeTooltip {
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for MaxModeTooltip {
|
||||
impl Render for BurnModeTooltip {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (icon, color) = if self.selected {
|
||||
(IconName::ZedBurnModeOn, Color::Error)
|
||||
@@ -1,6 +1,6 @@
|
||||
mod completion_provider;
|
||||
mod fetch_context_picker;
|
||||
mod file_context_picker;
|
||||
pub(crate) mod file_context_picker;
|
||||
mod rules_context_picker;
|
||||
mod symbol_context_picker;
|
||||
mod thread_context_picker;
|
||||
@@ -426,6 +426,7 @@ impl ContextPicker {
|
||||
this.add_recent_file(project_path.clone(), window, cx);
|
||||
})
|
||||
},
|
||||
None,
|
||||
)
|
||||
}
|
||||
RecentEntry::Thread(thread) => {
|
||||
@@ -443,6 +444,7 @@ impl ContextPicker {
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
},
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -661,7 +663,7 @@ fn recent_context_picker_entries(
|
||||
|
||||
let active_thread_id = workspace
|
||||
.panel::<AgentPanel>(cx)
|
||||
.and_then(|panel| Some(panel.read(cx).active_thread()?.read(cx).id()));
|
||||
.and_then(|panel| Some(panel.read(cx).active_thread(cx)?.read(cx).id()));
|
||||
|
||||
if let Some((thread_store, text_thread_store)) = thread_store
|
||||
.and_then(|store| store.upgrade())
|
||||
@@ -930,8 +932,8 @@ impl MentionLink {
|
||||
format!(
|
||||
"[@{} ({}-{})]({}:{}:{}-{})",
|
||||
file_name,
|
||||
line_range.start,
|
||||
line_range.end,
|
||||
line_range.start + 1,
|
||||
line_range.end + 1,
|
||||
Self::SELECTION,
|
||||
full_path,
|
||||
line_range.start,
|
||||
|
||||
@@ -686,6 +686,7 @@ impl ContextPickerCompletionProvider {
|
||||
let mut label = CodeLabel::plain(symbol.name.clone(), None);
|
||||
label.push_str(" ", None);
|
||||
label.push_str(&file_name, comment_id);
|
||||
label.push_str(&format!(" L{}", symbol.range.start.0.row + 1), comment_id);
|
||||
|
||||
let new_text = format!("{} ", MentionLink::for_symbol(&symbol.name, &full_path));
|
||||
let new_text_len = new_text.len();
|
||||
|
||||
@@ -161,7 +161,7 @@ impl ContextStrip {
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let panel = workspace.read(cx).panel::<AgentPanel>(cx)?.read(cx);
|
||||
|
||||
if let Some(active_thread) = panel.active_thread() {
|
||||
if let Some(active_thread) = panel.active_thread(cx) {
|
||||
let weak_active_thread = active_thread.downgrade();
|
||||
|
||||
let active_thread = active_thread.read(cx);
|
||||
|
||||
@@ -18,6 +18,7 @@ use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, HashSet, VecDeque, hash_map};
|
||||
use editor::SelectionEffects;
|
||||
use editor::{
|
||||
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange,
|
||||
MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
|
||||
@@ -1159,7 +1160,7 @@ impl InlineAssistant {
|
||||
|
||||
let position = assist.range.start;
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([position..position])
|
||||
});
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ use ui::{ListItem, ListItemSpacing, prelude::*};
|
||||
actions!(
|
||||
agent,
|
||||
[
|
||||
/// Toggles the language model selector dropdown.
|
||||
#[action(deprecated_aliases = ["assistant::ToggleModelSelector", "assistant2::ToggleModelSelector"])]
|
||||
ToggleModelSelector
|
||||
]
|
||||
@@ -399,7 +400,7 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
let all_models = self.all_models.clone();
|
||||
let current_index = self.selected_index;
|
||||
let active_model = (self.get_active_model)(cx);
|
||||
let bg_executor = cx.background_executor();
|
||||
|
||||
let language_model_registry = LanguageModelRegistry::global(cx);
|
||||
@@ -441,12 +442,9 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.delegate.filtered_entries = filtered_models.entries();
|
||||
// Preserve selection focus
|
||||
let new_index = if current_index >= this.delegate.filtered_entries.len() {
|
||||
0
|
||||
} else {
|
||||
current_index
|
||||
};
|
||||
// Finds the currently selected model in the list
|
||||
let new_index =
|
||||
Self::get_active_model_index(&this.delegate.filtered_entries, active_model);
|
||||
this.set_selected_index(new_index, Some(picker::Direction::Down), true, window, cx);
|
||||
cx.notify();
|
||||
})
|
||||
|
||||
@@ -47,13 +47,14 @@ use ui::{
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{CollaboratorId, Workspace};
|
||||
use zed_actions::agent::Chat;
|
||||
use zed_llm_client::CompletionIntent;
|
||||
|
||||
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::profile_selector::ProfileSelector;
|
||||
use crate::{
|
||||
ActiveThread, AgentDiffPane, Chat, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll,
|
||||
ActiveThread, AgentDiffPane, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll,
|
||||
ModelUsageContext, NewThread, OpenAgentDiff, RejectAll, RemoveAllContext, ToggleBurnMode,
|
||||
ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
|
||||
};
|
||||
@@ -575,7 +576,7 @@ impl MessageEditor {
|
||||
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
let thread = self.thread.read(cx);
|
||||
let model = thread.configured_model();
|
||||
if !model?.model.supports_max_mode() {
|
||||
if !model?.model.supports_burn_mode() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -1160,7 +1161,7 @@ impl MessageEditor {
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.id("file-name")
|
||||
.id(("file-name", index))
|
||||
.pr_8()
|
||||
.gap_1p5()
|
||||
.max_w_full()
|
||||
@@ -1171,9 +1172,16 @@ impl MessageEditor {
|
||||
.gap_0p5()
|
||||
.children(file_name)
|
||||
.children(file_path),
|
||||
), // TODO: Implement line diff
|
||||
// .child(Label::new("+").color(Color::Created))
|
||||
// .child(Label::new("-").color(Color::Deleted)),
|
||||
)
|
||||
.on_click({
|
||||
let buffer = buffer.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.handle_file_click(buffer.clone(), window, cx);
|
||||
})
|
||||
}), // TODO: Implement line diff
|
||||
// .child(Label::new("+").color(Color::Created))
|
||||
// .child(Label::new("-").color(Color::Deleted)),
|
||||
//
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -1250,9 +1258,7 @@ impl MessageEditor {
|
||||
self.thread
|
||||
.read(cx)
|
||||
.configured_model()
|
||||
.map_or(false, |model| {
|
||||
model.provider.id().0 == ZED_CLOUD_PROVIDER_ID
|
||||
})
|
||||
.map_or(false, |model| model.provider.id() == ZED_CLOUD_PROVIDER_ID)
|
||||
}
|
||||
|
||||
fn render_usage_callout(&self, line_height: Pixels, cx: &mut Context<Self>) -> Option<Div> {
|
||||
@@ -1448,6 +1454,7 @@ impl MessageEditor {
|
||||
tool_choice: None,
|
||||
stop: vec![],
|
||||
temperature: AgentSettings::temperature_for_model(&model.model, cx),
|
||||
thinking_allowed: true,
|
||||
};
|
||||
|
||||
Some(model.model.count_tokens(request, cx))
|
||||
@@ -1615,6 +1622,7 @@ impl Render for MessageEditor {
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.when(changed_buffers.len() > 0, |parent| {
|
||||
parent.child(self.render_edits_bar(&changed_buffers, window, cx))
|
||||
})
|
||||
|
||||
@@ -297,6 +297,7 @@ impl TerminalInlineAssistant {
|
||||
tool_choice: None,
|
||||
stop: Vec::new(),
|
||||
temperature,
|
||||
thinking_allowed: false,
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
burn_mode_tooltip::BurnModeTooltip,
|
||||
language_model_selector::{
|
||||
LanguageModelSelector, ToggleModelSelector, language_model_selector,
|
||||
},
|
||||
max_mode_tooltip::MaxModeTooltip,
|
||||
};
|
||||
use agent_settings::{AgentSettings, CompletionMode};
|
||||
use anyhow::Result;
|
||||
@@ -21,7 +21,6 @@ use editor::{
|
||||
BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId,
|
||||
RenderBlock, ToDisplayPoint,
|
||||
},
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
use editor::{FoldPlaceholder, display_map::CreaseId};
|
||||
use fs::Fs;
|
||||
@@ -39,8 +38,8 @@ use language::{
|
||||
language_settings::{SoftWrap, all_language_settings},
|
||||
};
|
||||
use language_model::{
|
||||
ConfigurationError, LanguageModelImage, LanguageModelProviderTosView, LanguageModelRegistry,
|
||||
Role,
|
||||
ConfigurationError, LanguageModelExt, LanguageModelImage, LanguageModelProviderTosView,
|
||||
LanguageModelRegistry, Role,
|
||||
};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use picker::{Picker, popover_menu::PickerPopoverMenu};
|
||||
@@ -69,7 +68,7 @@ use workspace::{
|
||||
searchable::{Direction, SearchableItemHandle},
|
||||
};
|
||||
use workspace::{
|
||||
Save, Toast, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
Save, Toast, Workspace,
|
||||
item::{self, FollowableItem, Item, ItemHandle},
|
||||
notifications::NotificationId,
|
||||
pane,
|
||||
@@ -86,16 +85,24 @@ use assistant_context::{
|
||||
actions!(
|
||||
assistant,
|
||||
[
|
||||
/// Sends the current message to the assistant.
|
||||
Assist,
|
||||
/// Confirms and executes the entered slash command.
|
||||
ConfirmCommand,
|
||||
/// Copies code from the assistant's response to the clipboard.
|
||||
CopyCode,
|
||||
/// Cycles between user and assistant message roles.
|
||||
CycleMessageRole,
|
||||
/// Inserts the selected text into the active editor.
|
||||
InsertIntoEditor,
|
||||
/// Quotes the current selection in the assistant conversation.
|
||||
QuoteSelection,
|
||||
/// Splits the conversation at the current cursor position.
|
||||
Split,
|
||||
]
|
||||
);
|
||||
|
||||
/// Inserts files that were dragged and dropped into the assistant conversation.
|
||||
#[derive(PartialEq, Clone, Action)]
|
||||
#[action(namespace = assistant, no_json, no_register)]
|
||||
pub enum InsertDraggedFiles {
|
||||
@@ -389,7 +396,7 @@ impl TextThreadEditor {
|
||||
cursor..cursor
|
||||
};
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_ranges([new_selection])
|
||||
});
|
||||
});
|
||||
@@ -449,8 +456,7 @@ impl TextThreadEditor {
|
||||
if let Some(command) = self.slash_commands.command(name, cx) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor
|
||||
.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel());
|
||||
editor.change_selections(Default::default(), window, cx, |s| s.try_cancel());
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let newest_cursor = editor.selections.newest::<Point>(cx).head();
|
||||
if newest_cursor.column > 0
|
||||
@@ -1583,7 +1589,7 @@ impl TextThreadEditor {
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.transact(window, cx, |this, window, cx| {
|
||||
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
s.select(selections);
|
||||
});
|
||||
this.insert("", window, cx);
|
||||
@@ -2075,12 +2081,12 @@ impl TextThreadEditor {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_max_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
let context = self.context().read(cx);
|
||||
let active_model = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()
|
||||
.map(|default| default.model)?;
|
||||
if !active_model.supports_max_mode() {
|
||||
if !active_model.supports_burn_mode() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -2107,7 +2113,7 @@ impl TextThreadEditor {
|
||||
});
|
||||
}))
|
||||
.tooltip(move |_window, cx| {
|
||||
cx.new(|_| MaxModeTooltip::new().selected(burn_mode_enabled))
|
||||
cx.new(|_| BurnModeTooltip::new().selected(burn_mode_enabled))
|
||||
.into()
|
||||
})
|
||||
.into_any_element(),
|
||||
@@ -2122,12 +2128,21 @@ impl TextThreadEditor {
|
||||
let active_model = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()
|
||||
.map(|default| default.model);
|
||||
let focus_handle = self.editor().focus_handle(cx).clone();
|
||||
let model_name = match active_model {
|
||||
Some(model) => model.name().0,
|
||||
None => SharedString::from("No model selected"),
|
||||
};
|
||||
|
||||
let active_provider = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()
|
||||
.map(|default| default.provider);
|
||||
let provider_icon = match active_provider {
|
||||
Some(provider) => provider.icon(),
|
||||
None => IconName::Ai,
|
||||
};
|
||||
|
||||
let focus_handle = self.editor().focus_handle(cx).clone();
|
||||
|
||||
PickerPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
@@ -2135,10 +2150,16 @@ impl TextThreadEditor {
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
Icon::new(provider_icon)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
.ml_0p5(),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
@@ -2575,7 +2596,7 @@ impl Render for TextThreadEditor {
|
||||
};
|
||||
|
||||
let language_model_selector = self.language_model_selector_menu_handle.clone();
|
||||
let max_mode_toggle = self.render_max_mode_toggle(cx);
|
||||
let burn_mode_toggle = self.render_burn_mode_toggle(cx);
|
||||
|
||||
v_flex()
|
||||
.key_context("ContextEditor")
|
||||
@@ -2630,7 +2651,7 @@ impl Render for TextThreadEditor {
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(self.render_inject_context_menu(cx))
|
||||
.when_some(max_mode_toggle, |this, element| this.child(element)),
|
||||
.when_some(burn_mode_toggle, |this, element| this.child(element)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -2924,13 +2945,6 @@ impl FollowableItem for TextThreadEditor {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContextEditorToolbarItem {
|
||||
active_context_editor: Option<WeakEntity<TextThreadEditor>>,
|
||||
model_summary_editor: Entity<Editor>,
|
||||
}
|
||||
|
||||
impl ContextEditorToolbarItem {}
|
||||
|
||||
pub fn render_remaining_tokens(
|
||||
context_editor: &Entity<TextThreadEditor>,
|
||||
cx: &App,
|
||||
@@ -2983,98 +2997,6 @@ pub fn render_remaining_tokens(
|
||||
)
|
||||
}
|
||||
|
||||
impl Render for ContextEditorToolbarItem {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let left_side = h_flex()
|
||||
.group("chat-title-group")
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.flex_grow()
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.when(self.active_context_editor.is_some(), |left_side| {
|
||||
left_side.child(self.model_summary_editor.clone())
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div().visible_on_hover("chat-title-group").child(
|
||||
IconButton::new("regenerate-context", IconName::RefreshTitle)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(Tooltip::text("Regenerate Title"))
|
||||
.on_click(cx.listener(move |_, _, _window, cx| {
|
||||
cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
let right_side = h_flex()
|
||||
.gap_2()
|
||||
// TODO display this in a nicer way, once we have a design for it.
|
||||
// .children({
|
||||
// let project = self
|
||||
// .workspace
|
||||
// .upgrade()
|
||||
// .map(|workspace| workspace.read(cx).project().downgrade());
|
||||
//
|
||||
// let scan_items_remaining = cx.update_global(|db: &mut SemanticDb, cx| {
|
||||
// project.and_then(|project| db.remaining_summaries(&project, cx))
|
||||
// });
|
||||
// scan_items_remaining
|
||||
// .map(|remaining_items| format!("Files to scan: {}", remaining_items))
|
||||
// })
|
||||
.children(
|
||||
self.active_context_editor
|
||||
.as_ref()
|
||||
.and_then(|editor| editor.upgrade())
|
||||
.and_then(|editor| render_remaining_tokens(&editor, cx)),
|
||||
);
|
||||
|
||||
h_flex()
|
||||
.px_0p5()
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(left_side)
|
||||
.child(right_side)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolbarItemView for ContextEditorToolbarItem {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
active_pane_item: Option<&dyn ItemHandle>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> ToolbarItemLocation {
|
||||
self.active_context_editor = active_pane_item
|
||||
.and_then(|item| item.act_as::<TextThreadEditor>(cx))
|
||||
.map(|editor| editor.downgrade());
|
||||
cx.notify();
|
||||
if self.active_context_editor.is_none() {
|
||||
ToolbarItemLocation::Hidden
|
||||
} else {
|
||||
ToolbarItemLocation::PrimaryRight
|
||||
}
|
||||
}
|
||||
|
||||
fn pane_focus_update(
|
||||
&mut self,
|
||||
_pane_focused: bool,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
|
||||
|
||||
pub enum ContextEditorToolbarItemEvent {
|
||||
RegenerateSummary,
|
||||
}
|
||||
impl EventEmitter<ContextEditorToolbarItemEvent> for ContextEditorToolbarItem {}
|
||||
|
||||
enum PendingSlashCommand {}
|
||||
|
||||
fn invoked_slash_command_fold_placeholder(
|
||||
@@ -3141,7 +3063,7 @@ fn token_state(context: &Entity<AssistantContext>, cx: &App) -> Option<TokenStat
|
||||
.default_model()?
|
||||
.model;
|
||||
let token_count = context.read(cx).token_count()?;
|
||||
let max_token_count = model.max_token_count();
|
||||
let max_token_count = model.max_token_count_for_mode(context.read(cx).completion_mode().into());
|
||||
let token_state = if max_token_count.saturating_sub(token_count) == 0 {
|
||||
TokenState::NoTokensLeft {
|
||||
max_token_count,
|
||||
@@ -3240,6 +3162,7 @@ pub fn make_lsp_adapter_delegate(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use editor::SelectionEffects;
|
||||
use fs::FakeFs;
|
||||
use gpui::{App, TestAppContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
@@ -3465,7 +3388,9 @@ mod tests {
|
||||
) {
|
||||
context_editor.update_in(cx, |context_editor, window, cx| {
|
||||
context_editor.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([range]));
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([range])
|
||||
});
|
||||
});
|
||||
|
||||
context_editor.copy(&Default::default(), window, cx);
|
||||
|
||||
@@ -42,8 +42,8 @@ impl IncompatibleToolsState {
|
||||
.profile()
|
||||
.enabled_tools(cx)
|
||||
.iter()
|
||||
.filter(|tool| tool.input_schema(model.tool_input_format()).is_err())
|
||||
.cloned()
|
||||
.filter(|(_, tool)| tool.input_schema(model.tool_input_format()).is_err())
|
||||
.map(|(_, tool)| tool.clone())
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
mod agent_notification;
|
||||
mod animated_label;
|
||||
mod burn_mode_tooltip;
|
||||
mod context_pill;
|
||||
mod max_mode_tooltip;
|
||||
mod onboarding_modal;
|
||||
pub mod preview;
|
||||
mod upsell;
|
||||
|
||||
pub use agent_notification::*;
|
||||
pub use animated_label::*;
|
||||
pub use burn_mode_tooltip::*;
|
||||
pub use context_pill::*;
|
||||
pub use max_mode_tooltip::*;
|
||||
pub use onboarding_modal::*;
|
||||
|
||||
@@ -6,7 +6,7 @@ use anyhow::{Context as _, Result, anyhow};
|
||||
use chrono::{DateTime, Utc};
|
||||
use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};
|
||||
use http_client::http::{self, HeaderMap, HeaderValue};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest, StatusCode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumIter, EnumString};
|
||||
use thiserror::Error;
|
||||
@@ -356,7 +356,7 @@ pub async fn complete(
|
||||
.send(request)
|
||||
.await
|
||||
.map_err(AnthropicError::HttpSend)?;
|
||||
let status = response.status();
|
||||
let status_code = response.status();
|
||||
let mut body = String::new();
|
||||
response
|
||||
.body_mut()
|
||||
@@ -364,12 +364,12 @@ pub async fn complete(
|
||||
.await
|
||||
.map_err(AnthropicError::ReadResponse)?;
|
||||
|
||||
if status.is_success() {
|
||||
if status_code.is_success() {
|
||||
Ok(serde_json::from_str(&body).map_err(AnthropicError::DeserializeResponse)?)
|
||||
} else {
|
||||
Err(AnthropicError::HttpResponseError {
|
||||
status: status.as_u16(),
|
||||
body,
|
||||
status_code,
|
||||
message: body,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -444,11 +444,7 @@ impl RateLimitInfo {
|
||||
}
|
||||
|
||||
Self {
|
||||
retry_after: headers
|
||||
.get("retry-after")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|v| v.parse::<u64>().ok())
|
||||
.map(Duration::from_secs),
|
||||
retry_after: parse_retry_after(headers),
|
||||
requests: RateLimit::from_headers("requests", headers).ok(),
|
||||
tokens: RateLimit::from_headers("tokens", headers).ok(),
|
||||
input_tokens: RateLimit::from_headers("input-tokens", headers).ok(),
|
||||
@@ -457,6 +453,17 @@ impl RateLimitInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the Retry-After header value as an integer number of seconds (anthropic always uses
|
||||
/// seconds). Note that other services might specify an HTTP date or some other format for this
|
||||
/// header. Returns `None` if the header is not present or cannot be parsed.
|
||||
pub fn parse_retry_after(headers: &HeaderMap<HeaderValue>) -> Option<Duration> {
|
||||
headers
|
||||
.get("retry-after")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|v| v.parse::<u64>().ok())
|
||||
.map(Duration::from_secs)
|
||||
}
|
||||
|
||||
fn get_header<'a>(key: &str, headers: &'a HeaderMap) -> anyhow::Result<&'a str> {
|
||||
Ok(headers
|
||||
.get(key)
|
||||
@@ -520,6 +527,10 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
})
|
||||
.boxed();
|
||||
Ok((stream, Some(rate_limits)))
|
||||
} else if response.status().as_u16() == 529 {
|
||||
Err(AnthropicError::ServerOverloaded {
|
||||
retry_after: rate_limits.retry_after,
|
||||
})
|
||||
} else if let Some(retry_after) = rate_limits.retry_after {
|
||||
Err(AnthropicError::RateLimit { retry_after })
|
||||
} else {
|
||||
@@ -532,10 +543,9 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
|
||||
match serde_json::from_str::<Event>(&body) {
|
||||
Ok(Event::Error { error }) => Err(AnthropicError::ApiError(error)),
|
||||
Ok(_) => Err(AnthropicError::UnexpectedResponseFormat(body)),
|
||||
Err(_) => Err(AnthropicError::HttpResponseError {
|
||||
status: response.status().as_u16(),
|
||||
body: body,
|
||||
Ok(_) | Err(_) => Err(AnthropicError::HttpResponseError {
|
||||
status_code: response.status(),
|
||||
message: body,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -801,16 +811,19 @@ pub enum AnthropicError {
|
||||
ReadResponse(io::Error),
|
||||
|
||||
/// HTTP error response from the API
|
||||
HttpResponseError { status: u16, body: String },
|
||||
HttpResponseError {
|
||||
status_code: StatusCode,
|
||||
message: String,
|
||||
},
|
||||
|
||||
/// Rate limit exceeded
|
||||
RateLimit { retry_after: Duration },
|
||||
|
||||
/// Server overloaded
|
||||
ServerOverloaded { retry_after: Option<Duration> },
|
||||
|
||||
/// API returned an error response
|
||||
ApiError(ApiError),
|
||||
|
||||
/// Unexpected response format
|
||||
UnexpectedResponseFormat(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Error)]
|
||||
|
||||
@@ -15,6 +15,8 @@ path = "src/askpass.rs"
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
net.workspace = true
|
||||
parking_lot.workspace = true
|
||||
smol.workspace = true
|
||||
tempfile.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use std::{ffi::OsStr, time::Duration};
|
||||
|
||||
#[cfg(unix)]
|
||||
use anyhow::Context as _;
|
||||
use anyhow::{Context as _, Result};
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
#[cfg(unix)]
|
||||
use futures::{AsyncBufReadExt as _, io::BufReader};
|
||||
#[cfg(unix)]
|
||||
use futures::{AsyncWriteExt as _, FutureExt as _, select_biased};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use futures::{
|
||||
AsyncBufReadExt as _, AsyncWriteExt as _, FutureExt as _, SinkExt, StreamExt, io::BufReader,
|
||||
select_biased,
|
||||
};
|
||||
use gpui::{AsyncApp, BackgroundExecutor, Task};
|
||||
#[cfg(unix)]
|
||||
use smol::fs;
|
||||
#[cfg(unix)]
|
||||
use smol::net::unix::UnixListener;
|
||||
#[cfg(unix)]
|
||||
use util::{ResultExt as _, fs::make_file_executable, get_shell_safe_zed_path};
|
||||
use util::ResultExt as _;
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum AskPassResult {
|
||||
@@ -42,41 +35,56 @@ impl AskPassDelegate {
|
||||
Self { tx, _task: task }
|
||||
}
|
||||
|
||||
pub async fn ask_password(&mut self, prompt: String) -> anyhow::Result<String> {
|
||||
pub async fn ask_password(&mut self, prompt: String) -> Result<String> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.tx.send((prompt, tx)).await?;
|
||||
Ok(rx.await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub struct AskPassSession {
|
||||
script_path: PathBuf,
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
script_path: std::path::PathBuf,
|
||||
#[cfg(target_os = "windows")]
|
||||
askpass_helper: String,
|
||||
#[cfg(target_os = "windows")]
|
||||
secret: std::sync::Arc<parking_lot::Mutex<String>>,
|
||||
_askpass_task: Task<()>,
|
||||
askpass_opened_rx: Option<oneshot::Receiver<()>>,
|
||||
askpass_kill_master_rx: Option<oneshot::Receiver<()>>,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
const ASKPASS_SCRIPT_NAME: &str = "askpass.sh";
|
||||
#[cfg(target_os = "windows")]
|
||||
const ASKPASS_SCRIPT_NAME: &str = "askpass.ps1";
|
||||
|
||||
impl AskPassSession {
|
||||
/// This will create a new AskPassSession.
|
||||
/// You must retain this session until the master process exits.
|
||||
#[must_use]
|
||||
pub async fn new(
|
||||
executor: &BackgroundExecutor,
|
||||
mut delegate: AskPassDelegate,
|
||||
) -> anyhow::Result<Self> {
|
||||
pub async fn new(executor: &BackgroundExecutor, mut delegate: AskPassDelegate) -> Result<Self> {
|
||||
use net::async_net::UnixListener;
|
||||
use util::fs::make_file_executable;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let secret = std::sync::Arc::new(parking_lot::Mutex::new(String::new()));
|
||||
let temp_dir = tempfile::Builder::new().prefix("zed-askpass").tempdir()?;
|
||||
let askpass_socket = temp_dir.path().join("askpass.sock");
|
||||
let askpass_script_path = temp_dir.path().join("askpass.sh");
|
||||
let askpass_script_path = temp_dir.path().join(ASKPASS_SCRIPT_NAME);
|
||||
let (askpass_opened_tx, askpass_opened_rx) = oneshot::channel::<()>();
|
||||
let listener =
|
||||
UnixListener::bind(&askpass_socket).context("failed to create askpass socket")?;
|
||||
let zed_path = get_shell_safe_zed_path()?;
|
||||
let listener = UnixListener::bind(&askpass_socket).context("creating askpass socket")?;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let zed_path = util::get_shell_safe_zed_path()?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let zed_path = std::env::current_exe()
|
||||
.context("finding current executable path for use in askpass")?;
|
||||
|
||||
let (askpass_kill_master_tx, askpass_kill_master_rx) = oneshot::channel::<()>();
|
||||
let mut kill_tx = Some(askpass_kill_master_tx);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let askpass_secret = secret.clone();
|
||||
let askpass_task = executor.spawn(async move {
|
||||
let mut askpass_opened_tx = Some(askpass_opened_tx);
|
||||
|
||||
@@ -93,10 +101,14 @@ impl AskPassSession {
|
||||
if let Some(password) = delegate
|
||||
.ask_password(prompt.to_string())
|
||||
.await
|
||||
.context("failed to get askpass password")
|
||||
.context("getting askpass password")
|
||||
.log_err()
|
||||
{
|
||||
stream.write_all(password.as_bytes()).await.log_err();
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
*askpass_secret.lock() = password;
|
||||
}
|
||||
} else {
|
||||
if let Some(kill_tx) = kill_tx.take() {
|
||||
kill_tx.send(()).log_err();
|
||||
@@ -112,34 +124,49 @@ impl AskPassSession {
|
||||
});
|
||||
|
||||
// Create an askpass script that communicates back to this process.
|
||||
let askpass_script = format!(
|
||||
"{shebang}\n{print_args} | {zed_exe} --askpass={askpass_socket} 2> /dev/null \n",
|
||||
zed_exe = zed_path,
|
||||
askpass_socket = askpass_socket.display(),
|
||||
print_args = "printf '%s\\0' \"$@\"",
|
||||
shebang = "#!/bin/sh",
|
||||
);
|
||||
fs::write(&askpass_script_path, askpass_script).await?;
|
||||
let askpass_script = generate_askpass_script(&zed_path, &askpass_socket);
|
||||
fs::write(&askpass_script_path, askpass_script)
|
||||
.await
|
||||
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
|
||||
make_file_executable(&askpass_script_path).await?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let askpass_helper = format!(
|
||||
"powershell.exe -ExecutionPolicy Bypass -File {}",
|
||||
askpass_script_path.display()
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
script_path: askpass_script_path,
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
secret,
|
||||
#[cfg(target_os = "windows")]
|
||||
askpass_helper,
|
||||
|
||||
_askpass_task: askpass_task,
|
||||
askpass_kill_master_rx: Some(askpass_kill_master_rx),
|
||||
askpass_opened_rx: Some(askpass_opened_rx),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn script_path(&self) -> &Path {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn script_path(&self) -> impl AsRef<OsStr> {
|
||||
&self.script_path
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn script_path(&self) -> impl AsRef<OsStr> {
|
||||
&self.askpass_helper
|
||||
}
|
||||
|
||||
// This will run the askpass task forever, resolving as many authentication requests as needed.
|
||||
// The caller is responsible for examining the result of their own commands and cancelling this
|
||||
// future when this is no longer needed. Note that this can only be called once, but due to the
|
||||
// drop order this takes an &mut, so you can `drop()` it after you're done with the master process.
|
||||
pub async fn run(&mut self) -> AskPassResult {
|
||||
let connection_timeout = Duration::from_secs(10);
|
||||
// This is the default timeout setting used by VSCode.
|
||||
let connection_timeout = Duration::from_secs(17);
|
||||
let askpass_opened_rx = self.askpass_opened_rx.take().expect("Only call run once");
|
||||
let askpass_kill_master_rx = self
|
||||
.askpass_kill_master_rx
|
||||
@@ -158,14 +185,19 @@ impl AskPassSession {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This will return the password that was last set by the askpass script.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn get_password(&self) -> String {
|
||||
self.secret.lock().clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// The main function for when Zed is running in netcat mode for use in askpass.
|
||||
/// Called from both the remote server binary and the zed binary in their respective main functions.
|
||||
#[cfg(unix)]
|
||||
pub fn main(socket: &str) {
|
||||
use net::UnixStream;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::process::exit;
|
||||
|
||||
let mut stream = match UnixStream::connect(socket) {
|
||||
@@ -182,6 +214,10 @@ pub fn main(socket: &str) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
while buffer.last().map_or(false, |&b| b == b'\n' || b == b'\r') {
|
||||
buffer.pop();
|
||||
}
|
||||
if buffer.last() != Some(&b'\0') {
|
||||
buffer.push(b'\0');
|
||||
}
|
||||
@@ -202,28 +238,28 @@ pub fn main(socket: &str) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
pub fn main(_socket: &str) {}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub struct AskPassSession {
|
||||
path: PathBuf,
|
||||
#[inline]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn generate_askpass_script(zed_path: &str, askpass_socket: &std::path::Path) -> String {
|
||||
format!(
|
||||
"{shebang}\n{print_args} | {zed_exe} --askpass={askpass_socket} 2> /dev/null \n",
|
||||
zed_exe = zed_path,
|
||||
askpass_socket = askpass_socket.display(),
|
||||
print_args = "printf '%s\\0' \"$@\"",
|
||||
shebang = "#!/bin/sh",
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
impl AskPassSession {
|
||||
pub async fn new(_: &BackgroundExecutor, _: AskPassDelegate) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
path: PathBuf::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn script_path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> AskPassResult {
|
||||
futures::FutureExt::fuse(smol::Timer::after(Duration::from_secs(20))).await;
|
||||
AskPassResult::Timedout
|
||||
}
|
||||
#[inline]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn generate_askpass_script(zed_path: &std::path::Path, askpass_socket: &std::path::Path) -> String {
|
||||
format!(
|
||||
r#"
|
||||
$ErrorActionPreference = 'Stop';
|
||||
($args -join [char]0) | & "{zed_exe}" --askpass={askpass_socket} 2> $null
|
||||
"#,
|
||||
zed_exe = zed_path.display(),
|
||||
askpass_socket = askpass_socket.display(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2140,7 +2140,8 @@ impl AssistantContext {
|
||||
);
|
||||
}
|
||||
LanguageModelCompletionEvent::ToolUse(_) |
|
||||
LanguageModelCompletionEvent::UsageUpdate(_) => {}
|
||||
LanguageModelCompletionEvent::ToolUseJsonParseError { .. } |
|
||||
LanguageModelCompletionEvent::UsageUpdate(_) => {}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2292,6 +2293,7 @@ impl AssistantContext {
|
||||
tool_choice: None,
|
||||
stop: Vec::new(),
|
||||
temperature: model.and_then(|model| AgentSettings::temperature_for_model(model, cx)),
|
||||
thinking_allowed: true,
|
||||
};
|
||||
for message in self.messages(cx) {
|
||||
if message.status != MessageStatus::Done {
|
||||
@@ -2346,13 +2348,13 @@ impl AssistantContext {
|
||||
completion_request.messages.push(request_message);
|
||||
}
|
||||
}
|
||||
let supports_max_mode = if let Some(model) = model {
|
||||
model.supports_max_mode()
|
||||
let supports_burn_mode = if let Some(model) = model {
|
||||
model.supports_burn_mode()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if supports_max_mode {
|
||||
if supports_burn_mode {
|
||||
completion_request.mode = Some(self.completion_mode.into());
|
||||
}
|
||||
completion_request
|
||||
|
||||
@@ -74,7 +74,7 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
.slice(section.range.to_offset(&context_buffer)),
|
||||
);
|
||||
file_command_new_outputs.push(Arc::new(FileSlashCommand).run(
|
||||
&[metadata.path.clone()],
|
||||
std::slice::from_ref(&metadata.path),
|
||||
context_slash_command_output_sections,
|
||||
context_buffer.clone(),
|
||||
workspace.clone(),
|
||||
|
||||
@@ -22,6 +22,7 @@ gpui.workspace = true
|
||||
icons.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
project.workspace = true
|
||||
regex.workspace = true
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use buffer_diff::BufferDiff;
|
||||
use clock;
|
||||
use collections::BTreeMap;
|
||||
use futures::{FutureExt, StreamExt, channel::mpsc};
|
||||
use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task, WeakEntity};
|
||||
@@ -17,6 +18,8 @@ pub struct ActionLog {
|
||||
edited_since_project_diagnostics_check: bool,
|
||||
/// The project this action log is associated with
|
||||
project: Entity<Project>,
|
||||
/// Tracks which buffer versions have already been notified as changed externally
|
||||
notified_versions: BTreeMap<Entity<Buffer>, clock::Global>,
|
||||
}
|
||||
|
||||
impl ActionLog {
|
||||
@@ -26,6 +29,7 @@ impl ActionLog {
|
||||
tracked_buffers: BTreeMap::default(),
|
||||
edited_since_project_diagnostics_check: false,
|
||||
project,
|
||||
notified_versions: BTreeMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +55,7 @@ impl ActionLog {
|
||||
) -> &mut TrackedBuffer {
|
||||
let status = if is_created {
|
||||
if let Some(tracked) = self.tracked_buffers.remove(&buffer) {
|
||||
self.notified_versions.remove(&buffer);
|
||||
match tracked.status {
|
||||
TrackedBufferStatus::Created {
|
||||
existing_file_content,
|
||||
@@ -106,7 +111,7 @@ impl ActionLog {
|
||||
TrackedBuffer {
|
||||
buffer: buffer.clone(),
|
||||
diff_base,
|
||||
unreviewed_edits: unreviewed_edits,
|
||||
unreviewed_edits,
|
||||
snapshot: text_snapshot.clone(),
|
||||
status,
|
||||
version: buffer.read(cx).version(),
|
||||
@@ -165,6 +170,7 @@ impl ActionLog {
|
||||
// If the buffer had been edited by a tool, but it got
|
||||
// deleted externally, we want to stop tracking it.
|
||||
self.tracked_buffers.remove(&buffer);
|
||||
self.notified_versions.remove(&buffer);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
@@ -178,6 +184,7 @@ impl ActionLog {
|
||||
// resurrected externally, we want to clear the edits we
|
||||
// were tracking and reset the buffer's state.
|
||||
self.tracked_buffers.remove(&buffer);
|
||||
self.notified_versions.remove(&buffer);
|
||||
self.track_buffer_internal(buffer, false, cx);
|
||||
}
|
||||
cx.notify();
|
||||
@@ -483,6 +490,7 @@ impl ActionLog {
|
||||
match tracked_buffer.status {
|
||||
TrackedBufferStatus::Created { .. } => {
|
||||
self.tracked_buffers.remove(&buffer);
|
||||
self.notified_versions.remove(&buffer);
|
||||
cx.notify();
|
||||
}
|
||||
TrackedBufferStatus::Modified => {
|
||||
@@ -508,6 +516,7 @@ impl ActionLog {
|
||||
match tracked_buffer.status {
|
||||
TrackedBufferStatus::Deleted => {
|
||||
self.tracked_buffers.remove(&buffer);
|
||||
self.notified_versions.remove(&buffer);
|
||||
cx.notify();
|
||||
}
|
||||
_ => {
|
||||
@@ -616,6 +625,7 @@ impl ActionLog {
|
||||
};
|
||||
|
||||
self.tracked_buffers.remove(&buffer);
|
||||
self.notified_versions.remove(&buffer);
|
||||
cx.notify();
|
||||
task
|
||||
}
|
||||
@@ -629,6 +639,7 @@ impl ActionLog {
|
||||
|
||||
// Clear all tracked edits for this buffer and start over as if we just read it.
|
||||
self.tracked_buffers.remove(&buffer);
|
||||
self.notified_versions.remove(&buffer);
|
||||
self.buffer_read(buffer.clone(), cx);
|
||||
cx.notify();
|
||||
save
|
||||
@@ -713,6 +724,33 @@ impl ActionLog {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns stale buffers that haven't been notified yet
|
||||
pub fn unnotified_stale_buffers<'a>(
|
||||
&'a self,
|
||||
cx: &'a App,
|
||||
) -> impl Iterator<Item = &'a Entity<Buffer>> {
|
||||
self.stale_buffers(cx).filter(|buffer| {
|
||||
let buffer_entity = buffer.read(cx);
|
||||
self.notified_versions
|
||||
.get(buffer)
|
||||
.map_or(true, |notified_version| {
|
||||
*notified_version != buffer_entity.version
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Marks the given buffers as notified at their current versions
|
||||
pub fn mark_buffers_as_notified(
|
||||
&mut self,
|
||||
buffers: impl IntoIterator<Item = Entity<Buffer>>,
|
||||
cx: &App,
|
||||
) {
|
||||
for buffer in buffers {
|
||||
let version = buffer.read(cx).version.clone();
|
||||
self.notified_versions.insert(buffer, version);
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over buffers changed since last read or edited by the model
|
||||
pub fn stale_buffers<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = &'a Entity<Buffer>> {
|
||||
self.tracked_buffers
|
||||
|
||||
@@ -25,10 +25,15 @@ fn preprocess_json_schema(json: &mut Value) -> Result<()> {
|
||||
// `additionalProperties` defaults to `false` unless explicitly specified.
|
||||
// This prevents models from hallucinating tool parameters.
|
||||
if let Value::Object(obj) = json {
|
||||
if let Some(Value::String(type_str)) = obj.get("type") {
|
||||
if type_str == "object" && !obj.contains_key("additionalProperties") {
|
||||
if matches!(obj.get("type"), Some(Value::String(s)) if s == "object") {
|
||||
if !obj.contains_key("additionalProperties") {
|
||||
obj.insert("additionalProperties".to_string(), Value::Bool(false));
|
||||
}
|
||||
|
||||
// OpenAI API requires non-missing `properties`
|
||||
if !obj.contains_key("properties") {
|
||||
obj.insert("properties".to_string(), Value::Object(Default::default()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -1,18 +1,52 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collections::{HashMap, IndexMap};
|
||||
use gpui::App;
|
||||
use std::{borrow::Borrow, sync::Arc};
|
||||
|
||||
use crate::{Tool, ToolRegistry, ToolSource};
|
||||
use collections::{HashMap, HashSet, IndexMap};
|
||||
use gpui::{App, SharedString};
|
||||
use util::debug_panic;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)]
|
||||
pub struct ToolId(usize);
|
||||
|
||||
/// A unique identifier for a tool within a working set.
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Default)]
|
||||
pub struct UniqueToolName(SharedString);
|
||||
|
||||
impl Borrow<str> for UniqueToolName {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for UniqueToolName {
|
||||
fn from(value: String) -> Self {
|
||||
UniqueToolName(SharedString::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for UniqueToolName {
|
||||
fn into(self) -> String {
|
||||
self.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for UniqueToolName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UniqueToolName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
/// A working set of tools for use in one instance of the Assistant Panel.
|
||||
#[derive(Default)]
|
||||
pub struct ToolWorkingSet {
|
||||
context_server_tools_by_id: HashMap<ToolId, Arc<dyn Tool>>,
|
||||
context_server_tools_by_name: HashMap<String, Arc<dyn Tool>>,
|
||||
context_server_tools_by_name: HashMap<UniqueToolName, Arc<dyn Tool>>,
|
||||
next_tool_id: ToolId,
|
||||
}
|
||||
|
||||
@@ -24,16 +58,20 @@ impl ToolWorkingSet {
|
||||
.or_else(|| ToolRegistry::global(cx).tool(name))
|
||||
}
|
||||
|
||||
pub fn tools(&self, cx: &App) -> Vec<Arc<dyn Tool>> {
|
||||
let mut tools = ToolRegistry::global(cx).tools();
|
||||
tools.extend(self.context_server_tools_by_id.values().cloned());
|
||||
pub fn tools(&self, cx: &App) -> Vec<(UniqueToolName, Arc<dyn Tool>)> {
|
||||
let mut tools = ToolRegistry::global(cx)
|
||||
.tools()
|
||||
.into_iter()
|
||||
.map(|tool| (UniqueToolName(tool.name().into()), tool))
|
||||
.collect::<Vec<_>>();
|
||||
tools.extend(self.context_server_tools_by_name.clone());
|
||||
tools
|
||||
}
|
||||
|
||||
pub fn tools_by_source(&self, cx: &App) -> IndexMap<ToolSource, Vec<Arc<dyn Tool>>> {
|
||||
let mut tools_by_source = IndexMap::default();
|
||||
|
||||
for tool in self.tools(cx) {
|
||||
for (_, tool) in self.tools(cx) {
|
||||
tools_by_source
|
||||
.entry(tool.source())
|
||||
.or_insert_with(Vec::new)
|
||||
@@ -49,27 +87,324 @@ impl ToolWorkingSet {
|
||||
tools_by_source
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, tool: Arc<dyn Tool>) -> ToolId {
|
||||
pub fn insert(&mut self, tool: Arc<dyn Tool>, cx: &App) -> ToolId {
|
||||
let tool_id = self.register_tool(tool);
|
||||
self.tools_changed(cx);
|
||||
tool_id
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, tools: impl Iterator<Item = Arc<dyn Tool>>, cx: &App) -> Vec<ToolId> {
|
||||
let ids = tools.map(|tool| self.register_tool(tool)).collect();
|
||||
self.tools_changed(cx);
|
||||
ids
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, tool_ids_to_remove: &[ToolId], cx: &App) {
|
||||
self.context_server_tools_by_id
|
||||
.retain(|id, _| !tool_ids_to_remove.contains(id));
|
||||
self.tools_changed(cx);
|
||||
}
|
||||
|
||||
fn register_tool(&mut self, tool: Arc<dyn Tool>) -> ToolId {
|
||||
let tool_id = self.next_tool_id;
|
||||
self.next_tool_id.0 += 1;
|
||||
self.context_server_tools_by_id
|
||||
.insert(tool_id, tool.clone());
|
||||
self.tools_changed();
|
||||
tool_id
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, tool_ids_to_remove: &[ToolId]) {
|
||||
self.context_server_tools_by_id
|
||||
.retain(|id, _| !tool_ids_to_remove.contains(id));
|
||||
self.tools_changed();
|
||||
}
|
||||
|
||||
fn tools_changed(&mut self) {
|
||||
self.context_server_tools_by_name.clear();
|
||||
self.context_server_tools_by_name.extend(
|
||||
self.context_server_tools_by_id
|
||||
fn tools_changed(&mut self, cx: &App) {
|
||||
self.context_server_tools_by_name = resolve_context_server_tool_name_conflicts(
|
||||
&self
|
||||
.context_server_tools_by_id
|
||||
.values()
|
||||
.map(|tool| (tool.name(), tool.clone())),
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
&ToolRegistry::global(cx).tools(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_context_server_tool_name_conflicts(
|
||||
context_server_tools: &[Arc<dyn Tool>],
|
||||
native_tools: &[Arc<dyn Tool>],
|
||||
) -> HashMap<UniqueToolName, Arc<dyn Tool>> {
|
||||
fn resolve_tool_name(tool: &Arc<dyn Tool>) -> String {
|
||||
let mut tool_name = tool.name();
|
||||
tool_name.truncate(MAX_TOOL_NAME_LENGTH);
|
||||
tool_name
|
||||
}
|
||||
|
||||
const MAX_TOOL_NAME_LENGTH: usize = 64;
|
||||
|
||||
let mut duplicated_tool_names = HashSet::default();
|
||||
let mut seen_tool_names = HashSet::default();
|
||||
seen_tool_names.extend(native_tools.iter().map(|tool| tool.name()));
|
||||
for tool in context_server_tools {
|
||||
let tool_name = resolve_tool_name(tool);
|
||||
if seen_tool_names.contains(&tool_name) {
|
||||
debug_assert!(
|
||||
tool.source() != ToolSource::Native,
|
||||
"Expected MCP tool but got a native tool: {}",
|
||||
tool_name
|
||||
);
|
||||
duplicated_tool_names.insert(tool_name);
|
||||
} else {
|
||||
seen_tool_names.insert(tool_name);
|
||||
}
|
||||
}
|
||||
|
||||
if duplicated_tool_names.is_empty() {
|
||||
return context_server_tools
|
||||
.into_iter()
|
||||
.map(|tool| (resolve_tool_name(tool).into(), tool.clone()))
|
||||
.collect();
|
||||
}
|
||||
|
||||
context_server_tools
|
||||
.into_iter()
|
||||
.filter_map(|tool| {
|
||||
let mut tool_name = resolve_tool_name(tool);
|
||||
if !duplicated_tool_names.contains(&tool_name) {
|
||||
return Some((tool_name.into(), tool.clone()));
|
||||
}
|
||||
match tool.source() {
|
||||
ToolSource::Native => {
|
||||
debug_panic!("Expected MCP tool but got a native tool: {}", tool_name);
|
||||
// Built-in tools always keep their original name
|
||||
Some((tool_name.into(), tool.clone()))
|
||||
}
|
||||
ToolSource::ContextServer { id } => {
|
||||
// Context server tools are prefixed with the context server ID, and truncated if necessary
|
||||
tool_name.insert(0, '_');
|
||||
if tool_name.len() + id.len() > MAX_TOOL_NAME_LENGTH {
|
||||
let len = MAX_TOOL_NAME_LENGTH - tool_name.len();
|
||||
let mut id = id.to_string();
|
||||
id.truncate(len);
|
||||
tool_name.insert_str(0, &id);
|
||||
} else {
|
||||
tool_name.insert_str(0, &id);
|
||||
}
|
||||
|
||||
tool_name.truncate(MAX_TOOL_NAME_LENGTH);
|
||||
|
||||
if seen_tool_names.contains(&tool_name) {
|
||||
log::error!("Cannot resolve tool name conflict for tool {}", tool.name());
|
||||
None
|
||||
} else {
|
||||
Some((tool_name.into(), tool.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::{AnyWindowHandle, Entity, Task, TestAppContext};
|
||||
use language_model::{LanguageModel, LanguageModelRequest};
|
||||
use project::Project;
|
||||
|
||||
use crate::{ActionLog, ToolResult};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_unique_tool_names(cx: &mut TestAppContext) {
|
||||
fn assert_tool(
|
||||
tool_working_set: &ToolWorkingSet,
|
||||
unique_name: &str,
|
||||
expected_name: &str,
|
||||
expected_source: ToolSource,
|
||||
cx: &App,
|
||||
) {
|
||||
let tool = tool_working_set.tool(unique_name, cx).unwrap();
|
||||
assert_eq!(tool.name(), expected_name);
|
||||
assert_eq!(tool.source(), expected_source);
|
||||
}
|
||||
|
||||
let tool_registry = cx.update(ToolRegistry::default_global);
|
||||
tool_registry.register_tool(TestTool::new("tool1", ToolSource::Native));
|
||||
tool_registry.register_tool(TestTool::new("tool2", ToolSource::Native));
|
||||
|
||||
let mut tool_working_set = ToolWorkingSet::default();
|
||||
cx.update(|cx| {
|
||||
tool_working_set.extend(
|
||||
vec![
|
||||
Arc::new(TestTool::new(
|
||||
"tool2",
|
||||
ToolSource::ContextServer { id: "mcp-1".into() },
|
||||
)) as Arc<dyn Tool>,
|
||||
Arc::new(TestTool::new(
|
||||
"tool2",
|
||||
ToolSource::ContextServer { id: "mcp-2".into() },
|
||||
)) as Arc<dyn Tool>,
|
||||
]
|
||||
.into_iter(),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
assert_tool(&tool_working_set, "tool1", "tool1", ToolSource::Native, cx);
|
||||
assert_tool(&tool_working_set, "tool2", "tool2", ToolSource::Native, cx);
|
||||
assert_tool(
|
||||
&tool_working_set,
|
||||
"mcp-1_tool2",
|
||||
"tool2",
|
||||
ToolSource::ContextServer { id: "mcp-1".into() },
|
||||
cx,
|
||||
);
|
||||
assert_tool(
|
||||
&tool_working_set,
|
||||
"mcp-2_tool2",
|
||||
"tool2",
|
||||
ToolSource::ContextServer { id: "mcp-2".into() },
|
||||
cx,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_resolve_context_server_tool_name_conflicts() {
|
||||
assert_resolve_context_server_tool_name_conflicts(
|
||||
vec![
|
||||
TestTool::new("tool1", ToolSource::Native),
|
||||
TestTool::new("tool2", ToolSource::Native),
|
||||
],
|
||||
vec![TestTool::new(
|
||||
"tool3",
|
||||
ToolSource::ContextServer { id: "mcp-1".into() },
|
||||
)],
|
||||
vec!["tool3"],
|
||||
);
|
||||
|
||||
assert_resolve_context_server_tool_name_conflicts(
|
||||
vec![
|
||||
TestTool::new("tool1", ToolSource::Native),
|
||||
TestTool::new("tool2", ToolSource::Native),
|
||||
],
|
||||
vec![
|
||||
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-1".into() }),
|
||||
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-2".into() }),
|
||||
],
|
||||
vec!["mcp-1_tool3", "mcp-2_tool3"],
|
||||
);
|
||||
|
||||
assert_resolve_context_server_tool_name_conflicts(
|
||||
vec![
|
||||
TestTool::new("tool1", ToolSource::Native),
|
||||
TestTool::new("tool2", ToolSource::Native),
|
||||
TestTool::new("tool3", ToolSource::Native),
|
||||
],
|
||||
vec![
|
||||
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-1".into() }),
|
||||
TestTool::new("tool3", ToolSource::ContextServer { id: "mcp-2".into() }),
|
||||
],
|
||||
vec!["mcp-1_tool3", "mcp-2_tool3"],
|
||||
);
|
||||
|
||||
// Test deduplication of tools with very long names, in this case the mcp server name should be truncated
|
||||
assert_resolve_context_server_tool_name_conflicts(
|
||||
vec![TestTool::new(
|
||||
"tool-with-very-very-very-long-name",
|
||||
ToolSource::Native,
|
||||
)],
|
||||
vec![TestTool::new(
|
||||
"tool-with-very-very-very-long-name",
|
||||
ToolSource::ContextServer {
|
||||
id: "mcp-with-very-very-very-long-name".into(),
|
||||
},
|
||||
)],
|
||||
vec!["mcp-with-very-very-very-long-_tool-with-very-very-very-long-name"],
|
||||
);
|
||||
|
||||
fn assert_resolve_context_server_tool_name_conflicts(
|
||||
builtin_tools: Vec<TestTool>,
|
||||
context_server_tools: Vec<TestTool>,
|
||||
expected: Vec<&'static str>,
|
||||
) {
|
||||
let context_server_tools: Vec<Arc<dyn Tool>> = context_server_tools
|
||||
.into_iter()
|
||||
.map(|t| Arc::new(t) as Arc<dyn Tool>)
|
||||
.collect();
|
||||
let builtin_tools: Vec<Arc<dyn Tool>> = builtin_tools
|
||||
.into_iter()
|
||||
.map(|t| Arc::new(t) as Arc<dyn Tool>)
|
||||
.collect();
|
||||
let tools =
|
||||
resolve_context_server_tool_name_conflicts(&context_server_tools, &builtin_tools);
|
||||
assert_eq!(tools.len(), expected.len());
|
||||
for (i, (name, _)) in tools.into_iter().enumerate() {
|
||||
assert_eq!(
|
||||
name.0.as_ref(),
|
||||
expected[i],
|
||||
"Expected '{}' got '{}' at index {}",
|
||||
expected[i],
|
||||
name,
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TestTool {
|
||||
name: String,
|
||||
source: ToolSource,
|
||||
}
|
||||
|
||||
impl TestTool {
|
||||
fn new(name: impl Into<String>, source: ToolSource) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
source,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tool for TestTool {
|
||||
fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn icon(&self) -> icons::IconName {
|
||||
icons::IconName::Ai
|
||||
}
|
||||
|
||||
fn may_perform_edits(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _input: &serde_json::Value, _cx: &App) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn source(&self) -> ToolSource {
|
||||
self.source.clone()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"Test tool".to_string()
|
||||
}
|
||||
|
||||
fn ui_text(&self, _input: &serde_json::Value) -> String {
|
||||
"Test tool".to_string()
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_input: serde_json::Value,
|
||||
_request: Arc<LanguageModelRequest>,
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_model: Arc<dyn LanguageModel>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
_cx: &mut App,
|
||||
) -> ToolResult {
|
||||
ToolResult {
|
||||
output: Task::ready(Err(anyhow::anyhow!("No content"))),
|
||||
card: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ mod list_directory_tool;
|
||||
mod move_path_tool;
|
||||
mod now_tool;
|
||||
mod open_tool;
|
||||
mod project_notifications_tool;
|
||||
mod read_file_tool;
|
||||
mod schema;
|
||||
mod templates;
|
||||
@@ -45,6 +46,7 @@ pub use edit_file_tool::{EditFileMode, EditFileToolInput};
|
||||
pub use find_path_tool::FindPathToolInput;
|
||||
pub use grep_tool::{GrepTool, GrepToolInput};
|
||||
pub use open_tool::OpenTool;
|
||||
pub use project_notifications_tool::ProjectNotificationsTool;
|
||||
pub use read_file_tool::{ReadFileTool, ReadFileToolInput};
|
||||
pub use terminal_tool::TerminalTool;
|
||||
|
||||
@@ -61,6 +63,7 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
|
||||
registry.register_tool(ListDirectoryTool);
|
||||
registry.register_tool(NowTool);
|
||||
registry.register_tool(OpenTool);
|
||||
registry.register_tool(ProjectNotificationsTool);
|
||||
registry.register_tool(FindPathTool);
|
||||
registry.register_tool(ReadFileTool);
|
||||
registry.register_tool(GrepTool);
|
||||
|
||||
@@ -57,7 +57,7 @@ impl Tool for CopyPathTool {
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::Clipboard
|
||||
IconName::ToolCopy
|
||||
}
|
||||
|
||||
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
|
||||
|
||||
@@ -46,7 +46,7 @@ impl Tool for CreateDirectoryTool {
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::Folder
|
||||
IconName::ToolFolder
|
||||
}
|
||||
|
||||
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
|
||||
|
||||
@@ -46,7 +46,7 @@ impl Tool for DeletePathTool {
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::FileDelete
|
||||
IconName::ToolDeleteFile
|
||||
}
|
||||
|
||||
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
|
||||
|
||||
@@ -59,7 +59,7 @@ impl Tool for DiagnosticsTool {
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::XCircle
|
||||
IconName::ToolDiagnostics
|
||||
}
|
||||
|
||||
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
|
||||
|
||||
@@ -719,6 +719,7 @@ impl EditAgent {
|
||||
tools,
|
||||
stop: Vec::new(),
|
||||
temperature: None,
|
||||
thinking_allowed: true,
|
||||
};
|
||||
|
||||
Ok(self.model.stream_completion_text(request, cx).await?.stream)
|
||||
|
||||
@@ -29,6 +29,7 @@ use std::{
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
sync::mpsc,
|
||||
time::Duration,
|
||||
};
|
||||
use util::path;
|
||||
|
||||
@@ -1262,6 +1263,7 @@ impl EvalAssertion {
|
||||
content: vec![prompt.into()],
|
||||
cache: false,
|
||||
}],
|
||||
thinking_allowed: true,
|
||||
..Default::default()
|
||||
};
|
||||
let mut response = retry_on_rate_limit(async || {
|
||||
@@ -1598,6 +1600,7 @@ impl EditAgentTest {
|
||||
let conversation = LanguageModelRequest {
|
||||
messages,
|
||||
tools,
|
||||
thinking_allowed: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -1658,12 +1661,14 @@ async fn retry_on_rate_limit<R>(mut request: impl AsyncFnMut() -> Result<R>) ->
|
||||
match request().await {
|
||||
Ok(result) => return Ok(result),
|
||||
Err(err) => match err.downcast::<LanguageModelCompletionError>() {
|
||||
Ok(err) => match err {
|
||||
LanguageModelCompletionError::RateLimitExceeded { retry_after } => {
|
||||
Ok(err) => match &err {
|
||||
LanguageModelCompletionError::RateLimitExceeded { retry_after, .. }
|
||||
| LanguageModelCompletionError::ServerOverloaded { retry_after, .. } => {
|
||||
let retry_after = retry_after.unwrap_or(Duration::from_secs(5));
|
||||
// Wait for the duration supplied, with some jitter to avoid all requests being made at the same time.
|
||||
let jitter = retry_after.mul_f64(rand::thread_rng().gen_range(0.0..1.0));
|
||||
eprintln!(
|
||||
"Attempt #{attempt}: Rate limit exceeded. Retry after {retry_after:?} + jitter of {jitter:?}"
|
||||
"Attempt #{attempt}: {err}. Retry after {retry_after:?} + jitter of {jitter:?}"
|
||||
);
|
||||
Timer::after(retry_after + jitter).await;
|
||||
continue;
|
||||
|
||||
@@ -9132,7 +9132,7 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
|
||||
self.manipulate_lines(window, cx, |lines| lines.sort())
|
||||
}
|
||||
|
||||
pub fn sort_lines_case_insensitive(
|
||||
@@ -9141,7 +9141,7 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.manipulate_immutable_lines(window, cx, |lines| {
|
||||
self.manipulate_lines(window, cx, |lines| {
|
||||
lines.sort_by_key(|line| line.to_lowercase())
|
||||
})
|
||||
}
|
||||
@@ -9152,7 +9152,7 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.manipulate_immutable_lines(window, cx, |lines| {
|
||||
self.manipulate_lines(window, cx, |lines| {
|
||||
let mut seen = HashSet::default();
|
||||
lines.retain(|line| seen.insert(line.to_lowercase()));
|
||||
})
|
||||
@@ -9164,7 +9164,7 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.manipulate_immutable_lines(window, cx, |lines| {
|
||||
self.manipulate_lines(window, cx, |lines| {
|
||||
let mut seen = HashSet::default();
|
||||
lines.retain(|line| seen.insert(*line));
|
||||
})
|
||||
@@ -9606,20 +9606,20 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
|
||||
self.manipulate_lines(window, cx, |lines| lines.reverse())
|
||||
}
|
||||
|
||||
pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
|
||||
self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
|
||||
}
|
||||
|
||||
fn manipulate_lines<M>(
|
||||
fn manipulate_lines<Fn>(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
mut manipulate: M,
|
||||
mut callback: Fn,
|
||||
) where
|
||||
M: FnMut(&str) -> LineManipulationResult,
|
||||
Fn: FnMut(&mut Vec<&str>),
|
||||
{
|
||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
|
||||
|
||||
@@ -9652,14 +9652,18 @@ impl Editor {
|
||||
.text_for_range(start_point..end_point)
|
||||
.collect::<String>();
|
||||
|
||||
let LineManipulationResult { new_text, line_count_before, line_count_after} = manipulate(&text);
|
||||
let mut lines = text.split('\n').collect_vec();
|
||||
|
||||
edits.push((start_point..end_point, new_text));
|
||||
let lines_before = lines.len();
|
||||
callback(&mut lines);
|
||||
let lines_after = lines.len();
|
||||
|
||||
edits.push((start_point..end_point, lines.join("\n")));
|
||||
|
||||
// Selections must change based on added and removed line count
|
||||
let start_row =
|
||||
MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
|
||||
let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
|
||||
let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
|
||||
new_selections.push(Selection {
|
||||
id: selection.id,
|
||||
start: start_row,
|
||||
@@ -9668,10 +9672,10 @@ impl Editor {
|
||||
reversed: selection.reversed,
|
||||
});
|
||||
|
||||
if line_count_after > line_count_before {
|
||||
added_lines += line_count_after - line_count_before;
|
||||
} else if line_count_before > line_count_after {
|
||||
removed_lines += line_count_before - line_count_after;
|
||||
if lines_after > lines_before {
|
||||
added_lines += lines_after - lines_before;
|
||||
} else if lines_before > lines_after {
|
||||
removed_lines += lines_before - lines_after;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9716,171 +9720,6 @@ impl Editor {
|
||||
})
|
||||
}
|
||||
|
||||
fn manipulate_immutable_lines<Fn>(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
mut callback: Fn,
|
||||
) where
|
||||
Fn: FnMut(&mut Vec<&str>),
|
||||
{
|
||||
self.manipulate_lines(window, cx, |text| {
|
||||
let mut lines: Vec<&str> = text.split('\n').collect();
|
||||
let line_count_before = lines.len();
|
||||
|
||||
callback(&mut lines);
|
||||
|
||||
LineManipulationResult {
|
||||
new_text: lines.join("\n"),
|
||||
line_count_before,
|
||||
line_count_after: lines.len(),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn manipulate_mutable_lines<Fn>(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
mut callback: Fn,
|
||||
) where
|
||||
Fn: FnMut(&mut Vec<Cow<'_, str>>),
|
||||
{
|
||||
self.manipulate_lines(window, cx, |text| {
|
||||
let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
|
||||
let line_count_before = lines.len();
|
||||
|
||||
callback(&mut lines);
|
||||
|
||||
LineManipulationResult {
|
||||
new_text: lines.join("\n"),
|
||||
line_count_before,
|
||||
line_count_after: lines.len(),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn convert_indentation_to_spaces(
|
||||
&mut self,
|
||||
_: &ConvertIndentationToSpaces,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let settings = self.buffer.read(cx).language_settings(cx);
|
||||
let tab_size = settings.tab_size.get() as usize;
|
||||
|
||||
self.manipulate_mutable_lines(window, cx, |lines| {
|
||||
// Allocates a reasonably sized scratch buffer once for the whole loop
|
||||
let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
|
||||
// Avoids recomputing spaces that could be inserted many times
|
||||
let space_cache: Vec<Vec<char>> = (1..=tab_size)
|
||||
.map(|n| IndentSize::spaces(n as u32).chars().collect())
|
||||
.collect();
|
||||
|
||||
for line in lines.iter_mut().filter(|line| !line.is_empty()) {
|
||||
let mut chars = line.as_ref().chars();
|
||||
let mut col = 0;
|
||||
let mut changed = false;
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
' ' => {
|
||||
reindented_line.push(' ');
|
||||
col += 1;
|
||||
}
|
||||
'\t' => {
|
||||
// \t are converted to spaces depending on the current column
|
||||
let spaces_len = tab_size - (col % tab_size);
|
||||
reindented_line.extend(&space_cache[spaces_len - 1]);
|
||||
col += spaces_len;
|
||||
changed = true;
|
||||
}
|
||||
_ => {
|
||||
// If we dont append before break, the character is consumed
|
||||
reindented_line.push(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !changed {
|
||||
reindented_line.clear();
|
||||
continue;
|
||||
}
|
||||
// Append the rest of the line and replace old reference with new one
|
||||
reindented_line.extend(chars);
|
||||
*line = Cow::Owned(reindented_line.clone());
|
||||
reindented_line.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn convert_indentation_to_tabs(
|
||||
&mut self,
|
||||
_: &ConvertIndentationToTabs,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let settings = self.buffer.read(cx).language_settings(cx);
|
||||
let tab_size = settings.tab_size.get() as usize;
|
||||
|
||||
self.manipulate_mutable_lines(window, cx, |lines| {
|
||||
// Allocates a reasonably sized buffer once for the whole loop
|
||||
let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
|
||||
// Avoids recomputing spaces that could be inserted many times
|
||||
let space_cache: Vec<Vec<char>> = (1..=tab_size)
|
||||
.map(|n| IndentSize::spaces(n as u32).chars().collect())
|
||||
.collect();
|
||||
|
||||
for line in lines.iter_mut().filter(|line| !line.is_empty()) {
|
||||
let mut chars = line.chars();
|
||||
let mut spaces_count = 0;
|
||||
let mut first_non_indent_char = None;
|
||||
let mut changed = false;
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
' ' => {
|
||||
// Keep track of spaces. Append \t when we reach tab_size
|
||||
spaces_count += 1;
|
||||
changed = true;
|
||||
if spaces_count == tab_size {
|
||||
reindented_line.push('\t');
|
||||
spaces_count = 0;
|
||||
}
|
||||
}
|
||||
'\t' => {
|
||||
reindented_line.push('\t');
|
||||
spaces_count = 0;
|
||||
}
|
||||
_ => {
|
||||
// Dont append it yet, we might have remaining spaces
|
||||
first_non_indent_char = Some(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !changed {
|
||||
reindented_line.clear();
|
||||
continue;
|
||||
}
|
||||
// Remaining spaces that didn't make a full tab stop
|
||||
if spaces_count > 0 {
|
||||
reindented_line.extend(&space_cache[spaces_count - 1]);
|
||||
}
|
||||
// If we consume an extra character that was not indentation, add it back
|
||||
if let Some(extra_char) = first_non_indent_char {
|
||||
reindented_line.push(extra_char);
|
||||
}
|
||||
// Append the rest of the line and replace old reference with new one
|
||||
reindented_line.extend(chars);
|
||||
*line = Cow::Owned(reindented_line.clone());
|
||||
reindented_line.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn convert_to_upper_case(
|
||||
&mut self,
|
||||
_: &ConvertToUpperCase,
|
||||
@@ -21318,13 +21157,6 @@ pub struct LineHighlight {
|
||||
pub type_id: Option<TypeId>,
|
||||
}
|
||||
|
||||
struct LineManipulationResult {
|
||||
pub new_text: String,
|
||||
pub line_count_before: usize,
|
||||
pub line_count_after: usize,
|
||||
}
|
||||
|
||||
|
||||
fn render_diff_hunk_controls(
|
||||
row: u32,
|
||||
status: &DiffHunkStatus,
|
||||
|
||||
@@ -4,17 +4,18 @@ use crate::{
|
||||
schema::json_schema_for,
|
||||
ui::{COLLAPSED_LINES, ToolOutputPreview},
|
||||
};
|
||||
use agent_settings;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{
|
||||
ActionLog, AnyToolCard, Tool, ToolCard, ToolResult, ToolResultContent, ToolResultOutput,
|
||||
ToolUseStatus,
|
||||
};
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey, scroll::Autoscroll};
|
||||
use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
Animation, AnimationExt, AnyWindowHandle, App, AppContext, AsyncApp, Entity, Task,
|
||||
TextStyleRefinement, WeakEntity, pulsating_between, px,
|
||||
TextStyleRefinement, Transformation, WeakEntity, percentage, pulsating_between, px,
|
||||
};
|
||||
use indoc::formatdoc;
|
||||
use language::{
|
||||
@@ -138,7 +139,7 @@ impl Tool for EditFileTool {
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::Pencil
|
||||
IconName::ToolPencil
|
||||
}
|
||||
|
||||
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
|
||||
@@ -515,7 +516,9 @@ pub struct EditFileToolCard {
|
||||
|
||||
impl EditFileToolCard {
|
||||
pub fn new(path: PathBuf, project: Entity<Project>, window: &mut Window, cx: &mut App) -> Self {
|
||||
let expand_edit_card = agent_settings::AgentSettings::get_global(cx).expand_edit_card;
|
||||
let multibuffer = cx.new(|_| MultiBuffer::without_headers(Capability::ReadOnly));
|
||||
|
||||
let editor = cx.new(|cx| {
|
||||
let mut editor = Editor::new(
|
||||
EditorMode::Full {
|
||||
@@ -556,7 +559,7 @@ impl EditFileToolCard {
|
||||
diff_task: None,
|
||||
preview_expanded: true,
|
||||
error_expanded: None,
|
||||
full_height_expanded: true,
|
||||
full_height_expanded: expand_edit_card,
|
||||
total_lines: None,
|
||||
}
|
||||
}
|
||||
@@ -755,6 +758,13 @@ impl ToolCard for EditFileToolCard {
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let running_or_pending = match status {
|
||||
ToolUseStatus::Running | ToolUseStatus::Pending => Some(()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let should_show_loading = running_or_pending.is_some() && !self.full_height_expanded;
|
||||
|
||||
let path_label_button = h_flex()
|
||||
.id(("edit-tool-path-label-button", self.editor.entity_id()))
|
||||
.w_full()
|
||||
@@ -773,8 +783,8 @@ impl ToolCard for EditFileToolCard {
|
||||
.child(
|
||||
h_flex()
|
||||
.child(
|
||||
Icon::new(IconName::Pencil)
|
||||
.size(IconSize::XSmall)
|
||||
Icon::new(IconName::ToolPencil)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
@@ -823,7 +833,7 @@ impl ToolCard for EditFileToolCard {
|
||||
let first_hunk_start =
|
||||
first_hunk.multi_buffer_range().start;
|
||||
editor.change_selections(
|
||||
Some(Autoscroll::fit()),
|
||||
Default::default(),
|
||||
window,
|
||||
cx,
|
||||
|selections| {
|
||||
@@ -863,6 +873,18 @@ impl ToolCard for EditFileToolCard {
|
||||
header.bg(codeblock_header_bg)
|
||||
})
|
||||
.child(path_label_button)
|
||||
.when(should_show_loading, |header| {
|
||||
header.pr_1p5().child(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
),
|
||||
)
|
||||
})
|
||||
.when_some(error_message, |header, error_message| {
|
||||
header.child(
|
||||
h_flex()
|
||||
@@ -1065,7 +1087,7 @@ fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
selection_background_color: cx.theme().colors().element_selection_background,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ impl Tool for FetchTool {
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::Globe
|
||||
IconName::ToolWeb
|
||||
}
|
||||
|
||||
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
|
||||
|
||||
@@ -68,7 +68,7 @@ impl Tool for FindPathTool {
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::SearchCode
|
||||
IconName::ToolSearch
|
||||
}
|
||||
|
||||
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
|
||||
@@ -313,7 +313,7 @@ impl ToolCard for FindPathToolCard {
|
||||
.mb_2()
|
||||
.gap_1()
|
||||
.child(
|
||||
ToolCallCardHeader::new(IconName::SearchCode, matches_label)
|
||||
ToolCallCardHeader::new(IconName::ToolSearch, matches_label)
|
||||
.with_code_path(&self.glob)
|
||||
.disclosure_slot(
|
||||
Disclosure::new("path-search-disclosure", self.expanded)
|
||||
|
||||