Compare commits
270 Commits
wasi-fs
...
assistant-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23ff3fbeac | ||
|
|
1723713dc2 | ||
|
|
ca4980df02 | ||
|
|
5e6d1814e5 | ||
|
|
1b612108ba | ||
|
|
c3f47b8040 | ||
|
|
43e005e936 | ||
|
|
b43b800a54 | ||
|
|
eef44aff7f | ||
|
|
106ca5076f | ||
|
|
2cd9a88f53 | ||
|
|
a62e8f6396 | ||
|
|
2c8a6ee7cc | ||
|
|
9016de5d63 | ||
|
|
97f5fcf8e6 | ||
|
|
71b6f739cd | ||
|
|
30ef7e62bf | ||
|
|
97dc1d193f | ||
|
|
772bda54a2 | ||
|
|
fb7a7a564a | ||
|
|
826777a257 | ||
|
|
eda7e88fd4 | ||
|
|
a7977aa64d | ||
|
|
373a17acf4 | ||
|
|
425c8f8c3e | ||
|
|
84f2e0ee37 | ||
|
|
1a62396b1e | ||
|
|
3ac201e448 | ||
|
|
3b153a54c2 | ||
|
|
430ce073d2 | ||
|
|
a149a50946 | ||
|
|
f68f4ab982 | ||
|
|
aae26ee33d | ||
|
|
550ceec549 | ||
|
|
d4e10dfba3 | ||
|
|
2699fa8d4a | ||
|
|
8e30229ec9 | ||
|
|
2e72fd210a | ||
|
|
4d074fc737 | ||
|
|
fbb402ef12 | ||
|
|
56f9e4c7b3 | ||
|
|
8e45bf71ca | ||
|
|
db18f7a2b0 | ||
|
|
e7912370e6 | ||
|
|
51faf4a1cd | ||
|
|
bdca342cdc | ||
|
|
8cc6df573c | ||
|
|
7814dd0301 | ||
|
|
8a6c65c63b | ||
|
|
d3d3a093b4 | ||
|
|
06a13c2983 | ||
|
|
c28b22d1cf | ||
|
|
447a5d6e6e | ||
|
|
869a72bb3f | ||
|
|
ab7a7d3480 | ||
|
|
fc43b21e78 | ||
|
|
e6c4076ef0 | ||
|
|
7246a0f39c | ||
|
|
345efa4e36 | ||
|
|
5cdca6d8dd | ||
|
|
ccfd4b1887 | ||
|
|
ee8668ef45 | ||
|
|
ac5c35b3df | ||
|
|
0070635b4d | ||
|
|
3d69942f71 | ||
|
|
76603a5fc6 | ||
|
|
accff826ca | ||
|
|
27f09957c2 | ||
|
|
e88b48a9c7 | ||
|
|
d5003e1121 | ||
|
|
7c54965b11 | ||
|
|
10cfaecffa | ||
|
|
469dfe759c | ||
|
|
ecd1830793 | ||
|
|
ddaee2e8dd | ||
|
|
54b8232be2 | ||
|
|
a20c0eb626 | ||
|
|
c48584fb79 | ||
|
|
f1d21362fa | ||
|
|
4139a9a758 | ||
|
|
103f757c11 | ||
|
|
2165d52d3e | ||
|
|
c34fc5c6e5 | ||
|
|
5f0925fb5d | ||
|
|
d56e3d99b4 | ||
|
|
7d97855ed7 | ||
|
|
4160824b10 | ||
|
|
1285504b3e | ||
|
|
83192c29e8 | ||
|
|
a141415bd3 | ||
|
|
d315405be1 | ||
|
|
37b2f4b9d3 | ||
|
|
4441150809 | ||
|
|
d7c45ccf2f | ||
|
|
bc5ed1334f | ||
|
|
b54b3d6246 | ||
|
|
e7d18ef359 | ||
|
|
243629cce8 | ||
|
|
67f149a4bc | ||
|
|
e66ea9e5d4 | ||
|
|
01bb10f518 | ||
|
|
ca2cce79ed | ||
|
|
dea85099a2 | ||
|
|
b48c2c5846 | ||
|
|
f3769322ad | ||
|
|
2c9d07663a | ||
|
|
784c3093ae | ||
|
|
ba5c1322ce | ||
|
|
28fb1fd19b | ||
|
|
90b77e125a | ||
|
|
fb79346e6f | ||
|
|
761129e373 | ||
|
|
22db569adf | ||
|
|
2cae6f3e08 | ||
|
|
2baa704af7 | ||
|
|
e3d54b2211 | ||
|
|
f986513d0d | ||
|
|
02dfe08ce8 | ||
|
|
4e1bb68620 | ||
|
|
96a5daaf3f | ||
|
|
29a5def12c | ||
|
|
cdc3791544 | ||
|
|
524a1a6fec | ||
|
|
4f251429c7 | ||
|
|
6f337de440 | ||
|
|
d56fa25830 | ||
|
|
d5268c5197 | ||
|
|
40a00fb224 | ||
|
|
00c0a7254a | ||
|
|
4d8c3855c2 | ||
|
|
e8a2dd92c8 | ||
|
|
ce848375fe | ||
|
|
fac9ee5f86 | ||
|
|
d245f5e75c | ||
|
|
e145c13f73 | ||
|
|
c71f052276 | ||
|
|
1b36c62188 | ||
|
|
adbe973f02 | ||
|
|
8f833ea029 | ||
|
|
de344c833b | ||
|
|
91ffa02e2c | ||
|
|
93a3e8bc94 | ||
|
|
3aeea93847 | ||
|
|
3613ebd93c | ||
|
|
461812d7b6 | ||
|
|
af819bf661 | ||
|
|
ee96d69e37 | ||
|
|
b9b62842f8 | ||
|
|
4d26f83d23 | ||
|
|
3b37db4140 | ||
|
|
bba380e41a | ||
|
|
6841f7b9d7 | ||
|
|
f39c175bd3 | ||
|
|
0043b0d957 | ||
|
|
b341079d8a | ||
|
|
02d5f320ad | ||
|
|
9db68ee6ae | ||
|
|
092f29d394 | ||
|
|
25b6e43b0f | ||
|
|
3a6a29f117 | ||
|
|
9407d86ce6 | ||
|
|
b5c42edf1e | ||
|
|
13c553c50f | ||
|
|
ec189fe884 | ||
|
|
331d28d479 | ||
|
|
8088d1a9b7 | ||
|
|
ef5a7e1642 | ||
|
|
ccf6f27b8f | ||
|
|
8f28445612 | ||
|
|
f374038da0 | ||
|
|
d1a47faeb7 | ||
|
|
d6663fcb29 | ||
|
|
b16af138e2 | ||
|
|
3ff81c2e86 | ||
|
|
3cea7ccbff | ||
|
|
48a16f9e70 | ||
|
|
19463b59e2 | ||
|
|
36eb1c15ea | ||
|
|
130f19d8f9 | ||
|
|
bd1ff476b9 | ||
|
|
a23e381096 | ||
|
|
1b627925d3 | ||
|
|
ae3880e71a | ||
|
|
06142f975b | ||
|
|
fb9d01b0d5 | ||
|
|
85f4c96fef | ||
|
|
0b0cd9005e | ||
|
|
5f61e3140f | ||
|
|
d5498c52f8 | ||
|
|
a7ac37156c | ||
|
|
a078cb104c | ||
|
|
93b3520c11 | ||
|
|
bf64c0899f | ||
|
|
929eff815c | ||
|
|
75256bdfe1 | ||
|
|
56bc3c36ad | ||
|
|
2fc74a1b71 | ||
|
|
f71cb14d7a | ||
|
|
da9601c698 | ||
|
|
c1193875e8 | ||
|
|
f92d0de58d | ||
|
|
59be07ad90 | ||
|
|
12dde17608 | ||
|
|
dd257b8412 | ||
|
|
fcf79c0f1d | ||
|
|
66ef318823 | ||
|
|
174e125686 | ||
|
|
657be0aa3e | ||
|
|
89ae97e5e9 | ||
|
|
894866da94 | ||
|
|
63188b6754 | ||
|
|
65961b80fc | ||
|
|
8985fd87c2 | ||
|
|
47aec5e64d | ||
|
|
b401f6951b | ||
|
|
a7da16d192 | ||
|
|
095a08d9c8 | ||
|
|
cfd43572c1 | ||
|
|
c90ae3d90a | ||
|
|
f91bf7fca5 | ||
|
|
832e3e7dd7 | ||
|
|
986e0113e5 | ||
|
|
0b17c72f4e | ||
|
|
0282c3a981 | ||
|
|
7180880047 | ||
|
|
2d06d5c906 | ||
|
|
903f92045a | ||
|
|
938c90fd3b | ||
|
|
54dd40878f | ||
|
|
e6c1c51b37 | ||
|
|
93249fc82b | ||
|
|
fbac7dcd10 | ||
|
|
a6b1c054a8 | ||
|
|
6af5fdd1c2 | ||
|
|
5f2a5ffadd | ||
|
|
fb35cd98c8 | ||
|
|
30f70ff110 | ||
|
|
b623958b7a | ||
|
|
b8d3af35fd | ||
|
|
629a412c2e | ||
|
|
2238439427 | ||
|
|
7fa4b11ed3 | ||
|
|
7c9abcfd6c | ||
|
|
e5858c8b91 | ||
|
|
46006a3c81 | ||
|
|
8a1e8e37bb | ||
|
|
b887f8df8c | ||
|
|
d6ab569e42 | ||
|
|
94b6d06a72 | ||
|
|
eecff753fb | ||
|
|
f7f8a5c5e0 | ||
|
|
96b592f00d | ||
|
|
a3d8dcda36 | ||
|
|
a7c46206de | ||
|
|
1e09884a22 | ||
|
|
01e40928d8 | ||
|
|
fe53113ee4 | ||
|
|
1b735b0d81 | ||
|
|
fef181a66f | ||
|
|
adc3e9fe1b | ||
|
|
3d99b5eb0f | ||
|
|
f413ea90bf | ||
|
|
a1c676128a | ||
|
|
8660719bd1 | ||
|
|
2aae3ab448 | ||
|
|
b54d1aa95e | ||
|
|
e1a865295b | ||
|
|
f2d539f762 | ||
|
|
7907ab32d7 | ||
|
|
9a5194b505 |
@@ -1,8 +1,8 @@
|
||||
We have two cloudflare workers that let us serve some assets of this repo
|
||||
from Cloudflare.
|
||||
|
||||
* `open-source-website-assets` is used for `install.sh`
|
||||
* `docs-proxy` is used for `https://zed.dev/docs`
|
||||
- `open-source-website-assets` is used for `install.sh`
|
||||
- `docs-proxy` is used for `https://zed.dev/docs`
|
||||
|
||||
On push to `main`, both of these (and the files they depend on) are uploaded to Cloudflare.
|
||||
|
||||
|
||||
23
.github/workflows/bump_nightly_tag.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Update Nightly Tag
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Fire every day at 7:00am UTC (Roughly before EU workday and after US workday)
|
||||
- cron: "0 7 * * *"
|
||||
|
||||
jobs:
|
||||
update-nightly-tag:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Update nightly tag
|
||||
run: |
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
git tag -f nightly
|
||||
git push origin nightly --force
|
||||
3
.github/workflows/bump_patch_version.yml
vendored
@@ -15,8 +15,7 @@ concurrency:
|
||||
jobs:
|
||||
bump_patch_version:
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
|
||||
59
.github/workflows/ci.yml
vendored
@@ -39,16 +39,7 @@ jobs:
|
||||
run: git clean -df
|
||||
|
||||
- name: Check spelling
|
||||
run: |
|
||||
if ! cargo install --list | grep "typos-cli v$TYPOS_CLI_VERSION" > /dev/null; then
|
||||
echo "Installing typos-cli@$TYPOS_CLI_VERSION..."
|
||||
cargo install "typos-cli@$TYPOS_CLI_VERSION"
|
||||
else
|
||||
echo "typos-cli@$TYPOS_CLI_VERSION is already installed."
|
||||
fi
|
||||
typos
|
||||
env:
|
||||
TYPOS_CLI_VERSION: "1.23.3"
|
||||
run: script/check-spelling
|
||||
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
@@ -110,8 +101,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
name: (Linux) Run Clippy and tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
@@ -121,6 +111,15 @@ jobs:
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "buildjet"
|
||||
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux
|
||||
|
||||
- name: cargo clippy
|
||||
run: ./script/clippy
|
||||
|
||||
@@ -145,6 +144,7 @@ jobs:
|
||||
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "github"
|
||||
|
||||
- name: cargo clippy
|
||||
# Windows can't run shell scripts, so we need to use `cargo xtask`.
|
||||
@@ -271,24 +271,20 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
name: Create a Linux bundle
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
needs: [linux_tests]
|
||||
env:
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux
|
||||
|
||||
- name: Determine version and release channel
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
@@ -339,11 +335,11 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
bundle-linux-aarch64:
|
||||
bundle-linux-aarch64: # this runs on ubuntu22.04
|
||||
timeout-minutes: 60
|
||||
name: Create arm64 Linux bundle
|
||||
runs-on:
|
||||
- hosted-linux-arm-1
|
||||
- buildjet-16vcpu-ubuntu-2204-arm
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
needs: [linux_tests]
|
||||
env:
|
||||
@@ -354,26 +350,9 @@ jobs:
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
clean: false
|
||||
- name: "Setup jq"
|
||||
uses: dcarbone/install-jq-action@8867ddb4788346d7c22b72ea2e2ffe4d514c7bcb # v2
|
||||
|
||||
- name: Set up Clang
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
|
||||
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
|
||||
|
||||
- uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1
|
||||
with:
|
||||
mold-version: 2.32.0
|
||||
|
||||
- name: rustup
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux
|
||||
|
||||
- name: Determine version and release channel
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
|
||||
9
.github/workflows/deploy_collab.yml
vendored
@@ -61,8 +61,7 @@ jobs:
|
||||
- style
|
||||
- tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
@@ -75,9 +74,6 @@ jobs:
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Set up default .cargo/config.toml
|
||||
run: cp ./.cargo/collab-config.toml ./.cargo/config.toml
|
||||
|
||||
- name: Build docker image
|
||||
run: docker build . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA
|
||||
|
||||
@@ -92,8 +88,7 @@ jobs:
|
||||
needs:
|
||||
- publish
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
|
||||
steps:
|
||||
- name: Sign into Kubernetes
|
||||
|
||||
8
.github/workflows/docs.yml
vendored
@@ -20,5 +20,11 @@ jobs:
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- run: pnpm dlx prettier . --check
|
||||
- run: |
|
||||
pnpm dlx prettier . --check || {
|
||||
echo "To fix, run from the root of the zed repo:"
|
||||
echo " cd docs && pnpm dlx prettier . --write && cd .."
|
||||
false
|
||||
}
|
||||
|
||||
working-directory: ./docs
|
||||
|
||||
1
.github/workflows/publish_extension_cli.yml
vendored
@@ -24,6 +24,7 @@ jobs:
|
||||
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "github"
|
||||
|
||||
- name: Configure linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
|
||||
3
.github/workflows/randomized_tests.yml
vendored
@@ -19,8 +19,7 @@ jobs:
|
||||
tests:
|
||||
name: Run randomized tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- randomized-tests
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
||||
|
||||
31
.github/workflows/release_nightly.yml
vendored
@@ -1,9 +1,6 @@
|
||||
name: Release Nightly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Fire every day at 7:00am UTC (Roughly before EU workday and after US workday)
|
||||
- cron: "0 7 * * *"
|
||||
push:
|
||||
tags:
|
||||
- "nightly"
|
||||
@@ -100,8 +97,7 @@ jobs:
|
||||
name: Create a Linux *.tar.gz bundle for x86
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
needs: tests
|
||||
env:
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
@@ -117,6 +113,12 @@ jobs:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Set release channel to nightly
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -148,23 +150,8 @@ jobs:
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: "Setup jq"
|
||||
uses: dcarbone/install-jq-action@8867ddb4788346d7c22b72ea2e2ffe4d514c7bcb # v2
|
||||
|
||||
- name: Set up Clang
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
|
||||
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
|
||||
|
||||
- uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1
|
||||
with:
|
||||
mold-version: 2.32.0
|
||||
|
||||
- name: rustup
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
828
Cargo.lock
generated
97
Cargo.toml
@@ -27,6 +27,7 @@ members = [
|
||||
"crates/diagnostics",
|
||||
"crates/docs_preprocessor",
|
||||
"crates/editor",
|
||||
"crates/evals",
|
||||
"crates/extension",
|
||||
"crates/extension_api",
|
||||
"crates/extension_cli",
|
||||
@@ -51,6 +52,7 @@ members = [
|
||||
"crates/indexed_docs",
|
||||
"crates/inline_completion_button",
|
||||
"crates/install_cli",
|
||||
"crates/isahc_http_client",
|
||||
"crates/journal",
|
||||
"crates/language",
|
||||
"crates/language_model",
|
||||
@@ -72,7 +74,6 @@ members = [
|
||||
"crates/outline",
|
||||
"crates/outline_panel",
|
||||
"crates/paths",
|
||||
"crates/performance",
|
||||
"crates/picker",
|
||||
"crates/prettier",
|
||||
"crates/project",
|
||||
@@ -119,10 +120,10 @@ members = [
|
||||
"crates/title_bar",
|
||||
"crates/ui",
|
||||
"crates/ui_input",
|
||||
"crates/ui_macros",
|
||||
"crates/util",
|
||||
"crates/vcs_menu",
|
||||
"crates/vim",
|
||||
"crates/wasi_fs",
|
||||
"crates/welcome",
|
||||
"crates/workspace",
|
||||
"crates/worktree",
|
||||
@@ -168,7 +169,7 @@ members = [
|
||||
# Tooling
|
||||
#
|
||||
|
||||
"tooling/xtask"
|
||||
"tooling/xtask",
|
||||
]
|
||||
default-members = ["crates/zed"]
|
||||
|
||||
@@ -226,6 +227,7 @@ image_viewer = { path = "crates/image_viewer" }
|
||||
indexed_docs = { path = "crates/indexed_docs" }
|
||||
inline_completion_button = { path = "crates/inline_completion_button" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
isahc_http_client = { path = "crates/isahc_http_client" }
|
||||
journal = { path = "crates/journal" }
|
||||
language = { path = "crates/language" }
|
||||
language_model = { path = "crates/language_model" }
|
||||
@@ -247,7 +249,6 @@ open_ai = { path = "crates/open_ai" }
|
||||
outline = { path = "crates/outline" }
|
||||
outline_panel = { path = "crates/outline_panel" }
|
||||
paths = { path = "crates/paths" }
|
||||
performance = { path = "crates/performance" }
|
||||
picker = { path = "crates/picker" }
|
||||
plugin = { path = "crates/plugin" }
|
||||
plugin_macros = { path = "crates/plugin_macros" }
|
||||
@@ -295,10 +296,10 @@ time_format = { path = "crates/time_format" }
|
||||
title_bar = { path = "crates/title_bar" }
|
||||
ui = { path = "crates/ui" }
|
||||
ui_input = { path = "crates/ui_input" }
|
||||
ui_macros = { path = "crates/ui_macros" }
|
||||
util = { path = "crates/util" }
|
||||
vcs_menu = { path = "crates/vcs_menu" }
|
||||
vim = { path = "crates/vim" }
|
||||
wasi_fs = { path = "crates/wasi_fs" }
|
||||
welcome = { path = "crates/welcome" }
|
||||
workspace = { path = "crates/workspace" }
|
||||
worktree = { path = "crates/worktree" }
|
||||
@@ -313,6 +314,7 @@ aho-corasick = "1.1"
|
||||
alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "91d034ff8b53867143c005acfaa14609147c9a2c" }
|
||||
any_vec = "0.14"
|
||||
anyhow = "1.0.86"
|
||||
arrayvec = { version = "0.7.4", features = ["serde"] }
|
||||
ashpd = "0.9.1"
|
||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||
async-dispatcher = "0.1"
|
||||
@@ -326,15 +328,17 @@ async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
base64 = "0.22"
|
||||
bitflags = "2.6.0"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "fee06c42f658b36dd9ac85444a9ee2a481383695" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "fee06c42f658b36dd9ac85444a9ee2a481383695" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "fee06c42f658b36dd9ac85444a9ee2a481383695" }
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
|
||||
blake3 = "1.5.3"
|
||||
cargo_metadata = "0.18"
|
||||
cargo_toml = "0.20"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
clickhouse = "0.11.6"
|
||||
cocoa = "0.26"
|
||||
convert_case = "0.6.0"
|
||||
core-foundation = "0.9.3"
|
||||
core-foundation-sys = "0.8.6"
|
||||
ctor = "0.2.6"
|
||||
@@ -369,7 +373,7 @@ linkify = "0.10.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
nanoid = "0.4"
|
||||
nix = "0.28"
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.19.0"
|
||||
ordered-float = "2.1.1"
|
||||
@@ -382,7 +386,7 @@ pretty_assertions = "1.3.0"
|
||||
prost = "0.9"
|
||||
prost-build = "0.9"
|
||||
prost-types = "0.9"
|
||||
pulldown-cmark = { version = "0.10.0", default-features = false }
|
||||
pulldown-cmark = { version = "0.12.0", default-features = false }
|
||||
rand = "0.8.5"
|
||||
regex = "1.5"
|
||||
repair_json = "0.1.0"
|
||||
@@ -390,15 +394,16 @@ rsa = "0.9.6"
|
||||
runtimelib = { version = "0.15", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
] }
|
||||
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
||||
rustc-demangle = "0.1.23"
|
||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||
rustls = "0.20.3"
|
||||
rustls-native-certs = "0.8.0"
|
||||
schemars = { version = "0.8", features = ["impl_json_schema"] }
|
||||
semver = "1.0"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
|
||||
serde_json_lenient = { version = "0.1", features = [
|
||||
serde_json_lenient = { version = "0.2", features = [
|
||||
"preserve_order",
|
||||
"raw_value",
|
||||
] }
|
||||
@@ -415,7 +420,7 @@ strsim = "0.11"
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
subtle = "2.5.0"
|
||||
sys-locale = "0.3.1"
|
||||
sysinfo = "0.30.7"
|
||||
sysinfo = "0.31.0"
|
||||
tempfile = "3.9.0"
|
||||
thiserror = "1.0.29"
|
||||
tiktoken-rs = "0.5.9"
|
||||
@@ -430,43 +435,43 @@ tiny_http = "0.8"
|
||||
toml = "0.8"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tower-http = "0.4.4"
|
||||
tree-sitter = { version = "0.22", features = ["wasm"] }
|
||||
tree-sitter-bash = "0.21"
|
||||
tree-sitter-c = "0.21"
|
||||
tree-sitter-cpp = "0.22"
|
||||
tree-sitter-css = "0.21"
|
||||
tree-sitter-elixir = "0.2"
|
||||
tree-sitter-embedded-template = "0.20.0"
|
||||
tree-sitter-go = "0.21"
|
||||
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "1f55029bacd0a6a11f6eb894c4312d429dcf735c", package = "tree-sitter-gomod" }
|
||||
tree-sitter-gowork = { git = "https://github.com/d1y/tree-sitter-go-work", rev = "dcbabff454703c3a4bc98a23cf8778d4be46fd22" }
|
||||
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "6dd0303acf7138dd2b9b432a229e16539581c701" }
|
||||
tree-sitter = { version = "0.23", features = ["wasm"] }
|
||||
tree-sitter-bash = "0.23"
|
||||
tree-sitter-c = "0.23"
|
||||
tree-sitter-cpp = "0.23"
|
||||
tree-sitter-css = "0.23"
|
||||
tree-sitter-elixir = "0.3"
|
||||
tree-sitter-embedded-template = "0.23.0"
|
||||
tree-sitter-go = "0.23"
|
||||
tree-sitter-go-mod = { git = "https://github.com/zed-industries/tree-sitter-go-mod", rev = "a9aea5e358cde4d0f8ff20b7bc4fa311e359c7ca", package = "tree-sitter-gomod" }
|
||||
tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
|
||||
tree-sitter-heex = { git = "https://github.com/zed-industries/tree-sitter-heex", rev = "1dd45142fbb05562e35b2040c6129c9bca346592" }
|
||||
tree-sitter-html = "0.20"
|
||||
tree-sitter-jsdoc = "0.21"
|
||||
tree-sitter-json = "0.21"
|
||||
tree-sitter-md = { git = "https://github.com/zed-industries/tree-sitter-markdown", rev = "e3855e37f8f2c71aa7513c18a9c95fb7461b1b10" }
|
||||
protols-tree-sitter-proto = "0.2"
|
||||
tree-sitter-python = "0.21"
|
||||
tree-sitter-regex = "0.21"
|
||||
tree-sitter-ruby = "0.21"
|
||||
tree-sitter-rust = "0.21"
|
||||
tree-sitter-typescript = "0.21"
|
||||
tree-sitter-yaml = "0.6"
|
||||
tree-sitter-jsdoc = "0.23"
|
||||
tree-sitter-json = "0.23"
|
||||
tree-sitter-md = { git = "https://github.com/zed-industries/tree-sitter-markdown", rev = "4cfa6aad6b75052a5077c80fd934757d9267d81b" }
|
||||
protols-tree-sitter-proto = { git = "https://github.com/zed-industries/tree-sitter-proto", rev = "0848bd30a64be48772e15fbb9d5ba8c0cc5772ad" }
|
||||
tree-sitter-python = "0.23"
|
||||
tree-sitter-regex = "0.23"
|
||||
tree-sitter-ruby = "0.23"
|
||||
tree-sitter-rust = "0.23"
|
||||
tree-sitter-typescript = "0.23"
|
||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
|
||||
unindent = "0.1.7"
|
||||
unicase = "2.6"
|
||||
unicode-segmentation = "1.10"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }
|
||||
wasmparser = "0.201"
|
||||
wasm-encoder = "0.201"
|
||||
wasmtime = { version = "21.0.1", default-features = false, features = [
|
||||
wasmparser = "0.215"
|
||||
wasm-encoder = "0.215"
|
||||
wasmtime = { version = "24", default-features = false, features = [
|
||||
"async",
|
||||
"demangle",
|
||||
"runtime",
|
||||
"cranelift",
|
||||
"component-model",
|
||||
] }
|
||||
wasmtime-wasi = "21.0.1"
|
||||
wasmtime-wasi = "24"
|
||||
which = "6.0.0"
|
||||
wit-component = "0.201"
|
||||
|
||||
@@ -488,7 +493,7 @@ version = "0.58"
|
||||
features = [
|
||||
"implement",
|
||||
"Foundation_Numerics",
|
||||
"System",
|
||||
"Storage",
|
||||
"System_Threading",
|
||||
"UI_ViewManagement",
|
||||
"Wdk_System_SystemServices",
|
||||
@@ -519,13 +524,10 @@ features = [
|
||||
"Win32_UI_Input_Ime",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_Shell_Common",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
# Patch Tree-sitter for updated wasmtime.
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7f4a57817d58a2f134fe863674acad6bbf007228" }
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
debug = "limited"
|
||||
@@ -577,8 +579,17 @@ single_range_in_vec_init = "allow"
|
||||
# allow all of those, for now.
|
||||
style = { level = "allow", priority = -1 }
|
||||
|
||||
# Temporary list of style lints that we've fixed so far.
|
||||
module_inception = { level = "deny" }
|
||||
question_mark = { level = "deny" }
|
||||
redundant_closure = { level = "deny" }
|
||||
# Individual rules that have violations in the codebase:
|
||||
type_complexity = "allow"
|
||||
# We often return trait objects from `new` functions.
|
||||
new_ret_no_self = { level = "allow" }
|
||||
# We have a few `next` functions that differ in lifetimes
|
||||
# compared to Iterator::next. Yet, clippy complains about those.
|
||||
should_implement_trait = { level = "allow" }
|
||||
|
||||
[workspace.metadata.cargo-machete]
|
||||
ignored = ["bindgen", "cbindgen", "prost_build", "serde"]
|
||||
|
||||
29
Dockerfile
@@ -1,14 +1,41 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
|
||||
FROM rust:1.80-bookworm as builder
|
||||
FROM rust:1.81-bookworm as builder
|
||||
WORKDIR app
|
||||
COPY . .
|
||||
|
||||
# Replace the Cargo configuration with the one used by collab.
|
||||
COPY ./.cargo/collab-config.toml ./.cargo/config.toml
|
||||
|
||||
# Compile collab server
|
||||
ARG CARGO_PROFILE_RELEASE_PANIC=abort
|
||||
ARG GITHUB_SHA
|
||||
|
||||
ENV GITHUB_SHA=$GITHUB_SHA
|
||||
|
||||
# At some point in the past 3 weeks, additional dependencies on `xkbcommon` and
|
||||
# `xkbcommon-x11` were introduced into collab.
|
||||
#
|
||||
# A `git bisect` points to this commit as being the culprit: `b8e6098f60e5dabe98fe8281f993858dacc04a55`.
|
||||
#
|
||||
# Now when we try to build collab for the Docker image, it fails with the following
|
||||
# error:
|
||||
#
|
||||
# ```
|
||||
# 985.3 = note: /usr/bin/ld: cannot find -lxkbcommon: No such file or directory
|
||||
# 985.3 /usr/bin/ld: cannot find -lxkbcommon-x11: No such file or directory
|
||||
# 985.3 collect2: error: ld returned 1 exit status
|
||||
# ```
|
||||
#
|
||||
# The last successful deploys were at:
|
||||
# - Staging: `4f408ec65a3867278322a189b4eb20f1ab51f508`
|
||||
# - Production: `fc4c533d0a8c489e5636a4249d2b52a80039fbd7`
|
||||
#
|
||||
# Installing these as a temporary workaround, but I think ideally we'd want to figure
|
||||
# out what caused them to be included in the first place.
|
||||
RUN apt-get update; \
|
||||
apt-get install -y --no-install-recommends libxkbcommon-dev libxkbcommon-x11-dev
|
||||
|
||||
RUN --mount=type=cache,target=./script/node_modules \
|
||||
--mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/usr/local/cargo/git \
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
|
||||
Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter).
|
||||
|
||||
--------
|
||||
---
|
||||
|
||||
### Installation
|
||||
|
||||
|
||||
<a href="https://repology.org/project/zed-editor/versions">
|
||||
<img src="https://repology.org/badge/vertical-allrepos/zed-editor.svg?minversion=0.143.5" alt="Packaging status" align="right">
|
||||
</a>
|
||||
|
||||
1
assets/icons/audio_off.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-volume-off"><path d="M16 9a5 5 0 0 1 .95 2.293"/><path d="M19.364 5.636a9 9 0 0 1 1.889 9.96"/><path d="m2 2 20 20"/><path d="m7 7-.587.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298V11"/><path d="M9.828 4.172A.686.686 0 0 1 11 4.657v.686"/></svg>
|
||||
|
After Width: | Height: | Size: 527 B |
1
assets/icons/audio_on.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-volume-2"><path d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"/><path d="M16 9a5 5 0 0 1 0 6"/><path d="M19.364 18.364a9 9 0 0 0 0-12.728"/></svg>
|
||||
|
After Width: | Height: | Size: 475 B |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 238 B After Width: | Height: | Size: 238 B |
|
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 345 B |
|
Before Width: | Height: | Size: 443 B After Width: | Height: | Size: 443 B |
6
assets/icons/file_doc.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 11V11.8374C13 11.9431 12.9665 12.046 12.9044 12.1315L12.1498 13.1691C12.0557 13.2985 11.9054 13.375 11.7454 13.375H4.25461C4.09464 13.375 3.94433 13.2985 3.85024 13.1691L3.09563 12.1315C3.03348 12.046 3 11.9431 3 11.8374V3" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M3 13V11L8 12H13V13H3Z" fill="black"/>
|
||||
<path d="M6.63246 3.04418C7.44914 3.31641 8 4.08069 8 4.94155V11.7306C8 12.0924 7.62757 12.3345 7.29693 12.1875L3.79693 10.632C3.61637 10.5518 3.5 10.3727 3.5 10.1751V2.69374C3.5 2.35246 3.83435 2.11148 4.15811 2.2194L6.63246 3.04418Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 3C8.67157 3 8 3.67157 8 4.5V13C8 12.1954 11.2366 12.0382 12.5017 12.0075C12.7778 12.0008 13 11.7761 13 11.5V3.5C13 3.22386 12.7761 3 12.5 3H9.5Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1009 B |
5
assets/icons/file_generic.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="M3 5H11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M3 8H13" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M3 11H9" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 334 B |
6
assets/icons/file_git.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 13C6.10457 13 7 12.1046 7 11C7 9.89543 6.10457 9 5 9C3.89543 9 3 9.89543 3 11C3 12.1046 3.89543 13 5 13Z" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M11 7C12.1046 7 13 6.10457 13 5C13 3.89543 12.1046 3 11 3C9.89543 3 9 3.89543 9 5C9 6.10457 9.89543 7 11 7Z" fill="black" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M4.625 3.625V8.375" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M11 7C11 9.20914 9.20914 11 7 11" stroke="black" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 591 B |
4
assets/icons/file_lock.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="M5 5C5 3.89543 5.89543 3 7 3H9C10.1046 3 11 3.89543 11 5V6H5V5Z" stroke="black" stroke-width="1.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.25 6.5C3.25 5.80964 3.80964 5.25 4.5 5.25H11.5C12.1904 5.25 12.75 5.80964 12.75 6.5V12.5C12.75 13.1904 12.1904 13.75 11.5 13.75H4.5C3.80964 13.75 3.25 13.1904 3.25 12.5V6.5ZM8.75 9.66146C8.90559 9.48517 9 9.25361 9 9C9 8.44772 8.55228 8 8 8C7.44772 8 7 8.44772 7 9C7 9.25361 7.09441 9.48517 7.25 9.66146V11C7.25 11.4142 7.58579 11.75 8 11.75C8.41421 11.75 8.75 11.4142 8.75 11V9.66146Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 667 B |
4
assets/icons/file_rust.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="M5.27935 10.9821C5.32063 10.4038 4.9204 9.89049 4.35998 9.80276L3.60081 9.68387C3.37979 9.64945 3.20167 9.48001 3.15225 9.25614L3.01378 8.63511C2.96382 8.41235 3.05233 8.1807 3.23696 8.05125L3.8631 7.61242C4.33337 7.28297 4.47456 6.6369 4.18621 6.13364L3.79467 5.45092C3.68118 5.25261 3.69801 5.00374 3.83757 4.82321L4.22314 4.32436C4.3627 4.14438 4.59621 4.06994 4.81071 4.13772L5.57531 4.37769C6.11944 4.54879 6.70048 4.26159 6.90683 3.71886L7.1811 2.99782C7.26255 2.78395 7.46345 2.64285 7.68772 2.6423L8.31007 2.64063C8.53434 2.64007 8.73579 2.78006 8.81834 2.99337L9.09965 3.72275C9.30821 4.26214 9.88655 4.54712 10.429 4.37714L11.1632 4.14716C11.3772 4.07994 11.6096 4.15382 11.7492 4.3327L12.1374 4.83099" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.76988 11.5933C8.76988 11.6595 8.8236 11.7133 8.88988 11.7133H8.97588C9.32602 11.7133 9.60988 11.9971 9.60988 12.3472C9.60988 12.6974 9.32602 12.9812 8.97588 12.9812H7.05587C6.70573 12.9812 6.42188 12.6974 6.42188 12.3472C6.42188 11.9971 6.70573 11.7133 7.05587 11.7133H7.14188C7.20815 11.7133 7.26188 11.6595 7.26188 11.5933V7.66925C7.26188 7.60298 7.20815 7.54925 7.14188 7.54925H7.05588C6.70573 7.54925 6.42188 7.2654 6.42188 6.91525C6.42188 6.5651 6.70573 6.28125 7.05588 6.28125H9.89988C11.0518 6.28125 12.8619 6.71487 12.8619 8.15185C12.8619 8.67078 12.7284 9.10362 12.4642 9.45348C12.1981 9.79765 11.8458 10.0564 11.4056 10.2293C11.3782 10.2401 11.3673 10.273 11.3829 10.298L12.2163 11.6342C12.247 11.6834 12.3008 11.7133 12.3588 11.7133H12.7319C13.082 11.7133 13.3659 11.9971 13.3659 12.3472C13.3659 12.6974 13.082 12.9812 12.7319 12.9812H11.5637C11.4955 12.9812 11.432 12.9465 11.3952 12.889L9.96523 10.6541C9.92847 10.5966 9.86495 10.5618 9.79675 10.5618H8.96988C8.85942 10.5618 8.76988 10.6514 8.76988 10.7619V11.5933ZM9.61188 7.54925C10.0296 7.54925 11.125 7.54925 11.2339 8.18785C11.2975 8.56123 11.1181 8.86557 10.8812 9.07715C10.6423 9.29046 10.2053 9.38985 9.58788 9.38985H8.86988C8.81465 9.38985 8.76988 9.34508 8.76988 9.28985V7.64925C8.76988 7.59402 8.81465 7.54925 8.86988 7.54925H9.61188Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
5
assets/icons/file_toml.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="M6 6H10" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M8 6V11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M5 3H3.5C3.22386 3 3 3.22386 3 3.5V12.5C3 12.7761 3.22386 13 3.5 13H5M11 3H12.5C12.7761 3 13 3.22386 13 3.5V12.5C13 12.7761 12.7761 13 12.5 13H11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 462 B |
3
assets/icons/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="M8.26046 3.97337C8.3527 4.17617 8.4795 4.47151 8.57375 4.69341C8.65258 4.87898 8.83437 4.99999 9.03599 4.99999H12.5C12.7761 4.99999 13 5.22385 13 5.49999V12.125C13 12.4011 12.7761 12.625 12.5 12.625H3.5C3.22386 12.625 3 12.4011 3 12.125V3.86932C3 3.59318 3.22386 3.36932 3.5 3.36932H7.34219C7.74141 3.36932 8.09483 3.60924 8.26046 3.97337Z" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 512 B |
4
assets/icons/folder_open.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.42782 7.2487C4.43495 6.97194 4.65009 6.75 4.91441 6.75H13.5293C13.7935 6.75 14.007 6.97194 13.9998 7.2487C13.9628 8.6885 13.7533 12.75 12.5721 12.75H3.375C4.55631 12.75 4.3907 8.6885 4.42782 7.2487Z" fill="black" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M5.19598 12.625H3.66515C3.42618 12.625 3.22289 12.4453 3.18626 12.2017L1.94333 3.93602C1.89776 3.63295 2.12496 3.35938 2.42223 3.35938H5.78585C6.11241 3.35938 6.41702 3.52903 6.59618 3.81071L6.94517 4.35938H9.92811C10.4007 4.35938 10.8044 4.71102 10.8836 5.1917L11.1251 6.65624" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 759 B |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1 +1,4 @@
|
||||
<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-pin"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/></svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 10V13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.71429 7.088C6.7142 7.26164 6.66963 7.43181 6.58559 7.57938C6.50156 7.72695 6.38138 7.84606 6.23857 7.92333L5.47571 8.34333C5.33291 8.4206 5.21273 8.53972 5.12869 8.68729C5.04465 8.83486 5.00008 9.00503 5 9.17867V9.53333C5 9.6571 5.04515 9.7758 5.12553 9.86332C5.2059 9.95083 5.31491 10 5.42857 10H10.5714C10.6851 10 10.7941 9.95083 10.8745 9.86332C10.9548 9.7758 11 9.6571 11 9.53333V9.17867C10.9999 9.00503 10.9553 8.83486 10.8713 8.68729C10.7873 8.53972 10.6671 8.4206 10.5243 8.34333L9.76143 7.92333C9.61862 7.84606 9.49844 7.72695 9.41441 7.57938C9.33037 7.43181 9.2858 7.26164 9.28571 7.088V5.33333C9.28571 5.20957 9.33087 5.09087 9.41124 5.00335C9.49161 4.91583 9.60062 4.86667 9.71429 4.86667C9.94161 4.86667 10.1596 4.76833 10.3204 4.5933C10.4811 4.41827 10.5714 4.18087 10.5714 3.93333C10.5714 3.6858 10.4811 3.4484 10.3204 3.27337C10.1596 3.09833 9.94161 3 9.71429 3H6.28571C6.05839 3 5.84037 3.09833 5.67962 3.27337C5.51888 3.4484 5.42857 3.6858 5.42857 3.93333C5.42857 4.18087 5.51888 4.41827 5.67962 4.5933C5.84037 4.76833 6.05839 4.86667 6.28571 4.86667C6.39938 4.86667 6.50839 4.91583 6.58876 5.00335C6.66913 5.09087 6.71429 5.20957 6.71429 5.33333V7.088Z" fill="black" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 447 B After Width: | Height: | Size: 1.5 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 10V13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.71429 7.088C6.7142 7.26164 6.66963 7.43181 6.58559 7.57938C6.50156 7.72695 6.38138 7.84606 6.23857 7.92333L5.47571 8.34333C5.33291 8.4206 5.21273 8.53972 5.12869 8.68729C5.04465 8.83486 5.00008 9.00503 5 9.17867V9.53333C5 9.6571 5.04515 9.7758 5.12553 9.86332C5.2059 9.95083 5.31491 10 5.42857 10H10.5714C10.6851 10 10.7941 9.95083 10.8745 9.86332C10.9548 9.7758 11 9.6571 11 9.53333V9.17867C10.9999 9.00503 10.9553 8.83486 10.8713 8.68729C10.7873 8.53972 10.6671 8.4206 10.5243 8.34333L9.76143 7.92333C9.61862 7.84606 9.49844 7.72695 9.41441 7.57938C9.33037 7.43181 9.2858 7.26164 9.28571 7.088V5.33333C9.28571 5.20957 9.33087 5.09087 9.41124 5.00335C9.49161 4.91583 9.60062 4.86667 9.71429 4.86667C9.94161 4.86667 10.1596 4.76833 10.3204 4.5933C10.4811 4.41827 10.5714 4.18087 10.5714 3.93333C10.5714 3.6858 10.4811 3.4484 10.3204 3.27337C10.1596 3.09833 9.94161 3 9.71429 3H6.28571C6.05839 3 5.84037 3.09833 5.67962 3.27337C5.51888 3.4484 5.42857 3.6858 5.42857 3.93333C5.42857 4.18087 5.51888 4.41827 5.67962 4.5933C5.84037 4.76833 6.05839 4.86667 6.28571 4.86667C6.39938 4.86667 6.50839 4.91583 6.58876 5.00335C6.66913 5.09087 6.71429 5.20957 6.71429 5.33333V7.088Z" fill="black" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 677 B After Width: | Height: | Size: 677 B |
4
assets/icons/settings.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.16089 10.2476L3.99598 10.3784C4.61244 10.4749 5.05269 11.0395 5.00728 11.6755L4.94576 12.5377C4.92784 12.789 5.06165 13.0255 5.28326 13.1348L5.90091 13.4391C6.12253 13.5485 6.38717 13.5075 6.56817 13.3371L7.1888 12.7505C7.64641 12.3178 8.35245 12.3178 8.81059 12.7505L9.43121 13.3371C9.61222 13.5081 9.87629 13.5485 10.0985 13.4391L10.7173 13.1341C10.9384 13.0255 11.0716 12.7895 11.0537 12.539L10.9921 11.6755C10.9467 11.0395 11.3869 10.4749 12.0033 10.3784L12.8385 10.2476C13.0817 10.2097 13.2776 10.0233 13.3325 9.77768L13.4848 9.09455C13.5398 8.8489 13.4425 8.59408 13.2393 8.45229L12.5422 7.96404C12.0279 7.60355 11.8708 6.89963 12.1814 6.34659L12.6025 5.59745C12.7249 5.3793 12.7047 5.10616 12.5511 4.9094L12.1241 4.36128C11.9706 4.16451 11.7149 4.08325 11.4795 4.15719L10.6719 4.41016C10.0752 4.59714 9.43903 4.28367 9.20962 3.69035L8.90017 2.88803C8.80937 2.65339 8.58777 2.4994 8.34108 2.5L7.65649 2.50184C7.40979 2.50244 7.1888 2.65766 7.09921 2.89291L6.79751 3.68607C6.57053 4.28307 5.93138 4.59898 5.33284 4.41077L4.49178 4.1468C4.25583 4.07225 3.99897 4.15413 3.84545 4.35212L3.42133 4.90084C3.26781 5.09943 3.2493 5.37319 3.37414 5.59133L3.80483 6.34232C4.12201 6.89591 3.96671 7.60659 3.44941 7.96897L2.76065 8.45169C2.55756 8.59408 2.4602 8.84891 2.51516 9.09393L2.66747 9.77708C2.72184 10.0233 2.91777 10.2097 3.16089 10.2476Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.41432 6.83576C8.63332 6.05481 7.36676 6.05476 6.58575 6.83571C5.8048 7.61672 5.80476 8.88327 6.58571 9.66427C7.36671 10.4452 8.63326 10.4452 9.41426 9.66432C10.1952 8.88332 10.1952 7.61676 9.41432 6.83576Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 450 B |
6
assets/icons/sliders_alt.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 4H8" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 10L11 10" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="4" cy="10" r="1.875" stroke="black" stroke-width="1.75"/>
|
||||
<circle cx="10" cy="4" r="1.875" stroke="black" stroke-width="1.75"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
11
assets/icons/sliders_vertical.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.6665 14V9.33333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3.6665 6.66667V2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 14V8" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 5.33333V2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.3335 14V10.6667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.3335 8V2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2.3335 9.33333H5.00016" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.6665 5.33334H9.33317" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 10.6667H13.6667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,8 +0,0 @@
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M7.72361 1.05279C7.893 1.13749 8 1.31062 8 1.5V13.5C8 13.6894 7.893 13.8625 7.72361 13.9472C7.55421 14.0319 7.35151 14.0136 7.2 13.9L3.33333 11H1.5C0.671573 11 0 10.3284 0 9.5V5.5C0 4.67158 0.671573 4 1.5 4H3.33333L7.2 1.1C7.35151 0.986371 7.55421 0.968093 7.72361 1.05279ZM7 2.5L3.8 4.9C3.71345 4.96491 3.60819 5 3.5 5H1.5C1.22386 5 1 5.22386 1 5.5V9.5C1 9.77614 1.22386 10 1.5 10H3.5C3.60819 10 3.71345 10.0351 3.8 10.1L7 12.5V2.5ZM14.8536 5.14645C15.0488 5.34171 15.0488 5.65829 14.8536 5.85355L13.2071 7.5L14.8536 9.14645C15.0488 9.34171 15.0488 9.65829 14.8536 9.85355C14.6583 10.0488 14.3417 10.0488 14.1464 9.85355L12.5 8.20711L10.8536 9.85355C10.6583 10.0488 10.3417 10.0488 10.1464 9.85355C9.95118 9.65829 9.95118 9.34171 10.1464 9.14645L11.7929 7.5L10.1464 5.85355C9.95118 5.65829 9.95118 5.34171 10.1464 5.14645C10.3417 4.95118 10.6583 4.95118 10.8536 5.14645L12.5 6.79289L14.1464 5.14645C14.3417 4.95118 14.6583 4.95118 14.8536 5.14645Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 610 B After Width: | Height: | Size: 610 B |
@@ -1 +1,4 @@
|
||||
<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-pin-off"><path d="M12 17v5"/><path d="M15 9.34V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H7.89"/><path d="m2 2 20 20"/><path d="M9 9v1.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h11"/></svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.28561 2.25C5.83777 2.25 5.42262 2.44429 5.12712 2.76607C4.8336 3.08568 4.67847 3.50652 4.67847 3.93333C4.67847 4.00474 4.68281 4.07598 4.69142 4.14661L10.0356 6.63864V5.58279C10.3579 5.51401 10.6495 5.3435 10.8728 5.10053C11.1662 4.78092 11.3213 4.36012 11.3213 3.93333C11.3213 3.50655 11.1661 3.08559 10.8726 2.76598C10.5771 2.44432 10.1621 2.25 9.71419 2.25H6.28561Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.70131 4.68303C1.87636 4.30763 2.3226 4.14521 2.698 4.32027L13.9361 9.5607C14.3115 9.73575 14.474 10.182 14.2989 10.5574C14.1239 10.9328 13.6776 11.0952 13.3022 10.9202L11.6006 10.1267C11.5536 10.2137 11.4958 10.2957 11.4272 10.3704C11.2121 10.6045 10.9057 10.75 10.5715 10.75H8.75007V13C8.75007 13.4142 8.41428 13.75 8.00007 13.75C7.58585 13.75 7.25007 13.4142 7.25007 13V10.75H5.42864C5.09445 10.75 4.78832 10.6049 4.57321 10.3706C4.36002 10.1385 4.25007 9.83637 4.25007 9.53334V9.17868C4.25021 8.87805 4.32715 8.57932 4.47703 8.31614C4.6266 8.05351 4.84546 7.83234 5.11609 7.68521L5.68873 7.36993L2.06407 5.67973C1.68867 5.50467 1.52625 5.05844 1.70131 4.68303Z" fill="black"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 947 B After Width: | Height: | Size: 947 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 785 B After Width: | Height: | Size: 785 B |
10
assets/images/zed_logo.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1957_1318)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 6C7.34315 6 6 7.34315 6 9V75H0V9C0 4.02944 4.02944 0 9 0H89.3787C93.3878 0 95.3955 4.84715 92.5607 7.68198L43.0551 57.1875H57V51H63V58.6875C63 61.1728 60.9853 63.1875 58.5 63.1875H37.0551L26.7426 73.5H73.5V36H79.5V73.5C79.5 76.8137 76.8137 79.5 73.5 79.5H20.7426L10.2426 90H87C88.6569 90 90 88.6569 90 87V21H96V87C96 91.9706 91.9706 96 87 96H6.62132C2.61224 96 0.604504 91.1529 3.43934 88.318L52.7574 39H39V45H33V37.5C33 35.0147 35.0147 33 37.5 33H58.7574L69.2574 22.5H22.5V60H16.5V22.5C16.5 19.1863 19.1863 16.5 22.5 16.5H75.2574L85.7574 6H9Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1957_1318">
|
||||
<rect width="96" height="96" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 861 B |
14
assets/images/zed_x_copilot.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="93" height="32" viewBox="0 0 93 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.03994 7.04962C8.00934 7.67635 7.30394 8.63219 7.30394 10.0149C7.30394 11.6908 7.72423 12.5893 8.20468 13.0744C8.68379 13.5581 9.40524 13.8149 10.4054 13.8149C11.815 13.8149 13.0291 13.5336 13.8802 12.9464C14.6756 12.3977 15.2708 11.5042 15.3438 9.96182C15.3991 8.79382 15.3678 8.01341 15.0568 7.45711C14.8094 7.01449 14.2326 6.47436 12.4901 6.27416C11.4684 6.15678 10.1114 6.39804 9.03994 7.04962ZM7.8731 5.13084C9.39145 4.2075 11.2531 3.87155 12.7464 4.04312C14.8843 4.28874 16.2844 5.05049 17.0171 6.36142C17.6863 7.55867 17.6384 8.98348 17.587 10.068C17.484 12.2439 16.5804 13.8118 15.1554 14.7949C13.7861 15.7396 12.0582 16.0606 10.4054 16.0606C9.04199 16.0606 7.65126 15.7069 6.60911 14.6547C5.5683 13.6038 5.05823 12.0408 5.05823 10.0149C5.05823 7.6958 6.31388 6.07903 7.8731 5.13084Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.983 18.2811C14.6595 18.2811 15.2079 18.8295 15.2079 19.506V22.16C15.2079 22.8365 14.6595 23.385 13.983 23.385C13.3065 23.385 12.758 22.8365 12.758 22.16V19.506C12.758 18.8295 13.3065 18.2811 13.983 18.2811Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25567 14.8903C5.48362 14.039 5.99219 12.9059 7.20735 12.5361L7.92069 14.8798L7.92376 14.8785C7.92375 14.8785 7.92323 14.8778 7.92069 14.8798C7.91365 14.8852 7.89134 14.9052 7.85879 14.951C7.78817 15.0505 7.70058 15.2311 7.62217 15.524C7.46355 16.1164 7.40615 16.9168 7.40617 17.7813V22.1675C7.43457 22.3571 7.45083 22.438 7.48149 22.5189C7.50698 22.5862 7.55955 22.695 7.72277 22.8617C8.0938 23.2406 8.94056 23.8264 10.9275 24.7081C12.5399 25.4236 13.2749 25.7456 13.9586 25.9166C14.6053 26.0784 15.2382 26.1115 16.6666 26.1115V28.5613C15.2536 28.5613 14.3395 28.5372 13.3641 28.2932C12.452 28.0651 11.5174 27.6502 10.0614 27.004C10.0193 26.9853 9.9768 26.9664 9.93383 26.9474C7.89761 26.0438 6.6867 25.3053 5.97233 24.5757C5.59285 24.1882 5.34579 23.7967 5.19056 23.387C5.05419 23.0271 5.00551 22.6875 4.9751 22.4754C4.97301 22.4608 4.971 22.4468 4.96906 22.4335L4.9563 22.3458V17.7814C4.95628 16.8606 5.01201 15.8003 5.25567 14.8903Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.98306 15.3589C4.62844 14.6695 5.39976 13.9884 5.99652 13.6348L7.24552 15.7424C6.93706 15.9252 6.34938 16.4159 5.77155 17.0332C5.21994 17.6224 4.85695 18.1377 4.73071 18.4254C4.70277 18.6674 4.71195 19.116 4.78977 19.648C4.87834 20.2536 5.01731 20.7033 5.10492 20.8628L5.19261 21.0224L5.23006 21.2007C5.28661 21.4698 5.55952 21.8651 6.30146 22.3907C6.88143 22.8015 7.56502 23.1703 8.29605 23.5648C8.45794 23.6521 8.62215 23.7407 8.78809 23.8313C9.5952 24.2156 10.848 24.7773 12.0191 25.2425C12.613 25.4784 13.1698 25.6831 13.6288 25.8268C13.8584 25.8987 14.0505 25.9512 14.2021 25.9847C14.3503 26.0175 14.4185 26.0227 14.4277 26.0234C14.4288 26.0234 14.4281 26.0234 14.4277 26.0234L14.4287 28.4733C13.9646 28.4733 13.3889 28.3188 12.8968 28.1648C12.3562 27.9955 11.7367 27.7664 11.1147 27.5193C9.86871 27.0244 8.54832 26.4314 7.70298 26.028L7.67249 26.0135L7.64284 25.9973C7.49779 25.918 7.34502 25.8357 7.18702 25.7506C6.4505 25.354 5.60004 24.896 4.88539 24.3898C4.08363 23.8219 3.18153 23.0135 2.87437 21.8785C2.61828 21.3365 2.4557 20.618 2.3657 20.0026C2.26537 19.3167 2.2169 18.4951 2.33888 17.8727L2.35434 17.7938L2.37996 17.7176C2.6522 16.9085 3.36219 16.0221 3.98306 15.3589Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.48529 21.264V17.1402H5.93516V21.264H3.48529Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.2932 7.04962C25.3238 7.67635 26.0292 8.63219 26.0292 10.0149C26.0292 11.6908 25.609 12.5893 25.1285 13.0744C24.6494 13.5581 23.9279 13.8149 22.9278 13.8149C21.5182 13.8149 20.3041 13.5336 19.453 12.9464C18.6576 12.3977 18.0624 11.5042 17.9894 9.96182C17.9341 8.79382 17.9654 8.01341 18.2764 7.45711C18.5238 7.01449 19.1006 6.47436 20.8431 6.27416C21.8648 6.15678 23.2218 6.39804 24.2932 7.04962ZM25.4601 5.13084C23.9417 4.2075 22.0801 3.87155 20.5868 4.04312C18.4489 4.28874 17.0488 5.05049 16.3161 6.36142C15.6469 7.55867 15.6948 8.98348 15.7462 10.068C15.8492 12.2439 16.7528 13.8118 18.1778 14.7949C19.5471 15.7396 21.275 16.0606 22.9278 16.0606C24.2912 16.0606 25.6819 15.7069 26.7241 14.6547C27.7649 13.6038 28.275 12.0408 28.275 10.0149C28.275 7.6958 27.0193 6.07903 25.4601 5.13084Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.3502 18.2811C18.6737 18.2811 18.1253 18.8295 18.1253 19.506V22.16C18.1253 22.8365 18.6737 23.385 19.3502 23.385C20.0267 23.385 20.5752 22.8365 20.5752 22.16V19.506C20.5752 18.8295 20.0267 18.2811 19.3502 18.2811Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.0775 14.8903C27.8496 14.039 27.341 12.9059 26.1259 12.5361L25.4125 14.8798L25.4095 14.8785C25.4095 14.8785 25.41 14.8778 25.4125 14.8798C25.4196 14.8852 25.4419 14.9052 25.4744 14.951C25.545 15.0505 25.6326 15.2311 25.711 15.524C25.8697 16.1164 25.9271 16.9168 25.927 17.7813V22.1675C25.8986 22.3571 25.8824 22.438 25.8517 22.5189C25.8262 22.5862 25.7737 22.695 25.6104 22.8617C25.2394 23.2406 24.3927 23.8264 22.4057 24.7081C20.7933 25.4236 20.0583 25.7456 19.3746 25.9166C18.7279 26.0784 18.0949 26.1115 16.6666 26.1115V28.5613C18.0796 28.5613 18.9937 28.5372 19.9691 28.2932C20.8812 28.0651 21.8158 27.6502 23.2718 27.004C23.3139 26.9853 23.3564 26.9664 23.3994 26.9474C25.4356 26.0438 26.6465 25.3053 27.3609 24.5757C27.7404 24.1882 27.9874 23.7967 28.1427 23.387C28.279 23.0271 28.3277 22.6875 28.3581 22.4754C28.3602 22.4608 28.3622 22.4468 28.3642 22.4335L28.3769 22.3458V17.7814C28.3769 16.8606 28.3212 15.8003 28.0775 14.8903Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.3501 15.3589C28.7048 14.6695 27.9334 13.9884 27.3367 13.6348L26.0877 15.7424C26.3961 15.9252 26.9838 16.4159 27.5616 17.0332C28.1133 17.6224 28.4763 18.1377 28.6025 18.4254C28.6304 18.6674 28.6213 19.116 28.5434 19.648C28.4549 20.2536 28.3159 20.7033 28.2283 20.8628L28.1406 21.0224L28.1031 21.2007C28.0466 21.4698 27.7737 21.8651 27.0317 22.3907C26.4518 22.8015 25.7682 23.1703 25.0372 23.5648C24.8753 23.6521 24.711 23.7407 24.5451 23.8313C23.738 24.2156 22.4852 24.7773 21.3141 25.2425C20.7202 25.4784 20.1634 25.6831 19.7044 25.8268C19.4748 25.8987 19.2827 25.9512 19.1311 25.9847C18.9829 26.0175 18.9147 26.0227 18.9055 26.0234C18.9051 26.0234 18.9044 26.0234 18.9055 26.0234L18.9045 28.4733C19.3686 28.4733 19.9443 28.3188 20.4364 28.1648C20.977 27.9955 21.5965 27.7664 22.2185 27.5193C23.4645 27.0244 24.7849 26.4314 25.6302 26.028L25.6607 26.0135L25.6904 25.9973C25.8354 25.918 25.9882 25.8357 26.1462 25.7506C26.8827 25.354 27.7332 24.896 28.4478 24.3898C29.2496 23.8219 30.1517 23.0135 30.4588 21.8785C30.7149 21.3365 30.8775 20.618 30.9675 20.0026C31.0678 19.3167 31.1163 18.4951 30.9943 17.8727L30.9789 17.7938L30.9532 17.7176C30.681 16.9085 29.971 16.0221 29.3501 15.3589Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.8479 21.264V17.1402H27.398V21.264H29.8479Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.6666 11C49.2189 11 49.6666 11.4477 49.6666 12V15H52.6666C53.2189 15 53.6666 15.4477 53.6666 16C53.6666 16.5523 53.2189 17 52.6666 17H49.6666V20C49.6666 20.5523 49.2189 21 48.6666 21C48.1143 21 47.6666 20.5523 47.6666 20V17H44.6666C44.1143 17 43.6666 16.5523 43.6666 16C43.6666 15.4477 44.1143 15 44.6666 15H47.6666V12C47.6666 11.4477 48.1143 11 48.6666 11Z" fill="black" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M67.1666 4.33329C66.7064 4.33329 66.3333 4.70639 66.3333 5.16663V23.5H64.6666V5.16663C64.6666 3.78591 65.7859 2.66663 67.1666 2.66663H89.494C90.6077 2.66663 91.1654 4.01306 90.3779 4.80051L76.6264 18.552H80.5V16.8333H82.1666V18.9687C82.1666 19.6591 81.607 20.2187 80.9166 20.2187H74.9597L72.0951 23.0833H85.0833V12.6666H86.75V23.0833C86.75 24.0038 86.0038 24.75 85.0833 24.75H70.4285L67.5118 27.6666H88.8333C89.2935 27.6666 89.6666 27.2935 89.6666 26.8333V8.49996H91.3333V26.8333C91.3333 28.214 90.214 29.3333 88.8333 29.3333H66.5059C65.3922 29.3333 64.8345 27.9869 65.622 27.1994L79.3214 13.5H75.5V15.1666H73.8333V13.0833C73.8333 12.3929 74.3929 11.8333 75.0833 11.8333H80.9881L83.9048 8.91663H70.9166V19.3333H69.25V8.91663C69.25 7.99615 69.9962 7.24996 70.9166 7.24996H85.5714L88.4881 4.33329H67.1666Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.2 KiB |
@@ -56,6 +56,7 @@
|
||||
"shift-tab": "editor::TabPrev",
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
// "ctrl-t": "editor::Transpose",
|
||||
"alt-q": "editor::Rewrap",
|
||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
||||
"shift-delete": "editor::Cut",
|
||||
@@ -165,6 +166,7 @@
|
||||
{
|
||||
"context": "AssistantPanel",
|
||||
"bindings": {
|
||||
"ctrl-k c": "assistant::CopyCode",
|
||||
"ctrl-g": "search::SelectNextMatch",
|
||||
"ctrl-shift-g": "search::SelectPrevMatch",
|
||||
"alt-m": "assistant::ToggleModelSelector",
|
||||
@@ -245,6 +247,8 @@
|
||||
"bindings": {
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-shift-pageup": "pane::SwapItemLeft",
|
||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||
"ctrl-w": "pane::CloseActiveItem",
|
||||
"ctrl-f4": "pane::CloseActiveItem",
|
||||
"alt-ctrl-t": "pane::CloseInactiveItems",
|
||||
@@ -262,7 +266,8 @@
|
||||
"alt-r": "search::ToggleRegex",
|
||||
"alt-ctrl-f": "project_search::ToggleFilters",
|
||||
"ctrl-alt-shift-r": "search::ToggleRegex",
|
||||
"ctrl-alt-shift-x": "search::ToggleRegex"
|
||||
"ctrl-alt-shift-x": "search::ToggleRegex",
|
||||
"ctrl-k shift-enter": "pane::TogglePinTab"
|
||||
}
|
||||
},
|
||||
// Bindings from VS Code
|
||||
@@ -552,6 +557,7 @@
|
||||
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
"shift-tab": "editor::TabPrev",
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
"ctrl-t": "editor::Transpose",
|
||||
"alt-q": "editor::Rewrap",
|
||||
"cmd-backspace": "editor::DeleteToBeginningOfLine",
|
||||
"cmd-delete": "editor::DeleteToEndOfLine",
|
||||
"alt-backspace": "editor::DeleteToPreviousWordStart",
|
||||
@@ -187,6 +188,7 @@
|
||||
{
|
||||
"context": "AssistantPanel",
|
||||
"bindings": {
|
||||
"cmd-k c": "assistant::CopyCode",
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-g": "search::SelectPrevMatch",
|
||||
"alt-m": "assistant::ToggleModelSelector",
|
||||
@@ -285,6 +287,8 @@
|
||||
"cmd-}": "pane::ActivateNextItem",
|
||||
"alt-cmd-left": "pane::ActivatePrevItem",
|
||||
"alt-cmd-right": "pane::ActivateNextItem",
|
||||
"ctrl-shift-pageup": "pane::SwapItemLeft",
|
||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||
"cmd-w": "pane::CloseActiveItem",
|
||||
"alt-cmd-t": "pane::CloseInactiveItems",
|
||||
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
|
||||
@@ -299,7 +303,8 @@
|
||||
"alt-cmd-c": "search::ToggleCaseSensitive",
|
||||
"alt-cmd-w": "search::ToggleWholeWord",
|
||||
"alt-cmd-f": "project_search::ToggleFilters",
|
||||
"alt-cmd-x": "search::ToggleRegex"
|
||||
"alt-cmd-x": "search::ToggleRegex",
|
||||
"cmd-k shift-enter": "pane::TogglePinTab"
|
||||
}
|
||||
},
|
||||
// Bindings from VS Code
|
||||
@@ -562,8 +567,8 @@
|
||||
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
|
||||
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-cmd-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
"shift-g": "vim::EndOfDocument",
|
||||
"{": "vim::StartOfParagraph",
|
||||
"}": "vim::EndOfParagraph",
|
||||
"(": "vim::SentenceBackward",
|
||||
")": "vim::SentenceForward",
|
||||
"|": "vim::GoToColumn",
|
||||
// Word motions
|
||||
"w": "vim::NextWordStart",
|
||||
@@ -122,6 +124,7 @@
|
||||
"g i": "vim::InsertAtPrevious",
|
||||
"g ,": "vim::ChangeListNewer",
|
||||
"g ;": "vim::ChangeListOlder",
|
||||
"g q": "editor::Rewrap",
|
||||
"shift-h": "vim::WindowTop",
|
||||
"shift-m": "vim::WindowMiddle",
|
||||
"shift-l": "vim::WindowBottom",
|
||||
@@ -212,7 +215,7 @@
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"y": ["vim::PushOperator", "Yank"],
|
||||
"shift-y": "vim::YankToEndOfLine",
|
||||
"shift-y": "vim::YankLine",
|
||||
"i": "vim::InsertBefore",
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
"a": "vim::InsertAfter",
|
||||
@@ -324,6 +327,13 @@
|
||||
"ctrl-r": ["vim::PushOperator", "Register"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ShowCompletions",
|
||||
"ctrl-n": "editor::ShowCompletions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == replace",
|
||||
"bindings": {
|
||||
@@ -392,7 +402,9 @@
|
||||
"context": "vim_operator == d",
|
||||
"bindings": {
|
||||
"d": "vim::CurrentLine",
|
||||
"s": ["vim::PushOperator", "DeleteSurrounds"]
|
||||
"s": ["vim::PushOperator", "DeleteSurrounds"],
|
||||
"o": "editor::ToggleHunkDiff", // "d o"
|
||||
"p": "editor::RevertSelectedHunks" // "d p"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -482,6 +494,7 @@
|
||||
"v": "project_panel::OpenPermanent",
|
||||
"p": "project_panel::Open",
|
||||
"x": "project_panel::RevealInFileManager",
|
||||
"s": "project_panel::OpenWithSystem",
|
||||
"shift-g": "menu::SelectLast",
|
||||
"g g": "menu::SelectFirst",
|
||||
"-": "project_panel::SelectParent",
|
||||
|
||||
@@ -111,6 +111,18 @@
|
||||
"use_system_path_prompts": true,
|
||||
// Whether the cursor blinks in the editor.
|
||||
"cursor_blink": true,
|
||||
// Cursor shape for the default editor.
|
||||
// 1. A vertical bar
|
||||
// "bar"
|
||||
// 2. A block that surrounds the following character
|
||||
// "block"
|
||||
// 3. An underline that runs along the following character
|
||||
// "underscore"
|
||||
// 4. A box drawn around the following character
|
||||
// "hollow"
|
||||
//
|
||||
// Default: not set, defaults to "bar"
|
||||
"cursor_shape": null,
|
||||
// How to highlight the current line in the editor.
|
||||
//
|
||||
// 1. Don't highlight the current line:
|
||||
@@ -279,6 +291,13 @@
|
||||
"relative_line_numbers": false,
|
||||
// If 'search_wrap' is disabled, search result do not wrap around the end of the file.
|
||||
"search_wrap": true,
|
||||
// Search options to enable by default when opening new project and buffer searches.
|
||||
"search": {
|
||||
"whole_word": false,
|
||||
"case_sensitive": false,
|
||||
"include_ignored": false,
|
||||
"regex": false
|
||||
},
|
||||
// When to populate a new search's query based on the text under the cursor.
|
||||
// This setting can take the following three values:
|
||||
//
|
||||
@@ -299,6 +318,10 @@
|
||||
"show_parameter_hints": true,
|
||||
// Corresponds to null/None LSP hint type value.
|
||||
"show_other_hints": true,
|
||||
// Whether to show a background for inlay hints.
|
||||
//
|
||||
// If set to `true`, the background will use the `hint.background` color from the current theme.
|
||||
"show_background": false,
|
||||
// Time to wait after editing the buffer, before requesting the hints,
|
||||
// set to 0 to disable debouncing.
|
||||
"edit_debounce_ms": 700,
|
||||
@@ -691,7 +714,7 @@
|
||||
// to the current working directory. We recommend overriding this
|
||||
// in your project's settings, rather than globally.
|
||||
"directories": [".env", "env", ".venv", "venv"],
|
||||
// Can also be `csh`, `fish`, and `nushell`
|
||||
// Can also be `csh`, `fish`, `nushell` and `power_shell`
|
||||
"activate_script": "default"
|
||||
}
|
||||
},
|
||||
@@ -909,7 +932,8 @@
|
||||
},
|
||||
"openai": {
|
||||
"version": "1",
|
||||
"api_url": "https://api.openai.com/v1"
|
||||
"api_url": "https://api.openai.com/v1",
|
||||
"low_speed_timeout_in_seconds": 600
|
||||
}
|
||||
},
|
||||
// Zed's Prettier integration settings.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//
|
||||
// To see all of Zed's default settings without changing your
|
||||
// custom settings, run `zed: open default settings` from the
|
||||
// command palette
|
||||
// command palette (cmd-shift-p / ctrl-shift-p)
|
||||
{
|
||||
"ui_font_size": 16,
|
||||
"buffer_font_size": 16,
|
||||
|
||||
1
clippy.toml
Normal file
@@ -0,0 +1 @@
|
||||
allow-private-module-inception = true
|
||||
@@ -262,7 +262,7 @@ impl ActivityIndicator {
|
||||
if !failed.is_empty() {
|
||||
return Some(Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ExclamationTriangle)
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
@@ -280,7 +280,7 @@ impl ActivityIndicator {
|
||||
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
|
||||
return Some(Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ExclamationTriangle)
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
@@ -333,7 +333,7 @@ impl ActivityIndicator {
|
||||
}),
|
||||
AutoUpdateStatus::Errored => Some(Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ExclamationTriangle)
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
|
||||
@@ -16,7 +16,7 @@ use util::ResultExt as _;
|
||||
|
||||
pub use supported_countries::*;
|
||||
|
||||
pub const ANTHROPIC_API_URL: &'static str = "https://api.anthropic.com";
|
||||
pub const ANTHROPIC_API_URL: &str = "https://api.anthropic.com";
|
||||
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
@@ -339,14 +339,12 @@ pub async fn extract_tool_args_from_events(
|
||||
while let Some(event) = events.next().await {
|
||||
if let Event::ContentBlockStart {
|
||||
index,
|
||||
content_block,
|
||||
content_block: ResponseContent::ToolUse { name, .. },
|
||||
} = event?
|
||||
{
|
||||
if let ResponseContent::ToolUse { name, .. } = content_block {
|
||||
if name == tool_name {
|
||||
tool_use_index = Some(index);
|
||||
break;
|
||||
}
|
||||
if name == tool_name {
|
||||
tool_use_index = Some(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -610,9 +608,6 @@ impl ApiError {
|
||||
}
|
||||
|
||||
pub fn is_rate_limit_error(&self) -> bool {
|
||||
match self.error_type.as_str() {
|
||||
"rate_limit_error" => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self.error_type.as_str(), "rate_limit_error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use rust_embed::RustEmbed;
|
||||
#[folder = "../../assets"]
|
||||
#[include = "fonts/**/*"]
|
||||
#[include = "icons/**/*"]
|
||||
#[include = "images/**/*"]
|
||||
#[include = "themes/**/*"]
|
||||
#[exclude = "themes/src/*"]
|
||||
#[include = "sounds/**/*"]
|
||||
|
||||
@@ -65,6 +65,7 @@ proto.workspace = true
|
||||
regex.workspace = true
|
||||
release_channel.workspace = true
|
||||
rope.workspace = true
|
||||
rpc.workspace = true
|
||||
schemars.workspace = true
|
||||
search.workspace = true
|
||||
semantic_index.workspace = true
|
||||
@@ -93,9 +94,11 @@ editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
language_model = { workspace = true, features = ["test-support"] }
|
||||
languages = { workspace = true, features = ["test-support"] }
|
||||
log.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
text = { workspace = true, features = ["test-support"] }
|
||||
tree-sitter-md.workspace = true
|
||||
unindent.workspace = true
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
> Hello, I'm Nathan Sobo.
|
||||
I'm currently your sole creator and the leader of Zed Industries.
|
||||
Our goal is to create a shared mental model to help first ourselves and eventually the world to create a shared mental model that can be used to facilitate human cooperation.
|
||||
|
||||
< Hello, Nathan! Welcome back to Zed. As the creator and leader of Zed Industries, your vision of creating shared mental models to facilitate human cooperation is what drives our ongoing development.
|
||||
|
||||
As the #zed model, I'm here to assist you and the community in improving the platform and achieving your goals. Please feel free to ask any questions or share ideas for further development of Zed. Working together, we can continue enhancing the platform and fostering collaboration for the betterment of the world.
|
||||
|
||||
> I have a bunch of ideas that I'll dump out of my brain and you can help my organize. Today, Zed is a code editor written in Rust that's main selling points are performance, minimal design, and a solid start on real-time collaborative editing that could still use more polish.
|
||||
|
||||
We've decided that our next milestone will be open-sourcing Zed on the Zed platform.
|
||||
|
||||
< Nathan, it's exciting to hear that you have more ideas to enhance Zed! Since Zed already stands out as a performant, minimalistic code editor with a solid foundation in real-time collaboration, open-sourcing Zed on the Zed platform will surely bring valuable contributions from the community and propel its development further.
|
||||
|
||||
Let's organize your ideas and create a plan for your next milestone. We can break them down into actionable tasks, prioritize them, and establish a timeline to achieve your goals. Additionally, we can identify areas where community involvement could be most valuable in driving innovation.
|
||||
|
||||
Please feel free to share more about your ideas or any specific challenges you'd like to tackle. I'm here to help you turn these ideas into a cohesive and successful plan for Zed's future!
|
||||
|
||||
@nathan> #zed, can you describe the conversation UI as you currently understand it.
|
||||
|
||||
< Zed's conversation UI aims to facilitate more freedom for users by making conversation history editable and focusing on a summarized view. Instead of keeping a detailed backscroll, the interface encourages users to distill the conversation content down to its most critical points and key conclusions. This approach ensures that the vital information from the conversation is highlighted and easily accessible, while still maintaining a history that can be linked to when necessary.
|
||||
|
||||
Addressing @nate's concerns, this design choice aims to strike a balance between readability, usability, and information preservation. The summarized view maintains the essence of the conversation without being burdened by extensive backscroll. Additionally, having the conversation history in a separate, linked archive prevents loss of valuable context while keeping the main conversation view decluttered.
|
||||
|
||||
@nathan> What details around the conversation view are worth exploring?
|
||||
|
||||
< @nathan, there are several aspects of the conversation view that are worth exploring and refining:
|
||||
|
||||
1. Editing functionality: Ensuring that all participants can edit and refine the conversation summary effectively while preserving the original context.
|
||||
2. Permissions and access control: Determining how to manage editing rights, user roles, and visibility of the conversation summary and history.
|
||||
3. Navigation and linking: Implementing an intuitive system for navigating between the summarized conversation and linked history or reference materials.
|
||||
4. Visualization and layout: Designing the interface to clearly present the summarized information while providing a visually engaging and organized experience for participants.
|
||||
5. Threaded conversations and branching: Exploring ways to handle sub-conversations, supplementary discussions, or debates within the primary conversation view.
|
||||
|
||||
Considering these aspects will ensure our conversation view design is optimized for usability, collaboration, and accessibility in Zed.
|
||||
|
||||
@nathan> @nate, which of these would you like to explore?
|
||||
|
||||
@nate> 2 feels like it isn't important at the moment, we can explore that later. Let's start with 4, which I think will lead us to discussion 3 and 5.
|
||||
|
||||
#zed share your thoughts on the points we need to consider to design a layout and visualization for a conversation view between you (#zed) and multiple people, or between multiple people and multiple bots (you and other bots).
|
||||
|
||||
@nathan> Agreed. I'm interested in threading I think more than anything. Or 4 yeah. I think we need to scope the threading conversation. Also, asking #zed to propose the solution... not sure it will be that effective but it's worth a try...
|
||||
|
||||
#zed describe a hypothetical conversation UI that meets the description.
|
||||
|
||||
< @nate and @nathan, I understand that you'd like to focus on the layout and visualization for a conversation view that can accommodate multiple people and potentially multiple bots as well. Here's a proposal for a conversation UI that meets the requirements:
|
||||
|
||||
1. Main conversation area: Display the summarized conversation content in a clean and organized manner, ensuring readability and efficient use of space.
|
||||
|
||||
2. Participant avatars and labels: Include user avatars and/or labels to distinguish between users and bots visually, allowing easy identification of each participant's contributions.
|
||||
|
||||
3. Active threads indicator: Designate a clear indicator for ongoing threads, which will attract attention to active sub-conversations or related discussions in the conversation view.
|
||||
|
||||
4. Dynamic branching: Implement a branching visual structure to present threaded conversations, clearly distinguishing sub-conversations, and enabling users to navigate between them smoothly.
|
||||
|
||||
5. Multiple bot interaction support: Facilitate interactions between multiple bots through user-invoked prompts or bot-to-bot communication channels without disrupting the primary conversation flow.
|
||||
|
||||
6. Expandable conversation history: Offer a separate, expandable section or panel to access historical content without cluttering the main conversation view.
|
||||
|
||||
7. Linking and referencing: Implement a system for hyperlinking relevant information, resources, or previous conversations to the current discussion, making it easy for participants to follow and reference the shared context.
|
||||
|
||||
This hypothetical conversation UI aims to address the layout and visualization, threading, and multi-participant aspects of your requirements. Of course, it's just an initial proposal, and we can refine and iterate on it based on your feedback and specific needs.
|
||||
@@ -27,8 +27,8 @@ use context_servers::ContextServerRegistry;
|
||||
pub use context_store::*;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use fs::Fs;
|
||||
use gpui::Context as _;
|
||||
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||
use gpui::{impl_actions, Context as _};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
pub(crate) use inline_assistant::*;
|
||||
use language_model::{
|
||||
@@ -37,14 +37,15 @@ use language_model::{
|
||||
pub(crate) use model_selector::*;
|
||||
pub use prompts::PromptBuilder;
|
||||
use prompts::PromptLoadingParams;
|
||||
use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
|
||||
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{update_settings_file, Settings, SettingsStore};
|
||||
use slash_command::{
|
||||
context_server_command, default_command, diagnostics_command, docs_command, fetch_command,
|
||||
file_command, now_command, project_command, prompt_command, search_command, symbols_command,
|
||||
tab_command, terminal_command, workflow_command,
|
||||
auto_command, context_server_command, default_command, delta_command, diagnostics_command,
|
||||
docs_command, fetch_command, file_command, now_command, project_command, prompt_command,
|
||||
search_command, symbols_command, tab_command, terminal_command, workflow_command,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
pub(crate) use streaming_diff::*;
|
||||
use util::ResultExt;
|
||||
@@ -57,6 +58,7 @@ actions!(
|
||||
[
|
||||
Assist,
|
||||
Split,
|
||||
CopyCode,
|
||||
CycleMessageRole,
|
||||
QuoteSelection,
|
||||
InsertIntoEditor,
|
||||
@@ -70,6 +72,14 @@ actions!(
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize)]
|
||||
pub enum InsertDraggedFiles {
|
||||
ProjectPaths(Vec<PathBuf>),
|
||||
ExternalFiles(Vec<PathBuf>),
|
||||
}
|
||||
|
||||
impl_actions!(assistant, [InsertDraggedFiles]);
|
||||
|
||||
const DEFAULT_CONTEXT_LINES: usize = 50;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
@@ -201,12 +211,13 @@ pub fn init(
|
||||
let client = client.clone();
|
||||
async move {
|
||||
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
||||
let semantic_index = SemanticIndex::new(
|
||||
let semantic_index = SemanticDb::new(
|
||||
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
|
||||
Arc::new(embedding_provider),
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
cx.update(|cx| cx.set_global(semantic_index))
|
||||
}
|
||||
})
|
||||
@@ -355,7 +366,9 @@ fn update_active_language_model_from_settings(cx: &mut AppContext) {
|
||||
|
||||
fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut AppContext) {
|
||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||
|
||||
slash_command_registry.register_command(file_command::FileSlashCommand, true);
|
||||
slash_command_registry.register_command(delta_command::DeltaSlashCommand, true);
|
||||
slash_command_registry.register_command(symbols_command::OutlineSlashCommand, true);
|
||||
slash_command_registry.register_command(tab_command::TabSlashCommand, true);
|
||||
slash_command_registry.register_command(project_command::ProjectSlashCommand, true);
|
||||
@@ -373,6 +386,17 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
||||
}
|
||||
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
||||
|
||||
cx.observe_flag::<auto_command::AutoSlashCommandFeatureFlag, _>({
|
||||
let slash_command_registry = slash_command_registry.clone();
|
||||
move |is_enabled, _cx| {
|
||||
if is_enabled {
|
||||
// [#auto-staff-ship] TODO remove this when /auto is no longer staff-shipped
|
||||
slash_command_registry.register_command(auto_command::AutoCommand, true);
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
update_slash_commands_from_settings(cx);
|
||||
cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
|
||||
.detach();
|
||||
|
||||
@@ -160,12 +160,16 @@ impl AssistantSettingsContent {
|
||||
.filter_map(|model| match model {
|
||||
OpenAiModel::Custom {
|
||||
name,
|
||||
display_name,
|
||||
max_tokens,
|
||||
max_output_tokens,
|
||||
max_completion_tokens: None,
|
||||
} => Some(open_ai::AvailableModel {
|
||||
name,
|
||||
display_name,
|
||||
max_tokens,
|
||||
max_output_tokens,
|
||||
max_completion_tokens: None,
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
@@ -517,6 +521,7 @@ impl Settings for AssistantSettings {
|
||||
&mut settings.default_model,
|
||||
value.default_model.map(Into::into),
|
||||
);
|
||||
// merge(&mut settings.infer_context, value.infer_context); TODO re-enable this once we ship context inference
|
||||
}
|
||||
|
||||
Ok(settings)
|
||||
|
||||
@@ -17,28 +17,27 @@ use feature_flags::{FeatureFlag, FeatureFlagAppExt};
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use futures::{
|
||||
future::{self, Shared},
|
||||
stream::FuturesUnordered,
|
||||
FutureExt, StreamExt,
|
||||
};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context as _, EventEmitter, Image, Model, ModelContext,
|
||||
RenderImage, SharedString, Subscription, Task,
|
||||
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, RenderImage,
|
||||
SharedString, Subscription, Task,
|
||||
};
|
||||
|
||||
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
|
||||
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelRequestTool, MessageContent, Role, StopReason,
|
||||
LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUse, MessageContent, Role,
|
||||
StopReason,
|
||||
};
|
||||
use open_ai::Model as OpenAiModel;
|
||||
use paths::{context_images_dir, contexts_dir};
|
||||
use paths::contexts_dir;
|
||||
use project::Project;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
cmp::{self, max, Ordering},
|
||||
collections::hash_map,
|
||||
fmt::Debug,
|
||||
iter, mem,
|
||||
ops::Range,
|
||||
@@ -47,7 +46,7 @@ use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use telemetry_events::AssistantKind;
|
||||
use telemetry_events::{AssistantKind, AssistantPhase};
|
||||
use text::BufferSnapshot;
|
||||
use util::{post_inc, ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
@@ -163,6 +162,9 @@ impl ContextOperation {
|
||||
)?,
|
||||
icon: section.icon_name.parse()?,
|
||||
label: section.label.into(),
|
||||
metadata: section
|
||||
.metadata
|
||||
.and_then(|metadata| serde_json::from_str(&metadata).log_err()),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
@@ -243,6 +245,9 @@ impl ContextOperation {
|
||||
)),
|
||||
icon_name: icon_name.to_string(),
|
||||
label: section.label.to_string(),
|
||||
metadata: section.metadata.as_ref().and_then(|metadata| {
|
||||
serde_json::to_string(metadata).log_err()
|
||||
}),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
@@ -307,6 +312,10 @@ pub enum ContextEvent {
|
||||
expand_result: bool,
|
||||
},
|
||||
UsePendingTools,
|
||||
ToolFinished {
|
||||
tool_use_id: Arc<str>,
|
||||
output_range: Range<language::Anchor>,
|
||||
},
|
||||
Operation(ContextOperation),
|
||||
}
|
||||
|
||||
@@ -373,23 +382,8 @@ impl MessageMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MessageImage {
|
||||
image_id: u64,
|
||||
image: Shared<Task<Option<LanguageModelImage>>>,
|
||||
}
|
||||
|
||||
impl PartialEq for MessageImage {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.image_id == other.image_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for MessageImage {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Message {
|
||||
pub image_offsets: SmallVec<[(usize, MessageImage); 1]>,
|
||||
pub offset_range: Range<usize>,
|
||||
pub index_range: Range<usize>,
|
||||
pub anchor_range: Range<language::Anchor>,
|
||||
@@ -399,60 +393,43 @@ pub struct Message {
|
||||
pub cache: Option<MessageCacheMetadata>,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
fn to_request_message(&self, buffer: &Buffer) -> Option<LanguageModelRequestMessage> {
|
||||
let mut content = Vec::new();
|
||||
|
||||
let mut range_start = self.offset_range.start;
|
||||
for (image_offset, message_image) in self.image_offsets.iter() {
|
||||
if *image_offset != range_start {
|
||||
if let Some(text) = Self::collect_text_content(buffer, range_start..*image_offset) {
|
||||
content.push(text);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(image) = message_image.image.clone().now_or_never().flatten() {
|
||||
content.push(language_model::MessageContent::Image(image));
|
||||
}
|
||||
|
||||
range_start = *image_offset;
|
||||
}
|
||||
|
||||
if range_start != self.offset_range.end {
|
||||
if let Some(text) =
|
||||
Self::collect_text_content(buffer, range_start..self.offset_range.end)
|
||||
{
|
||||
content.push(text);
|
||||
}
|
||||
}
|
||||
|
||||
if content.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(LanguageModelRequestMessage {
|
||||
role: self.role,
|
||||
content,
|
||||
cache: self.cache.as_ref().map_or(false, |cache| cache.is_anchor),
|
||||
})
|
||||
}
|
||||
|
||||
fn collect_text_content(buffer: &Buffer, range: Range<usize>) -> Option<MessageContent> {
|
||||
let text: String = buffer.text_for_range(range.clone()).collect();
|
||||
if text.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(MessageContent::Text(text))
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Content {
|
||||
Image {
|
||||
anchor: language::Anchor,
|
||||
image_id: u64,
|
||||
render_image: Arc<RenderImage>,
|
||||
image: Shared<Task<Option<LanguageModelImage>>>,
|
||||
},
|
||||
ToolUse {
|
||||
range: Range<language::Anchor>,
|
||||
tool_use: LanguageModelToolUse,
|
||||
},
|
||||
ToolResult {
|
||||
range: Range<language::Anchor>,
|
||||
tool_use_id: Arc<str>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ImageAnchor {
|
||||
pub anchor: language::Anchor,
|
||||
pub image_id: u64,
|
||||
pub render_image: Arc<RenderImage>,
|
||||
pub image: Shared<Task<Option<LanguageModelImage>>>,
|
||||
impl Content {
|
||||
fn range(&self) -> Range<language::Anchor> {
|
||||
match self {
|
||||
Self::Image { anchor, .. } => *anchor..*anchor,
|
||||
Self::ToolUse { range, .. } | Self::ToolResult { range, .. } => range.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cmp(&self, other: &Self, buffer: &BufferSnapshot) -> Ordering {
|
||||
let self_range = self.range();
|
||||
let other_range = other.range();
|
||||
if self_range.end.cmp(&other_range.start, buffer).is_lt() {
|
||||
Ordering::Less
|
||||
} else if self_range.start.cmp(&other_range.end, buffer).is_gt() {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PendingCompletion {
|
||||
@@ -496,8 +473,7 @@ pub struct Context {
|
||||
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||
pending_tool_uses_by_id: HashMap<Arc<str>, PendingToolUse>,
|
||||
message_anchors: Vec<MessageAnchor>,
|
||||
images: HashMap<u64, (Arc<RenderImage>, Shared<Task<Option<LanguageModelImage>>>)>,
|
||||
image_anchors: Vec<ImageAnchor>,
|
||||
contents: Vec<Content>,
|
||||
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
summary: Option<ContextSummary>,
|
||||
pending_summary: Task<Option<()>>,
|
||||
@@ -591,8 +567,7 @@ impl Context {
|
||||
pending_ops: Vec::new(),
|
||||
operations: Vec::new(),
|
||||
message_anchors: Default::default(),
|
||||
image_anchors: Default::default(),
|
||||
images: Default::default(),
|
||||
contents: Default::default(),
|
||||
messages_metadata: Default::default(),
|
||||
pending_slash_commands: Vec::new(),
|
||||
finished_slash_commands: HashSet::default(),
|
||||
@@ -655,11 +630,6 @@ impl Context {
|
||||
id: message.id,
|
||||
start: message.offset_range.start,
|
||||
metadata: self.messages_metadata[&message.id].clone(),
|
||||
image_offsets: message
|
||||
.image_offsets
|
||||
.iter()
|
||||
.map(|image_offset| (image_offset.0, image_offset.1.image_id))
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
summary: self
|
||||
@@ -671,12 +641,13 @@ impl Context {
|
||||
.slash_command_output_sections
|
||||
.iter()
|
||||
.filter_map(|section| {
|
||||
let range = section.range.to_offset(buffer);
|
||||
if section.range.start.is_valid(buffer) && !range.is_empty() {
|
||||
if section.is_valid(buffer) {
|
||||
let range = section.range.to_offset(buffer);
|
||||
Some(assistant_slash_command::SlashCommandOutputSection {
|
||||
range,
|
||||
icon: section.icon,
|
||||
label: section.label.clone(),
|
||||
metadata: section.metadata.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -696,7 +667,7 @@ impl Context {
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let id = saved_context.id.clone().unwrap_or_else(|| ContextId::new());
|
||||
let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
|
||||
let mut this = Self::new(
|
||||
id,
|
||||
ReplicaId::default(),
|
||||
@@ -1033,14 +1004,14 @@ impl Context {
|
||||
fn handle_buffer_event(
|
||||
&mut self,
|
||||
_: Model<Buffer>,
|
||||
event: &language::Event,
|
||||
event: &language::BufferEvent,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
language::Event::Operation(operation) => cx.emit(ContextEvent::Operation(
|
||||
language::BufferEvent::Operation(operation) => cx.emit(ContextEvent::Operation(
|
||||
ContextOperation::BufferOperation(operation.clone()),
|
||||
)),
|
||||
language::Event::Edited => {
|
||||
language::BufferEvent::Edited => {
|
||||
self.count_remaining_tokens(cx);
|
||||
self.reparse(cx);
|
||||
// Use `inclusive = true` to invalidate a step when an edit occurs
|
||||
@@ -1861,6 +1832,7 @@ impl Context {
|
||||
..buffer.anchor_before(start + section.range.end),
|
||||
icon: section.icon,
|
||||
label: section.label,
|
||||
metadata: section.metadata,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
||||
@@ -1923,28 +1895,51 @@ impl Context {
|
||||
|
||||
pub fn insert_tool_output(
|
||||
&mut self,
|
||||
tool_id: Arc<str>,
|
||||
tool_use_id: Arc<str>,
|
||||
output: Task<Result<String>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let insert_output_task = cx.spawn(|this, mut cx| {
|
||||
let tool_id = tool_id.clone();
|
||||
let tool_use_id = tool_use_id.clone();
|
||||
async move {
|
||||
let output = output.await;
|
||||
this.update(&mut cx, |this, cx| match output {
|
||||
Ok(mut output) => {
|
||||
if !output.ends_with('\n') {
|
||||
output.push('\n');
|
||||
const NEWLINE: char = '\n';
|
||||
|
||||
if !output.ends_with(NEWLINE) {
|
||||
output.push(NEWLINE);
|
||||
}
|
||||
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
let buffer_end = buffer.len().to_offset(buffer);
|
||||
let anchor_range = this.buffer.update(cx, |buffer, cx| {
|
||||
let insert_start = buffer.len().to_offset(buffer);
|
||||
let insert_end = insert_start;
|
||||
|
||||
buffer.edit([(buffer_end..buffer_end, output)], None, cx);
|
||||
let start = insert_start;
|
||||
let end = start + output.len() - NEWLINE.len_utf8();
|
||||
|
||||
buffer.edit([(insert_start..insert_end, output)], None, cx);
|
||||
|
||||
let output_range = buffer.anchor_after(start)..buffer.anchor_after(end);
|
||||
|
||||
output_range
|
||||
});
|
||||
|
||||
this.insert_content(
|
||||
Content::ToolResult {
|
||||
range: anchor_range.clone(),
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
|
||||
cx.emit(ContextEvent::ToolFinished {
|
||||
tool_use_id,
|
||||
output_range: anchor_range,
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
if let Some(tool_use) = this.pending_tool_uses_by_id.get_mut(&tool_id) {
|
||||
if let Some(tool_use) = this.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.status = PendingToolUseStatus::Error(err.to_string());
|
||||
}
|
||||
}
|
||||
@@ -1953,7 +1948,7 @@ impl Context {
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_id) {
|
||||
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.status = PendingToolUseStatus::Running {
|
||||
_task: insert_output_task.shared(),
|
||||
};
|
||||
@@ -2019,6 +2014,7 @@ impl Context {
|
||||
let stream_completion = async {
|
||||
let request_start = Instant::now();
|
||||
let mut events = stream.await?;
|
||||
let mut stop_reason = StopReason::EndTurn;
|
||||
|
||||
while let Some(event) = events.next().await {
|
||||
if response_latency.is_none() {
|
||||
@@ -2031,7 +2027,7 @@ impl Context {
|
||||
.message_anchors
|
||||
.iter()
|
||||
.position(|message| message.id == assistant_message_id)?;
|
||||
let event_to_emit = this.buffer.update(cx, |buffer, cx| {
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
let message_old_end_offset = this.message_anchors[message_ix + 1..]
|
||||
.iter()
|
||||
.find(|message| message.start.is_valid(buffer))
|
||||
@@ -2040,13 +2036,9 @@ impl Context {
|
||||
});
|
||||
|
||||
match event {
|
||||
LanguageModelCompletionEvent::Stop(reason) => match reason {
|
||||
StopReason::ToolUse => {
|
||||
return Some(ContextEvent::UsePendingTools);
|
||||
}
|
||||
StopReason::EndTurn => {}
|
||||
StopReason::MaxTokens => {}
|
||||
},
|
||||
LanguageModelCompletionEvent::Stop(reason) => {
|
||||
stop_reason = reason;
|
||||
}
|
||||
LanguageModelCompletionEvent::Text(chunk) => {
|
||||
buffer.edit(
|
||||
[(
|
||||
@@ -2097,14 +2089,9 @@ impl Context {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
});
|
||||
|
||||
cx.emit(ContextEvent::StreamedCompletion);
|
||||
if let Some(event) = event_to_emit {
|
||||
cx.emit(event);
|
||||
}
|
||||
|
||||
Some(())
|
||||
})?;
|
||||
@@ -2117,13 +2104,14 @@ impl Context {
|
||||
this.update_cache_status_for_completion(cx);
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
anyhow::Ok(stop_reason)
|
||||
};
|
||||
|
||||
let result = stream_completion.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let error_message = result
|
||||
.as_ref()
|
||||
.err()
|
||||
.map(|error| error.to_string().trim().to_string());
|
||||
|
||||
@@ -2146,11 +2134,22 @@ impl Context {
|
||||
telemetry.report_assistant_event(
|
||||
Some(this.id.0.clone()),
|
||||
AssistantKind::Panel,
|
||||
AssistantPhase::Response,
|
||||
model.telemetry_id(),
|
||||
response_latency,
|
||||
error_message,
|
||||
);
|
||||
}
|
||||
|
||||
if let Ok(stop_reason) = result {
|
||||
match stop_reason {
|
||||
StopReason::ToolUse => {
|
||||
cx.emit(ContextEvent::UsePendingTools);
|
||||
}
|
||||
StopReason::EndTurn => {}
|
||||
StopReason::MaxTokens => {}
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -2167,18 +2166,94 @@ impl Context {
|
||||
|
||||
pub fn to_completion_request(&self, cx: &AppContext) -> LanguageModelRequest {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let request_messages = self
|
||||
.messages(cx)
|
||||
.filter(|message| message.status == MessageStatus::Done)
|
||||
.filter_map(|message| message.to_request_message(&buffer))
|
||||
.collect();
|
||||
|
||||
LanguageModelRequest {
|
||||
messages: request_messages,
|
||||
let mut contents = self.contents(cx).peekable();
|
||||
|
||||
fn collect_text_content(buffer: &Buffer, range: Range<usize>) -> Option<String> {
|
||||
let text: String = buffer.text_for_range(range.clone()).collect();
|
||||
if text.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(text)
|
||||
}
|
||||
}
|
||||
|
||||
let mut completion_request = LanguageModelRequest {
|
||||
messages: Vec::new(),
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: 1.0,
|
||||
};
|
||||
for message in self.messages(cx) {
|
||||
if message.status != MessageStatus::Done {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut offset = message.offset_range.start;
|
||||
let mut request_message = LanguageModelRequestMessage {
|
||||
role: message.role,
|
||||
content: Vec::new(),
|
||||
cache: message
|
||||
.cache
|
||||
.as_ref()
|
||||
.map_or(false, |cache| cache.is_anchor),
|
||||
};
|
||||
|
||||
while let Some(content) = contents.peek() {
|
||||
if content
|
||||
.range()
|
||||
.end
|
||||
.cmp(&message.anchor_range.end, buffer)
|
||||
.is_lt()
|
||||
{
|
||||
let content = contents.next().unwrap();
|
||||
let range = content.range().to_offset(buffer);
|
||||
request_message.content.extend(
|
||||
collect_text_content(buffer, offset..range.start).map(MessageContent::Text),
|
||||
);
|
||||
|
||||
match content {
|
||||
Content::Image { image, .. } => {
|
||||
if let Some(image) = image.clone().now_or_never().flatten() {
|
||||
request_message
|
||||
.content
|
||||
.push(language_model::MessageContent::Image(image));
|
||||
}
|
||||
}
|
||||
Content::ToolUse { tool_use, .. } => {
|
||||
request_message
|
||||
.content
|
||||
.push(language_model::MessageContent::ToolUse(tool_use.clone()));
|
||||
}
|
||||
Content::ToolResult { tool_use_id, .. } => {
|
||||
request_message.content.push(
|
||||
language_model::MessageContent::ToolResult(
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.to_string(),
|
||||
is_error: false,
|
||||
content: collect_text_content(buffer, range.clone())
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
offset = range.end;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
request_message.content.extend(
|
||||
collect_text_content(buffer, offset..message.offset_range.end)
|
||||
.map(MessageContent::Text),
|
||||
);
|
||||
|
||||
completion_request.messages.push(request_message);
|
||||
}
|
||||
|
||||
completion_request
|
||||
}
|
||||
|
||||
pub fn cancel_last_assist(&mut self, cx: &mut ModelContext<Self>) -> bool {
|
||||
@@ -2305,53 +2380,31 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_image(&mut self, image: Image, cx: &mut ModelContext<Self>) -> Option<()> {
|
||||
if let hash_map::Entry::Vacant(entry) = self.images.entry(image.id()) {
|
||||
entry.insert((
|
||||
image.to_image_data(cx).log_err()?,
|
||||
LanguageModelImage::from_image(image, cx).shared(),
|
||||
));
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn insert_image_anchor(
|
||||
&mut self,
|
||||
image_id: u64,
|
||||
anchor: language::Anchor,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> bool {
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
|
||||
pub fn insert_content(&mut self, content: Content, cx: &mut ModelContext<Self>) {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let insertion_ix = match self
|
||||
.image_anchors
|
||||
.binary_search_by(|existing_anchor| anchor.cmp(&existing_anchor.anchor, buffer))
|
||||
.contents
|
||||
.binary_search_by(|probe| probe.cmp(&content, buffer))
|
||||
{
|
||||
Ok(ix) => ix,
|
||||
Ok(ix) => {
|
||||
self.contents.remove(ix);
|
||||
ix
|
||||
}
|
||||
Err(ix) => ix,
|
||||
};
|
||||
|
||||
if let Some((render_image, image)) = self.images.get(&image_id) {
|
||||
self.image_anchors.insert(
|
||||
insertion_ix,
|
||||
ImageAnchor {
|
||||
anchor,
|
||||
image_id,
|
||||
image: image.clone(),
|
||||
render_image: render_image.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
self.contents.insert(insertion_ix, content);
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
}
|
||||
|
||||
pub fn images<'a>(&'a self, _cx: &'a AppContext) -> impl 'a + Iterator<Item = ImageAnchor> {
|
||||
self.image_anchors.iter().cloned()
|
||||
pub fn contents<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Content> {
|
||||
let buffer = self.buffer.read(cx);
|
||||
self.contents
|
||||
.iter()
|
||||
.filter(|content| {
|
||||
let range = content.range();
|
||||
range.start.is_valid(buffer) && range.end.is_valid(buffer)
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn split_message(
|
||||
@@ -2514,22 +2567,14 @@ impl Context {
|
||||
return;
|
||||
}
|
||||
|
||||
let messages = self
|
||||
.messages(cx)
|
||||
.filter_map(|message| message.to_request_message(self.buffer.read(cx)))
|
||||
.chain(Some(LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![
|
||||
"Summarize the context into a short title without punctuation.".into(),
|
||||
],
|
||||
cache: false,
|
||||
}));
|
||||
let request = LanguageModelRequest {
|
||||
messages: messages.collect(),
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: 1.0,
|
||||
};
|
||||
let mut request = self.to_completion_request(cx);
|
||||
request.messages.push(LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![
|
||||
"Summarize the context into a short title without punctuation.".into(),
|
||||
],
|
||||
cache: false,
|
||||
});
|
||||
|
||||
self.pending_summary = cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
@@ -2629,10 +2674,8 @@ impl Context {
|
||||
cx: &'a AppContext,
|
||||
) -> impl 'a + Iterator<Item = Message> {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let messages = message_anchors.enumerate();
|
||||
let images = self.image_anchors.iter();
|
||||
|
||||
Self::messages_from_iters(buffer, &self.messages_metadata, messages, images)
|
||||
Self::messages_from_iters(buffer, &self.messages_metadata, message_anchors.enumerate())
|
||||
}
|
||||
|
||||
pub fn messages<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Message> {
|
||||
@@ -2643,10 +2686,8 @@ impl Context {
|
||||
buffer: &'a Buffer,
|
||||
metadata: &'a HashMap<MessageId, MessageMetadata>,
|
||||
messages: impl Iterator<Item = (usize, &'a MessageAnchor)> + 'a,
|
||||
images: impl Iterator<Item = &'a ImageAnchor> + 'a,
|
||||
) -> impl 'a + Iterator<Item = Message> {
|
||||
let mut messages = messages.peekable();
|
||||
let mut images = images.peekable();
|
||||
|
||||
iter::from_fn(move || {
|
||||
if let Some((start_ix, message_anchor)) = messages.next() {
|
||||
@@ -2667,22 +2708,6 @@ impl Context {
|
||||
let message_end_anchor = message_end.unwrap_or(language::Anchor::MAX);
|
||||
let message_end = message_end_anchor.to_offset(buffer);
|
||||
|
||||
let mut image_offsets = SmallVec::new();
|
||||
while let Some(image_anchor) = images.peek() {
|
||||
if image_anchor.anchor.cmp(&message_end_anchor, buffer).is_lt() {
|
||||
image_offsets.push((
|
||||
image_anchor.anchor.to_offset(buffer),
|
||||
MessageImage {
|
||||
image_id: image_anchor.image_id,
|
||||
image: image_anchor.image.clone(),
|
||||
},
|
||||
));
|
||||
images.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Some(Message {
|
||||
index_range: start_ix..end_ix,
|
||||
offset_range: message_start..message_end,
|
||||
@@ -2691,7 +2716,6 @@ impl Context {
|
||||
role: metadata.role,
|
||||
status: metadata.status.clone(),
|
||||
cache: metadata.cache.clone(),
|
||||
image_offsets,
|
||||
});
|
||||
}
|
||||
None
|
||||
@@ -2729,9 +2753,6 @@ impl Context {
|
||||
})?;
|
||||
|
||||
if let Some(summary) = summary {
|
||||
this.read_with(&cx, |this, cx| this.serialize_images(fs.clone(), cx))?
|
||||
.await;
|
||||
|
||||
let context = this.read_with(&cx, |this, cx| this.serialize(cx))?;
|
||||
let mut discriminant = 1;
|
||||
let mut new_path;
|
||||
@@ -2771,45 +2792,6 @@ impl Context {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn serialize_images(&self, fs: Arc<dyn Fs>, cx: &AppContext) -> Task<()> {
|
||||
let mut images_to_save = self
|
||||
.images
|
||||
.iter()
|
||||
.map(|(id, (_, llm_image))| {
|
||||
let fs = fs.clone();
|
||||
let llm_image = llm_image.clone();
|
||||
let id = *id;
|
||||
async move {
|
||||
if let Some(llm_image) = llm_image.await {
|
||||
let path: PathBuf =
|
||||
context_images_dir().join(&format!("{}.png.base64", id));
|
||||
if fs
|
||||
.metadata(path.as_path())
|
||||
.await
|
||||
.log_err()
|
||||
.flatten()
|
||||
.is_none()
|
||||
{
|
||||
fs.atomic_write(path, llm_image.source.to_string())
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
cx.background_executor().spawn(async move {
|
||||
if fs
|
||||
.create_dir(context_images_dir().as_ref())
|
||||
.await
|
||||
.log_err()
|
||||
.is_some()
|
||||
{
|
||||
while let Some(_) = images_to_save.next().await {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn custom_summary(&mut self, custom_summary: String, cx: &mut ModelContext<Self>) {
|
||||
let timestamp = self.next_timestamp();
|
||||
let summary = self.summary.get_or_insert(ContextSummary::default());
|
||||
@@ -2895,9 +2877,6 @@ pub struct SavedMessage {
|
||||
pub id: MessageId,
|
||||
pub start: usize,
|
||||
pub metadata: MessageMetadata,
|
||||
#[serde(default)]
|
||||
// This is defaulted for backwards compatibility with JSON files created before August 2024. We didn't always have this field.
|
||||
pub image_offsets: Vec<(usize, u64)>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -3007,6 +2986,7 @@ impl SavedContext {
|
||||
..buffer.anchor_before(section.range.end),
|
||||
icon: section.icon,
|
||||
label: section.label,
|
||||
metadata: section.metadata,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
@@ -3083,7 +3063,6 @@ impl SavedContextV0_3_0 {
|
||||
timestamp,
|
||||
cache: None,
|
||||
},
|
||||
image_offsets: Vec::new(),
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
|
||||
@@ -12,7 +12,7 @@ use assistant_slash_command::{
|
||||
use collections::HashSet;
|
||||
use fs::FakeFs;
|
||||
use gpui::{AppContext, Model, SharedString, Task, TestAppContext, WeakView};
|
||||
use language::{Buffer, LanguageRegistry, LspAdapterDelegate};
|
||||
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
|
||||
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
|
||||
use parking_lot::Mutex;
|
||||
use project::Project;
|
||||
@@ -1089,6 +1089,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
range: section_start..section_end,
|
||||
icon: ui::IconName::Ai,
|
||||
label: "section".into(),
|
||||
metadata: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1425,6 +1426,8 @@ impl SlashCommand for FakeSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
_cx: &mut WindowContext,
|
||||
|
||||
@@ -2,7 +2,6 @@ use crate::{
|
||||
prompts::PromptBuilder, Context, ContextEvent, ContextId, ContextOperation, ContextVersion,
|
||||
SavedContext, SavedContextMetadata,
|
||||
};
|
||||
use ::proto::AnyProtoClient;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
|
||||
use clock::ReplicaId;
|
||||
@@ -16,6 +15,7 @@ use language::LanguageRegistry;
|
||||
use paths::contexts_dir;
|
||||
use project::Project;
|
||||
use regex::Regex;
|
||||
use rpc::AnyProtoClient;
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
ffi::OsStr,
|
||||
@@ -390,7 +390,7 @@ impl ContextStore {
|
||||
context_proto
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(|op| ContextOperation::from_proto(op))
|
||||
.map(ContextOperation::from_proto)
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})
|
||||
.await?;
|
||||
@@ -527,7 +527,7 @@ impl ContextStore {
|
||||
context_proto
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(|op| ContextOperation::from_proto(op))
|
||||
.map(ContextOperation::from_proto)
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -134,8 +134,11 @@ impl InlineAssistant {
|
||||
})
|
||||
.detach();
|
||||
|
||||
let workspace = workspace.clone();
|
||||
let workspace = workspace.downgrade();
|
||||
cx.observe_global::<SettingsStore>(move |cx| {
|
||||
let Some(workspace) = workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
@@ -171,6 +174,18 @@ impl InlineAssistant {
|
||||
initial_prompt: Option<String>,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||
telemetry.report_assistant_event(
|
||||
None,
|
||||
telemetry_events::AssistantKind::Inline,
|
||||
telemetry_events::AssistantPhase::Invoked,
|
||||
model.telemetry_id(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||
|
||||
let mut selections = Vec::<Selection<Point>>::new();
|
||||
@@ -705,6 +720,22 @@ impl InlineAssistant {
|
||||
}
|
||||
|
||||
pub fn finish_assist(&mut self, assist_id: InlineAssistId, undo: bool, cx: &mut WindowContext) {
|
||||
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||
telemetry.report_assistant_event(
|
||||
None,
|
||||
telemetry_events::AssistantKind::Inline,
|
||||
if undo {
|
||||
telemetry_events::AssistantPhase::Rejected
|
||||
} else {
|
||||
telemetry_events::AssistantPhase::Accepted
|
||||
},
|
||||
model.telemetry_id(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(assist) = self.assists.get(&assist_id) {
|
||||
let assist_group_id = assist.group_id;
|
||||
if self.assist_groups[&assist_group_id].linked {
|
||||
@@ -1462,7 +1493,7 @@ impl Render for PromptEditor {
|
||||
.border_y_1()
|
||||
.border_color(cx.theme().status().info_border)
|
||||
.size_full()
|
||||
.py(cx.line_height() / 2.)
|
||||
.py(cx.line_height() / 2.5)
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
@@ -1475,7 +1506,7 @@ impl Render for PromptEditor {
|
||||
.child(
|
||||
ModelSelector::new(
|
||||
self.fs.clone(),
|
||||
IconButton::new("context", IconName::SlidersAlt)
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
@@ -1915,12 +1946,11 @@ impl PromptEditor {
|
||||
} else {
|
||||
cx.theme().colors().text
|
||||
},
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features.clone(),
|
||||
font_fallbacks: settings.ui_font.fallbacks.clone(),
|
||||
font_size: rems(0.875).into(),
|
||||
font_weight: settings.ui_font.weight,
|
||||
line_height: relative(1.3),
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: settings.buffer_font_size.into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
..Default::default()
|
||||
};
|
||||
EditorElement::new(
|
||||
@@ -2370,20 +2400,7 @@ impl Codegen {
|
||||
None
|
||||
};
|
||||
|
||||
// Higher Temperature increases the randomness of model outputs.
|
||||
// If Markdown or No Language is Known, increase the randomness for more creative output
|
||||
// If Code, decrease temperature to get more deterministic outputs
|
||||
let temperature = if let Some(language) = language_name.clone() {
|
||||
if language.as_ref() == "Markdown" {
|
||||
1.0
|
||||
} else {
|
||||
0.5
|
||||
}
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
let language_name = language_name.as_deref();
|
||||
let language_name = language_name.as_ref();
|
||||
let start = buffer.point_to_buffer_offset(edit_range.start);
|
||||
let end = buffer.point_to_buffer_offset(edit_range.end);
|
||||
let (buffer, range) = if let Some((start, end)) = start.zip(end) {
|
||||
@@ -2417,8 +2434,8 @@ impl Codegen {
|
||||
Ok(LanguageModelRequest {
|
||||
messages,
|
||||
tools: Vec::new(),
|
||||
stop: vec!["|END|>".to_string()],
|
||||
temperature,
|
||||
stop: Vec::new(),
|
||||
temperature: 1.,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2569,6 +2586,7 @@ impl Codegen {
|
||||
telemetry.report_assistant_event(
|
||||
None,
|
||||
telemetry_events::AssistantKind::Inline,
|
||||
telemetry_events::AssistantPhase::Response,
|
||||
model_telemetry_id,
|
||||
response_latency,
|
||||
error_message,
|
||||
@@ -3070,7 +3088,7 @@ mod tests {
|
||||
codegen.handle_stream(
|
||||
String::new(),
|
||||
range,
|
||||
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -3142,7 +3160,7 @@ mod tests {
|
||||
codegen.handle_stream(
|
||||
String::new(),
|
||||
range.clone(),
|
||||
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -3217,7 +3235,7 @@ mod tests {
|
||||
codegen.handle_stream(
|
||||
String::new(),
|
||||
range.clone(),
|
||||
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -3291,7 +3309,7 @@ mod tests {
|
||||
codegen.handle_stream(
|
||||
String::new(),
|
||||
range.clone(),
|
||||
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -3370,7 +3388,7 @@ mod tests {
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
)
|
||||
.with_indents_query(
|
||||
r#"
|
||||
|
||||
@@ -921,10 +921,8 @@ impl PromptLibrary {
|
||||
scrollbar_width: Pixels::ZERO,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
status: cx.theme().status().clone(),
|
||||
inlay_hints_style: HighlightStyle {
|
||||
color: Some(cx.theme().status().hint),
|
||||
..HighlightStyle::default()
|
||||
},
|
||||
inlay_hints_style:
|
||||
editor::make_inlay_hints_style(cx),
|
||||
suggestions_style: HighlightStyle {
|
||||
color: Some(cx.theme().status().predictive),
|
||||
..HighlightStyle::default()
|
||||
|
||||
@@ -4,7 +4,7 @@ use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use gpui::AssetSource;
|
||||
use handlebars::{Handlebars, RenderError};
|
||||
use language::BufferSnapshot;
|
||||
use language::{BufferSnapshot, LanguageName};
|
||||
use parking_lot::Mutex;
|
||||
use serde::Serialize;
|
||||
use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration};
|
||||
@@ -123,7 +123,7 @@ impl PromptBuilder {
|
||||
if params.fs.is_dir(parent_dir).await {
|
||||
let (mut changes, _watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await;
|
||||
while let Some(changed_paths) = changes.next().await {
|
||||
if changed_paths.iter().any(|p| p == &templates_dir) {
|
||||
if changed_paths.iter().any(|p| &p.path == &templates_dir) {
|
||||
let mut log_message = format!("Prompt template overrides directory detected at {}", templates_dir.display());
|
||||
if let Ok(target) = params.fs.read_link(&templates_dir).await {
|
||||
log_message.push_str(" -> ");
|
||||
@@ -162,18 +162,18 @@ impl PromptBuilder {
|
||||
let mut combined_changes = futures::stream::select(changes, parent_changes);
|
||||
|
||||
while let Some(changed_paths) = combined_changes.next().await {
|
||||
if changed_paths.iter().any(|p| p == &templates_dir) {
|
||||
if changed_paths.iter().any(|p| &p.path == &templates_dir) {
|
||||
if !params.fs.is_dir(&templates_dir).await {
|
||||
log::info!("Prompt template overrides directory removed. Restoring built-in prompt templates.");
|
||||
Self::register_built_in_templates(&mut handlebars.lock()).log_err();
|
||||
break;
|
||||
}
|
||||
}
|
||||
for changed_path in changed_paths {
|
||||
if changed_path.starts_with(&templates_dir) && changed_path.extension().map_or(false, |ext| ext == "hbs") {
|
||||
log::info!("Reloading prompt template override: {}", changed_path.display());
|
||||
if let Some(content) = params.fs.load(&changed_path).await.log_err() {
|
||||
let file_name = changed_path.file_stem().unwrap().to_string_lossy();
|
||||
for event in changed_paths {
|
||||
if event.path.starts_with(&templates_dir) && event.path.extension().map_or(false, |ext| ext == "hbs") {
|
||||
log::info!("Reloading prompt template override: {}", event.path.display());
|
||||
if let Some(content) = params.fs.load(&event.path).await.log_err() {
|
||||
let file_name = event.path.file_stem().unwrap().to_string_lossy();
|
||||
handlebars.lock().register_template_string(&file_name, content).log_err();
|
||||
}
|
||||
}
|
||||
@@ -204,11 +204,11 @@ impl PromptBuilder {
|
||||
pub fn generate_content_prompt(
|
||||
&self,
|
||||
user_prompt: String,
|
||||
language_name: Option<&str>,
|
||||
language_name: Option<&LanguageName>,
|
||||
buffer: BufferSnapshot,
|
||||
range: Range<usize>,
|
||||
) -> Result<String, RenderError> {
|
||||
let content_type = match language_name {
|
||||
let content_type = match language_name.as_ref().map(|l| l.0.as_ref()) {
|
||||
None | Some("Markdown" | "Plain Text") => "text",
|
||||
Some(_) => "code",
|
||||
};
|
||||
|
||||
@@ -19,8 +19,10 @@ use std::{
|
||||
use ui::ActiveTheme;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub mod auto_command;
|
||||
pub mod context_server_command;
|
||||
pub mod default_command;
|
||||
pub mod delta_command;
|
||||
pub mod diagnostics_command;
|
||||
pub mod docs_command;
|
||||
pub mod fetch_command;
|
||||
|
||||
362
crates/assistant/src/slash_command/auto_command.rs
Normal file
@@ -0,0 +1,362 @@
|
||||
use super::create_label_for_command;
|
||||
use super::{SlashCommand, SlashCommandOutput};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||
use feature_flags::FeatureFlag;
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
|
||||
use language::{CodeLabel, LspAdapterDelegate};
|
||||
use language_model::{
|
||||
LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use semantic_index::{FileSummary, SemanticDb};
|
||||
use smol::channel;
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
use ui::{BorrowAppContext, WindowContext};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct AutoSlashCommandFeatureFlag;
|
||||
|
||||
impl FeatureFlag for AutoSlashCommandFeatureFlag {
|
||||
const NAME: &'static str = "auto-slash-command";
|
||||
}
|
||||
|
||||
pub(crate) struct AutoCommand;
|
||||
|
||||
impl SlashCommand for AutoCommand {
|
||||
fn name(&self) -> String {
|
||||
"auto".into()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"Automatically infer what context to add, based on your prompt".into()
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
"Automatically Infer Context".into()
|
||||
}
|
||||
|
||||
fn label(&self, cx: &AppContext) -> CodeLabel {
|
||||
create_label_for_command("auto", &["--prompt"], cx)
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
// There's no autocomplete for a prompt, since it's arbitrary text.
|
||||
// However, we can use this opportunity to kick off a drain of the backlog.
|
||||
// That way, it can hopefully be done resummarizing by the time we've actually
|
||||
// typed out our prompt. This re-runs on every keystroke during autocomplete,
|
||||
// but in the future, we could instead do it only once, when /auto is first entered.
|
||||
let Some(workspace) = workspace.and_then(|ws| ws.upgrade()) else {
|
||||
log::warn!("workspace was dropped or unavailable during /auto autocomplete");
|
||||
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
};
|
||||
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let Some(project_index) =
|
||||
cx.update_global(|index: &mut SemanticDb, cx| index.project_index(project, cx))
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("No project indexer, cannot use /auto")));
|
||||
};
|
||||
|
||||
let cx: &mut AppContext = cx;
|
||||
|
||||
cx.spawn(|cx: gpui::AsyncAppContext| async move {
|
||||
let task = project_index.read_with(&cx, |project_index, cx| {
|
||||
project_index.flush_summary_backlogs(cx)
|
||||
})?;
|
||||
|
||||
cx.background_executor().spawn(task).await;
|
||||
|
||||
anyhow::Ok(Vec::new())
|
||||
})
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: language::BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<SlashCommandOutput>> {
|
||||
let Some(workspace) = workspace.upgrade() else {
|
||||
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
||||
};
|
||||
if arguments.is_empty() {
|
||||
return Task::ready(Err(anyhow!("missing prompt")));
|
||||
};
|
||||
let argument = arguments.join(" ");
|
||||
let original_prompt = argument.to_string();
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let Some(project_index) =
|
||||
cx.update_global(|index: &mut SemanticDb, cx| index.project_index(project, cx))
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("no project indexer")));
|
||||
};
|
||||
|
||||
let task = cx.spawn(|cx: gpui::AsyncWindowContext| async move {
|
||||
let summaries = project_index
|
||||
.read_with(&cx, |project_index, cx| project_index.all_summaries(cx))?
|
||||
.await?;
|
||||
|
||||
commands_for_summaries(&summaries, &original_prompt, &cx).await
|
||||
});
|
||||
|
||||
// As a convenience, append /auto's argument to the end of the prompt
|
||||
// so you don't have to write it again.
|
||||
let original_prompt = argument.to_string();
|
||||
|
||||
cx.background_executor().spawn(async move {
|
||||
let commands = task.await?;
|
||||
let mut prompt = String::new();
|
||||
|
||||
log::info!(
|
||||
"Translating this response into slash-commands: {:?}",
|
||||
commands
|
||||
);
|
||||
|
||||
for command in commands {
|
||||
prompt.push('/');
|
||||
prompt.push_str(&command.name);
|
||||
prompt.push(' ');
|
||||
prompt.push_str(&command.arg);
|
||||
prompt.push('\n');
|
||||
}
|
||||
|
||||
prompt.push('\n');
|
||||
prompt.push_str(&original_prompt);
|
||||
|
||||
Ok(SlashCommandOutput {
|
||||
text: prompt,
|
||||
sections: Vec::new(),
|
||||
run_commands_in_text: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const PROMPT_INSTRUCTIONS_BEFORE_SUMMARY: &str = include_str!("prompt_before_summary.txt");
|
||||
const PROMPT_INSTRUCTIONS_AFTER_SUMMARY: &str = include_str!("prompt_after_summary.txt");
|
||||
|
||||
fn summaries_prompt(summaries: &[FileSummary], original_prompt: &str) -> String {
|
||||
let json_summaries = serde_json::to_string(summaries).unwrap();
|
||||
|
||||
format!("{PROMPT_INSTRUCTIONS_BEFORE_SUMMARY}\n{json_summaries}\n{PROMPT_INSTRUCTIONS_AFTER_SUMMARY}\n{original_prompt}")
|
||||
}
|
||||
|
||||
/// The slash commands that the model is told about, and which we look for in the inference response.
|
||||
const SUPPORTED_SLASH_COMMANDS: &[&str] = &["search", "file"];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CommandToRun {
|
||||
name: String,
|
||||
arg: String,
|
||||
}
|
||||
|
||||
/// Given the pre-indexed file summaries for this project, as well as the original prompt
|
||||
/// string passed to `/auto`, get a list of slash commands to run, along with their arguments.
|
||||
///
|
||||
/// The prompt's output does not include the slashes (to reduce the chance that it makes a mistake),
|
||||
/// so taking one of these returned Strings and turning it into a real slash-command-with-argument
|
||||
/// involves prepending a slash to it.
|
||||
///
|
||||
/// This function will validate that each of the returned lines begins with one of SUPPORTED_SLASH_COMMANDS.
|
||||
/// Any other lines it encounters will be discarded, with a warning logged.
|
||||
async fn commands_for_summaries(
|
||||
summaries: &[FileSummary],
|
||||
original_prompt: &str,
|
||||
cx: &AsyncAppContext,
|
||||
) -> Result<Vec<CommandToRun>> {
|
||||
if summaries.is_empty() {
|
||||
log::warn!("Inferring no context because there were no summaries available.");
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
// Use the globally configured model to translate the summaries into slash-commands,
|
||||
// because Qwen2-7B-Instruct has not done a good job at that task.
|
||||
let Some(model) = cx.update(|cx| LanguageModelRegistry::read_global(cx).active_model())? else {
|
||||
log::warn!("Can't infer context because there's no active model.");
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
// Only go up to 90% of the actual max token count, to reduce chances of
|
||||
// exceeding the token count due to inaccuracies in the token counting heuristic.
|
||||
let max_token_count = (model.max_token_count() * 9) / 10;
|
||||
|
||||
// Rather than recursing (which would require this async function use a pinned box),
|
||||
// we use an explicit stack of arguments and answers for when we need to "recurse."
|
||||
let mut stack = vec![summaries];
|
||||
let mut final_response = Vec::new();
|
||||
let mut prompts = Vec::new();
|
||||
|
||||
// TODO We only need to create multiple Requests because we currently
|
||||
// don't have the ability to tell if a CompletionProvider::complete response
|
||||
// was a "too many tokens in this request" error. If we had that, then
|
||||
// we could try the request once, instead of having to make separate requests
|
||||
// to check the token count and then afterwards to run the actual prompt.
|
||||
let make_request = |prompt: String| LanguageModelRequest {
|
||||
messages: vec![LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![prompt.into()],
|
||||
// Nothing in here will benefit from caching
|
||||
cache: false,
|
||||
}],
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: 1.0,
|
||||
};
|
||||
|
||||
while let Some(current_summaries) = stack.pop() {
|
||||
// The split can result in one slice being empty and the other having one element.
|
||||
// Whenever that happens, skip the empty one.
|
||||
if current_summaries.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Inferring prompt context using {} file summaries",
|
||||
current_summaries.len()
|
||||
);
|
||||
|
||||
let prompt = summaries_prompt(¤t_summaries, original_prompt);
|
||||
let start = std::time::Instant::now();
|
||||
// Per OpenAI, 1 token ~= 4 chars in English (we go with 4.5 to overestimate a bit, because failed API requests cost a lot of perf)
|
||||
// Verifying this against an actual model.count_tokens() confirms that it's usually within ~5% of the correct answer, whereas
|
||||
// getting the correct answer from tiktoken takes hundreds of milliseconds (compared to this arithmetic being ~free).
|
||||
// source: https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them
|
||||
let token_estimate = prompt.len() * 2 / 9;
|
||||
let duration = start.elapsed();
|
||||
log::info!(
|
||||
"Time taken to count tokens for prompt of length {:?}B: {:?}",
|
||||
prompt.len(),
|
||||
duration
|
||||
);
|
||||
|
||||
if token_estimate < max_token_count {
|
||||
prompts.push(prompt);
|
||||
} else if current_summaries.len() == 1 {
|
||||
log::warn!("Inferring context for a single file's summary failed because the prompt's token length exceeded the model's token limit.");
|
||||
} else {
|
||||
log::info!(
|
||||
"Context inference using file summaries resulted in a prompt containing {token_estimate} tokens, which exceeded the model's max of {max_token_count}. Retrying as two separate prompts, each including half the number of summaries.",
|
||||
);
|
||||
let (left, right) = current_summaries.split_at(current_summaries.len() / 2);
|
||||
stack.push(right);
|
||||
stack.push(left);
|
||||
}
|
||||
}
|
||||
|
||||
let all_start = std::time::Instant::now();
|
||||
|
||||
let (tx, rx) = channel::bounded(1024);
|
||||
|
||||
let completion_streams = prompts
|
||||
.into_iter()
|
||||
.map(|prompt| {
|
||||
let request = make_request(prompt.clone());
|
||||
let model = model.clone();
|
||||
let tx = tx.clone();
|
||||
let stream = model.stream_completion(request, &cx);
|
||||
|
||||
(stream, tx)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
let futures = completion_streams
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, (stream, tx))| async move {
|
||||
let start = std::time::Instant::now();
|
||||
let events = stream.await?;
|
||||
log::info!("Time taken for awaiting /await chunk stream #{ix}: {:?}", start.elapsed());
|
||||
|
||||
let completion: String = events
|
||||
.filter_map(|event| async {
|
||||
if let Ok(LanguageModelCompletionEvent::Text(text)) = event {
|
||||
Some(text)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
log::info!("Time taken for all /auto chunks to come back for #{ix}: {:?}", start.elapsed());
|
||||
|
||||
for line in completion.split('\n') {
|
||||
if let Some(first_space) = line.find(' ') {
|
||||
let command = &line[..first_space].trim();
|
||||
let arg = &line[first_space..].trim();
|
||||
|
||||
tx.send(CommandToRun {
|
||||
name: command.to_string(),
|
||||
arg: arg.to_string(),
|
||||
})
|
||||
.await?;
|
||||
} else if !line.trim().is_empty() {
|
||||
// All slash-commands currently supported in context inference need a space for the argument.
|
||||
log::warn!(
|
||||
"Context inference returned a non-blank line that contained no spaces (meaning no argument for the slash command): {:?}",
|
||||
line
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let _ = futures::future::try_join_all(futures).await.log_err();
|
||||
|
||||
let duration = all_start.elapsed();
|
||||
eprintln!("All futures completed in {:?}", duration);
|
||||
})
|
||||
.await;
|
||||
|
||||
drop(tx); // Close the channel so that rx.collect() won't hang. This is safe because all futures have completed.
|
||||
let results = rx.collect::<Vec<_>>().await;
|
||||
eprintln!(
|
||||
"Finished collecting from the channel with {} results",
|
||||
results.len()
|
||||
);
|
||||
for command in results {
|
||||
// Don't return empty or duplicate commands
|
||||
if !command.name.is_empty()
|
||||
&& !final_response
|
||||
.iter()
|
||||
.any(|cmd: &CommandToRun| cmd.name == command.name && cmd.arg == command.arg)
|
||||
{
|
||||
if SUPPORTED_SLASH_COMMANDS
|
||||
.iter()
|
||||
.any(|supported| &command.name == supported)
|
||||
{
|
||||
final_response.push(command);
|
||||
} else {
|
||||
log::warn!(
|
||||
"Context inference returned an unrecognized slash command: {:?}",
|
||||
command
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the commands by name (reversed just so that /search appears before /file)
|
||||
final_response.sort_by(|cmd1, cmd2| cmd1.name.cmp(&cmd2.name).reverse());
|
||||
|
||||
Ok(final_response)
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use context_servers::{
|
||||
protocol::PromptInfo,
|
||||
};
|
||||
use gpui::{Task, WeakView, WindowContext};
|
||||
use language::{CodeLabel, LspAdapterDelegate};
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use text::LineEnding;
|
||||
@@ -44,10 +44,9 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
self.prompt
|
||||
.arguments
|
||||
.as_ref()
|
||||
.map_or(false, |args| !args.is_empty())
|
||||
self.prompt.arguments.as_ref().map_or(false, |args| {
|
||||
args.iter().any(|arg| arg.required == Some(true))
|
||||
})
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
@@ -97,7 +96,6 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
replace_previous_arguments: false,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(completions)
|
||||
})
|
||||
} else {
|
||||
@@ -108,6 +106,8 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
@@ -142,6 +142,7 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
.description
|
||||
.unwrap_or(format!("Result from {}", prompt_name)),
|
||||
),
|
||||
metadata: None,
|
||||
}],
|
||||
text: prompt,
|
||||
run_commands_in_text: false,
|
||||
@@ -179,6 +180,8 @@ fn prompt_arguments(prompt: &PromptInfo, arguments: &[String]) -> Result<HashMap
|
||||
let mut map = HashMap::default();
|
||||
map.insert(args[0].name.clone(), arguments.join(" "));
|
||||
Ok(map)
|
||||
} else if arguments.is_empty() && args[0].required == Some(false) {
|
||||
Ok(HashMap::default())
|
||||
} else {
|
||||
Err(anyhow!("Prompt expects argument but none given"))
|
||||
}
|
||||
@@ -199,7 +202,7 @@ fn prompt_arguments(prompt: &PromptInfo, arguments: &[String]) -> Result<HashMap
|
||||
pub fn acceptable_prompt(prompt: &PromptInfo) -> bool {
|
||||
match &prompt.arguments {
|
||||
None => true,
|
||||
Some(args) if args.len() == 1 => true,
|
||||
Some(args) if args.len() <= 1 => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::prompt_library::PromptStore;
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||
use gpui::{Task, WeakView};
|
||||
use language::LspAdapterDelegate;
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use std::{
|
||||
fmt::Write,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
@@ -43,6 +43,8 @@ impl SlashCommand for DefaultSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
@@ -70,6 +72,7 @@ impl SlashCommand for DefaultSlashCommand {
|
||||
range: 0..text.len(),
|
||||
icon: IconName::Library,
|
||||
label: "Default".into(),
|
||||
metadata: None,
|
||||
}],
|
||||
text,
|
||||
run_commands_in_text: true,
|
||||
|
||||
109
crates/assistant/src/slash_command/delta_command.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
use crate::slash_command::file_command::{FileCommandMetadata, FileSlashCommand};
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
};
|
||||
use collections::HashSet;
|
||||
use futures::future;
|
||||
use gpui::{Task, WeakView, WindowContext};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
use text::OffsetRangeExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct DeltaSlashCommand;
|
||||
|
||||
impl SlashCommand for DeltaSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
"delta".into()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"re-insert changed files".into()
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
"Re-insert Changed Files".into()
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancellation_flag: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
context_buffer: BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<SlashCommandOutput>> {
|
||||
let mut paths = HashSet::default();
|
||||
let mut file_command_old_outputs = Vec::new();
|
||||
let mut file_command_new_outputs = Vec::new();
|
||||
for section in context_slash_command_output_sections.iter().rev() {
|
||||
if let Some(metadata) = section
|
||||
.metadata
|
||||
.as_ref()
|
||||
.and_then(|value| serde_json::from_value::<FileCommandMetadata>(value.clone()).ok())
|
||||
{
|
||||
if paths.insert(metadata.path.clone()) {
|
||||
file_command_old_outputs.push(
|
||||
context_buffer
|
||||
.as_rope()
|
||||
.slice(section.range.to_offset(&context_buffer)),
|
||||
);
|
||||
file_command_new_outputs.push(Arc::new(FileSlashCommand).run(
|
||||
&[metadata.path.clone()],
|
||||
context_slash_command_output_sections,
|
||||
context_buffer.clone(),
|
||||
workspace.clone(),
|
||||
delegate.clone(),
|
||||
cx,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cx.background_executor().spawn(async move {
|
||||
let mut output = SlashCommandOutput::default();
|
||||
|
||||
let file_command_new_outputs = future::join_all(file_command_new_outputs).await;
|
||||
for (old_text, new_output) in file_command_old_outputs
|
||||
.into_iter()
|
||||
.zip(file_command_new_outputs)
|
||||
{
|
||||
if let Ok(new_output) = new_output {
|
||||
if let Some(file_command_range) = new_output.sections.first() {
|
||||
let new_text = &new_output.text[file_command_range.range.clone()];
|
||||
if old_text.chars().ne(new_text.chars()) {
|
||||
output.sections.extend(new_output.sections.into_iter().map(
|
||||
|section| SlashCommandOutputSection {
|
||||
range: output.text.len() + section.range.start
|
||||
..output.text.len() + section.range.end,
|
||||
icon: section.icon,
|
||||
label: section.label,
|
||||
metadata: section.metadata,
|
||||
},
|
||||
));
|
||||
output.text.push_str(&new_output.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,9 @@ use language::{
|
||||
};
|
||||
use project::{DiagnosticSummary, PathMatchCandidateSet, Project};
|
||||
use rope::Point;
|
||||
use std::fmt::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
ops::Range,
|
||||
fmt::Write,
|
||||
path::{Path, PathBuf},
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use ui::prelude::*;
|
||||
@@ -163,6 +162,8 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
@@ -175,68 +176,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
||||
|
||||
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
|
||||
|
||||
cx.spawn(move |_| async move {
|
||||
let Some((text, sections)) = task.await? else {
|
||||
return Ok(SlashCommandOutput {
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range: 0..1,
|
||||
icon: IconName::Library,
|
||||
label: "No Diagnostics".into(),
|
||||
}],
|
||||
text: "\n".to_string(),
|
||||
run_commands_in_text: true,
|
||||
});
|
||||
};
|
||||
|
||||
let sections = sections
|
||||
.into_iter()
|
||||
.map(|(range, placeholder_type)| SlashCommandOutputSection {
|
||||
range,
|
||||
icon: match placeholder_type {
|
||||
PlaceholderType::Root(_, _) => IconName::ExclamationTriangle,
|
||||
PlaceholderType::File(_) => IconName::File,
|
||||
PlaceholderType::Diagnostic(DiagnosticType::Error, _) => IconName::XCircle,
|
||||
PlaceholderType::Diagnostic(DiagnosticType::Warning, _) => {
|
||||
IconName::ExclamationTriangle
|
||||
}
|
||||
},
|
||||
label: match placeholder_type {
|
||||
PlaceholderType::Root(summary, source) => {
|
||||
let mut label = String::new();
|
||||
label.push_str("Diagnostics");
|
||||
if let Some(source) = source {
|
||||
write!(label, " ({})", source).unwrap();
|
||||
}
|
||||
|
||||
if summary.error_count > 0 || summary.warning_count > 0 {
|
||||
label.push(':');
|
||||
|
||||
if summary.error_count > 0 {
|
||||
write!(label, " {} errors", summary.error_count).unwrap();
|
||||
if summary.warning_count > 0 {
|
||||
label.push_str(",");
|
||||
}
|
||||
}
|
||||
|
||||
if summary.warning_count > 0 {
|
||||
write!(label, " {} warnings", summary.warning_count).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
label.into()
|
||||
}
|
||||
PlaceholderType::File(file_path) => file_path.into(),
|
||||
PlaceholderType::Diagnostic(_, message) => message.into(),
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(SlashCommandOutput {
|
||||
text,
|
||||
sections,
|
||||
run_commands_in_text: false,
|
||||
})
|
||||
})
|
||||
cx.spawn(move |_| async move { task.await?.ok_or_else(|| anyhow!("No diagnostics found")) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +217,7 @@ fn collect_diagnostics(
|
||||
project: Model<Project>,
|
||||
options: Options,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<Option<(String, Vec<(Range<usize>, PlaceholderType)>)>>> {
|
||||
) -> Task<Result<Option<SlashCommandOutput>>> {
|
||||
let error_source = if let Some(path_matcher) = &options.path_matcher {
|
||||
debug_assert_eq!(path_matcher.sources().len(), 1);
|
||||
Some(path_matcher.sources().first().cloned().unwrap_or_default())
|
||||
@@ -318,13 +258,13 @@ fn collect_diagnostics(
|
||||
.collect();
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
let mut text = String::new();
|
||||
let mut output = SlashCommandOutput::default();
|
||||
|
||||
if let Some(error_source) = error_source.as_ref() {
|
||||
writeln!(text, "diagnostics: {}", error_source).unwrap();
|
||||
writeln!(output.text, "diagnostics: {}", error_source).unwrap();
|
||||
} else {
|
||||
writeln!(text, "diagnostics").unwrap();
|
||||
writeln!(output.text, "diagnostics").unwrap();
|
||||
}
|
||||
let mut sections: Vec<(Range<usize>, PlaceholderType)> = Vec::new();
|
||||
|
||||
let mut project_summary = DiagnosticSummary::default();
|
||||
for (project_path, path, summary) in diagnostic_summaries {
|
||||
@@ -341,10 +281,10 @@ fn collect_diagnostics(
|
||||
continue;
|
||||
}
|
||||
|
||||
let last_end = text.len();
|
||||
let last_end = output.text.len();
|
||||
let file_path = path.to_string_lossy().to_string();
|
||||
if !glob_is_exact_file_match {
|
||||
writeln!(&mut text, "{file_path}").unwrap();
|
||||
writeln!(&mut output.text, "{file_path}").unwrap();
|
||||
}
|
||||
|
||||
if let Some(buffer) = project_handle
|
||||
@@ -352,75 +292,73 @@ fn collect_diagnostics(
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
collect_buffer_diagnostics(
|
||||
&mut text,
|
||||
&mut sections,
|
||||
cx.read_model(&buffer, |buffer, _| buffer.snapshot())?,
|
||||
options.include_warnings,
|
||||
);
|
||||
let snapshot = cx.read_model(&buffer, |buffer, _| buffer.snapshot())?;
|
||||
collect_buffer_diagnostics(&mut output, &snapshot, options.include_warnings);
|
||||
}
|
||||
|
||||
if !glob_is_exact_file_match {
|
||||
sections.push((
|
||||
last_end..text.len().saturating_sub(1),
|
||||
PlaceholderType::File(file_path),
|
||||
))
|
||||
output.sections.push(SlashCommandOutputSection {
|
||||
range: last_end..output.text.len().saturating_sub(1),
|
||||
icon: IconName::File,
|
||||
label: file_path.into(),
|
||||
metadata: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// No diagnostics found
|
||||
if sections.is_empty() {
|
||||
if output.sections.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
sections.push((
|
||||
0..text.len(),
|
||||
PlaceholderType::Root(project_summary, error_source),
|
||||
));
|
||||
Ok(Some((text, sections)))
|
||||
let mut label = String::new();
|
||||
label.push_str("Diagnostics");
|
||||
if let Some(source) = error_source {
|
||||
write!(label, " ({})", source).unwrap();
|
||||
}
|
||||
|
||||
if project_summary.error_count > 0 || project_summary.warning_count > 0 {
|
||||
label.push(':');
|
||||
|
||||
if project_summary.error_count > 0 {
|
||||
write!(label, " {} errors", project_summary.error_count).unwrap();
|
||||
if project_summary.warning_count > 0 {
|
||||
label.push_str(",");
|
||||
}
|
||||
}
|
||||
|
||||
if project_summary.warning_count > 0 {
|
||||
write!(label, " {} warnings", project_summary.warning_count).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
output.sections.insert(
|
||||
0,
|
||||
SlashCommandOutputSection {
|
||||
range: 0..output.text.len(),
|
||||
icon: IconName::Warning,
|
||||
label: label.into(),
|
||||
metadata: None,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(Some(output))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn buffer_has_error_diagnostics(snapshot: &BufferSnapshot) -> bool {
|
||||
for (_, group) in snapshot.diagnostic_groups(None) {
|
||||
let entry = &group.entries[group.primary_ix];
|
||||
if entry.diagnostic.severity == DiagnosticSeverity::ERROR {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn write_single_file_diagnostics(
|
||||
output: &mut String,
|
||||
path: Option<&Path>,
|
||||
pub fn collect_buffer_diagnostics(
|
||||
output: &mut SlashCommandOutput,
|
||||
snapshot: &BufferSnapshot,
|
||||
) -> bool {
|
||||
if let Some(path) = path {
|
||||
if buffer_has_error_diagnostics(&snapshot) {
|
||||
output.push_str("/diagnostics ");
|
||||
output.push_str(&path.to_string_lossy());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn collect_buffer_diagnostics(
|
||||
text: &mut String,
|
||||
sections: &mut Vec<(Range<usize>, PlaceholderType)>,
|
||||
snapshot: BufferSnapshot,
|
||||
include_warnings: bool,
|
||||
) {
|
||||
for (_, group) in snapshot.diagnostic_groups(None) {
|
||||
let entry = &group.entries[group.primary_ix];
|
||||
collect_diagnostic(text, sections, entry, &snapshot, include_warnings)
|
||||
collect_diagnostic(output, entry, &snapshot, include_warnings)
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_diagnostic(
|
||||
text: &mut String,
|
||||
sections: &mut Vec<(Range<usize>, PlaceholderType)>,
|
||||
output: &mut SlashCommandOutput,
|
||||
entry: &DiagnosticEntry<Anchor>,
|
||||
snapshot: &BufferSnapshot,
|
||||
include_warnings: bool,
|
||||
@@ -428,17 +366,17 @@ fn collect_diagnostic(
|
||||
const EXCERPT_EXPANSION_SIZE: u32 = 2;
|
||||
const MAX_MESSAGE_LENGTH: usize = 2000;
|
||||
|
||||
let ty = match entry.diagnostic.severity {
|
||||
let (ty, icon) = match entry.diagnostic.severity {
|
||||
DiagnosticSeverity::WARNING => {
|
||||
if !include_warnings {
|
||||
return;
|
||||
}
|
||||
DiagnosticType::Warning
|
||||
("warning", IconName::Warning)
|
||||
}
|
||||
DiagnosticSeverity::ERROR => DiagnosticType::Error,
|
||||
DiagnosticSeverity::ERROR => ("error", IconName::XCircle),
|
||||
_ => return,
|
||||
};
|
||||
let prev_len = text.len();
|
||||
let prev_len = output.text.len();
|
||||
|
||||
let range = entry.range.to_point(snapshot);
|
||||
let diagnostic_row_number = range.start.row + 1;
|
||||
@@ -448,11 +386,11 @@ fn collect_diagnostic(
|
||||
let excerpt_range =
|
||||
Point::new(start_row, 0).to_offset(&snapshot)..Point::new(end_row, 0).to_offset(&snapshot);
|
||||
|
||||
text.push_str("```");
|
||||
output.text.push_str("```");
|
||||
if let Some(language_name) = snapshot.language().map(|l| l.code_fence_block_name()) {
|
||||
text.push_str(&language_name);
|
||||
output.text.push_str(&language_name);
|
||||
}
|
||||
text.push('\n');
|
||||
output.text.push('\n');
|
||||
|
||||
let mut buffer_text = String::new();
|
||||
for chunk in snapshot.text_for_range(excerpt_range) {
|
||||
@@ -461,46 +399,26 @@ fn collect_diagnostic(
|
||||
|
||||
for (i, line) in buffer_text.lines().enumerate() {
|
||||
let line_number = start_row + i as u32 + 1;
|
||||
writeln!(text, "{}", line).unwrap();
|
||||
writeln!(output.text, "{}", line).unwrap();
|
||||
|
||||
if line_number == diagnostic_row_number {
|
||||
text.push_str("//");
|
||||
let prev_len = text.len();
|
||||
write!(text, " {}: ", ty.as_str()).unwrap();
|
||||
let padding = text.len() - prev_len;
|
||||
output.text.push_str("//");
|
||||
let prev_len = output.text.len();
|
||||
write!(output.text, " {}: ", ty).unwrap();
|
||||
let padding = output.text.len() - prev_len;
|
||||
|
||||
let message = util::truncate(&entry.diagnostic.message, MAX_MESSAGE_LENGTH)
|
||||
.replace('\n', format!("\n//{:padding$}", "").as_str());
|
||||
|
||||
writeln!(text, "{message}").unwrap();
|
||||
writeln!(output.text, "{message}").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(text, "```").unwrap();
|
||||
sections.push((
|
||||
prev_len..text.len().saturating_sub(1),
|
||||
PlaceholderType::Diagnostic(ty, entry.diagnostic.message.clone()),
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum PlaceholderType {
|
||||
Root(DiagnosticSummary, Option<String>),
|
||||
File(String),
|
||||
Diagnostic(DiagnosticType, String),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum DiagnosticType {
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl DiagnosticType {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
DiagnosticType::Warning => "warning",
|
||||
DiagnosticType::Error => "error",
|
||||
}
|
||||
}
|
||||
writeln!(output.text, "```").unwrap();
|
||||
output.sections.push(SlashCommandOutputSection {
|
||||
range: prev_len..output.text.len().saturating_sub(1),
|
||||
icon,
|
||||
label: entry.diagnostic.message.clone().into(),
|
||||
metadata: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use indexed_docs::{
|
||||
DocsDotRsProvider, IndexedDocsRegistry, IndexedDocsStore, LocalRustdocProvider, PackageName,
|
||||
ProviderId,
|
||||
};
|
||||
use language::LspAdapterDelegate;
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use project::{Project, ProjectPath};
|
||||
use ui::prelude::*;
|
||||
use util::{maybe, ResultExt};
|
||||
@@ -269,6 +269,8 @@ impl SlashCommand for DocsSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
@@ -349,6 +351,7 @@ impl SlashCommand for DocsSlashCommand {
|
||||
range,
|
||||
icon: IconName::FileDoc,
|
||||
label: format!("docs ({provider}): {key}",).into(),
|
||||
metadata: None,
|
||||
})
|
||||
.collect(),
|
||||
run_commands_in_text: false,
|
||||
|
||||
@@ -11,7 +11,7 @@ use futures::AsyncReadExt;
|
||||
use gpui::{Task, WeakView};
|
||||
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use language::LspAdapterDelegate;
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -128,6 +128,8 @@ impl SlashCommand for FetchSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
@@ -161,6 +163,7 @@ impl SlashCommand for FetchSlashCommand {
|
||||
range,
|
||||
icon: IconName::AtSign,
|
||||
label: format!("fetch {}", url).into(),
|
||||
metadata: None,
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
})
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use super::{diagnostics_command::write_single_file_diagnostics, SlashCommand, SlashCommandOutput};
|
||||
use super::{diagnostics_command::collect_buffer_diagnostics, SlashCommand, SlashCommandOutput};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use assistant_slash_command::{AfterCompletion, ArgumentCompletion, SlashCommandOutputSection};
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{AppContext, Model, Task, View, WeakView};
|
||||
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
|
||||
use project::{PathMatchCandidateSet, Project};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt::Write,
|
||||
ops::Range,
|
||||
ops::{Range, RangeInclusive},
|
||||
path::{Path, PathBuf},
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
@@ -164,11 +165,7 @@ impl SlashCommand for FileSlashCommand {
|
||||
Some(ArgumentCompletion {
|
||||
label,
|
||||
new_text: text,
|
||||
after_completion: if path_match.is_dir {
|
||||
AfterCompletion::Compose
|
||||
} else {
|
||||
AfterCompletion::Run
|
||||
},
|
||||
after_completion: AfterCompletion::Compose,
|
||||
replace_previous_arguments: false,
|
||||
})
|
||||
})
|
||||
@@ -179,6 +176,8 @@ impl SlashCommand for FileSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
@@ -191,54 +190,15 @@ impl SlashCommand for FileSlashCommand {
|
||||
return Task::ready(Err(anyhow!("missing path")));
|
||||
};
|
||||
|
||||
let task = collect_files(workspace.read(cx).project().clone(), arguments, cx);
|
||||
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let output = task.await?;
|
||||
Ok(SlashCommandOutput {
|
||||
text: output.completion_text,
|
||||
sections: output
|
||||
.files
|
||||
.into_iter()
|
||||
.map(|file| {
|
||||
build_entry_output_section(
|
||||
file.range_in_text,
|
||||
Some(&file.path),
|
||||
file.entry_type == EntryType::Directory,
|
||||
None,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
run_commands_in_text: true,
|
||||
})
|
||||
})
|
||||
collect_files(workspace.read(cx).project().clone(), arguments, cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
enum EntryType {
|
||||
File,
|
||||
Directory,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
struct FileCommandOutput {
|
||||
completion_text: String,
|
||||
files: Vec<OutputFile>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
struct OutputFile {
|
||||
range_in_text: Range<usize>,
|
||||
path: PathBuf,
|
||||
entry_type: EntryType,
|
||||
}
|
||||
|
||||
fn collect_files(
|
||||
project: Model<Project>,
|
||||
glob_inputs: &[String],
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<FileCommandOutput>> {
|
||||
) -> Task<Result<SlashCommandOutput>> {
|
||||
let Ok(matchers) = glob_inputs
|
||||
.into_iter()
|
||||
.map(|glob_input| {
|
||||
@@ -258,8 +218,7 @@ fn collect_files(
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
let mut text = String::new();
|
||||
let mut ranges = Vec::new();
|
||||
let mut output = SlashCommandOutput::default();
|
||||
for snapshot in snapshots {
|
||||
let worktree_id = snapshot.id();
|
||||
let mut directory_stack: Vec<(Arc<Path>, String, usize)> = Vec::new();
|
||||
@@ -283,11 +242,12 @@ fn collect_files(
|
||||
break;
|
||||
}
|
||||
let (_, entry_name, start) = directory_stack.pop().unwrap();
|
||||
ranges.push(OutputFile {
|
||||
range_in_text: start..text.len().saturating_sub(1),
|
||||
path: PathBuf::from(entry_name),
|
||||
entry_type: EntryType::Directory,
|
||||
});
|
||||
output.sections.push(build_entry_output_section(
|
||||
start..output.text.len().saturating_sub(1),
|
||||
Some(&PathBuf::from(entry_name)),
|
||||
true,
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
let filename = entry
|
||||
@@ -319,21 +279,23 @@ fn collect_files(
|
||||
continue;
|
||||
}
|
||||
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
|
||||
let entry_start = text.len();
|
||||
let entry_start = output.text.len();
|
||||
if prefix_paths.is_empty() {
|
||||
if is_top_level_directory {
|
||||
text.push_str(&path_including_worktree_name.to_string_lossy());
|
||||
output
|
||||
.text
|
||||
.push_str(&path_including_worktree_name.to_string_lossy());
|
||||
is_top_level_directory = false;
|
||||
} else {
|
||||
text.push_str(&filename);
|
||||
output.text.push_str(&filename);
|
||||
}
|
||||
directory_stack.push((entry.path.clone(), filename, entry_start));
|
||||
} else {
|
||||
let entry_name = format!("{}/{}", prefix_paths, &filename);
|
||||
text.push_str(&entry_name);
|
||||
output.text.push_str(&entry_name);
|
||||
directory_stack.push((entry.path.clone(), entry_name, entry_start));
|
||||
}
|
||||
text.push('\n');
|
||||
output.text.push('\n');
|
||||
} else if entry.is_file() {
|
||||
let Some(open_buffer_task) = project_handle
|
||||
.update(&mut cx, |project, cx| {
|
||||
@@ -344,28 +306,13 @@ fn collect_files(
|
||||
continue;
|
||||
};
|
||||
if let Some(buffer) = open_buffer_task.await.log_err() {
|
||||
let buffer_snapshot =
|
||||
cx.read_model(&buffer, |buffer, _| buffer.snapshot())?;
|
||||
let prev_len = text.len();
|
||||
collect_file_content(
|
||||
&mut text,
|
||||
&buffer_snapshot,
|
||||
path_including_worktree_name.to_string_lossy().to_string(),
|
||||
);
|
||||
text.push('\n');
|
||||
if !write_single_file_diagnostics(
|
||||
&mut text,
|
||||
let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
|
||||
append_buffer_to_output(
|
||||
&snapshot,
|
||||
Some(&path_including_worktree_name),
|
||||
&buffer_snapshot,
|
||||
) {
|
||||
text.pop();
|
||||
}
|
||||
ranges.push(OutputFile {
|
||||
range_in_text: prev_len..text.len(),
|
||||
path: path_including_worktree_name,
|
||||
entry_type: EntryType::File,
|
||||
});
|
||||
text.push('\n');
|
||||
&mut output,
|
||||
)
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,43 +322,30 @@ fn collect_files(
|
||||
let mut root_path = PathBuf::new();
|
||||
root_path.push(snapshot.root_name());
|
||||
root_path.push(&dir);
|
||||
ranges.push(OutputFile {
|
||||
range_in_text: start..text.len(),
|
||||
path: root_path,
|
||||
entry_type: EntryType::Directory,
|
||||
});
|
||||
output.sections.push(build_entry_output_section(
|
||||
start..output.text.len(),
|
||||
Some(&root_path),
|
||||
true,
|
||||
None,
|
||||
));
|
||||
} else {
|
||||
ranges.push(OutputFile {
|
||||
range_in_text: start..text.len(),
|
||||
path: PathBuf::from(entry.as_str()),
|
||||
entry_type: EntryType::Directory,
|
||||
});
|
||||
output.sections.push(build_entry_output_section(
|
||||
start..output.text.len(),
|
||||
Some(&PathBuf::from(entry.as_str())),
|
||||
true,
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(FileCommandOutput {
|
||||
completion_text: text,
|
||||
files: ranges,
|
||||
})
|
||||
Ok(output)
|
||||
})
|
||||
}
|
||||
|
||||
fn collect_file_content(buffer: &mut String, snapshot: &BufferSnapshot, filename: String) {
|
||||
let mut content = snapshot.text();
|
||||
LineEnding::normalize(&mut content);
|
||||
buffer.reserve(filename.len() + content.len() + 9);
|
||||
buffer.push_str(&codeblock_fence_for_path(
|
||||
Some(&PathBuf::from(filename)),
|
||||
None,
|
||||
));
|
||||
buffer.push_str(&content);
|
||||
if !buffer.ends_with('\n') {
|
||||
buffer.push('\n');
|
||||
}
|
||||
buffer.push_str("```");
|
||||
}
|
||||
|
||||
pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32>>) -> String {
|
||||
pub fn codeblock_fence_for_path(
|
||||
path: Option<&Path>,
|
||||
row_range: Option<RangeInclusive<u32>>,
|
||||
) -> String {
|
||||
let mut text = String::new();
|
||||
write!(text, "```").unwrap();
|
||||
|
||||
@@ -426,13 +360,18 @@ pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32
|
||||
}
|
||||
|
||||
if let Some(row_range) = row_range {
|
||||
write!(text, ":{}-{}", row_range.start + 1, row_range.end + 1).unwrap();
|
||||
write!(text, ":{}-{}", row_range.start() + 1, row_range.end() + 1).unwrap();
|
||||
}
|
||||
|
||||
text.push('\n');
|
||||
text
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct FileCommandMetadata {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
pub fn build_entry_output_section(
|
||||
range: Range<usize>,
|
||||
path: Option<&Path>,
|
||||
@@ -458,6 +397,16 @@ pub fn build_entry_output_section(
|
||||
range,
|
||||
icon,
|
||||
label: label.into(),
|
||||
metadata: if is_directory {
|
||||
None
|
||||
} else {
|
||||
path.and_then(|path| {
|
||||
serde_json::to_value(FileCommandMetadata {
|
||||
path: path.to_string_lossy().to_string(),
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,6 +492,36 @@ mod custom_path_matcher {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append_buffer_to_output(
|
||||
buffer: &BufferSnapshot,
|
||||
path: Option<&Path>,
|
||||
output: &mut SlashCommandOutput,
|
||||
) -> Result<()> {
|
||||
let prev_len = output.text.len();
|
||||
|
||||
let mut content = buffer.text();
|
||||
LineEnding::normalize(&mut content);
|
||||
output.text.push_str(&codeblock_fence_for_path(path, None));
|
||||
output.text.push_str(&content);
|
||||
if !output.text.ends_with('\n') {
|
||||
output.text.push('\n');
|
||||
}
|
||||
output.text.push_str("```");
|
||||
output.text.push('\n');
|
||||
|
||||
let section_ix = output.sections.len();
|
||||
collect_buffer_diagnostics(output, buffer, false);
|
||||
|
||||
output.sections.insert(
|
||||
section_ix,
|
||||
build_entry_output_section(prev_len..output.text.len(), path, false, None),
|
||||
);
|
||||
|
||||
output.text.push('\n');
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use fs::FakeFs;
|
||||
@@ -595,9 +574,9 @@ mod test {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(result_1.completion_text.starts_with("root/dir"));
|
||||
assert!(result_1.text.starts_with("root/dir"));
|
||||
// 4 files + 2 directories
|
||||
assert_eq!(6, result_1.files.len());
|
||||
assert_eq!(result_1.sections.len(), 6);
|
||||
|
||||
let result_2 = cx
|
||||
.update(|cx| collect_files(project.clone(), &["root/dir/".to_string()], cx))
|
||||
@@ -611,9 +590,9 @@ mod test {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(result.completion_text.starts_with("root/dir"));
|
||||
assert!(result.text.starts_with("root/dir"));
|
||||
// 5 files + 2 directories
|
||||
assert_eq!(7, result.files.len());
|
||||
assert_eq!(result.sections.len(), 7);
|
||||
|
||||
// Ensure that the project lasts until after the last await
|
||||
drop(project);
|
||||
@@ -658,36 +637,27 @@ mod test {
|
||||
.unwrap();
|
||||
|
||||
// Sanity check
|
||||
assert!(result.completion_text.starts_with("zed/assets/themes\n"));
|
||||
assert_eq!(7, result.files.len());
|
||||
assert!(result.text.starts_with("zed/assets/themes\n"));
|
||||
assert_eq!(result.sections.len(), 7);
|
||||
|
||||
// Ensure that full file paths are included in the real output
|
||||
assert!(result
|
||||
.completion_text
|
||||
.contains("zed/assets/themes/andromeda/LICENSE"));
|
||||
assert!(result
|
||||
.completion_text
|
||||
.contains("zed/assets/themes/ayu/LICENSE"));
|
||||
assert!(result
|
||||
.completion_text
|
||||
.contains("zed/assets/themes/summercamp/LICENSE"));
|
||||
assert!(result.text.contains("zed/assets/themes/andromeda/LICENSE"));
|
||||
assert!(result.text.contains("zed/assets/themes/ayu/LICENSE"));
|
||||
assert!(result.text.contains("zed/assets/themes/summercamp/LICENSE"));
|
||||
|
||||
assert_eq!("summercamp", result.files[5].path.to_string_lossy());
|
||||
assert_eq!(result.sections[5].label, "summercamp");
|
||||
|
||||
// Ensure that things are in descending order, with properly relativized paths
|
||||
assert_eq!(
|
||||
"zed/assets/themes/andromeda/LICENSE",
|
||||
result.files[0].path.to_string_lossy()
|
||||
result.sections[0].label,
|
||||
"zed/assets/themes/andromeda/LICENSE"
|
||||
);
|
||||
assert_eq!("andromeda", result.files[1].path.to_string_lossy());
|
||||
assert_eq!(result.sections[1].label, "andromeda");
|
||||
assert_eq!(result.sections[2].label, "zed/assets/themes/ayu/LICENSE");
|
||||
assert_eq!(result.sections[3].label, "ayu");
|
||||
assert_eq!(
|
||||
"zed/assets/themes/ayu/LICENSE",
|
||||
result.files[2].path.to_string_lossy()
|
||||
);
|
||||
assert_eq!("ayu", result.files[3].path.to_string_lossy());
|
||||
assert_eq!(
|
||||
"zed/assets/themes/summercamp/LICENSE",
|
||||
result.files[4].path.to_string_lossy()
|
||||
result.sections[4].label,
|
||||
"zed/assets/themes/summercamp/LICENSE"
|
||||
);
|
||||
|
||||
// Ensure that the project lasts until after the last await
|
||||
@@ -727,27 +697,24 @@ mod test {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(result.completion_text.starts_with("zed/assets/themes\n"));
|
||||
assert!(result.text.starts_with("zed/assets/themes\n"));
|
||||
assert_eq!(result.sections[0].label, "zed/assets/themes/LICENSE");
|
||||
assert_eq!(
|
||||
"zed/assets/themes/LICENSE",
|
||||
result.files[0].path.to_string_lossy()
|
||||
result.sections[1].label,
|
||||
"zed/assets/themes/summercamp/LICENSE"
|
||||
);
|
||||
assert_eq!(
|
||||
"zed/assets/themes/summercamp/LICENSE",
|
||||
result.files[1].path.to_string_lossy()
|
||||
result.sections[2].label,
|
||||
"zed/assets/themes/summercamp/subdir/LICENSE"
|
||||
);
|
||||
assert_eq!(
|
||||
"zed/assets/themes/summercamp/subdir/LICENSE",
|
||||
result.files[2].path.to_string_lossy()
|
||||
result.sections[3].label,
|
||||
"zed/assets/themes/summercamp/subdir/subsubdir/LICENSE"
|
||||
);
|
||||
assert_eq!(
|
||||
"zed/assets/themes/summercamp/subdir/subsubdir/LICENSE",
|
||||
result.files[3].path.to_string_lossy()
|
||||
);
|
||||
assert_eq!("subsubdir", result.files[4].path.to_string_lossy());
|
||||
assert_eq!("subdir", result.files[5].path.to_string_lossy());
|
||||
assert_eq!("summercamp", result.files[6].path.to_string_lossy());
|
||||
assert_eq!("zed/assets/themes", result.files[7].path.to_string_lossy());
|
||||
assert_eq!(result.sections[4].label, "subsubdir");
|
||||
assert_eq!(result.sections[5].label, "subdir");
|
||||
assert_eq!(result.sections[6].label, "summercamp");
|
||||
assert_eq!(result.sections[7].label, "zed/assets/themes");
|
||||
|
||||
// Ensure that the project lasts until after the last await
|
||||
drop(project);
|
||||
|
||||
@@ -7,7 +7,7 @@ use assistant_slash_command::{
|
||||
};
|
||||
use chrono::Local;
|
||||
use gpui::{Task, WeakView};
|
||||
use language::LspAdapterDelegate;
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -43,6 +43,8 @@ impl SlashCommand for NowSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
_cx: &mut WindowContext,
|
||||
@@ -57,6 +59,7 @@ impl SlashCommand for NowSlashCommand {
|
||||
range,
|
||||
icon: IconName::CountdownTimer,
|
||||
label: now.to_rfc2822().into(),
|
||||
metadata: None,
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
}))
|
||||
|
||||
@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result};
|
||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||
use fs::Fs;
|
||||
use gpui::{AppContext, Model, Task, WeakView};
|
||||
use language::LspAdapterDelegate;
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use project::{Project, ProjectPath};
|
||||
use std::{
|
||||
fmt::Write,
|
||||
@@ -118,6 +118,8 @@ impl SlashCommand for ProjectSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
@@ -140,6 +142,7 @@ impl SlashCommand for ProjectSlashCommand {
|
||||
range,
|
||||
icon: IconName::FileTree,
|
||||
label: "Project".into(),
|
||||
metadata: None,
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
})
|
||||
|
||||
24
crates/assistant/src/slash_command/prompt_after_summary.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
Actions have a cost, so only include actions that you think
|
||||
will be helpful to you in doing a great job answering the
|
||||
prompt in the future.
|
||||
|
||||
You must respond ONLY with a list of actions you would like to
|
||||
perform. Each action should be on its own line, and followed by a space and then its parameter.
|
||||
|
||||
Actions can be performed more than once with different parameters.
|
||||
Here is an example valid response:
|
||||
|
||||
```
|
||||
file path/to/my/file.txt
|
||||
file path/to/another/file.txt
|
||||
search something to search for
|
||||
search something else to search for
|
||||
```
|
||||
|
||||
Once again, do not forget: you must respond ONLY in the format of
|
||||
one action per line, and the action name should be followed by
|
||||
its parameter. Your response must not include anything other
|
||||
than a list of actions, with one action per line, in this format.
|
||||
It is extremely important that you do not deviate from this format even slightly!
|
||||
|
||||
This is the end of my instructions for how to respond. The rest is the prompt:
|
||||
31
crates/assistant/src/slash_command/prompt_before_summary.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
I'm going to give you a prompt. I don't want you to respond
|
||||
to the prompt itself. I want you to figure out which of the following
|
||||
actions on my project, if any, would help you answer the prompt.
|
||||
|
||||
Here are the actions:
|
||||
|
||||
## file
|
||||
|
||||
This action's parameter is a file path to one of the files
|
||||
in the project. If you ask for this action, I will tell you
|
||||
the full contents of the file, so you can learn all the
|
||||
details of the file.
|
||||
|
||||
## search
|
||||
|
||||
This action's parameter is a string to do a semantic search for
|
||||
across the files in the project. (You will have a JSON summary
|
||||
of all the files in the project.) It will tell you which files this string
|
||||
(or similar strings; it is a semantic search) appear in,
|
||||
as well as some context of the lines surrounding each result.
|
||||
It's very important that you only use this action when you think
|
||||
that searching across the specific files in this project for the query
|
||||
in question will be useful. For example, don't use this command to search
|
||||
for queries you might put into a general Web search engine, because those
|
||||
will be too general to give useful results in this project-specific search.
|
||||
|
||||
---
|
||||
|
||||
That was the end of the list of actions.
|
||||
|
||||
Here is a JSON summary of each of the files in my project:
|
||||
@@ -3,7 +3,7 @@ use crate::prompt_library::PromptStore;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||
use gpui::{Task, WeakView};
|
||||
use language::LspAdapterDelegate;
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
@@ -56,6 +56,8 @@ impl SlashCommand for PromptSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
@@ -95,6 +97,7 @@ impl SlashCommand for PromptSlashCommand {
|
||||
range,
|
||||
icon: IconName::Library,
|
||||
label: title,
|
||||
metadata: None,
|
||||
}],
|
||||
run_commands_in_text: true,
|
||||
})
|
||||
|
||||
@@ -8,14 +8,12 @@ use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||
use feature_flags::FeatureFlag;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use language::{CodeLabel, LineEnding, LspAdapterDelegate};
|
||||
use semantic_index::SemanticIndex;
|
||||
use semantic_index::{LoadedSearchResult, SemanticDb};
|
||||
use std::{
|
||||
fmt::Write,
|
||||
path::PathBuf,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use ui::{prelude::*, IconName};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct SearchSlashCommandFeatureFlag;
|
||||
@@ -60,6 +58,8 @@ impl SlashCommand for SearchSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: language::BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
@@ -92,8 +92,11 @@ impl SlashCommand for SearchSlashCommand {
|
||||
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let fs = project.read(cx).fs().clone();
|
||||
let project_index =
|
||||
cx.update_global(|index: &mut SemanticIndex, cx| index.project_index(project, cx));
|
||||
let Some(project_index) =
|
||||
cx.update_global(|index: &mut SemanticDb, cx| index.project_index(project, cx))
|
||||
else {
|
||||
return Task::ready(Err(anyhow::anyhow!("no project indexer")));
|
||||
};
|
||||
|
||||
cx.spawn(|cx| async move {
|
||||
let results = project_index
|
||||
@@ -102,52 +105,28 @@ impl SlashCommand for SearchSlashCommand {
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let mut loaded_results = Vec::new();
|
||||
for result in results {
|
||||
let (full_path, file_content) =
|
||||
result.worktree.read_with(&cx, |worktree, _cx| {
|
||||
let entry_abs_path = worktree.abs_path().join(&result.path);
|
||||
let mut entry_full_path = PathBuf::from(worktree.root_name());
|
||||
entry_full_path.push(&result.path);
|
||||
let file_content = async {
|
||||
let entry_abs_path = entry_abs_path;
|
||||
fs.load(&entry_abs_path).await
|
||||
};
|
||||
(entry_full_path, file_content)
|
||||
})?;
|
||||
if let Some(file_content) = file_content.await.log_err() {
|
||||
loaded_results.push((result, full_path, file_content));
|
||||
}
|
||||
}
|
||||
let loaded_results = SemanticDb::load_results(results, &fs, &cx).await?;
|
||||
|
||||
let output = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
let mut text = format!("Search results for {query}:\n");
|
||||
let mut sections = Vec::new();
|
||||
for (result, full_path, file_content) in loaded_results {
|
||||
let range_start = result.range.start.min(file_content.len());
|
||||
let range_end = result.range.end.min(file_content.len());
|
||||
|
||||
let start_row = file_content[0..range_start].matches('\n').count() as u32;
|
||||
let end_row = file_content[0..range_end].matches('\n').count() as u32;
|
||||
let start_line_byte_offset = file_content[0..range_start]
|
||||
.rfind('\n')
|
||||
.map(|pos| pos + 1)
|
||||
.unwrap_or_default();
|
||||
let end_line_byte_offset = file_content[range_end..]
|
||||
.find('\n')
|
||||
.map(|pos| range_end + pos)
|
||||
.unwrap_or_else(|| file_content.len());
|
||||
|
||||
for LoadedSearchResult {
|
||||
path,
|
||||
range,
|
||||
full_path,
|
||||
file_content,
|
||||
row_range,
|
||||
} in loaded_results
|
||||
{
|
||||
let section_start_ix = text.len();
|
||||
text.push_str(&codeblock_fence_for_path(
|
||||
Some(&result.path),
|
||||
Some(start_row..end_row),
|
||||
Some(&path),
|
||||
Some(row_range.clone()),
|
||||
));
|
||||
|
||||
let mut excerpt =
|
||||
file_content[start_line_byte_offset..end_line_byte_offset].to_string();
|
||||
let mut excerpt = file_content[range].to_string();
|
||||
LineEnding::normalize(&mut excerpt);
|
||||
text.push_str(&excerpt);
|
||||
writeln!(text, "\n```\n").unwrap();
|
||||
@@ -156,7 +135,7 @@ impl SlashCommand for SearchSlashCommand {
|
||||
section_start_ix..section_end_ix,
|
||||
Some(&full_path),
|
||||
false,
|
||||
Some(start_row + 1..end_row + 1),
|
||||
Some(row_range.start() + 1..row_range.end() + 1),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -165,6 +144,7 @@ impl SlashCommand for SearchSlashCommand {
|
||||
range: 0..text.len(),
|
||||
icon: IconName::MagnifyingGlass,
|
||||
label: query,
|
||||
metadata: None,
|
||||
});
|
||||
|
||||
SlashCommandOutput {
|
||||
|
||||
@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context as _, Result};
|
||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||
use editor::Editor;
|
||||
use gpui::{Task, WeakView};
|
||||
use language::LspAdapterDelegate;
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use std::sync::Arc;
|
||||
use std::{path::Path, sync::atomic::AtomicBool};
|
||||
use ui::{IconName, WindowContext};
|
||||
@@ -41,6 +41,8 @@ impl SlashCommand for OutlineSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
@@ -77,6 +79,7 @@ impl SlashCommand for OutlineSlashCommand {
|
||||
range: 0..outline_text.len(),
|
||||
icon: IconName::ListTree,
|
||||
label: path.to_string_lossy().to_string().into(),
|
||||
metadata: None,
|
||||
}],
|
||||
text: outline_text,
|
||||
run_commands_in_text: false,
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
use super::{
|
||||
diagnostics_command::write_single_file_diagnostics,
|
||||
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
||||
SlashCommand, SlashCommandOutput,
|
||||
};
|
||||
use super::{file_command::append_buffer_to_output, SlashCommand, SlashCommandOutput};
|
||||
use anyhow::{Context, Result};
|
||||
use assistant_slash_command::ArgumentCompletion;
|
||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::Editor;
|
||||
use futures::future::join_all;
|
||||
use gpui::{Entity, Task, WeakView};
|
||||
use language::{BufferSnapshot, CodeLabel, HighlightId, LspAdapterDelegate};
|
||||
use std::{
|
||||
fmt::Write,
|
||||
path::PathBuf,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use ui::{ActiveTheme, WindowContext};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct TabSlashCommand;
|
||||
@@ -131,6 +127,8 @@ impl SlashCommand for TabSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
@@ -144,40 +142,11 @@ impl SlashCommand for TabSlashCommand {
|
||||
);
|
||||
|
||||
cx.background_executor().spawn(async move {
|
||||
let mut sections = Vec::new();
|
||||
let mut text = String::new();
|
||||
let mut has_diagnostics = false;
|
||||
let mut output = SlashCommandOutput::default();
|
||||
for (full_path, buffer, _) in tab_items_search.await? {
|
||||
let section_start_ix = text.len();
|
||||
text.push_str(&codeblock_fence_for_path(full_path.as_deref(), None));
|
||||
for chunk in buffer.as_rope().chunks() {
|
||||
text.push_str(chunk);
|
||||
}
|
||||
if !text.ends_with('\n') {
|
||||
text.push('\n');
|
||||
}
|
||||
writeln!(text, "```").unwrap();
|
||||
if write_single_file_diagnostics(&mut text, full_path.as_deref(), &buffer) {
|
||||
has_diagnostics = true;
|
||||
}
|
||||
if !text.ends_with('\n') {
|
||||
text.push('\n');
|
||||
}
|
||||
|
||||
let section_end_ix = text.len() - 1;
|
||||
sections.push(build_entry_output_section(
|
||||
section_start_ix..section_end_ix,
|
||||
full_path.as_deref(),
|
||||
false,
|
||||
None,
|
||||
));
|
||||
append_buffer_to_output(&buffer, full_path.as_deref(), &mut output).log_err();
|
||||
}
|
||||
|
||||
Ok(SlashCommandOutput {
|
||||
text,
|
||||
sections,
|
||||
run_commands_in_text: has_diagnostics,
|
||||
})
|
||||
Ok(output)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -253,7 +222,7 @@ fn tab_items_for_queries(
|
||||
.fold(HashMap::default(), |mut candidates, (id, path_string)| {
|
||||
candidates
|
||||
.entry(path_string)
|
||||
.or_insert_with(|| Vec::new())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(id);
|
||||
candidates
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
};
|
||||
use gpui::{AppContext, Task, View, WeakView};
|
||||
use language::{CodeLabel, LspAdapterDelegate};
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||
use ui::prelude::*;
|
||||
use workspace::{dock::Panel, Workspace};
|
||||
@@ -57,6 +57,8 @@ impl SlashCommand for TerminalSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
@@ -91,6 +93,7 @@ impl SlashCommand for TerminalSlashCommand {
|
||||
range,
|
||||
icon: IconName::Terminal,
|
||||
label: "Terminal".into(),
|
||||
metadata: None,
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
}))
|
||||
|
||||
@@ -8,7 +8,7 @@ use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
};
|
||||
use gpui::{Task, WeakView};
|
||||
use language::LspAdapterDelegate;
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use ui::prelude::*;
|
||||
|
||||
use workspace::Workspace;
|
||||
@@ -53,6 +53,8 @@ impl SlashCommand for WorkflowSlashCommand {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
@@ -68,6 +70,7 @@ impl SlashCommand for WorkflowSlashCommand {
|
||||
range,
|
||||
icon: IconName::Route,
|
||||
label: "Workflow".into(),
|
||||
metadata: None,
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
})
|
||||
|
||||
@@ -465,7 +465,8 @@ impl EventEmitter<PromptEditorEvent> for PromptEditor {}
|
||||
|
||||
impl Render for PromptEditor {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let buttons = match &self.codegen.read(cx).status {
|
||||
let status = &self.codegen.read(cx).status;
|
||||
let buttons = match status {
|
||||
CodegenStatus::Idle => {
|
||||
vec![
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
@@ -516,7 +517,8 @@ impl Render for PromptEditor {
|
||||
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)));
|
||||
|
||||
if self.edited_since_done {
|
||||
let has_error = matches!(status, CodegenStatus::Error(_));
|
||||
if has_error || self.edited_since_done {
|
||||
vec![
|
||||
cancel,
|
||||
IconButton::new("restart", IconName::RotateCw)
|
||||
@@ -568,7 +570,7 @@ impl Render for PromptEditor {
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border_y_1()
|
||||
.border_color(cx.theme().status().info_border)
|
||||
.py_1p5()
|
||||
.py_2()
|
||||
.h_full()
|
||||
.w_full()
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
@@ -583,7 +585,7 @@ impl Render for PromptEditor {
|
||||
.gap_2()
|
||||
.child(ModelSelector::new(
|
||||
self.fs.clone(),
|
||||
IconButton::new("context", IconName::SlidersAlt)
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
@@ -947,12 +949,11 @@ impl PromptEditor {
|
||||
} else {
|
||||
cx.theme().colors().text
|
||||
},
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features.clone(),
|
||||
font_fallbacks: settings.ui_font.fallbacks.clone(),
|
||||
font_size: rems(0.875).into(),
|
||||
font_weight: settings.ui_font.weight,
|
||||
line_height: relative(1.3),
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: settings.buffer_font_size.into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
..Default::default()
|
||||
};
|
||||
EditorElement::new(
|
||||
@@ -988,7 +989,7 @@ impl TerminalTransaction {
|
||||
|
||||
pub fn push(&mut self, hunk: String, cx: &mut AppContext) {
|
||||
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
|
||||
let input = hunk.replace(CARRIAGE_RETURN, " ");
|
||||
let input = Self::sanitize_input(hunk);
|
||||
self.terminal
|
||||
.update(cx, |terminal, _| terminal.input(input));
|
||||
}
|
||||
@@ -1003,6 +1004,10 @@ impl TerminalTransaction {
|
||||
terminal.input(CARRIAGE_RETURN.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
fn sanitize_input(input: String) -> String {
|
||||
input.replace(['\r', '\n'], "")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Codegen {
|
||||
@@ -1061,6 +1066,7 @@ impl Codegen {
|
||||
telemetry.report_assistant_event(
|
||||
None,
|
||||
telemetry_events::AssistantKind::Inline,
|
||||
telemetry_events::AssistantPhase::Response,
|
||||
model_telemetry_id,
|
||||
response_latency,
|
||||
error_message,
|
||||
|
||||
@@ -19,4 +19,5 @@ gpui.workspace = true
|
||||
language.workspace = true
|
||||
parking_lot.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -2,7 +2,7 @@ mod slash_command_registry;
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
|
||||
use language::{CodeLabel, LspAdapterDelegate};
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use slash_command_registry::*;
|
||||
use std::{
|
||||
@@ -77,6 +77,8 @@ pub trait SlashCommand: 'static + Send + Sync {
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
context_buffer: BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
// TODO: We're just using the `LspAdapterDelegate` here because that is
|
||||
// what the extension API is already expecting.
|
||||
@@ -94,7 +96,7 @@ pub type RenderFoldPlaceholder = Arc<
|
||||
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
|
||||
>;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct SlashCommandOutput {
|
||||
pub text: String,
|
||||
pub sections: Vec<SlashCommandOutputSection<usize>>,
|
||||
@@ -106,4 +108,11 @@ pub struct SlashCommandOutputSection<T> {
|
||||
pub range: Range<T>,
|
||||
pub icon: IconName,
|
||||
pub label: SharedString,
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl SlashCommandOutputSection<language::Anchor> {
|
||||
pub fn is_valid(&self, buffer: &language::TextBuffer) -> bool {
|
||||
self.range.start.is_valid(buffer) && !self.range.to_offset(buffer).is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,5 +18,5 @@ collections.workspace = true
|
||||
derive_more.workspace = true
|
||||
gpui.workspace = true
|
||||
parking_lot.workspace = true
|
||||
rodio = { version = "0.17.1", default-features = false, features = ["wav"] }
|
||||
rodio = { version = "0.19.0", default-features = false, features = ["wav"] }
|
||||
util.workspace = true
|
||||
|
||||
@@ -44,7 +44,7 @@ impl SoundRegistry {
|
||||
let bytes = self
|
||||
.assets
|
||||
.load(&path)?
|
||||
.map(|asset| Ok(asset))
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| Err(anyhow::anyhow!("No such asset available")))?
|
||||
.into_owned();
|
||||
let cursor = Cursor::new(bytes);
|
||||
|
||||
@@ -33,6 +33,7 @@ impl Sound {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Audio {
|
||||
_output_stream: Option<OutputStream>,
|
||||
output_handle: Option<OutputStreamHandle>,
|
||||
@@ -45,10 +46,7 @@ impl Global for GlobalAudio {}
|
||||
|
||||
impl Audio {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
_output_stream: None,
|
||||
output_handle: None,
|
||||
}
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn ensure_output_exists(&mut self) -> Option<&OutputStreamHandle> {
|
||||
|
||||
@@ -19,7 +19,6 @@ db.workspace = true
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc.workspace = true
|
||||
log.workspace = true
|
||||
markdown_preview.workspace = true
|
||||
menu.workspace = true
|
||||
|
||||
@@ -9,7 +9,6 @@ use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
|
||||
SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use isahc::AsyncBody;
|
||||
|
||||
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
|
||||
use schemars::JsonSchema;
|
||||
@@ -20,7 +19,7 @@ use smol::{fs, io::AsyncReadExt};
|
||||
use settings::{Settings, SettingsSources, SettingsStore};
|
||||
use smol::{fs::File, process::Command};
|
||||
|
||||
use http_client::{HttpClient, HttpClientWithUrl};
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
|
||||
use std::{
|
||||
env::{
|
||||
@@ -95,7 +94,7 @@ struct MacOsUnmounter {
|
||||
impl Drop for MacOsUnmounter {
|
||||
fn drop(&mut self) {
|
||||
let unmount_output = std::process::Command::new("hdiutil")
|
||||
.args(&["detach", "-force"])
|
||||
.args(["detach", "-force"])
|
||||
.arg(&self.mount_path)
|
||||
.output();
|
||||
|
||||
@@ -211,7 +210,7 @@ pub fn check(_: &Check, cx: &mut WindowContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(message) = env::var("ZED_UPDATE_EXPLANATION").ok() {
|
||||
if let Ok(message) = env::var("ZED_UPDATE_EXPLANATION") {
|
||||
drop(cx.prompt(
|
||||
gpui::PromptLevel::Info,
|
||||
"Zed was installed via a package manager.",
|
||||
@@ -244,19 +243,22 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
|
||||
let auto_updater = AutoUpdater::get(cx)?;
|
||||
let release_channel = ReleaseChannel::try_global(cx)?;
|
||||
|
||||
if matches!(
|
||||
release_channel,
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview
|
||||
) {
|
||||
let auto_updater = auto_updater.read(cx);
|
||||
let release_channel = release_channel.dev_name();
|
||||
let current_version = auto_updater.current_version;
|
||||
let url = &auto_updater
|
||||
.http_client
|
||||
.build_url(&format!("/releases/{release_channel}/{current_version}"));
|
||||
cx.open_url(&url);
|
||||
match release_channel {
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview => {
|
||||
let auto_updater = auto_updater.read(cx);
|
||||
let current_version = auto_updater.current_version;
|
||||
let release_channel = release_channel.dev_name();
|
||||
let path = format!("/releases/{release_channel}/{current_version}");
|
||||
let url = &auto_updater.http_client.build_url(&path);
|
||||
cx.open_url(url);
|
||||
}
|
||||
ReleaseChannel::Nightly => {
|
||||
cx.open_url("https://github.com/zed-industries/zed/commits/nightly/");
|
||||
}
|
||||
ReleaseChannel::Dev => {
|
||||
cx.open_url("https://github.com/zed-industries/zed/commits/main/");
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@@ -722,7 +724,7 @@ async fn install_release_linux(
|
||||
}
|
||||
|
||||
let output = Command::new("rsync")
|
||||
.args(&["-av", "--delete"])
|
||||
.args(["-av", "--delete"])
|
||||
.arg(&from)
|
||||
.arg(&to)
|
||||
.output()
|
||||
@@ -754,10 +756,10 @@ async fn install_release_macos(
|
||||
|
||||
mounted_app_path.push("/");
|
||||
let output = Command::new("hdiutil")
|
||||
.args(&["attach", "-nobrowse"])
|
||||
.args(["attach", "-nobrowse"])
|
||||
.arg(&downloaded_dmg)
|
||||
.arg("-mountroot")
|
||||
.arg(&temp_dir.path())
|
||||
.arg(temp_dir.path())
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
@@ -773,7 +775,7 @@ async fn install_release_macos(
|
||||
};
|
||||
|
||||
let output = Command::new("rsync")
|
||||
.args(&["-av", "--delete"])
|
||||
.args(["-av", "--delete"])
|
||||
.arg(&mounted_app_path)
|
||||
.arg(&running_app_path)
|
||||
.output()
|
||||
|
||||
@@ -18,6 +18,12 @@ pub struct Breadcrumbs {
|
||||
subscription: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl Default for Breadcrumbs {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Breadcrumbs {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -529,14 +529,13 @@ mod test {
|
||||
let (a, b) = cx.update(|cx| {
|
||||
(
|
||||
one_at_a_time.spawn(cx, |_| async {
|
||||
assert!(false);
|
||||
Ok(2)
|
||||
panic!("");
|
||||
}),
|
||||
one_at_a_time.spawn(cx, |_| async { Ok(3) }),
|
||||
)
|
||||
});
|
||||
|
||||
assert_eq!(a.await.unwrap(), None);
|
||||
assert_eq!(a.await.unwrap(), None::<u32>);
|
||||
assert_eq!(b.await.unwrap(), Some(3));
|
||||
|
||||
let promise = cx.update(|cx| one_at_a_time.spawn(cx, |_| async { Ok(4) }));
|
||||
|
||||
@@ -259,13 +259,13 @@ impl Room {
|
||||
None
|
||||
};
|
||||
|
||||
match room
|
||||
let did_join = room
|
||||
.update(&mut cx, |room, cx| {
|
||||
room.leave_when_empty = true;
|
||||
room.call(called_user_id, initial_project_id, cx)
|
||||
})?
|
||||
.await
|
||||
{
|
||||
.await;
|
||||
match did_join {
|
||||
Ok(()) => Ok(room),
|
||||
Err(error) => Err(error.context("room creation failed")),
|
||||
}
|
||||
@@ -493,7 +493,7 @@ impl Room {
|
||||
// we leave the room and return an error.
|
||||
if let Some(this) = this.upgrade() {
|
||||
log::info!("reconnection failed, leaving room");
|
||||
let _ = this.update(&mut cx, |this, cx| this.leave(cx))?.await?;
|
||||
this.update(&mut cx, |this, cx| this.leave(cx))?.await?;
|
||||
}
|
||||
Err(anyhow!(
|
||||
"can't reconnect to room: client failed to re-establish connection"
|
||||
@@ -933,7 +933,7 @@ impl Room {
|
||||
let list = this
|
||||
.follows_by_leader_id_project_id
|
||||
.entry((leader, project_id))
|
||||
.or_insert(Vec::new());
|
||||
.or_default();
|
||||
if !list.contains(&follower) {
|
||||
list.push(follower);
|
||||
}
|
||||
@@ -942,7 +942,7 @@ impl Room {
|
||||
this.pending_room_update.take();
|
||||
if this.should_leave() {
|
||||
log::info!("room is empty, leaving");
|
||||
let _ = this.leave(cx).detach();
|
||||
this.leave(cx).detach();
|
||||
}
|
||||
|
||||
this.user_store.update(cx, |user_store, cx| {
|
||||
@@ -1017,19 +1017,11 @@ impl Room {
|
||||
.collect::<Vec<u64>>();
|
||||
speaker_ids.sort_unstable();
|
||||
for (sid, participant) in &mut self.remote_participants {
|
||||
if let Ok(_) = speaker_ids.binary_search(sid) {
|
||||
participant.speaking = true;
|
||||
} else {
|
||||
participant.speaking = false;
|
||||
}
|
||||
participant.speaking = speaker_ids.binary_search(sid).is_ok();
|
||||
}
|
||||
if let Some(id) = self.client.user_id() {
|
||||
if let Some(room) = &mut self.live_kit {
|
||||
if let Ok(_) = speaker_ids.binary_search(&id) {
|
||||
room.speaking = true;
|
||||
} else {
|
||||
room.speaking = false;
|
||||
}
|
||||
room.speaking = speaker_ids.binary_search(&id).is_ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1208,6 +1200,7 @@ impl Room {
|
||||
room_id: self.id(),
|
||||
worktrees: vec![],
|
||||
dev_server_project_id: Some(dev_server_project_id.0),
|
||||
is_ssh_project: false,
|
||||
})
|
||||
} else {
|
||||
if let Some(project_id) = project.read(cx).remote_id() {
|
||||
@@ -1218,6 +1211,7 @@ impl Room {
|
||||
room_id: self.id(),
|
||||
worktrees: project.read(cx).worktree_metadata_protos(cx),
|
||||
dev_server_project_id: None,
|
||||
is_ssh_project: project.read(cx).is_via_ssh(),
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ use collections::HashMap;
|
||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
|
||||
use language::proto::serialize_version;
|
||||
use rpc::{
|
||||
proto::{self, AnyProtoClient, PeerId},
|
||||
TypedEnvelope,
|
||||
proto::{self, PeerId},
|
||||
AnyProtoClient, TypedEnvelope,
|
||||
};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use text::BufferId;
|
||||
@@ -171,19 +171,16 @@ impl ChannelBuffer {
|
||||
fn on_buffer_update(
|
||||
&mut self,
|
||||
_: Model<language::Buffer>,
|
||||
event: &language::Event,
|
||||
event: &language::BufferEvent,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
language::Event::Operation(operation) => {
|
||||
language::BufferEvent::Operation(operation) => {
|
||||
if *ZED_ALWAYS_ACTIVE {
|
||||
match operation {
|
||||
language::Operation::UpdateSelections { selections, .. } => {
|
||||
if selections.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let language::Operation::UpdateSelections { selections, .. } = operation {
|
||||
if selections.is_empty() {
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let operation = language::proto::serialize_operation(operation);
|
||||
@@ -194,7 +191,7 @@ impl ChannelBuffer {
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
language::Event::Edited => {
|
||||
language::BufferEvent::Edited => {
|
||||
cx.emit(ChannelBufferEvent::BufferEdited);
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -11,7 +11,7 @@ use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use rpc::proto::AnyProtoClient;
|
||||
use rpc::AnyProtoClient;
|
||||
use std::{
|
||||
ops::{ControlFlow, Range},
|
||||
sync::Arc,
|
||||
@@ -61,9 +61,9 @@ pub enum ChannelMessageId {
|
||||
Pending(usize),
|
||||
}
|
||||
|
||||
impl Into<Option<u64>> for ChannelMessageId {
|
||||
fn into(self) -> Option<u64> {
|
||||
match self {
|
||||
impl From<ChannelMessageId> for Option<u64> {
|
||||
fn from(val: ChannelMessageId) -> Self {
|
||||
match val {
|
||||
ChannelMessageId::Saved(id) => Some(id),
|
||||
ChannelMessageId::Pending(_) => None,
|
||||
}
|
||||
@@ -332,7 +332,7 @@ impl ChannelChat {
|
||||
.update(&mut cx, |chat, cx| {
|
||||
if let Some(first_id) = chat.first_loaded_message_id() {
|
||||
if first_id <= message_id {
|
||||
let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>();
|
||||
let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>(&());
|
||||
let message_id = ChannelMessageId::Saved(message_id);
|
||||
cursor.seek(&message_id, Bias::Left, &());
|
||||
return ControlFlow::Break(
|
||||
@@ -498,7 +498,7 @@ impl ChannelChat {
|
||||
}
|
||||
|
||||
pub fn message(&self, ix: usize) -> &ChannelMessage {
|
||||
let mut cursor = self.messages.cursor::<Count>();
|
||||
let mut cursor = self.messages.cursor::<Count>(&());
|
||||
cursor.seek(&Count(ix), Bias::Right, &());
|
||||
cursor.item().unwrap()
|
||||
}
|
||||
@@ -515,13 +515,13 @@ impl ChannelChat {
|
||||
}
|
||||
|
||||
pub fn messages_in_range(&self, range: Range<usize>) -> impl Iterator<Item = &ChannelMessage> {
|
||||
let mut cursor = self.messages.cursor::<Count>();
|
||||
let mut cursor = self.messages.cursor::<Count>(&());
|
||||
cursor.seek(&Count(range.start), Bias::Right, &());
|
||||
cursor.take(range.len())
|
||||
}
|
||||
|
||||
pub fn pending_messages(&self) -> impl Iterator<Item = &ChannelMessage> {
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>();
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>(&());
|
||||
cursor.seek(&ChannelMessageId::Pending(0), Bias::Left, &());
|
||||
cursor
|
||||
}
|
||||
@@ -589,11 +589,11 @@ impl ChannelChat {
|
||||
fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut ModelContext<Self>) {
|
||||
if let Some((first_message, last_message)) = messages.first().zip(messages.last()) {
|
||||
let nonces = messages
|
||||
.cursor::<()>()
|
||||
.cursor::<()>(&())
|
||||
.map(|m| m.nonce)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let mut old_cursor = self.messages.cursor::<(ChannelMessageId, Count)>();
|
||||
let mut old_cursor = self.messages.cursor::<(ChannelMessageId, Count)>(&());
|
||||
let mut new_messages = old_cursor.slice(&first_message.id, Bias::Left, &());
|
||||
let start_ix = old_cursor.start().1 .0;
|
||||
let removed_messages = old_cursor.slice(&last_message.id, Bias::Right, &());
|
||||
@@ -646,7 +646,7 @@ impl ChannelChat {
|
||||
}
|
||||
|
||||
fn message_removed(&mut self, id: u64, cx: &mut ModelContext<Self>) {
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>();
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>(&());
|
||||
let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left, &());
|
||||
if let Some(item) = cursor.item() {
|
||||
if item.id == ChannelMessageId::Saved(id) {
|
||||
@@ -685,7 +685,7 @@ impl ChannelChat {
|
||||
edited_at: Option<OffsetDateTime>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>();
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>(&());
|
||||
let mut messages = cursor.slice(&id, Bias::Left, &());
|
||||
let ix = messages.summary().count;
|
||||
|
||||
@@ -716,7 +716,7 @@ async fn messages_from_proto(
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<SumTree<ChannelMessage>> {
|
||||
let messages = ChannelMessage::from_proto_vec(proto_messages, user_store, cx).await?;
|
||||
let mut result = SumTree::new();
|
||||
let mut result = SumTree::default();
|
||||
result.extend(messages, &());
|
||||
Ok(result)
|
||||
}
|
||||
@@ -825,6 +825,10 @@ impl Default for ChannelMessageId {
|
||||
impl sum_tree::Summary for ChannelMessageSummary {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &Self::Context) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||
self.max_id = summary.max_id;
|
||||
self.count += summary.count;
|
||||
@@ -832,6 +836,10 @@ impl sum_tree::Summary for ChannelMessageSummary {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for ChannelMessageId {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
|
||||
debug_assert!(summary.max_id > *self);
|
||||
*self = summary.max_id;
|
||||
@@ -839,6 +847,10 @@ impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for ChannelMessageId {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for Count {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
|
||||
self.0 += summary.count;
|
||||
}
|
||||
|
||||
@@ -249,15 +249,14 @@ impl ChannelStore {
|
||||
}
|
||||
|
||||
pub fn initialize(&mut self) {
|
||||
if !self.did_subscribe {
|
||||
if self
|
||||
if !self.did_subscribe
|
||||
&& self
|
||||
.client
|
||||
.send(proto::SubscribeToChannels {})
|
||||
.log_err()
|
||||
.is_some()
|
||||
{
|
||||
self.did_subscribe = true;
|
||||
}
|
||||
{
|
||||
self.did_subscribe = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +422,7 @@ impl ChannelStore {
|
||||
) {
|
||||
self.channel_states
|
||||
.entry(channel_id)
|
||||
.or_insert_with(|| Default::default())
|
||||
.or_default()
|
||||
.acknowledge_message_id(message_id);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -436,7 +435,7 @@ impl ChannelStore {
|
||||
) {
|
||||
self.channel_states
|
||||
.entry(channel_id)
|
||||
.or_insert_with(|| Default::default())
|
||||
.or_default()
|
||||
.update_latest_message_id(message_id);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -450,7 +449,7 @@ impl ChannelStore {
|
||||
) {
|
||||
self.channel_states
|
||||
.entry(channel_id)
|
||||
.or_insert_with(|| Default::default())
|
||||
.or_default()
|
||||
.acknowledge_notes_version(epoch, version);
|
||||
cx.notify()
|
||||
}
|
||||
@@ -464,7 +463,7 @@ impl ChannelStore {
|
||||
) {
|
||||
self.channel_states
|
||||
.entry(channel_id)
|
||||
.or_insert_with(|| Default::default())
|
||||
.or_default()
|
||||
.update_latest_notes_version(epoch, version);
|
||||
cx.notify()
|
||||
}
|
||||
@@ -924,7 +923,7 @@ impl ChannelStore {
|
||||
if let Some(role) = ChannelRole::from_i32(membership.role) {
|
||||
this.channel_states
|
||||
.entry(ChannelId(membership.channel_id))
|
||||
.or_insert_with(|| ChannelState::default())
|
||||
.or_default()
|
||||
.set_role(role)
|
||||
}
|
||||
}
|
||||
@@ -1094,11 +1093,7 @@ impl ChannelStore {
|
||||
id: ChannelId(channel.id),
|
||||
visibility: channel.visibility(),
|
||||
name: channel.name.into(),
|
||||
parent_path: channel
|
||||
.parent_path
|
||||
.into_iter()
|
||||
.map(|cid| ChannelId(cid))
|
||||
.collect(),
|
||||
parent_path: channel.parent_path.into_iter().map(ChannelId).collect(),
|
||||
}),
|
||||
),
|
||||
}
|
||||
@@ -1113,14 +1108,11 @@ impl ChannelStore {
|
||||
|
||||
if channels_changed {
|
||||
if !payload.delete_channels.is_empty() {
|
||||
let delete_channels: Vec<ChannelId> = payload
|
||||
.delete_channels
|
||||
.into_iter()
|
||||
.map(|cid| ChannelId(cid))
|
||||
.collect();
|
||||
let delete_channels: Vec<ChannelId> =
|
||||
payload.delete_channels.into_iter().map(ChannelId).collect();
|
||||
self.channel_index.delete_channels(&delete_channels);
|
||||
self.channel_participants
|
||||
.retain(|channel_id, _| !delete_channels.contains(&channel_id));
|
||||
.retain(|channel_id, _| !delete_channels.contains(channel_id));
|
||||
|
||||
for channel_id in &delete_channels {
|
||||
let channel_id = *channel_id;
|
||||
|
||||
@@ -18,7 +18,7 @@ test-support = ["clock/test-support", "collections/test-support", "gpui/test-sup
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-recursion = "0.3"
|
||||
async-tungstenite = { workspace = true, features = ["async-std", "async-native-tls"] }
|
||||
async-tungstenite = { workspace = true, features = ["async-std", "async-tls"] }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
@@ -34,7 +34,9 @@ parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
release_channel.workspace = true
|
||||
rpc.workspace = true
|
||||
rpc = { workspace = true, features = ["gpui"] }
|
||||
rustls.workspace = true
|
||||
rustls-native-certs.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -22,7 +22,6 @@ use gpui::{actions, AppContext, AsyncAppContext, Global, Model, Task, WeakModel}
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use parking_lot::RwLock;
|
||||
use postage::watch;
|
||||
use proto::{AnyProtoClient, EntityMessageSubscriber, ProtoClient, ProtoMessageHandlerSet};
|
||||
use rand::prelude::*;
|
||||
use release_channel::{AppVersion, ReleaseChannel};
|
||||
use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage};
|
||||
@@ -117,7 +116,7 @@ impl Settings for ClientSettings {
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||
let mut result = sources.json_merge::<Self>()?;
|
||||
if let Some(server_url) = &*ZED_SERVER_URL {
|
||||
result.server_url.clone_from(&server_url)
|
||||
result.server_url.clone_from(server_url)
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
@@ -241,8 +240,6 @@ pub enum EstablishConnectionError {
|
||||
#[error("{0}")]
|
||||
Other(#[from] anyhow::Error),
|
||||
#[error("{0}")]
|
||||
Http(#[from] http_client::Error),
|
||||
#[error("{0}")]
|
||||
InvalidHeaderValue(#[from] async_tungstenite::tungstenite::http::header::InvalidHeaderValue),
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
@@ -530,19 +527,13 @@ impl Client {
|
||||
}
|
||||
|
||||
pub fn production(cx: &mut AppContext) -> Arc<Self> {
|
||||
let user_agent = format!(
|
||||
"Zed/{} ({}; {})",
|
||||
AppVersion::global(cx),
|
||||
std::env::consts::OS,
|
||||
std::env::consts::ARCH
|
||||
);
|
||||
let clock = Arc::new(clock::RealSystemClock);
|
||||
let http = Arc::new(HttpClientWithUrl::new(
|
||||
let http = Arc::new(HttpClientWithUrl::new_uri(
|
||||
cx.http_client(),
|
||||
&ClientSettings::get_global(cx).server_url,
|
||||
Some(user_agent),
|
||||
ProxySettings::get_global(cx).proxy.clone(),
|
||||
cx.http_client().proxy().cloned(),
|
||||
));
|
||||
Self::new(clock, http.clone(), cx)
|
||||
Self::new(clock, http, cx)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u64 {
|
||||
@@ -1141,13 +1132,37 @@ impl Client {
|
||||
request_headers.insert("x-zed-app-version", HeaderValue::from_str(&app_version)?);
|
||||
request_headers.insert(
|
||||
"x-zed-release-channel",
|
||||
HeaderValue::from_str(&release_channel.map(|r| r.dev_name()).unwrap_or("unknown"))?,
|
||||
HeaderValue::from_str(release_channel.map(|r| r.dev_name()).unwrap_or("unknown"))?,
|
||||
);
|
||||
|
||||
match url_scheme {
|
||||
Https => {
|
||||
let client_config = {
|
||||
let mut root_store = rustls::RootCertStore::empty();
|
||||
|
||||
let root_certs = rustls_native_certs::load_native_certs();
|
||||
for error in root_certs.errors {
|
||||
log::warn!("error loading native certs: {:?}", error);
|
||||
}
|
||||
root_store.add_parsable_certificates(
|
||||
&root_certs
|
||||
.certs
|
||||
.into_iter()
|
||||
.map(|cert| cert.as_ref().to_owned())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth()
|
||||
};
|
||||
let (stream, _) =
|
||||
async_tungstenite::async_std::client_async_tls(request, stream).await?;
|
||||
async_tungstenite::async_tls::client_async_tls_with_connector(
|
||||
request,
|
||||
stream,
|
||||
Some(client_config.into()),
|
||||
)
|
||||
.await?;
|
||||
Ok(Connection::new(
|
||||
stream
|
||||
.map_err(|error| anyhow!(error))
|
||||
@@ -1344,16 +1359,14 @@ impl Client {
|
||||
);
|
||||
}
|
||||
|
||||
let user = serde_json::from_slice::<GithubUser>(body.as_slice()).map_err(|err| {
|
||||
serde_json::from_slice::<GithubUser>(body.as_slice()).map_err(|err| {
|
||||
log::error!("Error deserializing: {:?}", err);
|
||||
log::error!(
|
||||
"GitHub API response text: {:?}",
|
||||
String::from_utf8_lossy(body.as_slice())
|
||||
);
|
||||
anyhow!("error deserializing GitHub user")
|
||||
})?;
|
||||
|
||||
user
|
||||
})?
|
||||
};
|
||||
|
||||
let query_params = [
|
||||
@@ -1408,7 +1421,7 @@ impl Client {
|
||||
|
||||
pub async fn sign_out(self: &Arc<Self>, cx: &AsyncAppContext) {
|
||||
self.state.write().credentials = None;
|
||||
self.disconnect(&cx);
|
||||
self.disconnect(cx);
|
||||
|
||||
if self.has_credentials(cx).await {
|
||||
self.credentials_provider
|
||||
|
||||