Compare commits
331 Commits
buffer-ext
...
get_libs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
684d9d9ef0 | ||
|
|
432de00e89 | ||
|
|
09424edc35 | ||
|
|
74cba2407f | ||
|
|
053e31994f | ||
|
|
69e698c3be | ||
|
|
215bce1974 | ||
|
|
e64a86ce9f | ||
|
|
8ae74bc6df | ||
|
|
65f6a7e5bc | ||
|
|
533416c5a9 | ||
|
|
57ad5778fa | ||
|
|
707ccb04d2 | ||
|
|
1f72069b42 | ||
|
|
ed5eb725f9 | ||
|
|
3fafdeb1a8 | ||
|
|
898d48a574 | ||
|
|
5b40debb5f | ||
|
|
e39695bf1c | ||
|
|
77df7e56f7 | ||
|
|
250f2e76eb | ||
|
|
5f35fa5d92 | ||
|
|
84ce81caf1 | ||
|
|
8aeab4800c | ||
|
|
1021f0e288 | ||
|
|
675673ed54 | ||
|
|
3737d4eb4f | ||
|
|
0daa070448 | ||
|
|
689da9d0b1 | ||
|
|
1c5be9de4e | ||
|
|
d5f67406b0 | ||
|
|
c3075dfe9a | ||
|
|
caaa9a00a9 | ||
|
|
ffd1083cc1 | ||
|
|
6d4ecac610 | ||
|
|
dc5ffe6994 | ||
|
|
03c7f08581 | ||
|
|
73ff8c0f1f | ||
|
|
1c5d9c221a | ||
|
|
a1d2e1106e | ||
|
|
568a21a700 | ||
|
|
5199135b54 | ||
|
|
8559731e0d | ||
|
|
02d0561586 | ||
|
|
1be3c44550 | ||
|
|
32605e9ea4 | ||
|
|
c83d007138 | ||
|
|
48c6eb9ac7 | ||
|
|
e28496d4e2 | ||
|
|
c1a039a5d7 | ||
|
|
71da81c743 | ||
|
|
11058765be | ||
|
|
c7a79cfc02 | ||
|
|
84a6ded657 | ||
|
|
e5bbd378a6 | ||
|
|
82eb753b31 | ||
|
|
de1889d6a8 | ||
|
|
f143396825 | ||
|
|
7eea1a6f51 | ||
|
|
1a4f9b2891 | ||
|
|
1deed247eb | ||
|
|
db92a31067 | ||
|
|
31902a1b73 | ||
|
|
3f415f3587 | ||
|
|
140d70289e | ||
|
|
b9b689d322 | ||
|
|
2d2e20f9d4 | ||
|
|
b701eab44f | ||
|
|
6167688a63 | ||
|
|
3161aedcb0 | ||
|
|
64532e94e4 | ||
|
|
40408e731e | ||
|
|
7398f795e3 | ||
|
|
4b4565fb7a | ||
|
|
21a023980d | ||
|
|
ae6a3d15af | ||
|
|
dc7c49bd0b | ||
|
|
1eddd2f38d | ||
|
|
dc48af0ca1 | ||
|
|
1f54fde4d2 | ||
|
|
19162c3160 | ||
|
|
9300dbc834 | ||
|
|
bbf5ed2ba1 | ||
|
|
500c3c54a6 | ||
|
|
59dc3985a1 | ||
|
|
ccc871c44c | ||
|
|
4e2ae06ca6 | ||
|
|
300bf87f77 | ||
|
|
a6cb17fb51 | ||
|
|
9d197ddc99 | ||
|
|
623a6eca75 | ||
|
|
7bb510971a | ||
|
|
eb71d2f1a8 | ||
|
|
fc9db97ac7 | ||
|
|
e9bc9ed5d5 | ||
|
|
9a8601227d | ||
|
|
d33600525e | ||
|
|
fdb03d3058 | ||
|
|
c4e0f5e0ee | ||
|
|
da1ef13442 | ||
|
|
5045f984a9 | ||
|
|
2d71c36ad3 | ||
|
|
d2ffad0f34 | ||
|
|
692590bff4 | ||
|
|
87ac4cff60 | ||
|
|
9606858436 | ||
|
|
f39e54decc | ||
|
|
8a7ef4db59 | ||
|
|
fd07fef4db | ||
|
|
4a4d8c1cab | ||
|
|
b69c6ee7df | ||
|
|
0e86ba0983 | ||
|
|
5e62bbfd29 | ||
|
|
21be70f278 | ||
|
|
2470db4901 | ||
|
|
e87d6da2a6 | ||
|
|
437bcc0ce6 | ||
|
|
3a2f0653d1 | ||
|
|
336b4a5690 | ||
|
|
93a4295f66 | ||
|
|
f019ad563f | ||
|
|
399e094f02 | ||
|
|
dbc325ea12 | ||
|
|
6b56530a4a | ||
|
|
20c06545b6 | ||
|
|
d989183f94 | ||
|
|
3ba071b993 | ||
|
|
e4080ef565 | ||
|
|
e95e1c9ae5 | ||
|
|
1ff10b71c8 | ||
|
|
7051bc00c2 | ||
|
|
1efe87029b | ||
|
|
11953bbc16 | ||
|
|
65bb989c61 | ||
|
|
20826336d9 | ||
|
|
3c95a64a23 | ||
|
|
bc751d6c19 | ||
|
|
a36706aed6 | ||
|
|
35a80f07e0 | ||
|
|
2ff8dde925 | ||
|
|
d784e72027 | ||
|
|
8a36278c95 | ||
|
|
05d18321db | ||
|
|
bb7d9d3525 | ||
|
|
75cb199a54 | ||
|
|
0f4ebdfbca | ||
|
|
37c93d8fea | ||
|
|
e7fcf83ce8 | ||
|
|
1f35c8d09d | ||
|
|
3ca18af40b | ||
|
|
4f227fd3bf | ||
|
|
743feb98bc | ||
|
|
e309fbda2a | ||
|
|
5905fbb9ac | ||
|
|
7d62fda5a3 | ||
|
|
45388805ad | ||
|
|
7dac5594cd | ||
|
|
5d12e3ce3a | ||
|
|
601090511b | ||
|
|
8bd624b5db | ||
|
|
9f6ff29a54 | ||
|
|
d97427f69e | ||
|
|
99bef27300 | ||
|
|
f8195c41e0 | ||
|
|
759646e0a3 | ||
|
|
ab1d466c5f | ||
|
|
5f1046b3cd | ||
|
|
d6c184b494 | ||
|
|
16d2afc662 | ||
|
|
90a12f5564 | ||
|
|
ca033e6475 | ||
|
|
97708fdf43 | ||
|
|
ace4d5185d | ||
|
|
93730983dd | ||
|
|
579267f399 | ||
|
|
8103ac12bf | ||
|
|
15b4130fa5 | ||
|
|
ae34872f73 | ||
|
|
740803d745 | ||
|
|
edf2c19250 | ||
|
|
82e6b1e0e5 | ||
|
|
28a54ce122 | ||
|
|
fbbf0393cb | ||
|
|
00b1c81c9f | ||
|
|
27c1106fad | ||
|
|
1fc391f696 | ||
|
|
8074fba76b | ||
|
|
ac0d5d3152 | ||
|
|
c3bdc1c178 | ||
|
|
ce4f07bd3c | ||
|
|
157c57aa8d | ||
|
|
6670c9eb3b | ||
|
|
3986bcf9dc | ||
|
|
713b39bac0 | ||
|
|
3fd690ade4 | ||
|
|
e9f2e72ff0 | ||
|
|
7d0a7541bf | ||
|
|
a944bb2f24 | ||
|
|
d2894ce9c9 | ||
|
|
d91e62524f | ||
|
|
3d5c023fda | ||
|
|
4338ff6be4 | ||
|
|
23e1faa485 | ||
|
|
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 |
2
.github/actions/run_tests/action.yml
vendored
2
.github/actions/run_tests/action.yml
vendored
@@ -10,7 +10,7 @@ runs:
|
|||||||
cargo install cargo-nextest
|
cargo install cargo-nextest
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
|
|||||||
23
.github/workflows/bump_collab_staging.yml
vendored
Normal file
23
.github/workflows/bump_collab_staging.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: Bump collab-staging Tag
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Fire every day at 16:00 UTC (At the start of the US workday)
|
||||||
|
- cron: "0 16 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-collab-staging-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 collab-staging tag
|
||||||
|
run: |
|
||||||
|
git config user.name github-actions
|
||||||
|
git config user.email github-actions@github.com
|
||||||
|
git tag -f collab-staging
|
||||||
|
git push origin collab-staging --force
|
||||||
5
.github/workflows/bump_patch_version.yml
vendored
5
.github/workflows/bump_patch_version.yml
vendored
@@ -15,8 +15,7 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
bump_patch_version:
|
bump_patch_version:
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
- test
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||||
@@ -42,7 +41,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
which cargo-set-version > /dev/null || cargo install cargo-edit --features vendored-openssl
|
which cargo-set-version > /dev/null || cargo install cargo-edit
|
||||||
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
|
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
|
||||||
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
|
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
|
||||||
git tag v${output}${tag_suffix}
|
git tag v${output}${tag_suffix}
|
||||||
|
|||||||
136
.github/workflows/ci.yml
vendored
136
.github/workflows/ci.yml
vendored
@@ -39,16 +39,7 @@ jobs:
|
|||||||
run: git clean -df
|
run: git clean -df
|
||||||
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
run: |
|
run: script/check-spelling
|
||||||
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"
|
|
||||||
|
|
||||||
- name: Run style checks
|
- name: Run style checks
|
||||||
uses: ./.github/actions/check_style
|
uses: ./.github/actions/check_style
|
||||||
@@ -110,8 +101,7 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: (Linux) Run Clippy and tests
|
name: (Linux) Run Clippy and tests
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
- deploy
|
|
||||||
steps:
|
steps:
|
||||||
- name: Add Rust to the PATH
|
- name: Add Rust to the PATH
|
||||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||||
@@ -121,6 +111,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
clean: false
|
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
|
- name: cargo clippy
|
||||||
run: ./script/clippy
|
run: ./script/clippy
|
||||||
|
|
||||||
@@ -145,6 +144,7 @@ jobs:
|
|||||||
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
|
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
cache-provider: "github"
|
||||||
|
|
||||||
- name: cargo clippy
|
- name: cargo clippy
|
||||||
# Windows can't run shell scripts, so we need to use `cargo xtask`.
|
# Windows can't run shell scripts, so we need to use `cargo xtask`.
|
||||||
@@ -172,7 +172,7 @@ jobs:
|
|||||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
@@ -192,29 +192,12 @@ jobs:
|
|||||||
- name: Determine version and release channel
|
- name: Determine version and release channel
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
||||||
|
script/determine-release-channel
|
||||||
|
|
||||||
version=$(script/get-crate-version zed)
|
- name: Draft release notes
|
||||||
channel=$(cat crates/zed/RELEASE_CHANNEL)
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
echo "Publishing version: ${version} on release channel ${channel}"
|
run: |
|
||||||
echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
expected_tag_name=""
|
|
||||||
case ${channel} in
|
|
||||||
stable)
|
|
||||||
expected_tag_name="v${version}";;
|
|
||||||
preview)
|
|
||||||
expected_tag_name="v${version}-pre";;
|
|
||||||
nightly)
|
|
||||||
expected_tag_name="v${version}-nightly";;
|
|
||||||
*)
|
|
||||||
echo "can't publish a release on channel ${channel}"
|
|
||||||
exit 1;;
|
|
||||||
esac
|
|
||||||
if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
|
|
||||||
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
mkdir -p target/
|
mkdir -p target/
|
||||||
# Ignore any errors that occur while drafting release notes to not fail the build.
|
# Ignore any errors that occur while drafting release notes to not fail the build.
|
||||||
script/draft-release-notes "$version" "$channel" > target/release-notes.md || true
|
script/draft-release-notes "$version" "$channel" > target/release-notes.md || true
|
||||||
@@ -271,51 +254,26 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: Create a Linux bundle
|
name: Create a Linux bundle
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- buildjet-16vcpu-ubuntu-2004
|
||||||
- deploy
|
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||||
needs: [linux_tests]
|
needs: [linux_tests]
|
||||||
env:
|
env:
|
||||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||||
steps:
|
steps:
|
||||||
- name: Add Rust to the PATH
|
|
||||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
- name: Limit target directory size
|
- name: Install Linux dependencies
|
||||||
run: script/clear-target-dir-if-larger-than 100
|
run: ./script/linux && ./script/install-mold 2.34.0
|
||||||
|
|
||||||
- name: Determine version and release channel
|
- name: Determine version and release channel
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
||||||
|
script/determine-release-channel
|
||||||
version=$(script/get-crate-version zed)
|
|
||||||
channel=$(cat crates/zed/RELEASE_CHANNEL)
|
|
||||||
echo "Publishing version: ${version} on release channel ${channel}"
|
|
||||||
echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
expected_tag_name=""
|
|
||||||
case ${channel} in
|
|
||||||
stable)
|
|
||||||
expected_tag_name="v${version}";;
|
|
||||||
preview)
|
|
||||||
expected_tag_name="v${version}-pre";;
|
|
||||||
nightly)
|
|
||||||
expected_tag_name="v${version}-nightly";;
|
|
||||||
*)
|
|
||||||
echo "can't publish a release on channel ${channel}"
|
|
||||||
exit 1;;
|
|
||||||
esac
|
|
||||||
if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
|
|
||||||
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Create Linux .tar.gz bundle
|
- name: Create Linux .tar.gz bundle
|
||||||
run: script/bundle-linux
|
run: script/bundle-linux
|
||||||
@@ -343,7 +301,7 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: Create arm64 Linux bundle
|
name: Create arm64 Linux bundle
|
||||||
runs-on:
|
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') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||||
needs: [linux_tests]
|
needs: [linux_tests]
|
||||||
env:
|
env:
|
||||||
@@ -354,53 +312,15 @@ jobs:
|
|||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
- name: "Setup jq"
|
|
||||||
uses: dcarbone/install-jq-action@8867ddb4788346d7c22b72ea2e2ffe4d514c7bcb # v2
|
|
||||||
|
|
||||||
- name: Set up Clang
|
- name: Install Linux dependencies
|
||||||
run: |
|
run: ./script/linux
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y llvm-15 clang-15 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-15/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: Determine version and release channel
|
- name: Determine version and release channel
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
||||||
|
script/determine-release-channel
|
||||||
version=$(script/get-crate-version zed)
|
|
||||||
channel=$(cat crates/zed/RELEASE_CHANNEL)
|
|
||||||
echo "Publishing version: ${version} on release channel ${channel}"
|
|
||||||
echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
expected_tag_name=""
|
|
||||||
case ${channel} in
|
|
||||||
stable)
|
|
||||||
expected_tag_name="v${version}";;
|
|
||||||
preview)
|
|
||||||
expected_tag_name="v${version}-pre";;
|
|
||||||
nightly)
|
|
||||||
expected_tag_name="v${version}-nightly";;
|
|
||||||
*)
|
|
||||||
echo "can't publish a release on channel ${channel}"
|
|
||||||
exit 1;;
|
|
||||||
esac
|
|
||||||
if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
|
|
||||||
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Create and upload Linux .tar.gz bundle
|
- name: Create and upload Linux .tar.gz bundle
|
||||||
run: script/bundle-linux
|
run: script/bundle-linux
|
||||||
|
|||||||
31
.github/workflows/close_stale_issues.yml
vendored
Normal file
31
.github/workflows/close_stale_issues.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: "Close Stale Issues"
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 11 * * 2"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
stale-issue-message: >
|
||||||
|
Hi there! 👋
|
||||||
|
|
||||||
|
We're working to clean up our issue tracker by closing older issues that might not be relevant anymore. Are you able to reproduce this issue in the latest version of Zed? If so, please let us know by commenting on this issue and we will keep it open; otherwise, we'll close it in 10 days. Feel free to open a new issue if you're seeing this message after the issue has been closed.
|
||||||
|
|
||||||
|
Thanks for your help!
|
||||||
|
close-issue-message: "This issue was closed due to inactivity; feel free to open a new issue if you're still experiencing this problem!"
|
||||||
|
# We will increase `days-before-stale` to 365 on or after Jan 24th,
|
||||||
|
# 2024. This date marks one year since migrating issues from
|
||||||
|
# 'community' to 'zed' repository. The migration added activity to all
|
||||||
|
# issues, preventing 365 days from working until then.
|
||||||
|
days-before-stale: 180
|
||||||
|
days-before-close: 10
|
||||||
|
any-of-issue-labels: "defect,panic / crash"
|
||||||
|
operations-per-run: 1000
|
||||||
|
ascending: true
|
||||||
|
enable-statistics: true
|
||||||
|
stale-issue-label: "stale"
|
||||||
2
.github/workflows/danger.yml
vendored
2
.github/workflows/danger.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
version: 9
|
version: 9
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "20"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|||||||
32
.github/workflows/deploy_collab.yml
vendored
32
.github/workflows/deploy_collab.yml
vendored
@@ -8,7 +8,6 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILDKIT: 1
|
DOCKER_BUILDKIT: 1
|
||||||
DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
style:
|
style:
|
||||||
@@ -61,11 +60,12 @@ jobs:
|
|||||||
- style
|
- style
|
||||||
- tests
|
- tests
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
- deploy
|
|
||||||
steps:
|
steps:
|
||||||
- name: Add Rust to the PATH
|
- name: Install doctl
|
||||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
uses: digitalocean/action-doctl@v2
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||||
|
|
||||||
- name: Sign into DigitalOcean docker registry
|
- name: Sign into DigitalOcean docker registry
|
||||||
run: doctl registry login
|
run: doctl registry login
|
||||||
@@ -75,11 +75,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
- name: Set up default .cargo/config.toml
|
|
||||||
run: cp ./.cargo/collab-config.toml ./.cargo/config.toml
|
|
||||||
|
|
||||||
- name: Build docker image
|
- name: Build docker image
|
||||||
run: docker build . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA
|
run: |
|
||||||
|
docker build -f Dockerfile-collab \
|
||||||
|
--build-arg GITHUB_SHA=$GITHUB_SHA \
|
||||||
|
--tag registry.digitalocean.com/zed/collab:$GITHUB_SHA \
|
||||||
|
.
|
||||||
|
|
||||||
- name: Publish docker image
|
- name: Publish docker image
|
||||||
run: docker push registry.digitalocean.com/zed/collab:${GITHUB_SHA}
|
run: docker push registry.digitalocean.com/zed/collab:${GITHUB_SHA}
|
||||||
@@ -92,10 +93,19 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- publish
|
- publish
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
- deploy
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||||
|
with:
|
||||||
|
clean: false
|
||||||
|
|
||||||
|
- name: Install doctl
|
||||||
|
uses: digitalocean/action-doctl@v2
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||||
|
|
||||||
- name: Sign into Kubernetes
|
- name: Sign into Kubernetes
|
||||||
run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 ${{ secrets.CLUSTER_NAME }}
|
run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 ${{ secrets.CLUSTER_NAME }}
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/docs.yml
vendored
8
.github/workflows/docs.yml
vendored
@@ -20,5 +20,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
version: 9
|
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
|
working-directory: ./docs
|
||||||
|
|||||||
1
.github/workflows/publish_extension_cli.yml
vendored
1
.github/workflows/publish_extension_cli.yml
vendored
@@ -24,6 +24,7 @@ jobs:
|
|||||||
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
|
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
cache-provider: "github"
|
||||||
|
|
||||||
- name: Configure linux
|
- name: Configure linux
|
||||||
shell: bash -euxo pipefail {0}
|
shell: bash -euxo pipefail {0}
|
||||||
|
|||||||
5
.github/workflows/randomized_tests.yml
vendored
5
.github/workflows/randomized_tests.yml
vendored
@@ -19,11 +19,10 @@ jobs:
|
|||||||
tests:
|
tests:
|
||||||
name: Run randomized tests
|
name: Run randomized tests
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
- randomized-tests
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
|
|||||||
55
.github/workflows/release_nightly.yml
vendored
55
.github/workflows/release_nightly.yml
vendored
@@ -70,7 +70,7 @@ jobs:
|
|||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
@@ -100,8 +100,7 @@ jobs:
|
|||||||
name: Create a Linux *.tar.gz bundle for x86
|
name: Create a Linux *.tar.gz bundle for x86
|
||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- buildjet-16vcpu-ubuntu-2004
|
||||||
- deploy
|
|
||||||
needs: tests
|
needs: tests
|
||||||
env:
|
env:
|
||||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||||
@@ -117,6 +116,12 @@ jobs:
|
|||||||
- name: Add Rust to the PATH
|
- name: Add Rust to the PATH
|
||||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- name: Install Linux dependencies
|
||||||
|
run: ./script/linux && ./script/install-mold 2.34.0
|
||||||
|
|
||||||
|
- name: Limit target directory size
|
||||||
|
run: script/clear-target-dir-if-larger-than 100
|
||||||
|
|
||||||
- name: Set release channel to nightly
|
- name: Set release channel to nightly
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -148,23 +153,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
- name: "Setup jq"
|
- name: Install Linux dependencies
|
||||||
uses: dcarbone/install-jq-action@8867ddb4788346d7c22b72ea2e2ffe4d514c7bcb # v2
|
run: ./script/linux
|
||||||
|
|
||||||
- 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
|
- name: Limit target directory size
|
||||||
run: script/clear-target-dir-if-larger-than 100
|
run: script/clear-target-dir-if-larger-than 100
|
||||||
@@ -181,3 +171,28 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Zed Nightly
|
- name: Upload Zed Nightly
|
||||||
run: script/upload-nightly linux-targz
|
run: script/upload-nightly linux-targz
|
||||||
|
|
||||||
|
update-nightly-tag:
|
||||||
|
name: Update nightly tag
|
||||||
|
if: github.repository_owner == 'zed-industries'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- bundle-mac
|
||||||
|
- bundle-linux-x86
|
||||||
|
- bundle-linux-arm
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Update nightly tag
|
||||||
|
run: |
|
||||||
|
if [ "$(git rev-parse nightly)" = "$(git rev-parse HEAD)" ]; then
|
||||||
|
echo "Nightly tag already points to current commit. Skipping tagging."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
git config user.name github-actions
|
||||||
|
git config user.email github-actions@github.com
|
||||||
|
git tag -f nightly
|
||||||
|
git push origin nightly --force
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,7 +10,7 @@
|
|||||||
/crates/collab/seed.json
|
/crates/collab/seed.json
|
||||||
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
|
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
|
||||||
/dev.zed.Zed*.json
|
/dev.zed.Zed*.json
|
||||||
/assets/*licenses.md
|
/assets/*licenses.*
|
||||||
**/venv
|
**/venv
|
||||||
.build
|
.build
|
||||||
*.wasm
|
*.wasm
|
||||||
|
|||||||
@@ -38,6 +38,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"file_types": {
|
||||||
|
"Dockerfile": ["Dockerfile*[!dockerignore]"],
|
||||||
|
"Git Ignore": ["dockerignore"]
|
||||||
|
},
|
||||||
"hard_tabs": false,
|
"hard_tabs": false,
|
||||||
"formatter": "auto",
|
"formatter": "auto",
|
||||||
"remove_trailing_whitespace_on_save": true,
|
"remove_trailing_whitespace_on_save": true,
|
||||||
|
|||||||
693
Cargo.lock
generated
693
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
64
Cargo.toml
64
Cargo.toml
@@ -27,6 +27,7 @@ members = [
|
|||||||
"crates/diagnostics",
|
"crates/diagnostics",
|
||||||
"crates/docs_preprocessor",
|
"crates/docs_preprocessor",
|
||||||
"crates/editor",
|
"crates/editor",
|
||||||
|
"crates/evals",
|
||||||
"crates/extension",
|
"crates/extension",
|
||||||
"crates/extension_api",
|
"crates/extension_api",
|
||||||
"crates/extension_cli",
|
"crates/extension_cli",
|
||||||
@@ -51,6 +52,7 @@ members = [
|
|||||||
"crates/indexed_docs",
|
"crates/indexed_docs",
|
||||||
"crates/inline_completion_button",
|
"crates/inline_completion_button",
|
||||||
"crates/install_cli",
|
"crates/install_cli",
|
||||||
|
"crates/isahc_http_client",
|
||||||
"crates/journal",
|
"crates/journal",
|
||||||
"crates/language",
|
"crates/language",
|
||||||
"crates/language_model",
|
"crates/language_model",
|
||||||
@@ -159,7 +161,6 @@ members = [
|
|||||||
"extensions/terraform",
|
"extensions/terraform",
|
||||||
"extensions/test-extension",
|
"extensions/test-extension",
|
||||||
"extensions/toml",
|
"extensions/toml",
|
||||||
"extensions/uppercase",
|
|
||||||
"extensions/uiua",
|
"extensions/uiua",
|
||||||
"extensions/vue",
|
"extensions/vue",
|
||||||
"extensions/zig",
|
"extensions/zig",
|
||||||
@@ -226,6 +227,7 @@ image_viewer = { path = "crates/image_viewer" }
|
|||||||
indexed_docs = { path = "crates/indexed_docs" }
|
indexed_docs = { path = "crates/indexed_docs" }
|
||||||
inline_completion_button = { path = "crates/inline_completion_button" }
|
inline_completion_button = { path = "crates/inline_completion_button" }
|
||||||
install_cli = { path = "crates/install_cli" }
|
install_cli = { path = "crates/install_cli" }
|
||||||
|
isahc_http_client = { path = "crates/isahc_http_client" }
|
||||||
journal = { path = "crates/journal" }
|
journal = { path = "crates/journal" }
|
||||||
language = { path = "crates/language" }
|
language = { path = "crates/language" }
|
||||||
language_model = { path = "crates/language_model" }
|
language_model = { path = "crates/language_model" }
|
||||||
@@ -394,6 +396,8 @@ runtimelib = { version = "0.15", default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
rustc-demangle = "0.1.23"
|
rustc-demangle = "0.1.23"
|
||||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
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"] }
|
schemars = { version = "0.8", features = ["impl_json_schema"] }
|
||||||
semver = "1.0"
|
semver = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
@@ -416,7 +420,7 @@ strsim = "0.11"
|
|||||||
strum = { version = "0.25.0", features = ["derive"] }
|
strum = { version = "0.25.0", features = ["derive"] }
|
||||||
subtle = "2.5.0"
|
subtle = "2.5.0"
|
||||||
sys-locale = "0.3.1"
|
sys-locale = "0.3.1"
|
||||||
sysinfo = "0.30.7"
|
sysinfo = "0.31.0"
|
||||||
tempfile = "3.9.0"
|
tempfile = "3.9.0"
|
||||||
thiserror = "1.0.29"
|
thiserror = "1.0.29"
|
||||||
tiktoken-rs = "0.5.9"
|
tiktoken-rs = "0.5.9"
|
||||||
@@ -431,43 +435,43 @@ tiny_http = "0.8"
|
|||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tower-http = "0.4.4"
|
tower-http = "0.4.4"
|
||||||
tree-sitter = { version = "0.22", features = ["wasm"] }
|
tree-sitter = { version = "0.23", features = ["wasm"] }
|
||||||
tree-sitter-bash = "0.21"
|
tree-sitter-bash = "0.23"
|
||||||
tree-sitter-c = "0.21"
|
tree-sitter-c = "0.23"
|
||||||
tree-sitter-cpp = "0.22"
|
tree-sitter-cpp = "0.23"
|
||||||
tree-sitter-css = "0.21"
|
tree-sitter-css = "0.23"
|
||||||
tree-sitter-elixir = "0.2"
|
tree-sitter-elixir = "0.3"
|
||||||
tree-sitter-embedded-template = "0.20.0"
|
tree-sitter-embedded-template = "0.23.0"
|
||||||
tree-sitter-go = "0.21"
|
tree-sitter-go = "0.23"
|
||||||
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "1f55029bacd0a6a11f6eb894c4312d429dcf735c", package = "tree-sitter-gomod" }
|
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/d1y/tree-sitter-go-work", rev = "dcbabff454703c3a4bc98a23cf8778d4be46fd22" }
|
tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
|
||||||
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "6dd0303acf7138dd2b9b432a229e16539581c701" }
|
tree-sitter-heex = { git = "https://github.com/zed-industries/tree-sitter-heex", rev = "1dd45142fbb05562e35b2040c6129c9bca346592" }
|
||||||
tree-sitter-html = "0.20"
|
tree-sitter-html = "0.20"
|
||||||
tree-sitter-jsdoc = "0.21"
|
tree-sitter-jsdoc = "0.23"
|
||||||
tree-sitter-json = "0.21"
|
tree-sitter-json = "0.23"
|
||||||
tree-sitter-md = { git = "https://github.com/zed-industries/tree-sitter-markdown", rev = "e3855e37f8f2c71aa7513c18a9c95fb7461b1b10" }
|
tree-sitter-md = { git = "https://github.com/zed-industries/tree-sitter-markdown", rev = "4cfa6aad6b75052a5077c80fd934757d9267d81b" }
|
||||||
protols-tree-sitter-proto = "0.2"
|
protols-tree-sitter-proto = { git = "https://github.com/zed-industries/tree-sitter-proto", rev = "0848bd30a64be48772e15fbb9d5ba8c0cc5772ad" }
|
||||||
tree-sitter-python = "0.21"
|
tree-sitter-python = "0.23"
|
||||||
tree-sitter-regex = "0.21"
|
tree-sitter-regex = "0.23"
|
||||||
tree-sitter-ruby = "0.21"
|
tree-sitter-ruby = "0.23"
|
||||||
tree-sitter-rust = "0.21"
|
tree-sitter-rust = "0.23"
|
||||||
tree-sitter-typescript = "0.21"
|
tree-sitter-typescript = "0.23"
|
||||||
tree-sitter-yaml = "0.6"
|
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
|
||||||
unindent = "0.1.7"
|
unindent = "0.1.7"
|
||||||
unicase = "2.6"
|
unicase = "2.6"
|
||||||
unicode-segmentation = "1.10"
|
unicode-segmentation = "1.10"
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }
|
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }
|
||||||
wasmparser = "0.201"
|
wasmparser = "0.215"
|
||||||
wasm-encoder = "0.201"
|
wasm-encoder = "0.215"
|
||||||
wasmtime = { version = "21.0.1", default-features = false, features = [
|
wasmtime = { version = "24", default-features = false, features = [
|
||||||
"async",
|
"async",
|
||||||
"demangle",
|
"demangle",
|
||||||
"runtime",
|
"runtime",
|
||||||
"cranelift",
|
"cranelift",
|
||||||
"component-model",
|
"component-model",
|
||||||
] }
|
] }
|
||||||
wasmtime-wasi = "21.0.1"
|
wasmtime-wasi = "24"
|
||||||
which = "6.0.0"
|
which = "6.0.0"
|
||||||
wit-component = "0.201"
|
wit-component = "0.201"
|
||||||
|
|
||||||
@@ -490,7 +494,6 @@ features = [
|
|||||||
"implement",
|
"implement",
|
||||||
"Foundation_Numerics",
|
"Foundation_Numerics",
|
||||||
"Storage",
|
"Storage",
|
||||||
"System",
|
|
||||||
"System_Threading",
|
"System_Threading",
|
||||||
"UI_ViewManagement",
|
"UI_ViewManagement",
|
||||||
"Wdk_System_SystemServices",
|
"Wdk_System_SystemServices",
|
||||||
@@ -521,13 +524,10 @@ features = [
|
|||||||
"Win32_UI_Input_Ime",
|
"Win32_UI_Input_Ime",
|
||||||
"Win32_UI_Input_KeyboardAndMouse",
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
"Win32_UI_Shell",
|
"Win32_UI_Shell",
|
||||||
|
"Win32_UI_Shell_Common",
|
||||||
"Win32_UI_WindowsAndMessaging",
|
"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]
|
[profile.dev]
|
||||||
split-debuginfo = "unpacked"
|
split-debuginfo = "unpacked"
|
||||||
debug = "limited"
|
debug = "limited"
|
||||||
|
|||||||
@@ -4,11 +4,38 @@ FROM rust:1.81-bookworm as builder
|
|||||||
WORKDIR app
|
WORKDIR app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Replace the Cargo configuration with the one used by collab.
|
||||||
|
COPY ./.cargo/collab-config.toml ./.cargo/config.toml
|
||||||
|
|
||||||
# Compile collab server
|
# Compile collab server
|
||||||
ARG CARGO_PROFILE_RELEASE_PANIC=abort
|
ARG CARGO_PROFILE_RELEASE_PANIC=abort
|
||||||
ARG GITHUB_SHA
|
ARG GITHUB_SHA
|
||||||
|
|
||||||
ENV GITHUB_SHA=$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 \
|
RUN --mount=type=cache,target=./script/node_modules \
|
||||||
--mount=type=cache,target=/usr/local/cargo/registry \
|
--mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
--mount=type=cache,target=/usr/local/cargo/git \
|
--mount=type=cache,target=/usr/local/cargo/git \
|
||||||
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-select"><path d="M5 3a2 2 0 0 0-2 2"/><path d="M19 3a2 2 0 0 1 2 2"/><path d="M21 19a2 2 0 0 1-2 2"/><path d="M5 21a2 2 0 0 1-2-2"/><path d="M9 3h1"/><path d="M9 21h1"/><path d="M14 3h1"/><path d="M14 21h1"/><path d="M3 9v1"/><path d="M21 9v1"/><path d="M3 14v1"/><path d="M21 14v1"/><line x1="7" x2="15" y1="8" y2="8"/><line x1="7" x2="17" y1="12" y2="12"/><line x1="7" x2="13" y1="16" y2="16"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-cursor"><path d="M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1"/><path d="M7 22h1a4 4 0 0 0 4-4v-1"/><path d="M7 2h1a4 4 0 0 1 4 4v1"/></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 610 B After Width: | Height: | Size: 345 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-cursor"><path d="M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1"/><path d="M7 22h1a4 4 0 0 0 4-4v-1"/><path d="M7 2h1a4 4 0 0 1 4 4v1"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 345 B |
6
assets/icons/sliders_alt.svg
Normal file
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
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
assets/icons/text_snippet.svg
Normal file
1
assets/icons/text_snippet.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-text-select"><path d="M5 3a2 2 0 0 0-2 2"/><path d="M19 3a2 2 0 0 1 2 2"/><path d="M21 19a2 2 0 0 1-2 2"/><path d="M5 21a2 2 0 0 1-2-2"/><path d="M9 3h1"/><path d="M9 21h1"/><path d="M14 3h1"/><path d="M14 21h1"/><path d="M3 9v1"/><path d="M21 9v1"/><path d="M3 14v1"/><path d="M21 14v1"/><line x1="7" x2="15" y1="8" y2="8"/><line x1="7" x2="17" y1="12" y2="12"/><line x1="7" x2="13" y1="16" y2="16"/></svg>
|
||||||
|
After Width: | Height: | Size: 610 B |
@@ -56,6 +56,7 @@
|
|||||||
"shift-tab": "editor::TabPrev",
|
"shift-tab": "editor::TabPrev",
|
||||||
"ctrl-k": "editor::CutToEndOfLine",
|
"ctrl-k": "editor::CutToEndOfLine",
|
||||||
// "ctrl-t": "editor::Transpose",
|
// "ctrl-t": "editor::Transpose",
|
||||||
|
"alt-q": "editor::Rewrap",
|
||||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||||
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
||||||
"shift-delete": "editor::Cut",
|
"shift-delete": "editor::Cut",
|
||||||
@@ -165,6 +166,7 @@
|
|||||||
{
|
{
|
||||||
"context": "AssistantPanel",
|
"context": "AssistantPanel",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
"ctrl-k c": "assistant::CopyCode",
|
||||||
"ctrl-g": "search::SelectNextMatch",
|
"ctrl-g": "search::SelectNextMatch",
|
||||||
"ctrl-shift-g": "search::SelectPrevMatch",
|
"ctrl-shift-g": "search::SelectPrevMatch",
|
||||||
"alt-m": "assistant::ToggleModelSelector",
|
"alt-m": "assistant::ToggleModelSelector",
|
||||||
@@ -194,7 +196,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar && in_replace",
|
"context": "BufferSearchBar && in_replace > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "search::ReplaceNext",
|
"enter": "search::ReplaceNext",
|
||||||
"ctrl-enter": "search::ReplaceAll"
|
"ctrl-enter": "search::ReplaceAll"
|
||||||
@@ -308,6 +310,11 @@
|
|||||||
"ctrl-shift-\\": "editor::MoveToEnclosingBracket",
|
"ctrl-shift-\\": "editor::MoveToEnclosingBracket",
|
||||||
"ctrl-shift-[": "editor::Fold",
|
"ctrl-shift-[": "editor::Fold",
|
||||||
"ctrl-shift-]": "editor::UnfoldLines",
|
"ctrl-shift-]": "editor::UnfoldLines",
|
||||||
|
"ctrl-k ctrl-l": "editor::ToggleFold",
|
||||||
|
"ctrl-k ctrl-[": "editor::FoldRecursive",
|
||||||
|
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
|
||||||
|
"ctrl-k ctrl-0": "editor::FoldAll",
|
||||||
|
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
||||||
"ctrl-space": "editor::ShowCompletions",
|
"ctrl-space": "editor::ShowCompletions",
|
||||||
"ctrl-.": "editor::ToggleCodeActions",
|
"ctrl-.": "editor::ToggleCodeActions",
|
||||||
"alt-ctrl-r": "editor::RevealInFileManager",
|
"alt-ctrl-r": "editor::RevealInFileManager",
|
||||||
@@ -518,6 +525,13 @@
|
|||||||
"alt-enter": "editor::Newline"
|
"alt-enter": "editor::Newline"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "PromptEditor",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-[": "assistant::CyclePreviousInlineAssist",
|
||||||
|
"ctrl-]": "assistant::CycleNextInlineAssist"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectSearchBar && !in_replace",
|
"context": "ProjectSearchBar && !in_replace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
"shift-tab": "editor::TabPrev",
|
"shift-tab": "editor::TabPrev",
|
||||||
"ctrl-k": "editor::CutToEndOfLine",
|
"ctrl-k": "editor::CutToEndOfLine",
|
||||||
"ctrl-t": "editor::Transpose",
|
"ctrl-t": "editor::Transpose",
|
||||||
|
"alt-q": "editor::Rewrap",
|
||||||
"cmd-backspace": "editor::DeleteToBeginningOfLine",
|
"cmd-backspace": "editor::DeleteToBeginningOfLine",
|
||||||
"cmd-delete": "editor::DeleteToEndOfLine",
|
"cmd-delete": "editor::DeleteToEndOfLine",
|
||||||
"alt-backspace": "editor::DeleteToPreviousWordStart",
|
"alt-backspace": "editor::DeleteToPreviousWordStart",
|
||||||
@@ -187,6 +188,7 @@
|
|||||||
{
|
{
|
||||||
"context": "AssistantPanel",
|
"context": "AssistantPanel",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
"cmd-k c": "assistant::CopyCode",
|
||||||
"cmd-g": "search::SelectNextMatch",
|
"cmd-g": "search::SelectNextMatch",
|
||||||
"cmd-shift-g": "search::SelectPrevMatch",
|
"cmd-shift-g": "search::SelectPrevMatch",
|
||||||
"alt-m": "assistant::ToggleModelSelector",
|
"alt-m": "assistant::ToggleModelSelector",
|
||||||
@@ -230,7 +232,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar && in_replace",
|
"context": "BufferSearchBar && in_replace > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "search::ReplaceNext",
|
"enter": "search::ReplaceNext",
|
||||||
"cmd-enter": "search::ReplaceAll"
|
"cmd-enter": "search::ReplaceAll"
|
||||||
@@ -345,6 +347,11 @@
|
|||||||
"cmd-shift-\\": "editor::MoveToEnclosingBracket",
|
"cmd-shift-\\": "editor::MoveToEnclosingBracket",
|
||||||
"alt-cmd-[": "editor::Fold",
|
"alt-cmd-[": "editor::Fold",
|
||||||
"alt-cmd-]": "editor::UnfoldLines",
|
"alt-cmd-]": "editor::UnfoldLines",
|
||||||
|
"cmd-k cmd-l": "editor::ToggleFold",
|
||||||
|
"cmd-k cmd-[": "editor::FoldRecursive",
|
||||||
|
"cmd-k cmd-]": "editor::UnfoldRecursive",
|
||||||
|
"cmd-k cmd-0": "editor::FoldAll",
|
||||||
|
"cmd-k cmd-j": "editor::UnfoldAll",
|
||||||
"ctrl-space": "editor::ShowCompletions",
|
"ctrl-space": "editor::ShowCompletions",
|
||||||
"cmd-.": "editor::ToggleCodeActions",
|
"cmd-.": "editor::ToggleCodeActions",
|
||||||
"alt-cmd-r": "editor::RevealInFileManager",
|
"alt-cmd-r": "editor::RevealInFileManager",
|
||||||
@@ -525,6 +532,13 @@
|
|||||||
"ctrl-enter": "assistant::InlineAssist"
|
"ctrl-enter": "assistant::InlineAssist"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "PromptEditor",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-[": "assistant::CyclePreviousInlineAssist",
|
||||||
|
"ctrl-]": "assistant::CycleNextInlineAssist"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectSearchBar && !in_replace",
|
"context": "ProjectSearchBar && !in_replace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|||||||
@@ -132,9 +132,15 @@
|
|||||||
"z z": "editor::ScrollCursorCenter",
|
"z z": "editor::ScrollCursorCenter",
|
||||||
"z .": ["workspace::SendKeystrokes", "z z ^"],
|
"z .": ["workspace::SendKeystrokes", "z z ^"],
|
||||||
"z b": "editor::ScrollCursorBottom",
|
"z b": "editor::ScrollCursorBottom",
|
||||||
|
"z a": "editor::ToggleFold",
|
||||||
|
"z A": "editor::ToggleFoldRecursive",
|
||||||
"z c": "editor::Fold",
|
"z c": "editor::Fold",
|
||||||
|
"z C": "editor::FoldRecursive",
|
||||||
"z o": "editor::UnfoldLines",
|
"z o": "editor::UnfoldLines",
|
||||||
|
"z O": "editor::UnfoldRecursive",
|
||||||
"z f": "editor::FoldSelectedRanges",
|
"z f": "editor::FoldSelectedRanges",
|
||||||
|
"z M": "editor::FoldAll",
|
||||||
|
"z R": "editor::UnfoldAll",
|
||||||
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
|
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
|
||||||
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
|
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
|
||||||
// Count support
|
// Count support
|
||||||
@@ -239,6 +245,8 @@
|
|||||||
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
||||||
"g ~": ["vim::PushOperator", "OppositeCase"],
|
"g ~": ["vim::PushOperator", "OppositeCase"],
|
||||||
"\"": ["vim::PushOperator", "Register"],
|
"\"": ["vim::PushOperator", "Register"],
|
||||||
|
"g q": ["vim::PushOperator", "Rewrap"],
|
||||||
|
"g w": ["vim::PushOperator", "Rewrap"],
|
||||||
"q": "vim::ToggleRecord",
|
"q": "vim::ToggleRecord",
|
||||||
"shift-q": "vim::ReplayLastRecording",
|
"shift-q": "vim::ReplayLastRecording",
|
||||||
"@": ["vim::PushOperator", "ReplayRegister"],
|
"@": ["vim::PushOperator", "ReplayRegister"],
|
||||||
@@ -290,6 +298,8 @@
|
|||||||
"g ctrl-x": ["vim::Decrement", { "step": true }],
|
"g ctrl-x": ["vim::Decrement", { "step": true }],
|
||||||
"shift-i": "vim::InsertBefore",
|
"shift-i": "vim::InsertBefore",
|
||||||
"shift-a": "vim::InsertAfter",
|
"shift-a": "vim::InsertAfter",
|
||||||
|
"g I": "vim::VisualInsertFirstNonWhiteSpace",
|
||||||
|
"g A": "vim::VisualInsertEndOfLine",
|
||||||
"shift-j": "vim::JoinLines",
|
"shift-j": "vim::JoinLines",
|
||||||
"r": ["vim::PushOperator", "Replace"],
|
"r": ["vim::PushOperator", "Replace"],
|
||||||
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||||
@@ -300,6 +310,7 @@
|
|||||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||||
"g c": "vim::ToggleComments",
|
"g c": "vim::ToggleComments",
|
||||||
|
"g q": "vim::Rewrap",
|
||||||
"\"": ["vim::PushOperator", "Register"],
|
"\"": ["vim::PushOperator", "Register"],
|
||||||
// tree-sitter related commands
|
// tree-sitter related commands
|
||||||
"[ x": "editor::SelectLargerSyntaxNode",
|
"[ x": "editor::SelectLargerSyntaxNode",
|
||||||
@@ -427,6 +438,15 @@
|
|||||||
"~": "vim::CurrentLine"
|
"~": "vim::CurrentLine"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "vim_operator == gq",
|
||||||
|
"bindings": {
|
||||||
|
"g q": "vim::CurrentLine",
|
||||||
|
"q": "vim::CurrentLine",
|
||||||
|
"g w": "vim::CurrentLine",
|
||||||
|
"w": "vim::CurrentLine"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == y",
|
"context": "vim_operator == y",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|||||||
@@ -47,6 +47,17 @@ And here's the section to rewrite based on that prompt again for reference:
|
|||||||
<rewrite_this>
|
<rewrite_this>
|
||||||
{{{rewrite_section}}}
|
{{{rewrite_section}}}
|
||||||
</rewrite_this>
|
</rewrite_this>
|
||||||
|
|
||||||
|
{{#if diagnostic_errors}}
|
||||||
|
{{#each diagnostic_errors}}
|
||||||
|
<diagnostic_error>
|
||||||
|
<line_number>{{line_number}}</line_number>
|
||||||
|
<error_message>{{error_message}}</error_message>
|
||||||
|
<code_content>{{code_content}}</code_content>
|
||||||
|
</diagnostic_error>
|
||||||
|
{{/each}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding {{content_type}} will be preserved.
|
Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding {{content_type}} will be preserved.
|
||||||
|
|||||||
8
assets/prompts/project_slash_command.hbs
Normal file
8
assets/prompts/project_slash_command.hbs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
A software developer is asking a question about their project. The source files in their project have been indexed into a database of semantic text embeddings.
|
||||||
|
Your task is to generate a list of 4 diverse search queries that can be run on this embedding database, in order to retrieve a list of code snippets
|
||||||
|
that are relevant to the developer's question. Redundant search queries will be heavily penalized, so only include another query if it's sufficiently
|
||||||
|
distinct from previous ones.
|
||||||
|
|
||||||
|
Here is the question that's been asked, together with context that the developer has added manually:
|
||||||
|
|
||||||
|
{{{context_buffer}}}
|
||||||
@@ -15,9 +15,11 @@
|
|||||||
// text editor:
|
// text editor:
|
||||||
//
|
//
|
||||||
// 1. "VSCode"
|
// 1. "VSCode"
|
||||||
// 2. "JetBrains"
|
// 2. "Atom"
|
||||||
// 3. "SublimeText"
|
// 3. "JetBrains"
|
||||||
// 4. "Atom"
|
// 4. "None"
|
||||||
|
// 5. "SublimeText"
|
||||||
|
// 6. "TextMate"
|
||||||
"base_keymap": "VSCode",
|
"base_keymap": "VSCode",
|
||||||
// Features that can be globally enabled or disabled
|
// Features that can be globally enabled or disabled
|
||||||
"features": {
|
"features": {
|
||||||
@@ -111,6 +113,18 @@
|
|||||||
"use_system_path_prompts": true,
|
"use_system_path_prompts": true,
|
||||||
// Whether the cursor blinks in the editor.
|
// Whether the cursor blinks in the editor.
|
||||||
"cursor_blink": true,
|
"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.
|
// How to highlight the current line in the editor.
|
||||||
//
|
//
|
||||||
// 1. Don't highlight the current line:
|
// 1. Don't highlight the current line:
|
||||||
@@ -306,6 +320,10 @@
|
|||||||
"show_parameter_hints": true,
|
"show_parameter_hints": true,
|
||||||
// Corresponds to null/None LSP hint type value.
|
// Corresponds to null/None LSP hint type value.
|
||||||
"show_other_hints": true,
|
"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,
|
// Time to wait after editing the buffer, before requesting the hints,
|
||||||
// set to 0 to disable debouncing.
|
// set to 0 to disable debouncing.
|
||||||
"edit_debounce_ms": 700,
|
"edit_debounce_ms": 700,
|
||||||
@@ -480,6 +498,11 @@
|
|||||||
// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab.
|
// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab.
|
||||||
"enable_preview_from_code_navigation": false
|
"enable_preview_from_code_navigation": false
|
||||||
},
|
},
|
||||||
|
// Settings related to the file finder.
|
||||||
|
"file_finder": {
|
||||||
|
// Whether to show file icons in the file finder.
|
||||||
|
"file_icons": true
|
||||||
|
},
|
||||||
// Whether or not to remove any trailing whitespace from lines of a buffer
|
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||||
// before saving it.
|
// before saving it.
|
||||||
"remove_trailing_whitespace_on_save": true,
|
"remove_trailing_whitespace_on_save": true,
|
||||||
@@ -512,17 +535,16 @@
|
|||||||
// How to soft-wrap long lines of text.
|
// How to soft-wrap long lines of text.
|
||||||
// Possible values:
|
// Possible values:
|
||||||
//
|
//
|
||||||
// 1. Do not soft wrap.
|
// 1. Prefer a single line generally, unless an overly long line is encountered.
|
||||||
// "soft_wrap": "none",
|
// "soft_wrap": "none",
|
||||||
// 2. Prefer a single line generally, unless an overly long line is encountered.
|
// "soft_wrap": "prefer_line", // (deprecated, same as "none")
|
||||||
// "soft_wrap": "prefer_line",
|
// 2. Soft wrap lines that overflow the editor.
|
||||||
// 3. Soft wrap lines that overflow the editor.
|
|
||||||
// "soft_wrap": "editor_width",
|
// "soft_wrap": "editor_width",
|
||||||
// 4. Soft wrap lines at the preferred line length.
|
// 3. Soft wrap lines at the preferred line length.
|
||||||
// "soft_wrap": "preferred_line_length",
|
// "soft_wrap": "preferred_line_length",
|
||||||
// 5. Soft wrap lines at the preferred line length or the editor width (whichever is smaller).
|
// 4. Soft wrap lines at the preferred line length or the editor width (whichever is smaller).
|
||||||
// "soft_wrap": "bounded",
|
// "soft_wrap": "bounded",
|
||||||
"soft_wrap": "prefer_line",
|
"soft_wrap": "none",
|
||||||
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
||||||
// is enabled.
|
// is enabled.
|
||||||
"preferred_line_length": 80,
|
"preferred_line_length": 80,
|
||||||
@@ -577,13 +599,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Configuration for how direnv configuration should be loaded. May take 2 values:
|
// Configuration for how direnv configuration should be loaded. May take 2 values:
|
||||||
// 1. Load direnv configuration through the shell hook, works for POSIX shells and fish.
|
// 1. Load direnv configuration using `direnv export json` directly.
|
||||||
// "load_direnv": "shell_hook"
|
|
||||||
// 2. Load direnv configuration using `direnv export json` directly.
|
|
||||||
// This can help with some shells that otherwise would not detect
|
|
||||||
// the direnv environment, such as nushell or elvish.
|
|
||||||
// "load_direnv": "direct"
|
// "load_direnv": "direct"
|
||||||
"load_direnv": "shell_hook",
|
// 2. Load direnv configuration through the shell hook, works for POSIX shells and fish.
|
||||||
|
// "load_direnv": "shell_hook"
|
||||||
|
"load_direnv": "direct",
|
||||||
"inline_completions": {
|
"inline_completions": {
|
||||||
// A list of globs representing files that inline completions should be disabled for.
|
// A list of globs representing files that inline completions should be disabled for.
|
||||||
"disabled_globs": [".env"]
|
"disabled_globs": [".env"]
|
||||||
@@ -649,6 +669,18 @@
|
|||||||
// 3. Always blink the cursor, ignoring the terminal mode
|
// 3. Always blink the cursor, ignoring the terminal mode
|
||||||
// "blinking": "on",
|
// "blinking": "on",
|
||||||
"blinking": "terminal_controlled",
|
"blinking": "terminal_controlled",
|
||||||
|
// Default cursor shape for the terminal.
|
||||||
|
// 1. A block that surrounds the following character
|
||||||
|
// "block"
|
||||||
|
// 2. A vertical bar
|
||||||
|
// "bar"
|
||||||
|
// 3. An underline that runs along the following character
|
||||||
|
// "underscore"
|
||||||
|
// 4. A box drawn around the following character
|
||||||
|
// "hollow"
|
||||||
|
//
|
||||||
|
// Default: not set, defaults to "block"
|
||||||
|
"cursor_shape": null,
|
||||||
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
|
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
|
||||||
// Alternate Scroll mode converts mouse scroll events into up / down key
|
// Alternate Scroll mode converts mouse scroll events into up / down key
|
||||||
// presses when in the alternate screen (e.g. when running applications
|
// presses when in the alternate screen (e.g. when running applications
|
||||||
@@ -698,7 +730,7 @@
|
|||||||
// to the current working directory. We recommend overriding this
|
// to the current working directory. We recommend overriding this
|
||||||
// in your project's settings, rather than globally.
|
// in your project's settings, rather than globally.
|
||||||
"directories": [".env", "env", ".venv", "venv"],
|
"directories": [".env", "env", ".venv", "venv"],
|
||||||
// Can also be `csh`, `fish`, and `nushell`
|
// Can also be `csh`, `fish`, `nushell` and `power_shell`
|
||||||
"activate_script": "default"
|
"activate_script": "default"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -739,6 +771,7 @@
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
"file_types": {
|
"file_types": {
|
||||||
|
"Plain Text": ["txt"],
|
||||||
"JSON": ["flake.lock"],
|
"JSON": ["flake.lock"],
|
||||||
"JSONC": [
|
"JSONC": [
|
||||||
"**/.zed/**/*.json",
|
"**/.zed/**/*.json",
|
||||||
@@ -746,8 +779,24 @@
|
|||||||
"**/Zed/**/*.json",
|
"**/Zed/**/*.json",
|
||||||
"tsconfig.json",
|
"tsconfig.json",
|
||||||
"pyrightconfig.json"
|
"pyrightconfig.json"
|
||||||
]
|
],
|
||||||
|
"TOML": ["uv.lock"]
|
||||||
},
|
},
|
||||||
|
/// By default use a recent system version of node, or install our own.
|
||||||
|
/// You can override this to use a version of node that is not in $PATH with:
|
||||||
|
/// {
|
||||||
|
/// "node": {
|
||||||
|
/// "node_path": "/path/to/node"
|
||||||
|
/// "npm_path": "/path/to/npm" (defaults to node_path/../npm)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// or to ensure Zed always downloads and installs an isolated version of node:
|
||||||
|
/// {
|
||||||
|
/// "node": {
|
||||||
|
/// "ignore_system_version": true,
|
||||||
|
/// }
|
||||||
|
/// NOTE: changing this setting currently requires restarting Zed.
|
||||||
|
"node": {},
|
||||||
// The extensions that Zed should automatically install on startup.
|
// The extensions that Zed should automatically install on startup.
|
||||||
//
|
//
|
||||||
// If you don't want any of these extensions, add this field to your settings
|
// If you don't want any of these extensions, add this field to your settings
|
||||||
@@ -1013,7 +1062,7 @@
|
|||||||
// environment variables.
|
// environment variables.
|
||||||
//
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
// - "proxy": "socks5://localhost:10808"
|
// - "proxy": "socks5h://localhost:10808"
|
||||||
// - "proxy": "http://127.0.0.1:10809"
|
// - "proxy": "http://127.0.0.1:10809"
|
||||||
"proxy": null,
|
"proxy": null,
|
||||||
// Set to configure aliases for the command palette.
|
// Set to configure aliases for the command palette.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
//
|
//
|
||||||
// To see all of Zed's default settings without changing your
|
// To see all of Zed's default settings without changing your
|
||||||
// custom settings, run `zed: open default settings` from the
|
// custom settings, run `zed: open default settings` from the
|
||||||
// command palette
|
// command palette (cmd-shift-p / ctrl-shift-p)
|
||||||
{
|
{
|
||||||
"ui_font_size": 16,
|
"ui_font_size": 16,
|
||||||
"buffer_font_size": 16,
|
"buffer_font_size": 16,
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
|||||||
actions!(activity_indicator, [ShowErrorMessage]);
|
actions!(activity_indicator, [ShowErrorMessage]);
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
ShowError { lsp_name: Arc<str>, error: String },
|
ShowError {
|
||||||
|
lsp_name: LanguageServerName,
|
||||||
|
error: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ActivityIndicator {
|
pub struct ActivityIndicator {
|
||||||
@@ -123,7 +126,7 @@ impl ActivityIndicator {
|
|||||||
self.statuses.retain(|status| {
|
self.statuses.retain(|status| {
|
||||||
if let LanguageServerBinaryStatus::Failed { error } = &status.status {
|
if let LanguageServerBinaryStatus::Failed { error } = &status.status {
|
||||||
cx.emit(Event::ShowError {
|
cx.emit(Event::ShowError {
|
||||||
lsp_name: status.name.0.clone(),
|
lsp_name: status.name.clone(),
|
||||||
error: error.clone(),
|
error: error.clone(),
|
||||||
});
|
});
|
||||||
false
|
false
|
||||||
@@ -224,10 +227,10 @@ impl ActivityIndicator {
|
|||||||
for status in &self.statuses {
|
for status in &self.statuses {
|
||||||
match status.status {
|
match status.status {
|
||||||
LanguageServerBinaryStatus::CheckingForUpdate => {
|
LanguageServerBinaryStatus::CheckingForUpdate => {
|
||||||
checking_for_update.push(status.name.0.as_ref())
|
checking_for_update.push(status.name.clone())
|
||||||
}
|
}
|
||||||
LanguageServerBinaryStatus::Downloading => downloading.push(status.name.0.as_ref()),
|
LanguageServerBinaryStatus::Downloading => downloading.push(status.name.clone()),
|
||||||
LanguageServerBinaryStatus::Failed { .. } => failed.push(status.name.0.as_ref()),
|
LanguageServerBinaryStatus::Failed { .. } => failed.push(status.name.clone()),
|
||||||
LanguageServerBinaryStatus::None => {}
|
LanguageServerBinaryStatus::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,8 +242,24 @@ impl ActivityIndicator {
|
|||||||
.size(IconSize::Small)
|
.size(IconSize::Small)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: format!("Downloading {}...", downloading.join(", "),),
|
message: format!(
|
||||||
on_click: None,
|
"Downloading {}...",
|
||||||
|
downloading.iter().map(|name| name.0.as_ref()).fold(
|
||||||
|
String::new(),
|
||||||
|
|mut acc, s| {
|
||||||
|
if !acc.is_empty() {
|
||||||
|
acc.push_str(", ");
|
||||||
|
}
|
||||||
|
acc.push_str(s);
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
on_click: Some(Arc::new(move |this, cx| {
|
||||||
|
this.statuses
|
||||||
|
.retain(|status| !downloading.contains(&status.name));
|
||||||
|
this.dismiss_error_message(&DismissErrorMessage, cx)
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,9 +272,22 @@ impl ActivityIndicator {
|
|||||||
),
|
),
|
||||||
message: format!(
|
message: format!(
|
||||||
"Checking for updates to {}...",
|
"Checking for updates to {}...",
|
||||||
checking_for_update.join(", "),
|
checking_for_update.iter().map(|name| name.0.as_ref()).fold(
|
||||||
|
String::new(),
|
||||||
|
|mut acc, s| {
|
||||||
|
if !acc.is_empty() {
|
||||||
|
acc.push_str(", ");
|
||||||
|
}
|
||||||
|
acc.push_str(s);
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
),
|
||||||
),
|
),
|
||||||
on_click: None,
|
on_click: Some(Arc::new(move |this, cx| {
|
||||||
|
this.statuses
|
||||||
|
.retain(|status| !checking_for_update.contains(&status.name));
|
||||||
|
this.dismiss_error_message(&DismissErrorMessage, cx)
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,8 +299,17 @@ impl ActivityIndicator {
|
|||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: format!(
|
message: format!(
|
||||||
"Failed to download {}. Click to show error.",
|
"Failed to run {}. Click to show error.",
|
||||||
failed.join(", "),
|
failed
|
||||||
|
.iter()
|
||||||
|
.map(|name| name.0.as_ref())
|
||||||
|
.fold(String::new(), |mut acc, s| {
|
||||||
|
if !acc.is_empty() {
|
||||||
|
acc.push_str(", ");
|
||||||
|
}
|
||||||
|
acc.push_str(s);
|
||||||
|
acc
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
on_click: Some(Arc::new(|this, cx| {
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
this.show_error_message(&Default::default(), cx)
|
this.show_error_message(&Default::default(), cx)
|
||||||
@@ -277,7 +318,7 @@ impl ActivityIndicator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show any formatting failure
|
// Show any formatting failure
|
||||||
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
|
if let Some(failure) = self.project.read(cx).last_formatting_failure(cx) {
|
||||||
return Some(Content {
|
return Some(Content {
|
||||||
icon: Some(
|
icon: Some(
|
||||||
Icon::new(IconName::Warning)
|
Icon::new(IconName::Warning)
|
||||||
@@ -301,7 +342,9 @@ impl ActivityIndicator {
|
|||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: "Checking for Zed updates…".to_string(),
|
message: "Checking for Zed updates…".to_string(),
|
||||||
on_click: None,
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
|
this.dismiss_error_message(&DismissErrorMessage, cx)
|
||||||
|
})),
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Downloading => Some(Content {
|
AutoUpdateStatus::Downloading => Some(Content {
|
||||||
icon: Some(
|
icon: Some(
|
||||||
@@ -310,7 +353,9 @@ impl ActivityIndicator {
|
|||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: "Downloading Zed update…".to_string(),
|
message: "Downloading Zed update…".to_string(),
|
||||||
on_click: None,
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
|
this.dismiss_error_message(&DismissErrorMessage, cx)
|
||||||
|
})),
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Installing => Some(Content {
|
AutoUpdateStatus::Installing => Some(Content {
|
||||||
icon: Some(
|
icon: Some(
|
||||||
@@ -319,7 +364,9 @@ impl ActivityIndicator {
|
|||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: "Installing Zed update…".to_string(),
|
message: "Installing Zed update…".to_string(),
|
||||||
on_click: None,
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
|
this.dismiss_error_message(&DismissErrorMessage, cx)
|
||||||
|
})),
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Updated { binary_path } => Some(Content {
|
AutoUpdateStatus::Updated { binary_path } => Some(Content {
|
||||||
icon: None,
|
icon: None,
|
||||||
@@ -339,7 +386,7 @@ impl ActivityIndicator {
|
|||||||
),
|
),
|
||||||
message: "Auto update failed".to_string(),
|
message: "Auto update failed".to_string(),
|
||||||
on_click: Some(Arc::new(|this, cx| {
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
this.dismiss_error_message(&Default::default(), cx)
|
this.dismiss_error_message(&DismissErrorMessage, cx)
|
||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Idle => None,
|
AutoUpdateStatus::Idle => None,
|
||||||
@@ -357,7 +404,9 @@ impl ActivityIndicator {
|
|||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: format!("Updating {extension_id} extension…"),
|
message: format!("Updating {extension_id} extension…"),
|
||||||
on_click: None,
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
|
this.dismiss_error_message(&DismissErrorMessage, cx)
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ anyhow.workspace = true
|
|||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
isahc.workspace = true
|
|
||||||
schemars = { workspace = true, optional = true }
|
schemars = { workspace = true, optional = true }
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ use std::{pin::Pin, str::FromStr};
|
|||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
|
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
|
||||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
use http_client::http::{HeaderMap, HeaderValue};
|
||||||
use isahc::config::Configurable;
|
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
|
||||||
use isahc::http::{HeaderMap, HeaderValue};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::{EnumIter, EnumString};
|
use strum::{EnumIter, EnumString};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -49,6 +48,7 @@ pub enum Model {
|
|||||||
/// Indicates whether this custom model supports caching.
|
/// Indicates whether this custom model supports caching.
|
||||||
cache_configuration: Option<AnthropicModelCacheConfiguration>,
|
cache_configuration: Option<AnthropicModelCacheConfiguration>,
|
||||||
max_output_tokens: Option<u32>,
|
max_output_tokens: Option<u32>,
|
||||||
|
default_temperature: Option<f32>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +124,19 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn default_temperature(&self) -> f32 {
|
||||||
|
match self {
|
||||||
|
Self::Claude3_5Sonnet
|
||||||
|
| Self::Claude3Opus
|
||||||
|
| Self::Claude3Sonnet
|
||||||
|
| Self::Claude3Haiku => 1.0,
|
||||||
|
Self::Custom {
|
||||||
|
default_temperature,
|
||||||
|
..
|
||||||
|
} => default_temperature.unwrap_or(1.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn tool_model_id(&self) -> &str {
|
pub fn tool_model_id(&self) -> &str {
|
||||||
if let Self::Custom {
|
if let Self::Custom {
|
||||||
tool_override: Some(tool_override),
|
tool_override: Some(tool_override),
|
||||||
@@ -275,7 +288,7 @@ pub async fn stream_completion_with_rate_limit_info(
|
|||||||
.header("X-Api-Key", api_key)
|
.header("X-Api-Key", api_key)
|
||||||
.header("Content-Type", "application/json");
|
.header("Content-Type", "application/json");
|
||||||
if let Some(low_speed_timeout) = low_speed_timeout {
|
if let Some(low_speed_timeout) = low_speed_timeout {
|
||||||
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
|
request_builder = request_builder.read_timeout(low_speed_timeout);
|
||||||
}
|
}
|
||||||
let serialized_request =
|
let serialized_request =
|
||||||
serde_json::to_string(&request).context("failed to serialize request")?;
|
serde_json::to_string(&request).context("failed to serialize request")?;
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ indoc.workspace = true
|
|||||||
language.workspace = true
|
language.workspace = true
|
||||||
language_model.workspace = true
|
language_model.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
lsp.workspace = true
|
||||||
markdown.workspace = true
|
markdown.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
multi_buffer.workspace = true
|
multi_buffer.workspace = true
|
||||||
@@ -65,6 +66,7 @@ proto.workspace = true
|
|||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
release_channel.workspace = true
|
release_channel.workspace = true
|
||||||
rope.workspace = true
|
rope.workspace = true
|
||||||
|
rpc.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
search.workspace = true
|
search.workspace = true
|
||||||
semantic_index.workspace = true
|
semantic_index.workspace = true
|
||||||
@@ -93,9 +95,11 @@ editor = { workspace = true, features = ["test-support"] }
|
|||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
language = { workspace = true, features = ["test-support"] }
|
language = { workspace = true, features = ["test-support"] }
|
||||||
language_model = { workspace = true, features = ["test-support"] }
|
language_model = { workspace = true, features = ["test-support"] }
|
||||||
|
languages = { workspace = true, features = ["test-support"] }
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
project = { workspace = true, features = ["test-support"] }
|
project = { workspace = true, features = ["test-support"] }
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
serde_json_lenient.workspace = true
|
serde_json_lenient.workspace = true
|
||||||
text = { workspace = true, features = ["test-support"] }
|
text = { workspace = true, features = ["test-support"] }
|
||||||
|
tree-sitter-md.workspace = true
|
||||||
unindent.workspace = true
|
unindent.workspace = true
|
||||||
|
|||||||
@@ -41,9 +41,10 @@ use semantic_index::{CloudEmbeddingProvider, SemanticDb};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{update_settings_file, Settings, SettingsStore};
|
use settings::{update_settings_file, Settings, SettingsStore};
|
||||||
use slash_command::{
|
use slash_command::{
|
||||||
auto_command, context_server_command, default_command, diagnostics_command, docs_command,
|
auto_command, cargo_workspace_command, context_server_command, default_command, delta_command,
|
||||||
fetch_command, file_command, now_command, project_command, prompt_command, search_command,
|
diagnostics_command, docs_command, fetch_command, file_command, now_command, project_command,
|
||||||
symbols_command, tab_command, terminal_command, workflow_command,
|
prompt_command, search_command, symbols_command, tab_command, terminal_command,
|
||||||
|
workflow_command,
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -58,6 +59,7 @@ actions!(
|
|||||||
[
|
[
|
||||||
Assist,
|
Assist,
|
||||||
Split,
|
Split,
|
||||||
|
CopyCode,
|
||||||
CycleMessageRole,
|
CycleMessageRole,
|
||||||
QuoteSelection,
|
QuoteSelection,
|
||||||
InsertIntoEditor,
|
InsertIntoEditor,
|
||||||
@@ -68,6 +70,8 @@ actions!(
|
|||||||
ConfirmCommand,
|
ConfirmCommand,
|
||||||
NewContext,
|
NewContext,
|
||||||
ToggleModelSelector,
|
ToggleModelSelector,
|
||||||
|
CycleNextInlineAssist,
|
||||||
|
CyclePreviousInlineAssist
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -358,8 +362,19 @@ fn update_active_language_model_from_settings(cx: &mut AppContext) {
|
|||||||
let settings = AssistantSettings::get_global(cx);
|
let settings = AssistantSettings::get_global(cx);
|
||||||
let provider_name = LanguageModelProviderId::from(settings.default_model.provider.clone());
|
let provider_name = LanguageModelProviderId::from(settings.default_model.provider.clone());
|
||||||
let model_id = LanguageModelId::from(settings.default_model.model.clone());
|
let model_id = LanguageModelId::from(settings.default_model.model.clone());
|
||||||
|
let inline_alternatives = settings
|
||||||
|
.inline_alternatives
|
||||||
|
.iter()
|
||||||
|
.map(|alternative| {
|
||||||
|
(
|
||||||
|
LanguageModelProviderId::from(alternative.provider.clone()),
|
||||||
|
LanguageModelId::from(alternative.model.clone()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||||
registry.select_active_model(&provider_name, &model_id, cx);
|
registry.select_active_model(&provider_name, &model_id, cx);
|
||||||
|
registry.select_inline_alternative_models(inline_alternatives, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,22 +382,36 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
|||||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||||
|
|
||||||
slash_command_registry.register_command(file_command::FileSlashCommand, true);
|
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(symbols_command::OutlineSlashCommand, true);
|
||||||
slash_command_registry.register_command(tab_command::TabSlashCommand, true);
|
slash_command_registry.register_command(tab_command::TabSlashCommand, true);
|
||||||
slash_command_registry.register_command(project_command::ProjectSlashCommand, true);
|
slash_command_registry
|
||||||
|
.register_command(cargo_workspace_command::CargoWorkspaceSlashCommand, true);
|
||||||
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
|
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
|
||||||
slash_command_registry.register_command(default_command::DefaultSlashCommand, false);
|
slash_command_registry.register_command(default_command::DefaultSlashCommand, false);
|
||||||
slash_command_registry.register_command(terminal_command::TerminalSlashCommand, true);
|
slash_command_registry.register_command(terminal_command::TerminalSlashCommand, true);
|
||||||
slash_command_registry.register_command(now_command::NowSlashCommand, false);
|
slash_command_registry.register_command(now_command::NowSlashCommand, false);
|
||||||
slash_command_registry.register_command(diagnostics_command::DiagnosticsSlashCommand, true);
|
slash_command_registry.register_command(diagnostics_command::DiagnosticsSlashCommand, true);
|
||||||
|
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
||||||
|
|
||||||
if let Some(prompt_builder) = prompt_builder {
|
if let Some(prompt_builder) = prompt_builder {
|
||||||
slash_command_registry.register_command(
|
slash_command_registry.register_command(
|
||||||
workflow_command::WorkflowSlashCommand::new(prompt_builder.clone()),
|
workflow_command::WorkflowSlashCommand::new(prompt_builder.clone()),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
cx.observe_flag::<project_command::ProjectSlashCommandFeatureFlag, _>({
|
||||||
|
let slash_command_registry = slash_command_registry.clone();
|
||||||
|
move |is_enabled, _cx| {
|
||||||
|
if is_enabled {
|
||||||
|
slash_command_registry.register_command(
|
||||||
|
project_command::ProjectSlashCommand::new(prompt_builder.clone()),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
}
|
}
|
||||||
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
|
||||||
|
|
||||||
cx.observe_flag::<auto_command::AutoSlashCommandFeatureFlag, _>({
|
cx.observe_flag::<auto_command::AutoSlashCommandFeatureFlag, _>({
|
||||||
let slash_command_registry = slash_command_registry.clone();
|
let slash_command_registry = slash_command_registry.clone();
|
||||||
@@ -420,10 +449,12 @@ fn update_slash_commands_from_settings(cx: &mut AppContext) {
|
|||||||
slash_command_registry.unregister_command(docs_command::DocsSlashCommand);
|
slash_command_registry.unregister_command(docs_command::DocsSlashCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.project.enabled {
|
if settings.cargo_workspace.enabled {
|
||||||
slash_command_registry.register_command(project_command::ProjectSlashCommand, true);
|
slash_command_registry
|
||||||
|
.register_command(cargo_workspace_command::CargoWorkspaceSlashCommand, true);
|
||||||
} else {
|
} else {
|
||||||
slash_command_registry.unregister_command(project_command::ProjectSlashCommand);
|
slash_command_registry
|
||||||
|
.unregister_command(cargo_workspace_command::CargoWorkspaceSlashCommand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ use crate::{
|
|||||||
slash_command_picker,
|
slash_command_picker,
|
||||||
terminal_inline_assistant::TerminalInlineAssistant,
|
terminal_inline_assistant::TerminalInlineAssistant,
|
||||||
Assist, CacheStatus, ConfirmCommand, Content, Context, ContextEvent, ContextId, ContextStore,
|
Assist, CacheStatus, ConfirmCommand, Content, Context, ContextEvent, ContextId, ContextStore,
|
||||||
ContextStoreEvent, CycleMessageRole, DeployHistory, DeployPromptLibrary, InlineAssistId,
|
ContextStoreEvent, CopyCode, CycleMessageRole, DeployHistory, DeployPromptLibrary,
|
||||||
InlineAssistant, InsertDraggedFiles, InsertIntoEditor, Message, MessageId, MessageMetadata,
|
InlineAssistId, InlineAssistant, InsertDraggedFiles, InsertIntoEditor, Message, MessageId,
|
||||||
MessageStatus, ModelPickerDelegate, ModelSelector, NewContext, PendingSlashCommand,
|
MessageMetadata, MessageStatus, ModelPickerDelegate, ModelSelector, NewContext,
|
||||||
PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, SavedContextMetadata, Split,
|
PendingSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata,
|
||||||
ToggleFocus, ToggleModelSelector, WorkflowStepResolution,
|
SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector, WorkflowStepResolution,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
|
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
|
||||||
@@ -45,7 +45,8 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use indexed_docs::IndexedDocsStore;
|
use indexed_docs::IndexedDocsStore;
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::SoftWrap, Capability, LanguageRegistry, LspAdapterDelegate, Point, ToOffset,
|
language_settings::SoftWrap, BufferSnapshot, Capability, LanguageRegistry, LspAdapterDelegate,
|
||||||
|
ToOffset,
|
||||||
};
|
};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
|
provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
|
||||||
@@ -54,8 +55,9 @@ use language_model::{
|
|||||||
use language_model::{LanguageModelImage, LanguageModelToolUse};
|
use language_model::{LanguageModelImage, LanguageModelToolUse};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use project::lsp_store::ProjectLspAdapterDelegate;
|
use project::lsp_store::LocalLspAdapterDelegate;
|
||||||
use project::{Project, Worktree};
|
use project::{Project, Worktree};
|
||||||
|
use rope::Point;
|
||||||
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{update_settings_file, Settings};
|
use settings::{update_settings_file, Settings};
|
||||||
@@ -70,6 +72,7 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||||
|
use text::SelectionGoal;
|
||||||
use ui::TintColor;
|
use ui::TintColor;
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@@ -81,9 +84,10 @@ use util::{maybe, ResultExt};
|
|||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
item::{self, FollowableItem, Item, ItemHandle},
|
item::{self, FollowableItem, Item, ItemHandle},
|
||||||
|
notifications::NotificationId,
|
||||||
pane::{self, SaveIntent},
|
pane::{self, SaveIntent},
|
||||||
searchable::{SearchEvent, SearchableItem},
|
searchable::{SearchEvent, SearchableItem},
|
||||||
DraggedSelection, Pane, Save, ShowConfiguration, ToggleZoom, ToolbarItemEvent,
|
DraggedSelection, Pane, Save, ShowConfiguration, Toast, ToggleZoom, ToolbarItemEvent,
|
||||||
ToolbarItemLocation, ToolbarItemView, Workspace,
|
ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||||
};
|
};
|
||||||
use workspace::{searchable::SearchableItemHandle, DraggedTab};
|
use workspace::{searchable::SearchableItemHandle, DraggedTab};
|
||||||
@@ -105,6 +109,7 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
.register_action(AssistantPanel::inline_assist)
|
.register_action(AssistantPanel::inline_assist)
|
||||||
.register_action(ContextEditor::quote_selection)
|
.register_action(ContextEditor::quote_selection)
|
||||||
.register_action(ContextEditor::insert_selection)
|
.register_action(ContextEditor::insert_selection)
|
||||||
|
.register_action(ContextEditor::copy_code)
|
||||||
.register_action(ContextEditor::insert_dragged_files)
|
.register_action(ContextEditor::insert_dragged_files)
|
||||||
.register_action(AssistantPanel::show_configuration)
|
.register_action(AssistantPanel::show_configuration)
|
||||||
.register_action(AssistantPanel::create_new_context);
|
.register_action(AssistantPanel::create_new_context);
|
||||||
@@ -956,7 +961,8 @@ impl AssistantPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
|
fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
|
||||||
if self.project.read(cx).is_via_collab() {
|
let project = self.project.read(cx);
|
||||||
|
if project.is_via_collab() && project.dev_server_project_id().is_none() {
|
||||||
let task = self
|
let task = self
|
||||||
.context_store
|
.context_store
|
||||||
.update(cx, |store, cx| store.create_remote_context(cx));
|
.update(cx, |store, cx| store.create_remote_context(cx));
|
||||||
@@ -1906,7 +1912,22 @@ impl ContextEditor {
|
|||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
|
if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
|
||||||
let output = command.run(arguments, workspace, self.lsp_adapter_delegate.clone(), cx);
|
let context = self.context.read(cx);
|
||||||
|
let sections = context
|
||||||
|
.slash_command_output_sections()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|section| section.is_valid(context.buffer().read(cx)))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let snapshot = context.buffer().read(cx).snapshot();
|
||||||
|
let output = command.run(
|
||||||
|
arguments,
|
||||||
|
§ions,
|
||||||
|
snapshot,
|
||||||
|
workspace,
|
||||||
|
self.lsp_adapter_delegate.clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
self.context.update(cx, |context, cx| {
|
self.context.update(cx, |context, cx| {
|
||||||
context.insert_command_output(
|
context.insert_command_output(
|
||||||
command_range,
|
command_range,
|
||||||
@@ -2795,9 +2816,8 @@ impl ContextEditor {
|
|||||||
} else {
|
} else {
|
||||||
// If there are multiple buffers or suggestion groups, create a multibuffer
|
// If there are multiple buffers or suggestion groups, create a multibuffer
|
||||||
let multibuffer = cx.new_model(|cx| {
|
let multibuffer = cx.new_model(|cx| {
|
||||||
let replica_id = project.read(cx).replica_id();
|
let mut multibuffer =
|
||||||
let mut multibuffer = MultiBuffer::new(replica_id, Capability::ReadWrite)
|
MultiBuffer::new(Capability::ReadWrite).with_title(resolved_step.title.clone());
|
||||||
.with_title(resolved_step.title.clone());
|
|
||||||
for (buffer, groups) in &resolved_step.suggestion_groups {
|
for (buffer, groups) in &resolved_step.suggestion_groups {
|
||||||
let excerpt_ids = multibuffer.push_excerpts(
|
let excerpt_ids = multibuffer.push_excerpts(
|
||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
@@ -3085,6 +3105,49 @@ impl ContextEditor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns either the selected text, or the content of the Markdown code
|
||||||
|
/// block surrounding the cursor.
|
||||||
|
fn get_selection_or_code_block(
|
||||||
|
context_editor_view: &View<ContextEditor>,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) -> Option<(String, bool)> {
|
||||||
|
const CODE_FENCE_DELIMITER: &'static str = "```";
|
||||||
|
|
||||||
|
let context_editor = context_editor_view.read(cx).editor.read(cx);
|
||||||
|
|
||||||
|
if context_editor.selections.newest::<Point>(cx).is_empty() {
|
||||||
|
let snapshot = context_editor.buffer().read(cx).snapshot(cx);
|
||||||
|
let (_, _, snapshot) = snapshot.as_singleton()?;
|
||||||
|
|
||||||
|
let head = context_editor.selections.newest::<Point>(cx).head();
|
||||||
|
let offset = snapshot.point_to_offset(head);
|
||||||
|
|
||||||
|
let surrounding_code_block_range = find_surrounding_code_block(snapshot, offset)?;
|
||||||
|
let mut text = snapshot
|
||||||
|
.text_for_range(surrounding_code_block_range)
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
// If there is no newline trailing the closing three-backticks, then
|
||||||
|
// tree-sitter-md extends the range of the content node to include
|
||||||
|
// the backticks.
|
||||||
|
if text.ends_with(CODE_FENCE_DELIMITER) {
|
||||||
|
text.drain((text.len() - CODE_FENCE_DELIMITER.len())..);
|
||||||
|
}
|
||||||
|
|
||||||
|
(!text.is_empty()).then_some((text, true))
|
||||||
|
} else {
|
||||||
|
let anchor = context_editor.selections.newest_anchor();
|
||||||
|
let text = context_editor
|
||||||
|
.buffer()
|
||||||
|
.read(cx)
|
||||||
|
.read(cx)
|
||||||
|
.text_for_range(anchor.range())
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
(!text.is_empty()).then_some((text, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn insert_selection(
|
fn insert_selection(
|
||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
_: &InsertIntoEditor,
|
_: &InsertIntoEditor,
|
||||||
@@ -3103,17 +3166,7 @@ impl ContextEditor {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let context_editor = context_editor_view.read(cx).editor.read(cx);
|
if let Some((text, _)) = Self::get_selection_or_code_block(&context_editor_view, cx) {
|
||||||
let anchor = context_editor.selections.newest_anchor();
|
|
||||||
let text = context_editor
|
|
||||||
.buffer()
|
|
||||||
.read(cx)
|
|
||||||
.read(cx)
|
|
||||||
.text_for_range(anchor.range())
|
|
||||||
.collect::<String>();
|
|
||||||
|
|
||||||
// If nothing is selected, don't delete the current selection; instead, be a no-op.
|
|
||||||
if !text.is_empty() {
|
|
||||||
active_editor_view.update(cx, |editor, cx| {
|
active_editor_view.update(cx, |editor, cx| {
|
||||||
editor.insert(&text, cx);
|
editor.insert(&text, cx);
|
||||||
editor.focus(cx);
|
editor.focus(cx);
|
||||||
@@ -3121,6 +3174,36 @@ impl ContextEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn copy_code(workspace: &mut Workspace, _: &CopyCode, cx: &mut ViewContext<Workspace>) {
|
||||||
|
let result = maybe!({
|
||||||
|
let panel = workspace.panel::<AssistantPanel>(cx)?;
|
||||||
|
let context_editor_view = panel.read(cx).active_context_editor(cx)?;
|
||||||
|
Self::get_selection_or_code_block(&context_editor_view, cx)
|
||||||
|
});
|
||||||
|
let Some((text, is_code_block)) = result else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.write_to_clipboard(ClipboardItem::new_string(text));
|
||||||
|
|
||||||
|
struct CopyToClipboardToast;
|
||||||
|
workspace.show_toast(
|
||||||
|
Toast::new(
|
||||||
|
NotificationId::unique::<CopyToClipboardToast>(),
|
||||||
|
format!(
|
||||||
|
"{} copied to clipboard.",
|
||||||
|
if is_code_block {
|
||||||
|
"Code block"
|
||||||
|
} else {
|
||||||
|
"Selection"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.autohide(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn insert_dragged_files(
|
fn insert_dragged_files(
|
||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
action: &InsertDraggedFiles,
|
action: &InsertDraggedFiles,
|
||||||
@@ -3267,7 +3350,7 @@ impl ContextEditor {
|
|||||||
|
|
||||||
let fence = codeblock_fence_for_path(
|
let fence = codeblock_fence_for_path(
|
||||||
filename.as_deref(),
|
filename.as_deref(),
|
||||||
Some(selection.start.row..selection.end.row),
|
Some(selection.start.row..=selection.end.row),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some((line_comment_prefix, outline_text)) =
|
if let Some((line_comment_prefix, outline_text)) =
|
||||||
@@ -3356,7 +3439,7 @@ impl ContextEditor {
|
|||||||
|
|
||||||
fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
|
fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
|
||||||
if self.editor.read(cx).selections.count() == 1 {
|
if self.editor.read(cx).selections.count() == 1 {
|
||||||
let (copied_text, metadata) = self.get_clipboard_contents(cx);
|
let (copied_text, metadata, _) = self.get_clipboard_contents(cx);
|
||||||
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
|
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
|
||||||
copied_text,
|
copied_text,
|
||||||
metadata,
|
metadata,
|
||||||
@@ -3370,11 +3453,9 @@ impl ContextEditor {
|
|||||||
|
|
||||||
fn cut(&mut self, _: &editor::actions::Cut, cx: &mut ViewContext<Self>) {
|
fn cut(&mut self, _: &editor::actions::Cut, cx: &mut ViewContext<Self>) {
|
||||||
if self.editor.read(cx).selections.count() == 1 {
|
if self.editor.read(cx).selections.count() == 1 {
|
||||||
let (copied_text, metadata) = self.get_clipboard_contents(cx);
|
let (copied_text, metadata, selections) = self.get_clipboard_contents(cx);
|
||||||
|
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
let selections = editor.selections.all::<Point>(cx);
|
|
||||||
|
|
||||||
editor.transact(cx, |this, cx| {
|
editor.transact(cx, |this, cx| {
|
||||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
s.select(selections);
|
s.select(selections);
|
||||||
@@ -3394,52 +3475,71 @@ impl ContextEditor {
|
|||||||
cx.propagate();
|
cx.propagate();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_clipboard_contents(&mut self, cx: &mut ViewContext<Self>) -> (String, CopyMetadata) {
|
fn get_clipboard_contents(
|
||||||
let creases = self.editor.update(cx, |editor, cx| {
|
&mut self,
|
||||||
let selection = editor.selections.newest::<Point>(cx);
|
cx: &mut ViewContext<Self>,
|
||||||
let selection_start = editor.selections.newest::<usize>(cx).start;
|
) -> (String, CopyMetadata, Vec<text::Selection<usize>>) {
|
||||||
|
let (snapshot, selection, creases) = self.editor.update(cx, |editor, cx| {
|
||||||
|
let mut selection = editor.selections.newest::<Point>(cx);
|
||||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
editor.display_map.update(cx, |display_map, cx| {
|
|
||||||
display_map
|
|
||||||
.snapshot(cx)
|
|
||||||
.crease_snapshot
|
|
||||||
.creases_in_range(
|
|
||||||
MultiBufferRow(selection.start.row)..MultiBufferRow(selection.end.row + 1),
|
|
||||||
&snapshot,
|
|
||||||
)
|
|
||||||
.filter_map(|crease| {
|
|
||||||
if let Some(metadata) = &crease.metadata {
|
|
||||||
let start = crease
|
|
||||||
.range
|
|
||||||
.start
|
|
||||||
.to_offset(&snapshot)
|
|
||||||
.saturating_sub(selection_start);
|
|
||||||
let end = crease
|
|
||||||
.range
|
|
||||||
.end
|
|
||||||
.to_offset(&snapshot)
|
|
||||||
.saturating_sub(selection_start);
|
|
||||||
|
|
||||||
let range_relative_to_selection = start..end;
|
let is_entire_line = selection.is_empty() || editor.selections.line_mode;
|
||||||
|
if is_entire_line {
|
||||||
|
selection.start = Point::new(selection.start.row, 0);
|
||||||
|
selection.end =
|
||||||
|
cmp::min(snapshot.max_point(), Point::new(selection.start.row + 1, 0));
|
||||||
|
selection.goal = SelectionGoal::None;
|
||||||
|
}
|
||||||
|
|
||||||
if range_relative_to_selection.is_empty() {
|
let selection_start = snapshot.point_to_offset(selection.start);
|
||||||
None
|
|
||||||
|
(
|
||||||
|
snapshot.clone(),
|
||||||
|
selection.clone(),
|
||||||
|
editor.display_map.update(cx, |display_map, cx| {
|
||||||
|
display_map
|
||||||
|
.snapshot(cx)
|
||||||
|
.crease_snapshot
|
||||||
|
.creases_in_range(
|
||||||
|
MultiBufferRow(selection.start.row)
|
||||||
|
..MultiBufferRow(selection.end.row + 1),
|
||||||
|
&snapshot,
|
||||||
|
)
|
||||||
|
.filter_map(|crease| {
|
||||||
|
if let Some(metadata) = &crease.metadata {
|
||||||
|
let start = crease
|
||||||
|
.range
|
||||||
|
.start
|
||||||
|
.to_offset(&snapshot)
|
||||||
|
.saturating_sub(selection_start);
|
||||||
|
let end = crease
|
||||||
|
.range
|
||||||
|
.end
|
||||||
|
.to_offset(&snapshot)
|
||||||
|
.saturating_sub(selection_start);
|
||||||
|
|
||||||
|
let range_relative_to_selection = start..end;
|
||||||
|
|
||||||
|
if range_relative_to_selection.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(SelectedCreaseMetadata {
|
||||||
|
range_relative_to_selection,
|
||||||
|
crease: metadata.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Some(SelectedCreaseMetadata {
|
None
|
||||||
range_relative_to_selection,
|
|
||||||
crease: metadata.clone(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
None
|
.collect::<Vec<_>>()
|
||||||
}
|
}),
|
||||||
})
|
)
|
||||||
.collect::<Vec<_>>()
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let selection = selection.map(|point| snapshot.point_to_offset(point));
|
||||||
let context = self.context.read(cx);
|
let context = self.context.read(cx);
|
||||||
let selection = self.editor.read(cx).selections.newest::<usize>(cx);
|
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
for message in context.messages(cx) {
|
for message in context.messages(cx) {
|
||||||
if message.offset_range.start >= selection.range().end {
|
if message.offset_range.start >= selection.range().end {
|
||||||
@@ -3451,12 +3551,14 @@ impl ContextEditor {
|
|||||||
for chunk in context.buffer().read(cx).text_for_range(range) {
|
for chunk in context.buffer().read(cx).text_for_range(range) {
|
||||||
text.push_str(chunk);
|
text.push_str(chunk);
|
||||||
}
|
}
|
||||||
text.push('\n');
|
if message.offset_range.end < selection.range().end {
|
||||||
|
text.push('\n');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(text, CopyMetadata { creases })
|
(text, CopyMetadata { creases }, vec![selection])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paste(&mut self, action: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
|
fn paste(&mut self, action: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
|
||||||
@@ -4117,9 +4219,11 @@ impl ContextEditor {
|
|||||||
.child(Label::new(label)),
|
.child(Label::new(label)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("open-configuration", "Open configuration")
|
Button::new("open-configuration", "Configure Providers")
|
||||||
.size(ButtonSize::Compact)
|
.size(ButtonSize::Compact)
|
||||||
|
.icon(Some(IconName::SlidersVertical))
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
|
.icon_position(IconPosition::Start)
|
||||||
.style(ButtonStyle::Filled)
|
.style(ButtonStyle::Filled)
|
||||||
.on_click({
|
.on_click({
|
||||||
let focus_handle = self.focus_handle(cx).clone();
|
let focus_handle = self.focus_handle(cx).clone();
|
||||||
@@ -4198,6 +4302,48 @@ impl ContextEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the contents of the *outermost* fenced code block that contains the given offset.
|
||||||
|
fn find_surrounding_code_block(snapshot: &BufferSnapshot, offset: usize) -> Option<Range<usize>> {
|
||||||
|
const CODE_BLOCK_NODE: &'static str = "fenced_code_block";
|
||||||
|
const CODE_BLOCK_CONTENT: &'static str = "code_fence_content";
|
||||||
|
|
||||||
|
let layer = snapshot.syntax_layers().next()?;
|
||||||
|
|
||||||
|
let root_node = layer.node();
|
||||||
|
let mut cursor = root_node.walk();
|
||||||
|
|
||||||
|
// Go to the first child for the given offset
|
||||||
|
while cursor.goto_first_child_for_byte(offset).is_some() {
|
||||||
|
// If we're at the end of the node, go to the next one.
|
||||||
|
// Example: if you have a fenced-code-block, and you're on the start of the line
|
||||||
|
// right after the closing ```, you want to skip the fenced-code-block and
|
||||||
|
// go to the next sibling.
|
||||||
|
if cursor.node().end_byte() == offset {
|
||||||
|
cursor.goto_next_sibling();
|
||||||
|
}
|
||||||
|
|
||||||
|
if cursor.node().start_byte() > offset {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We found the fenced code block.
|
||||||
|
if cursor.node().kind() == CODE_BLOCK_NODE {
|
||||||
|
// Now we need to find the child node that contains the code.
|
||||||
|
cursor.goto_first_child();
|
||||||
|
loop {
|
||||||
|
if cursor.node().kind() == CODE_BLOCK_CONTENT {
|
||||||
|
return Some(cursor.node().byte_range());
|
||||||
|
}
|
||||||
|
if !cursor.goto_next_sibling() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn render_fold_icon_button(
|
fn render_fold_icon_button(
|
||||||
editor: WeakView<Editor>,
|
editor: WeakView<Editor>,
|
||||||
icon: IconName,
|
icon: IconName,
|
||||||
@@ -5235,7 +5381,7 @@ fn quote_selection_fold_placeholder(title: String, editor: WeakView<Editor>) ->
|
|||||||
ButtonLike::new(fold_id)
|
ButtonLike::new(fold_id)
|
||||||
.style(ButtonStyle::Filled)
|
.style(ButtonStyle::Filled)
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
.layer(ElevationIndex::ElevatedSurface)
|
||||||
.child(Icon::new(IconName::CursorIBeam))
|
.child(Icon::new(IconName::TextSnippet))
|
||||||
.child(Label::new(title.clone()).single_line())
|
.child(Label::new(title.clone()).single_line())
|
||||||
.on_click(move |_, cx| {
|
.on_click(move |_, cx| {
|
||||||
editor
|
editor
|
||||||
@@ -5367,18 +5513,16 @@ fn make_lsp_adapter_delegate(
|
|||||||
let worktree = project
|
let worktree = project
|
||||||
.worktrees(cx)
|
.worktrees(cx)
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
|
.ok_or_else(|| anyhow!("no worktrees when constructing LocalLspAdapterDelegate"))?;
|
||||||
let fs = if project.is_local() {
|
|
||||||
Some(project.fs().clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let http_client = project.client().http_client().clone();
|
let http_client = project.client().http_client().clone();
|
||||||
project.lsp_store().update(cx, |lsp_store, cx| {
|
project.lsp_store().update(cx, |lsp_store, cx| {
|
||||||
Ok(
|
Ok(LocalLspAdapterDelegate::new(
|
||||||
ProjectLspAdapterDelegate::new(lsp_store, &worktree, http_client, fs, None, cx)
|
lsp_store,
|
||||||
as Arc<dyn LspAdapterDelegate>,
|
&worktree,
|
||||||
)
|
http_client,
|
||||||
|
project.fs().clone(),
|
||||||
|
cx,
|
||||||
|
) as Arc<dyn LspAdapterDelegate>)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -5482,3 +5626,85 @@ fn configuration_error(cx: &AppContext) -> Option<ConfigurationError> {
|
|||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use gpui::{AppContext, Context};
|
||||||
|
use language::Buffer;
|
||||||
|
use unindent::Unindent;
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_find_code_blocks(cx: &mut AppContext) {
|
||||||
|
let markdown = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
|
||||||
|
|
||||||
|
let buffer = cx.new_model(|cx| {
|
||||||
|
let text = r#"
|
||||||
|
line 0
|
||||||
|
line 1
|
||||||
|
```rust
|
||||||
|
fn main() {}
|
||||||
|
```
|
||||||
|
line 5
|
||||||
|
line 6
|
||||||
|
line 7
|
||||||
|
```go
|
||||||
|
func main() {}
|
||||||
|
```
|
||||||
|
line 11
|
||||||
|
```
|
||||||
|
this is plain text code block
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
func another() {}
|
||||||
|
```
|
||||||
|
line 19
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
let mut buffer = Buffer::local(text, cx);
|
||||||
|
buffer.set_language(Some(markdown.clone()), cx);
|
||||||
|
buffer
|
||||||
|
});
|
||||||
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
|
|
||||||
|
let code_blocks = vec![
|
||||||
|
Point::new(3, 0)..Point::new(4, 0),
|
||||||
|
Point::new(9, 0)..Point::new(10, 0),
|
||||||
|
Point::new(13, 0)..Point::new(14, 0),
|
||||||
|
Point::new(17, 0)..Point::new(18, 0),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(|range| snapshot.point_to_offset(range.start)..snapshot.point_to_offset(range.end))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let expected_results = vec![
|
||||||
|
(0, None),
|
||||||
|
(1, None),
|
||||||
|
(2, Some(code_blocks[0].clone())),
|
||||||
|
(3, Some(code_blocks[0].clone())),
|
||||||
|
(4, Some(code_blocks[0].clone())),
|
||||||
|
(5, None),
|
||||||
|
(6, None),
|
||||||
|
(7, None),
|
||||||
|
(8, Some(code_blocks[1].clone())),
|
||||||
|
(9, Some(code_blocks[1].clone())),
|
||||||
|
(10, Some(code_blocks[1].clone())),
|
||||||
|
(11, None),
|
||||||
|
(12, Some(code_blocks[2].clone())),
|
||||||
|
(13, Some(code_blocks[2].clone())),
|
||||||
|
(14, Some(code_blocks[2].clone())),
|
||||||
|
(15, None),
|
||||||
|
(16, Some(code_blocks[3].clone())),
|
||||||
|
(17, Some(code_blocks[3].clone())),
|
||||||
|
(18, Some(code_blocks[3].clone())),
|
||||||
|
(19, None),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (row, expected) in expected_results {
|
||||||
|
let offset = snapshot.point_to_offset(Point::new(row, 0));
|
||||||
|
let range = find_surrounding_code_block(&snapshot, offset);
|
||||||
|
assert_eq!(range, expected, "unexpected result on row {:?}", row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ pub struct AssistantSettings {
|
|||||||
pub default_width: Pixels,
|
pub default_width: Pixels,
|
||||||
pub default_height: Pixels,
|
pub default_height: Pixels,
|
||||||
pub default_model: LanguageModelSelection,
|
pub default_model: LanguageModelSelection,
|
||||||
|
pub inline_alternatives: Vec<LanguageModelSelection>,
|
||||||
pub using_outdated_settings_version: bool,
|
pub using_outdated_settings_version: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,6 +237,7 @@ impl AssistantSettingsContent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
inline_alternatives: None,
|
||||||
},
|
},
|
||||||
VersionedAssistantSettingsContent::V2(settings) => settings.clone(),
|
VersionedAssistantSettingsContent::V2(settings) => settings.clone(),
|
||||||
},
|
},
|
||||||
@@ -254,6 +256,7 @@ impl AssistantSettingsContent {
|
|||||||
.id()
|
.id()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
}),
|
}),
|
||||||
|
inline_alternatives: None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,6 +372,7 @@ impl Default for VersionedAssistantSettingsContent {
|
|||||||
default_width: None,
|
default_width: None,
|
||||||
default_height: None,
|
default_height: None,
|
||||||
default_model: None,
|
default_model: None,
|
||||||
|
inline_alternatives: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -397,6 +401,8 @@ pub struct AssistantSettingsContentV2 {
|
|||||||
default_height: Option<f32>,
|
default_height: Option<f32>,
|
||||||
/// The default model to use when creating new contexts.
|
/// The default model to use when creating new contexts.
|
||||||
default_model: Option<LanguageModelSelection>,
|
default_model: Option<LanguageModelSelection>,
|
||||||
|
/// Additional models with which to generate alternatives when performing inline assists.
|
||||||
|
inline_alternatives: Option<Vec<LanguageModelSelection>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||||
@@ -517,10 +523,8 @@ impl Settings for AssistantSettings {
|
|||||||
&mut settings.default_height,
|
&mut settings.default_height,
|
||||||
value.default_height.map(Into::into),
|
value.default_height.map(Into::into),
|
||||||
);
|
);
|
||||||
merge(
|
merge(&mut settings.default_model, value.default_model);
|
||||||
&mut settings.default_model,
|
merge(&mut settings.inline_alternatives, value.inline_alternatives);
|
||||||
value.default_model.map(Into::into),
|
|
||||||
);
|
|
||||||
// merge(&mut settings.infer_context, value.infer_context); TODO re-enable this once we ship context inference
|
// merge(&mut settings.infer_context, value.infer_context); TODO re-enable this once we ship context inference
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,6 +578,7 @@ mod tests {
|
|||||||
provider: "test-provider".into(),
|
provider: "test-provider".into(),
|
||||||
model: "gpt-99".into(),
|
model: "gpt-99".into(),
|
||||||
}),
|
}),
|
||||||
|
inline_alternatives: None,
|
||||||
enabled: None,
|
enabled: None,
|
||||||
button: None,
|
button: None,
|
||||||
dock: None,
|
dock: None,
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use telemetry_events::AssistantKind;
|
use telemetry_events::{AssistantKind, AssistantPhase};
|
||||||
use text::BufferSnapshot;
|
use text::BufferSnapshot;
|
||||||
use util::{post_inc, TryFutureExt};
|
use util::{post_inc, ResultExt, TryFutureExt};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
@@ -162,6 +162,9 @@ impl ContextOperation {
|
|||||||
)?,
|
)?,
|
||||||
icon: section.icon_name.parse()?,
|
icon: section.icon_name.parse()?,
|
||||||
label: section.label.into(),
|
label: section.label.into(),
|
||||||
|
metadata: section
|
||||||
|
.metadata
|
||||||
|
.and_then(|metadata| serde_json::from_str(&metadata).log_err()),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?,
|
.collect::<Result<Vec<_>>>()?,
|
||||||
@@ -242,6 +245,9 @@ impl ContextOperation {
|
|||||||
)),
|
)),
|
||||||
icon_name: icon_name.to_string(),
|
icon_name: icon_name.to_string(),
|
||||||
label: section.label.to_string(),
|
label: section.label.to_string(),
|
||||||
|
metadata: section.metadata.as_ref().and_then(|metadata| {
|
||||||
|
serde_json::to_string(metadata).log_err()
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
@@ -635,12 +641,13 @@ impl Context {
|
|||||||
.slash_command_output_sections
|
.slash_command_output_sections
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|section| {
|
.filter_map(|section| {
|
||||||
let range = section.range.to_offset(buffer);
|
if section.is_valid(buffer) {
|
||||||
if section.range.start.is_valid(buffer) && !range.is_empty() {
|
let range = section.range.to_offset(buffer);
|
||||||
Some(assistant_slash_command::SlashCommandOutputSection {
|
Some(assistant_slash_command::SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
icon: section.icon,
|
icon: section.icon,
|
||||||
label: section.label.clone(),
|
label: section.label.clone(),
|
||||||
|
metadata: section.metadata.clone(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -676,7 +683,7 @@ impl Context {
|
|||||||
buffer.set_text(saved_context.text.as_str(), cx)
|
buffer.set_text(saved_context.text.as_str(), cx)
|
||||||
});
|
});
|
||||||
let operations = saved_context.into_ops(&this.buffer, cx);
|
let operations = saved_context.into_ops(&this.buffer, cx);
|
||||||
this.apply_ops(operations, cx).unwrap();
|
this.apply_ops(operations, cx);
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -749,7 +756,7 @@ impl Context {
|
|||||||
&mut self,
|
&mut self,
|
||||||
ops: impl IntoIterator<Item = ContextOperation>,
|
ops: impl IntoIterator<Item = ContextOperation>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Result<()> {
|
) {
|
||||||
let mut buffer_ops = Vec::new();
|
let mut buffer_ops = Vec::new();
|
||||||
for op in ops {
|
for op in ops {
|
||||||
match op {
|
match op {
|
||||||
@@ -758,10 +765,8 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.buffer
|
self.buffer
|
||||||
.update(cx, |buffer, cx| buffer.apply_ops(buffer_ops, cx))?;
|
.update(cx, |buffer, cx| buffer.apply_ops(buffer_ops, cx));
|
||||||
self.flush_ops(cx);
|
self.flush_ops(cx);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush_ops(&mut self, cx: &mut ModelContext<Context>) {
|
fn flush_ops(&mut self, cx: &mut ModelContext<Context>) {
|
||||||
@@ -997,14 +1002,17 @@ impl Context {
|
|||||||
fn handle_buffer_event(
|
fn handle_buffer_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Model<Buffer>,
|
_: Model<Buffer>,
|
||||||
event: &language::Event,
|
event: &language::BufferEvent,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
language::Event::Operation(operation) => cx.emit(ContextEvent::Operation(
|
language::BufferEvent::Operation {
|
||||||
ContextOperation::BufferOperation(operation.clone()),
|
operation,
|
||||||
)),
|
is_local: true,
|
||||||
language::Event::Edited => {
|
} => cx.emit(ContextEvent::Operation(ContextOperation::BufferOperation(
|
||||||
|
operation.clone(),
|
||||||
|
))),
|
||||||
|
language::BufferEvent::Edited => {
|
||||||
self.count_remaining_tokens(cx);
|
self.count_remaining_tokens(cx);
|
||||||
self.reparse(cx);
|
self.reparse(cx);
|
||||||
// Use `inclusive = true` to invalidate a step when an edit occurs
|
// Use `inclusive = true` to invalidate a step when an edit occurs
|
||||||
@@ -1825,6 +1833,7 @@ impl Context {
|
|||||||
..buffer.anchor_before(start + section.range.end),
|
..buffer.anchor_before(start + section.range.end),
|
||||||
icon: section.icon,
|
icon: section.icon,
|
||||||
label: section.label,
|
label: section.label,
|
||||||
|
metadata: section.metadata,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
||||||
@@ -1961,8 +1970,9 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn assist(&mut self, cx: &mut ModelContext<Self>) -> Option<MessageAnchor> {
|
pub fn assist(&mut self, cx: &mut ModelContext<Self>) -> Option<MessageAnchor> {
|
||||||
let provider = LanguageModelRegistry::read_global(cx).active_provider()?;
|
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||||
let model = LanguageModelRegistry::read_global(cx).active_model()?;
|
let provider = model_registry.active_provider()?;
|
||||||
|
let model = model_registry.active_model()?;
|
||||||
let last_message_id = self.get_last_valid_message_id(cx)?;
|
let last_message_id = self.get_last_valid_message_id(cx)?;
|
||||||
|
|
||||||
if !provider.is_authenticated(cx) {
|
if !provider.is_authenticated(cx) {
|
||||||
@@ -2126,6 +2136,7 @@ impl Context {
|
|||||||
telemetry.report_assistant_event(
|
telemetry.report_assistant_event(
|
||||||
Some(this.id.0.clone()),
|
Some(this.id.0.clone()),
|
||||||
AssistantKind::Panel,
|
AssistantKind::Panel,
|
||||||
|
AssistantPhase::Response,
|
||||||
model.telemetry_id(),
|
model.telemetry_id(),
|
||||||
response_latency,
|
response_latency,
|
||||||
error_message,
|
error_message,
|
||||||
@@ -2173,7 +2184,7 @@ impl Context {
|
|||||||
messages: Vec::new(),
|
messages: Vec::new(),
|
||||||
tools: Vec::new(),
|
tools: Vec::new(),
|
||||||
stop: Vec::new(),
|
stop: Vec::new(),
|
||||||
temperature: 1.0,
|
temperature: None,
|
||||||
};
|
};
|
||||||
for message in self.messages(cx) {
|
for message in self.messages(cx) {
|
||||||
if message.status != MessageStatus::Done {
|
if message.status != MessageStatus::Done {
|
||||||
@@ -2977,6 +2988,7 @@ impl SavedContext {
|
|||||||
..buffer.anchor_before(section.range.end),
|
..buffer.anchor_before(section.range.end),
|
||||||
icon: section.icon,
|
icon: section.icon,
|
||||||
label: section.label,
|
label: section.label,
|
||||||
|
metadata: section.metadata,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use assistant_slash_command::{
|
|||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
use gpui::{AppContext, Model, SharedString, Task, TestAppContext, WeakView};
|
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 language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
@@ -1089,6 +1089,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
|||||||
range: section_start..section_end,
|
range: section_start..section_end,
|
||||||
icon: ui::IconName::Ai,
|
icon: ui::IconName::Ai,
|
||||||
label: "section".into(),
|
label: "section".into(),
|
||||||
|
metadata: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1165,9 +1166,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
|||||||
);
|
);
|
||||||
|
|
||||||
network.lock().broadcast(replica_id, ops_to_send);
|
network.lock().broadcast(replica_id, ops_to_send);
|
||||||
context
|
context.update(cx, |context, cx| context.apply_ops(ops_to_receive, cx));
|
||||||
.update(cx, |context, cx| context.apply_ops(ops_to_receive, cx))
|
|
||||||
.unwrap();
|
|
||||||
} else if rng.gen_bool(0.1) && replica_id != 0 {
|
} else if rng.gen_bool(0.1) && replica_id != 0 {
|
||||||
log::info!("Context {}: disconnecting", context_index);
|
log::info!("Context {}: disconnecting", context_index);
|
||||||
network.lock().disconnect_peer(replica_id);
|
network.lock().disconnect_peer(replica_id);
|
||||||
@@ -1179,9 +1178,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
|||||||
.map(ContextOperation::from_proto)
|
.map(ContextOperation::from_proto)
|
||||||
.collect::<Result<Vec<_>>>()
|
.collect::<Result<Vec<_>>>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
context
|
context.update(cx, |context, cx| context.apply_ops(ops, cx));
|
||||||
.update(cx, |context, cx| context.apply_ops(ops, cx))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1425,6 +1422,8 @@ impl SlashCommand for FakeSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: BufferSnapshot,
|
||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
_cx: &mut WindowContext,
|
_cx: &mut WindowContext,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use crate::{
|
|||||||
prompts::PromptBuilder, Context, ContextEvent, ContextId, ContextOperation, ContextVersion,
|
prompts::PromptBuilder, Context, ContextEvent, ContextId, ContextOperation, ContextVersion,
|
||||||
SavedContext, SavedContextMetadata,
|
SavedContext, SavedContextMetadata,
|
||||||
};
|
};
|
||||||
use ::proto::AnyProtoClient;
|
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
|
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
@@ -16,6 +15,7 @@ use language::LanguageRegistry;
|
|||||||
use paths::contexts_dir;
|
use paths::contexts_dir;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use rpc::AnyProtoClient;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Reverse,
|
cmp::Reverse,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
@@ -223,7 +223,7 @@ impl ContextStore {
|
|||||||
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
|
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
|
||||||
let operation_proto = envelope.payload.operation.context("invalid operation")?;
|
let operation_proto = envelope.payload.operation.context("invalid operation")?;
|
||||||
let operation = ContextOperation::from_proto(operation_proto)?;
|
let operation = ContextOperation::from_proto(operation_proto)?;
|
||||||
context.update(cx, |context, cx| context.apply_ops([operation], cx))?;
|
context.update(cx, |context, cx| context.apply_ops([operation], cx));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})?
|
})?
|
||||||
@@ -357,9 +357,6 @@ impl ContextStore {
|
|||||||
let Some(project_id) = project.remote_id() else {
|
let Some(project_id) = project.remote_id() else {
|
||||||
return Task::ready(Err(anyhow!("project was not remote")));
|
return Task::ready(Err(anyhow!("project was not remote")));
|
||||||
};
|
};
|
||||||
if project.is_local_or_ssh() {
|
|
||||||
return Task::ready(Err(anyhow!("cannot create remote contexts as the host")));
|
|
||||||
}
|
|
||||||
|
|
||||||
let replica_id = project.replica_id();
|
let replica_id = project.replica_id();
|
||||||
let capability = project.capability();
|
let capability = project.capability();
|
||||||
@@ -394,7 +391,7 @@ impl ContextStore {
|
|||||||
.collect::<Result<Vec<_>>>()
|
.collect::<Result<Vec<_>>>()
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
context.update(&mut cx, |context, cx| context.apply_ops(operations, cx))??;
|
context.update(&mut cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
||||||
existing_context
|
existing_context
|
||||||
@@ -488,9 +485,6 @@ impl ContextStore {
|
|||||||
let Some(project_id) = project.remote_id() else {
|
let Some(project_id) = project.remote_id() else {
|
||||||
return Task::ready(Err(anyhow!("project was not remote")));
|
return Task::ready(Err(anyhow!("project was not remote")));
|
||||||
};
|
};
|
||||||
if project.is_local_or_ssh() {
|
|
||||||
return Task::ready(Err(anyhow!("cannot open remote contexts as the host")));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(context) = self.loaded_context_for_id(&context_id, cx) {
|
if let Some(context) = self.loaded_context_for_id(&context_id, cx) {
|
||||||
return Task::ready(Ok(context));
|
return Task::ready(Ok(context));
|
||||||
@@ -531,7 +525,7 @@ impl ContextStore {
|
|||||||
.collect::<Result<Vec<_>>>()
|
.collect::<Result<Vec<_>>>()
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
context.update(&mut cx, |context, cx| context.apply_ops(operations, cx))??;
|
context.update(&mut cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
||||||
existing_context
|
existing_context
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -796,7 +796,7 @@ impl PromptLibrary {
|
|||||||
}],
|
}],
|
||||||
tools: Vec::new(),
|
tools: Vec::new(),
|
||||||
stop: Vec::new(),
|
stop: Vec::new(),
|
||||||
temperature: 1.,
|
temperature: None,
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -921,10 +921,8 @@ impl PromptLibrary {
|
|||||||
scrollbar_width: Pixels::ZERO,
|
scrollbar_width: Pixels::ZERO,
|
||||||
syntax: cx.theme().syntax().clone(),
|
syntax: cx.theme().syntax().clone(),
|
||||||
status: cx.theme().status().clone(),
|
status: cx.theme().status().clone(),
|
||||||
inlay_hints_style: HighlightStyle {
|
inlay_hints_style:
|
||||||
color: Some(cx.theme().status().hint),
|
editor::make_inlay_hints_style(cx),
|
||||||
..HighlightStyle::default()
|
|
||||||
},
|
|
||||||
suggestions_style: HighlightStyle {
|
suggestions_style: HighlightStyle {
|
||||||
color: Some(cx.theme().status().predictive),
|
color: Some(cx.theme().status().predictive),
|
||||||
..HighlightStyle::default()
|
..HighlightStyle::default()
|
||||||
|
|||||||
@@ -4,13 +4,20 @@ use fs::Fs;
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::AssetSource;
|
use gpui::AssetSource;
|
||||||
use handlebars::{Handlebars, RenderError};
|
use handlebars::{Handlebars, RenderError};
|
||||||
use language::{BufferSnapshot, LanguageName};
|
use language::{BufferSnapshot, LanguageName, Point};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration};
|
use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration};
|
||||||
use text::LineEnding;
|
use text::LineEnding;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ContentPromptDiagnosticContext {
|
||||||
|
pub line_number: usize,
|
||||||
|
pub error_message: String,
|
||||||
|
pub code_content: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ContentPromptContext {
|
pub struct ContentPromptContext {
|
||||||
pub content_type: String,
|
pub content_type: String,
|
||||||
@@ -20,6 +27,7 @@ pub struct ContentPromptContext {
|
|||||||
pub document_content: String,
|
pub document_content: String,
|
||||||
pub user_prompt: String,
|
pub user_prompt: String,
|
||||||
pub rewrite_section: Option<String>,
|
pub rewrite_section: Option<String>,
|
||||||
|
pub diagnostic_errors: Vec<ContentPromptDiagnosticContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@@ -32,6 +40,11 @@ pub struct TerminalAssistantPromptContext {
|
|||||||
pub user_prompt: String,
|
pub user_prompt: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ProjectSlashCommandPromptContext {
|
||||||
|
pub context_buffer: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Context required to generate a workflow step resolution prompt.
|
/// Context required to generate a workflow step resolution prompt.
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct StepResolutionContext {
|
pub struct StepResolutionContext {
|
||||||
@@ -82,10 +95,9 @@ impl PromptBuilder {
|
|||||||
/// and application context.
|
/// and application context.
|
||||||
/// * `handlebars` - An `Arc<Mutex<Handlebars>>` for registering and updating templates.
|
/// * `handlebars` - An `Arc<Mutex<Handlebars>>` for registering and updating templates.
|
||||||
fn watch_fs_for_template_overrides(
|
fn watch_fs_for_template_overrides(
|
||||||
mut params: PromptLoadingParams,
|
params: PromptLoadingParams,
|
||||||
handlebars: Arc<Mutex<Handlebars<'static>>>,
|
handlebars: Arc<Mutex<Handlebars<'static>>>,
|
||||||
) {
|
) {
|
||||||
params.repo_path = None;
|
|
||||||
let templates_dir = paths::prompt_overrides_dir(params.repo_path.as_deref());
|
let templates_dir = paths::prompt_overrides_dir(params.repo_path.as_deref());
|
||||||
params.cx.background_executor()
|
params.cx.background_executor()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
@@ -220,7 +232,8 @@ impl PromptBuilder {
|
|||||||
let before_range = 0..range.start;
|
let before_range = 0..range.start;
|
||||||
let truncated_before = if before_range.len() > MAX_CTX {
|
let truncated_before = if before_range.len() > MAX_CTX {
|
||||||
is_truncated = true;
|
is_truncated = true;
|
||||||
range.start - MAX_CTX..range.start
|
let start = buffer.clip_offset(range.start - MAX_CTX, text::Bias::Right);
|
||||||
|
start..range.start
|
||||||
} else {
|
} else {
|
||||||
before_range
|
before_range
|
||||||
};
|
};
|
||||||
@@ -228,7 +241,8 @@ impl PromptBuilder {
|
|||||||
let after_range = range.end..buffer.len();
|
let after_range = range.end..buffer.len();
|
||||||
let truncated_after = if after_range.len() > MAX_CTX {
|
let truncated_after = if after_range.len() > MAX_CTX {
|
||||||
is_truncated = true;
|
is_truncated = true;
|
||||||
range.end..range.end + MAX_CTX
|
let end = buffer.clip_offset(range.end + MAX_CTX, text::Bias::Left);
|
||||||
|
range.end..end
|
||||||
} else {
|
} else {
|
||||||
after_range
|
after_range
|
||||||
};
|
};
|
||||||
@@ -259,6 +273,17 @@ impl PromptBuilder {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
let diagnostics = buffer.diagnostics_in_range::<_, Point>(range, false);
|
||||||
|
let diagnostic_errors: Vec<ContentPromptDiagnosticContext> = diagnostics
|
||||||
|
.map(|entry| {
|
||||||
|
let start = entry.range.start;
|
||||||
|
ContentPromptDiagnosticContext {
|
||||||
|
line_number: (start.row + 1) as usize,
|
||||||
|
error_message: entry.diagnostic.message.clone(),
|
||||||
|
code_content: buffer.text_for_range(entry.range.clone()).collect(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let context = ContentPromptContext {
|
let context = ContentPromptContext {
|
||||||
content_type: content_type.to_string(),
|
content_type: content_type.to_string(),
|
||||||
@@ -268,8 +293,8 @@ impl PromptBuilder {
|
|||||||
document_content,
|
document_content,
|
||||||
user_prompt,
|
user_prompt,
|
||||||
rewrite_section,
|
rewrite_section,
|
||||||
|
diagnostic_errors,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.handlebars.lock().render("content_prompt", &context)
|
self.handlebars.lock().render("content_prompt", &context)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,4 +322,14 @@ impl PromptBuilder {
|
|||||||
pub fn generate_workflow_prompt(&self) -> Result<String, RenderError> {
|
pub fn generate_workflow_prompt(&self) -> Result<String, RenderError> {
|
||||||
self.handlebars.lock().render("edit_workflow", &())
|
self.handlebars.lock().render("edit_workflow", &())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_project_slash_command_prompt(
|
||||||
|
&self,
|
||||||
|
context_buffer: String,
|
||||||
|
) -> Result<String, RenderError> {
|
||||||
|
self.handlebars.lock().render(
|
||||||
|
"project_slash_command",
|
||||||
|
&ProjectSlashCommandPromptContext { context_buffer },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,11 @@ use std::{
|
|||||||
};
|
};
|
||||||
use ui::ActiveTheme;
|
use ui::ActiveTheme;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub mod auto_command;
|
pub mod auto_command;
|
||||||
|
pub mod cargo_workspace_command;
|
||||||
pub mod context_server_command;
|
pub mod context_server_command;
|
||||||
pub mod default_command;
|
pub mod default_command;
|
||||||
|
pub mod delta_command;
|
||||||
pub mod diagnostics_command;
|
pub mod diagnostics_command;
|
||||||
pub mod docs_command;
|
pub mod docs_command;
|
||||||
pub mod fetch_command;
|
pub mod fetch_command;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::create_label_for_command;
|
use super::create_label_for_command;
|
||||||
use super::{SlashCommand, SlashCommandOutput};
|
use super::{SlashCommand, SlashCommandOutput};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::ArgumentCompletion;
|
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||||
use feature_flags::FeatureFlag;
|
use feature_flags::FeatureFlag;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
|
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
|
||||||
@@ -87,6 +87,8 @@ impl SlashCommand for AutoCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: language::BufferSnapshot,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
@@ -214,7 +216,7 @@ async fn commands_for_summaries(
|
|||||||
}],
|
}],
|
||||||
tools: Vec::new(),
|
tools: Vec::new(),
|
||||||
stop: Vec::new(),
|
stop: Vec::new(),
|
||||||
temperature: 1.0,
|
temperature: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
while let Some(current_summaries) = stack.pop() {
|
while let Some(current_summaries) = stack.pop() {
|
||||||
|
|||||||
153
crates/assistant/src/slash_command/cargo_workspace_command.rs
Normal file
153
crates/assistant/src/slash_command/cargo_workspace_command.rs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
use super::{SlashCommand, SlashCommandOutput};
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||||
|
use fs::Fs;
|
||||||
|
use gpui::{AppContext, Model, Task, WeakView};
|
||||||
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
|
use project::{Project, ProjectPath};
|
||||||
|
use std::{
|
||||||
|
fmt::Write,
|
||||||
|
path::Path,
|
||||||
|
sync::{atomic::AtomicBool, Arc},
|
||||||
|
};
|
||||||
|
use ui::prelude::*;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
pub(crate) struct CargoWorkspaceSlashCommand;
|
||||||
|
|
||||||
|
impl CargoWorkspaceSlashCommand {
|
||||||
|
async fn build_message(fs: Arc<dyn Fs>, path_to_cargo_toml: &Path) -> Result<String> {
|
||||||
|
let buffer = fs.load(path_to_cargo_toml).await?;
|
||||||
|
let cargo_toml: cargo_toml::Manifest = toml::from_str(&buffer)?;
|
||||||
|
|
||||||
|
let mut message = String::new();
|
||||||
|
writeln!(message, "You are in a Rust project.")?;
|
||||||
|
|
||||||
|
if let Some(workspace) = cargo_toml.workspace {
|
||||||
|
writeln!(
|
||||||
|
message,
|
||||||
|
"The project is a Cargo workspace with the following members:"
|
||||||
|
)?;
|
||||||
|
for member in workspace.members {
|
||||||
|
writeln!(message, "- {member}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !workspace.default_members.is_empty() {
|
||||||
|
writeln!(message, "The default members are:")?;
|
||||||
|
for member in workspace.default_members {
|
||||||
|
writeln!(message, "- {member}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !workspace.dependencies.is_empty() {
|
||||||
|
writeln!(
|
||||||
|
message,
|
||||||
|
"The following workspace dependencies are installed:"
|
||||||
|
)?;
|
||||||
|
for dependency in workspace.dependencies.keys() {
|
||||||
|
writeln!(message, "- {dependency}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(package) = cargo_toml.package {
|
||||||
|
writeln!(
|
||||||
|
message,
|
||||||
|
"The project name is \"{name}\".",
|
||||||
|
name = package.name
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let description = package
|
||||||
|
.description
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|description| description.get().ok().cloned());
|
||||||
|
if let Some(description) = description.as_ref() {
|
||||||
|
writeln!(message, "It describes itself as \"{description}\".")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cargo_toml.dependencies.is_empty() {
|
||||||
|
writeln!(message, "The following dependencies are installed:")?;
|
||||||
|
for dependency in cargo_toml.dependencies.keys() {
|
||||||
|
writeln!(message, "- {dependency}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
|
||||||
|
let worktree = project.read(cx).worktrees(cx).next()?;
|
||||||
|
let worktree = worktree.read(cx);
|
||||||
|
let entry = worktree.entry_for_path("Cargo.toml")?;
|
||||||
|
let path = ProjectPath {
|
||||||
|
worktree_id: worktree.id(),
|
||||||
|
path: entry.path.clone(),
|
||||||
|
};
|
||||||
|
Some(Arc::from(
|
||||||
|
project.read(cx).absolute_path(&path, cx)?.as_path(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SlashCommand for CargoWorkspaceSlashCommand {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"cargo-workspace".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> String {
|
||||||
|
"insert project workspace metadata".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn menu_text(&self) -> String {
|
||||||
|
"Insert Project Workspace Metadata".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_argument(
|
||||||
|
self: Arc<Self>,
|
||||||
|
_arguments: &[String],
|
||||||
|
_cancel: Arc<AtomicBool>,
|
||||||
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
|
_cx: &mut WindowContext,
|
||||||
|
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||||
|
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requires_argument(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
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 output = workspace.update(cx, |workspace, cx| {
|
||||||
|
let project = workspace.project().clone();
|
||||||
|
let fs = workspace.project().read(cx).fs().clone();
|
||||||
|
let path = Self::path_to_cargo_toml(project, cx);
|
||||||
|
let output = cx.background_executor().spawn(async move {
|
||||||
|
let path = path.with_context(|| "Cargo.toml not found")?;
|
||||||
|
Self::build_message(fs, &path).await
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.foreground_executor().spawn(async move {
|
||||||
|
let text = output.await?;
|
||||||
|
let range = 0..text.len();
|
||||||
|
Ok(SlashCommandOutput {
|
||||||
|
text,
|
||||||
|
sections: vec![SlashCommandOutputSection {
|
||||||
|
range,
|
||||||
|
icon: IconName::FileTree,
|
||||||
|
label: "Project".into(),
|
||||||
|
metadata: None,
|
||||||
|
}],
|
||||||
|
run_commands_in_text: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
output.unwrap_or_else(|error| Task::ready(Err(error)))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ use context_servers::{
|
|||||||
protocol::PromptInfo,
|
protocol::PromptInfo,
|
||||||
};
|
};
|
||||||
use gpui::{Task, WeakView, WindowContext};
|
use gpui::{Task, WeakView, WindowContext};
|
||||||
use language::{CodeLabel, LspAdapterDelegate};
|
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use text::LineEnding;
|
use text::LineEnding;
|
||||||
@@ -96,7 +96,6 @@ impl SlashCommand for ContextServerSlashCommand {
|
|||||||
replace_previous_arguments: false,
|
replace_previous_arguments: false,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(completions)
|
Ok(completions)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -107,6 +106,8 @@ impl SlashCommand for ContextServerSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: BufferSnapshot,
|
||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
@@ -141,6 +142,7 @@ impl SlashCommand for ContextServerSlashCommand {
|
|||||||
.description
|
.description
|
||||||
.unwrap_or(format!("Result from {}", prompt_name)),
|
.unwrap_or(format!("Result from {}", prompt_name)),
|
||||||
),
|
),
|
||||||
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
text: prompt,
|
text: prompt,
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::prompt_library::PromptStore;
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||||
use gpui::{Task, WeakView};
|
use gpui::{Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
@@ -43,6 +43,8 @@ impl SlashCommand for DefaultSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: BufferSnapshot,
|
||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
@@ -70,6 +72,7 @@ impl SlashCommand for DefaultSlashCommand {
|
|||||||
range: 0..text.len(),
|
range: 0..text.len(),
|
||||||
icon: IconName::Library,
|
icon: IconName::Library,
|
||||||
label: "Default".into(),
|
label: "Default".into(),
|
||||||
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
text,
|
text,
|
||||||
run_commands_in_text: true,
|
run_commands_in_text: true,
|
||||||
|
|||||||
109
crates/assistant/src/slash_command/delta_command.rs
Normal file
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 project::{DiagnosticSummary, PathMatchCandidateSet, Project};
|
||||||
use rope::Point;
|
use rope::Point;
|
||||||
use std::fmt::Write;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::{
|
use std::{
|
||||||
ops::Range,
|
fmt::Write,
|
||||||
|
path::{Path, PathBuf},
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
@@ -163,6 +162,8 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: BufferSnapshot,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
@@ -175,68 +176,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
|||||||
|
|
||||||
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
|
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
|
||||||
|
|
||||||
cx.spawn(move |_| async move {
|
cx.spawn(move |_| async move { task.await?.ok_or_else(|| anyhow!("No diagnostics found")) })
|
||||||
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::Warning,
|
|
||||||
PlaceholderType::File(_) => IconName::File,
|
|
||||||
PlaceholderType::Diagnostic(DiagnosticType::Error, _) => IconName::XCircle,
|
|
||||||
PlaceholderType::Diagnostic(DiagnosticType::Warning, _) => {
|
|
||||||
IconName::Warning
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +217,7 @@ fn collect_diagnostics(
|
|||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
options: Options,
|
options: Options,
|
||||||
cx: &mut AppContext,
|
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 {
|
let error_source = if let Some(path_matcher) = &options.path_matcher {
|
||||||
debug_assert_eq!(path_matcher.sources().len(), 1);
|
debug_assert_eq!(path_matcher.sources().len(), 1);
|
||||||
Some(path_matcher.sources().first().cloned().unwrap_or_default())
|
Some(path_matcher.sources().first().cloned().unwrap_or_default())
|
||||||
@@ -318,13 +258,13 @@ fn collect_diagnostics(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
cx.spawn(|mut cx| async move {
|
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() {
|
if let Some(error_source) = error_source.as_ref() {
|
||||||
writeln!(text, "diagnostics: {}", error_source).unwrap();
|
writeln!(output.text, "diagnostics: {}", error_source).unwrap();
|
||||||
} else {
|
} 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();
|
let mut project_summary = DiagnosticSummary::default();
|
||||||
for (project_path, path, summary) in diagnostic_summaries {
|
for (project_path, path, summary) in diagnostic_summaries {
|
||||||
@@ -341,10 +281,10 @@ fn collect_diagnostics(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let last_end = text.len();
|
let last_end = output.text.len();
|
||||||
let file_path = path.to_string_lossy().to_string();
|
let file_path = path.to_string_lossy().to_string();
|
||||||
if !glob_is_exact_file_match {
|
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
|
if let Some(buffer) = project_handle
|
||||||
@@ -352,75 +292,73 @@ fn collect_diagnostics(
|
|||||||
.await
|
.await
|
||||||
.log_err()
|
.log_err()
|
||||||
{
|
{
|
||||||
collect_buffer_diagnostics(
|
let snapshot = cx.read_model(&buffer, |buffer, _| buffer.snapshot())?;
|
||||||
&mut text,
|
collect_buffer_diagnostics(&mut output, &snapshot, options.include_warnings);
|
||||||
&mut sections,
|
|
||||||
cx.read_model(&buffer, |buffer, _| buffer.snapshot())?,
|
|
||||||
options.include_warnings,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !glob_is_exact_file_match {
|
if !glob_is_exact_file_match {
|
||||||
sections.push((
|
output.sections.push(SlashCommandOutputSection {
|
||||||
last_end..text.len().saturating_sub(1),
|
range: last_end..output.text.len().saturating_sub(1),
|
||||||
PlaceholderType::File(file_path),
|
icon: IconName::File,
|
||||||
))
|
label: file_path.into(),
|
||||||
|
metadata: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No diagnostics found
|
// No diagnostics found
|
||||||
if sections.is_empty() {
|
if output.sections.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
sections.push((
|
let mut label = String::new();
|
||||||
0..text.len(),
|
label.push_str("Diagnostics");
|
||||||
PlaceholderType::Root(project_summary, error_source),
|
if let Some(source) = error_source {
|
||||||
));
|
write!(label, " ({})", source).unwrap();
|
||||||
Ok(Some((text, sections)))
|
}
|
||||||
|
|
||||||
|
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 {
|
pub fn collect_buffer_diagnostics(
|
||||||
for (_, group) in snapshot.diagnostic_groups(None) {
|
output: &mut SlashCommandOutput,
|
||||||
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>,
|
|
||||||
snapshot: &BufferSnapshot,
|
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,
|
include_warnings: bool,
|
||||||
) {
|
) {
|
||||||
for (_, group) in snapshot.diagnostic_groups(None) {
|
for (_, group) in snapshot.diagnostic_groups(None) {
|
||||||
let entry = &group.entries[group.primary_ix];
|
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(
|
fn collect_diagnostic(
|
||||||
text: &mut String,
|
output: &mut SlashCommandOutput,
|
||||||
sections: &mut Vec<(Range<usize>, PlaceholderType)>,
|
|
||||||
entry: &DiagnosticEntry<Anchor>,
|
entry: &DiagnosticEntry<Anchor>,
|
||||||
snapshot: &BufferSnapshot,
|
snapshot: &BufferSnapshot,
|
||||||
include_warnings: bool,
|
include_warnings: bool,
|
||||||
@@ -428,17 +366,17 @@ fn collect_diagnostic(
|
|||||||
const EXCERPT_EXPANSION_SIZE: u32 = 2;
|
const EXCERPT_EXPANSION_SIZE: u32 = 2;
|
||||||
const MAX_MESSAGE_LENGTH: usize = 2000;
|
const MAX_MESSAGE_LENGTH: usize = 2000;
|
||||||
|
|
||||||
let ty = match entry.diagnostic.severity {
|
let (ty, icon) = match entry.diagnostic.severity {
|
||||||
DiagnosticSeverity::WARNING => {
|
DiagnosticSeverity::WARNING => {
|
||||||
if !include_warnings {
|
if !include_warnings {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DiagnosticType::Warning
|
("warning", IconName::Warning)
|
||||||
}
|
}
|
||||||
DiagnosticSeverity::ERROR => DiagnosticType::Error,
|
DiagnosticSeverity::ERROR => ("error", IconName::XCircle),
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
let prev_len = text.len();
|
let prev_len = output.text.len();
|
||||||
|
|
||||||
let range = entry.range.to_point(snapshot);
|
let range = entry.range.to_point(snapshot);
|
||||||
let diagnostic_row_number = range.start.row + 1;
|
let diagnostic_row_number = range.start.row + 1;
|
||||||
@@ -448,11 +386,11 @@ fn collect_diagnostic(
|
|||||||
let excerpt_range =
|
let excerpt_range =
|
||||||
Point::new(start_row, 0).to_offset(&snapshot)..Point::new(end_row, 0).to_offset(&snapshot);
|
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()) {
|
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();
|
let mut buffer_text = String::new();
|
||||||
for chunk in snapshot.text_for_range(excerpt_range) {
|
for chunk in snapshot.text_for_range(excerpt_range) {
|
||||||
@@ -461,46 +399,26 @@ fn collect_diagnostic(
|
|||||||
|
|
||||||
for (i, line) in buffer_text.lines().enumerate() {
|
for (i, line) in buffer_text.lines().enumerate() {
|
||||||
let line_number = start_row + i as u32 + 1;
|
let line_number = start_row + i as u32 + 1;
|
||||||
writeln!(text, "{}", line).unwrap();
|
writeln!(output.text, "{}", line).unwrap();
|
||||||
|
|
||||||
if line_number == diagnostic_row_number {
|
if line_number == diagnostic_row_number {
|
||||||
text.push_str("//");
|
output.text.push_str("//");
|
||||||
let prev_len = text.len();
|
let prev_len = output.text.len();
|
||||||
write!(text, " {}: ", ty.as_str()).unwrap();
|
write!(output.text, " {}: ", ty).unwrap();
|
||||||
let padding = text.len() - prev_len;
|
let padding = output.text.len() - prev_len;
|
||||||
|
|
||||||
let message = util::truncate(&entry.diagnostic.message, MAX_MESSAGE_LENGTH)
|
let message = util::truncate(&entry.diagnostic.message, MAX_MESSAGE_LENGTH)
|
||||||
.replace('\n', format!("\n//{:padding$}", "").as_str());
|
.replace('\n', format!("\n//{:padding$}", "").as_str());
|
||||||
|
|
||||||
writeln!(text, "{message}").unwrap();
|
writeln!(output.text, "{message}").unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(text, "```").unwrap();
|
writeln!(output.text, "```").unwrap();
|
||||||
sections.push((
|
output.sections.push(SlashCommandOutputSection {
|
||||||
prev_len..text.len().saturating_sub(1),
|
range: prev_len..output.text.len().saturating_sub(1),
|
||||||
PlaceholderType::Diagnostic(ty, entry.diagnostic.message.clone()),
|
icon,
|
||||||
))
|
label: entry.diagnostic.message.clone().into(),
|
||||||
}
|
metadata: None,
|
||||||
|
});
|
||||||
#[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",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use indexed_docs::{
|
|||||||
DocsDotRsProvider, IndexedDocsRegistry, IndexedDocsStore, LocalRustdocProvider, PackageName,
|
DocsDotRsProvider, IndexedDocsRegistry, IndexedDocsStore, LocalRustdocProvider, PackageName,
|
||||||
ProviderId,
|
ProviderId,
|
||||||
};
|
};
|
||||||
use language::LspAdapterDelegate;
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use project::{Project, ProjectPath};
|
use project::{Project, ProjectPath};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use util::{maybe, ResultExt};
|
use util::{maybe, ResultExt};
|
||||||
@@ -269,6 +269,8 @@ impl SlashCommand for DocsSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: BufferSnapshot,
|
||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
@@ -349,6 +351,7 @@ impl SlashCommand for DocsSlashCommand {
|
|||||||
range,
|
range,
|
||||||
icon: IconName::FileDoc,
|
icon: IconName::FileDoc,
|
||||||
label: format!("docs ({provider}): {key}",).into(),
|
label: format!("docs ({provider}): {key}",).into(),
|
||||||
|
metadata: None,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use futures::AsyncReadExt;
|
|||||||
use gpui::{Task, WeakView};
|
use gpui::{Task, WeakView};
|
||||||
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
||||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||||
use language::LspAdapterDelegate;
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
@@ -128,6 +128,8 @@ impl SlashCommand for FetchSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: BufferSnapshot,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
@@ -161,6 +163,7 @@ impl SlashCommand for FetchSlashCommand {
|
|||||||
range,
|
range,
|
||||||
icon: IconName::AtSign,
|
icon: IconName::AtSign,
|
||||||
label: format!("fetch {}", url).into(),
|
label: format!("fetch {}", url).into(),
|
||||||
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
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 anyhow::{anyhow, Context as _, Result};
|
||||||
use assistant_slash_command::{AfterCompletion, ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{AfterCompletion, ArgumentCompletion, SlashCommandOutputSection};
|
||||||
use fuzzy::PathMatch;
|
use fuzzy::PathMatch;
|
||||||
use gpui::{AppContext, Model, Task, View, WeakView};
|
use gpui::{AppContext, Model, Task, View, WeakView};
|
||||||
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
|
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
|
||||||
use project::{PathMatchCandidateSet, Project};
|
use project::{PathMatchCandidateSet, Project};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
ops::Range,
|
ops::{Range, RangeInclusive},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
@@ -175,6 +176,8 @@ impl SlashCommand for FileSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: BufferSnapshot,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
@@ -187,54 +190,15 @@ impl SlashCommand for FileSlashCommand {
|
|||||||
return Task::ready(Err(anyhow!("missing path")));
|
return Task::ready(Err(anyhow!("missing path")));
|
||||||
};
|
};
|
||||||
|
|
||||||
let task = collect_files(workspace.read(cx).project().clone(), arguments, cx);
|
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,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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(
|
fn collect_files(
|
||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
glob_inputs: &[String],
|
glob_inputs: &[String],
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<Result<FileCommandOutput>> {
|
) -> Task<Result<SlashCommandOutput>> {
|
||||||
let Ok(matchers) = glob_inputs
|
let Ok(matchers) = glob_inputs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|glob_input| {
|
.map(|glob_input| {
|
||||||
@@ -254,8 +218,7 @@ fn collect_files(
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
let mut text = String::new();
|
let mut output = SlashCommandOutput::default();
|
||||||
let mut ranges = Vec::new();
|
|
||||||
for snapshot in snapshots {
|
for snapshot in snapshots {
|
||||||
let worktree_id = snapshot.id();
|
let worktree_id = snapshot.id();
|
||||||
let mut directory_stack: Vec<(Arc<Path>, String, usize)> = Vec::new();
|
let mut directory_stack: Vec<(Arc<Path>, String, usize)> = Vec::new();
|
||||||
@@ -279,11 +242,12 @@ fn collect_files(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let (_, entry_name, start) = directory_stack.pop().unwrap();
|
let (_, entry_name, start) = directory_stack.pop().unwrap();
|
||||||
ranges.push(OutputFile {
|
output.sections.push(build_entry_output_section(
|
||||||
range_in_text: start..text.len().saturating_sub(1),
|
start..output.text.len().saturating_sub(1),
|
||||||
path: PathBuf::from(entry_name),
|
Some(&PathBuf::from(entry_name)),
|
||||||
entry_type: EntryType::Directory,
|
true,
|
||||||
});
|
None,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let filename = entry
|
let filename = entry
|
||||||
@@ -315,21 +279,23 @@ fn collect_files(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
|
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 prefix_paths.is_empty() {
|
||||||
if is_top_level_directory {
|
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;
|
is_top_level_directory = false;
|
||||||
} else {
|
} else {
|
||||||
text.push_str(&filename);
|
output.text.push_str(&filename);
|
||||||
}
|
}
|
||||||
directory_stack.push((entry.path.clone(), filename, entry_start));
|
directory_stack.push((entry.path.clone(), filename, entry_start));
|
||||||
} else {
|
} else {
|
||||||
let entry_name = format!("{}/{}", prefix_paths, &filename);
|
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));
|
directory_stack.push((entry.path.clone(), entry_name, entry_start));
|
||||||
}
|
}
|
||||||
text.push('\n');
|
output.text.push('\n');
|
||||||
} else if entry.is_file() {
|
} else if entry.is_file() {
|
||||||
let Some(open_buffer_task) = project_handle
|
let Some(open_buffer_task) = project_handle
|
||||||
.update(&mut cx, |project, cx| {
|
.update(&mut cx, |project, cx| {
|
||||||
@@ -340,28 +306,13 @@ fn collect_files(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if let Some(buffer) = open_buffer_task.await.log_err() {
|
if let Some(buffer) = open_buffer_task.await.log_err() {
|
||||||
let buffer_snapshot =
|
let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
|
||||||
cx.read_model(&buffer, |buffer, _| buffer.snapshot())?;
|
append_buffer_to_output(
|
||||||
let prev_len = text.len();
|
&snapshot,
|
||||||
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,
|
|
||||||
Some(&path_including_worktree_name),
|
Some(&path_including_worktree_name),
|
||||||
&buffer_snapshot,
|
&mut output,
|
||||||
) {
|
)
|
||||||
text.pop();
|
.log_err();
|
||||||
}
|
|
||||||
ranges.push(OutputFile {
|
|
||||||
range_in_text: prev_len..text.len(),
|
|
||||||
path: path_including_worktree_name,
|
|
||||||
entry_type: EntryType::File,
|
|
||||||
});
|
|
||||||
text.push('\n');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,43 +322,30 @@ fn collect_files(
|
|||||||
let mut root_path = PathBuf::new();
|
let mut root_path = PathBuf::new();
|
||||||
root_path.push(snapshot.root_name());
|
root_path.push(snapshot.root_name());
|
||||||
root_path.push(&dir);
|
root_path.push(&dir);
|
||||||
ranges.push(OutputFile {
|
output.sections.push(build_entry_output_section(
|
||||||
range_in_text: start..text.len(),
|
start..output.text.len(),
|
||||||
path: root_path,
|
Some(&root_path),
|
||||||
entry_type: EntryType::Directory,
|
true,
|
||||||
});
|
None,
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
ranges.push(OutputFile {
|
output.sections.push(build_entry_output_section(
|
||||||
range_in_text: start..text.len(),
|
start..output.text.len(),
|
||||||
path: PathBuf::from(entry.as_str()),
|
Some(&PathBuf::from(entry.as_str())),
|
||||||
entry_type: EntryType::Directory,
|
true,
|
||||||
});
|
None,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(FileCommandOutput {
|
Ok(output)
|
||||||
completion_text: text,
|
|
||||||
files: ranges,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_file_content(buffer: &mut String, snapshot: &BufferSnapshot, filename: String) {
|
pub fn codeblock_fence_for_path(
|
||||||
let mut content = snapshot.text();
|
path: Option<&Path>,
|
||||||
LineEnding::normalize(&mut content);
|
row_range: Option<RangeInclusive<u32>>,
|
||||||
buffer.reserve(filename.len() + content.len() + 9);
|
) -> String {
|
||||||
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 {
|
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
write!(text, "```").unwrap();
|
write!(text, "```").unwrap();
|
||||||
|
|
||||||
@@ -422,13 +360,18 @@ pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(row_range) = row_range {
|
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.push('\n');
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct FileCommandMetadata {
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build_entry_output_section(
|
pub fn build_entry_output_section(
|
||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
path: Option<&Path>,
|
path: Option<&Path>,
|
||||||
@@ -454,6 +397,16 @@ pub fn build_entry_output_section(
|
|||||||
range,
|
range,
|
||||||
icon,
|
icon,
|
||||||
label: label.into(),
|
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()
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
@@ -591,9 +574,9 @@ mod test {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(result_1.completion_text.starts_with("root/dir"));
|
assert!(result_1.text.starts_with("root/dir"));
|
||||||
// 4 files + 2 directories
|
// 4 files + 2 directories
|
||||||
assert_eq!(6, result_1.files.len());
|
assert_eq!(result_1.sections.len(), 6);
|
||||||
|
|
||||||
let result_2 = cx
|
let result_2 = cx
|
||||||
.update(|cx| collect_files(project.clone(), &["root/dir/".to_string()], cx))
|
.update(|cx| collect_files(project.clone(), &["root/dir/".to_string()], cx))
|
||||||
@@ -607,9 +590,9 @@ mod test {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(result.completion_text.starts_with("root/dir"));
|
assert!(result.text.starts_with("root/dir"));
|
||||||
// 5 files + 2 directories
|
// 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
|
// Ensure that the project lasts until after the last await
|
||||||
drop(project);
|
drop(project);
|
||||||
@@ -654,36 +637,27 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Sanity check
|
// Sanity check
|
||||||
assert!(result.completion_text.starts_with("zed/assets/themes\n"));
|
assert!(result.text.starts_with("zed/assets/themes\n"));
|
||||||
assert_eq!(7, result.files.len());
|
assert_eq!(result.sections.len(), 7);
|
||||||
|
|
||||||
// Ensure that full file paths are included in the real output
|
// Ensure that full file paths are included in the real output
|
||||||
assert!(result
|
assert!(result.text.contains("zed/assets/themes/andromeda/LICENSE"));
|
||||||
.completion_text
|
assert!(result.text.contains("zed/assets/themes/ayu/LICENSE"));
|
||||||
.contains("zed/assets/themes/andromeda/LICENSE"));
|
assert!(result.text.contains("zed/assets/themes/summercamp/LICENSE"));
|
||||||
assert!(result
|
|
||||||
.completion_text
|
|
||||||
.contains("zed/assets/themes/ayu/LICENSE"));
|
|
||||||
assert!(result
|
|
||||||
.completion_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
|
// Ensure that things are in descending order, with properly relativized paths
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"zed/assets/themes/andromeda/LICENSE",
|
result.sections[0].label,
|
||||||
result.files[0].path.to_string_lossy()
|
"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!(
|
assert_eq!(
|
||||||
"zed/assets/themes/ayu/LICENSE",
|
result.sections[4].label,
|
||||||
result.files[2].path.to_string_lossy()
|
"zed/assets/themes/summercamp/LICENSE"
|
||||||
);
|
|
||||||
assert_eq!("ayu", result.files[3].path.to_string_lossy());
|
|
||||||
assert_eq!(
|
|
||||||
"zed/assets/themes/summercamp/LICENSE",
|
|
||||||
result.files[4].path.to_string_lossy()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure that the project lasts until after the last await
|
// Ensure that the project lasts until after the last await
|
||||||
@@ -723,27 +697,24 @@ mod test {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.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!(
|
assert_eq!(
|
||||||
"zed/assets/themes/LICENSE",
|
result.sections[1].label,
|
||||||
result.files[0].path.to_string_lossy()
|
"zed/assets/themes/summercamp/LICENSE"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"zed/assets/themes/summercamp/LICENSE",
|
result.sections[2].label,
|
||||||
result.files[1].path.to_string_lossy()
|
"zed/assets/themes/summercamp/subdir/LICENSE"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"zed/assets/themes/summercamp/subdir/LICENSE",
|
result.sections[3].label,
|
||||||
result.files[2].path.to_string_lossy()
|
"zed/assets/themes/summercamp/subdir/subsubdir/LICENSE"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(result.sections[4].label, "subsubdir");
|
||||||
"zed/assets/themes/summercamp/subdir/subsubdir/LICENSE",
|
assert_eq!(result.sections[5].label, "subdir");
|
||||||
result.files[3].path.to_string_lossy()
|
assert_eq!(result.sections[6].label, "summercamp");
|
||||||
);
|
assert_eq!(result.sections[7].label, "zed/assets/themes");
|
||||||
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());
|
|
||||||
|
|
||||||
// Ensure that the project lasts until after the last await
|
// Ensure that the project lasts until after the last await
|
||||||
drop(project);
|
drop(project);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use assistant_slash_command::{
|
|||||||
};
|
};
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use gpui::{Task, WeakView};
|
use gpui::{Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
@@ -43,6 +43,8 @@ impl SlashCommand for NowSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: BufferSnapshot,
|
||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
_cx: &mut WindowContext,
|
_cx: &mut WindowContext,
|
||||||
@@ -57,6 +59,7 @@ impl SlashCommand for NowSlashCommand {
|
|||||||
range,
|
range,
|
||||||
icon: IconName::CountdownTimer,
|
icon: IconName::CountdownTimer,
|
||||||
label: now.to_rfc2822().into(),
|
label: now.to_rfc2822().into(),
|
||||||
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -1,90 +1,39 @@
|
|||||||
use super::{SlashCommand, SlashCommandOutput};
|
use super::{
|
||||||
use anyhow::{anyhow, Context, Result};
|
create_label_for_command, search_command::add_search_result_section, SlashCommand,
|
||||||
|
SlashCommandOutput,
|
||||||
|
};
|
||||||
|
use crate::PromptBuilder;
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||||
use fs::Fs;
|
use feature_flags::FeatureFlag;
|
||||||
use gpui::{AppContext, Model, Task, WeakView};
|
use gpui::{AppContext, Task, WeakView, WindowContext};
|
||||||
use language::LspAdapterDelegate;
|
use language::{Anchor, CodeLabel, LspAdapterDelegate};
|
||||||
use project::{Project, ProjectPath};
|
use language_model::{LanguageModelRegistry, LanguageModelTool};
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use semantic_index::SemanticDb;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
pub struct ProjectSlashCommandFeatureFlag;
|
||||||
|
|
||||||
|
impl FeatureFlag for ProjectSlashCommandFeatureFlag {
|
||||||
|
const NAME: &'static str = "project-slash-command";
|
||||||
|
}
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Write,
|
fmt::Write as _,
|
||||||
path::Path,
|
ops::DerefMut,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::prelude::*;
|
use ui::{BorrowAppContext as _, IconName};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct ProjectSlashCommand;
|
pub struct ProjectSlashCommand {
|
||||||
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
|
}
|
||||||
|
|
||||||
impl ProjectSlashCommand {
|
impl ProjectSlashCommand {
|
||||||
async fn build_message(fs: Arc<dyn Fs>, path_to_cargo_toml: &Path) -> Result<String> {
|
pub fn new(prompt_builder: Arc<PromptBuilder>) -> Self {
|
||||||
let buffer = fs.load(path_to_cargo_toml).await?;
|
Self { prompt_builder }
|
||||||
let cargo_toml: cargo_toml::Manifest = toml::from_str(&buffer)?;
|
|
||||||
|
|
||||||
let mut message = String::new();
|
|
||||||
writeln!(message, "You are in a Rust project.")?;
|
|
||||||
|
|
||||||
if let Some(workspace) = cargo_toml.workspace {
|
|
||||||
writeln!(
|
|
||||||
message,
|
|
||||||
"The project is a Cargo workspace with the following members:"
|
|
||||||
)?;
|
|
||||||
for member in workspace.members {
|
|
||||||
writeln!(message, "- {member}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !workspace.default_members.is_empty() {
|
|
||||||
writeln!(message, "The default members are:")?;
|
|
||||||
for member in workspace.default_members {
|
|
||||||
writeln!(message, "- {member}")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !workspace.dependencies.is_empty() {
|
|
||||||
writeln!(
|
|
||||||
message,
|
|
||||||
"The following workspace dependencies are installed:"
|
|
||||||
)?;
|
|
||||||
for dependency in workspace.dependencies.keys() {
|
|
||||||
writeln!(message, "- {dependency}")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let Some(package) = cargo_toml.package {
|
|
||||||
writeln!(
|
|
||||||
message,
|
|
||||||
"The project name is \"{name}\".",
|
|
||||||
name = package.name
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let description = package
|
|
||||||
.description
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|description| description.get().ok().cloned());
|
|
||||||
if let Some(description) = description.as_ref() {
|
|
||||||
writeln!(message, "It describes itself as \"{description}\".")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cargo_toml.dependencies.is_empty() {
|
|
||||||
writeln!(message, "The following dependencies are installed:")?;
|
|
||||||
for dependency in cargo_toml.dependencies.keys() {
|
|
||||||
writeln!(message, "- {dependency}")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
|
|
||||||
let worktree = project.read(cx).worktrees(cx).next()?;
|
|
||||||
let worktree = worktree.read(cx);
|
|
||||||
let entry = worktree.entry_for_path("Cargo.toml")?;
|
|
||||||
let path = ProjectPath {
|
|
||||||
worktree_id: worktree.id(),
|
|
||||||
path: entry.path.clone(),
|
|
||||||
};
|
|
||||||
Some(Arc::from(
|
|
||||||
project.read(cx).absolute_path(&path, cx)?.as_path(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,12 +42,20 @@ impl SlashCommand for ProjectSlashCommand {
|
|||||||
"project".into()
|
"project".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn label(&self, cx: &AppContext) -> CodeLabel {
|
||||||
|
create_label_for_command("project", &[], cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> String {
|
||||||
"insert project metadata".into()
|
"Generate semantic searches based on the current context".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
fn menu_text(&self) -> String {
|
||||||
"Insert Project Metadata".into()
|
"Project Context".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requires_argument(&self) -> bool {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
@@ -108,43 +65,126 @@ impl SlashCommand for ProjectSlashCommand {
|
|||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
_cx: &mut WindowContext,
|
_cx: &mut WindowContext,
|
||||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
Task::ready(Ok(Vec::new()))
|
||||||
}
|
|
||||||
|
|
||||||
fn requires_argument(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<Anchor>],
|
||||||
|
context_buffer: language::BufferSnapshot,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<Result<SlashCommandOutput>> {
|
||||||
let output = workspace.update(cx, |workspace, cx| {
|
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||||
let project = workspace.project().clone();
|
let current_model = model_registry.active_model();
|
||||||
let fs = workspace.project().read(cx).fs().clone();
|
let prompt_builder = self.prompt_builder.clone();
|
||||||
let path = Self::path_to_cargo_toml(project, cx);
|
|
||||||
let output = cx.background_executor().spawn(async move {
|
|
||||||
let path = path.with_context(|| "Cargo.toml not found")?;
|
|
||||||
Self::build_message(fs, &path).await
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.foreground_executor().spawn(async move {
|
let Some(workspace) = workspace.upgrade() else {
|
||||||
let text = output.await?;
|
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
||||||
let range = 0..text.len();
|
};
|
||||||
Ok(SlashCommandOutput {
|
let project = workspace.read(cx).project().clone();
|
||||||
text,
|
let fs = project.read(cx).fs().clone();
|
||||||
sections: vec![SlashCommandOutputSection {
|
let Some(project_index) =
|
||||||
range,
|
cx.update_global(|index: &mut SemanticDb, cx| index.project_index(project, cx))
|
||||||
icon: IconName::FileTree,
|
else {
|
||||||
label: "Project".into(),
|
return Task::ready(Err(anyhow::anyhow!("no project indexer")));
|
||||||
}],
|
};
|
||||||
run_commands_in_text: false,
|
|
||||||
|
cx.spawn(|mut cx| async move {
|
||||||
|
let current_model = current_model.ok_or_else(|| anyhow!("no model selected"))?;
|
||||||
|
|
||||||
|
let prompt =
|
||||||
|
prompt_builder.generate_project_slash_command_prompt(context_buffer.text())?;
|
||||||
|
|
||||||
|
let search_queries = current_model
|
||||||
|
.use_tool::<SearchQueries>(
|
||||||
|
language_model::LanguageModelRequest {
|
||||||
|
messages: vec![language_model::LanguageModelRequestMessage {
|
||||||
|
role: language_model::Role::User,
|
||||||
|
content: vec![language_model::MessageContent::Text(prompt)],
|
||||||
|
cache: false,
|
||||||
|
}],
|
||||||
|
tools: vec![],
|
||||||
|
stop: vec![],
|
||||||
|
temperature: None,
|
||||||
|
},
|
||||||
|
cx.deref_mut(),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.search_queries;
|
||||||
|
|
||||||
|
let results = project_index
|
||||||
|
.read_with(&cx, |project_index, cx| {
|
||||||
|
project_index.search(search_queries.clone(), 25, cx)
|
||||||
|
})?
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let results = SemanticDb::load_results(results, &fs, &cx).await?;
|
||||||
|
|
||||||
|
cx.background_executor()
|
||||||
|
.spawn(async move {
|
||||||
|
let mut output = "Project context:\n".to_string();
|
||||||
|
let mut sections = Vec::new();
|
||||||
|
|
||||||
|
for (ix, query) in search_queries.into_iter().enumerate() {
|
||||||
|
let start_ix = output.len();
|
||||||
|
writeln!(&mut output, "Results for {query}:").unwrap();
|
||||||
|
let mut has_results = false;
|
||||||
|
for result in &results {
|
||||||
|
if result.query_index == ix {
|
||||||
|
add_search_result_section(result, &mut output, &mut sections);
|
||||||
|
has_results = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if has_results {
|
||||||
|
sections.push(SlashCommandOutputSection {
|
||||||
|
range: start_ix..output.len(),
|
||||||
|
icon: IconName::MagnifyingGlass,
|
||||||
|
label: query.into(),
|
||||||
|
metadata: None,
|
||||||
|
});
|
||||||
|
output.push('\n');
|
||||||
|
} else {
|
||||||
|
output.truncate(start_ix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sections.push(SlashCommandOutputSection {
|
||||||
|
range: 0..output.len(),
|
||||||
|
icon: IconName::Book,
|
||||||
|
label: "Project context".into(),
|
||||||
|
metadata: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(SlashCommandOutput {
|
||||||
|
text: output,
|
||||||
|
sections,
|
||||||
|
run_commands_in_text: true,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
.await
|
||||||
});
|
})
|
||||||
output.unwrap_or_else(|error| Task::ready(Err(error)))
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(JsonSchema, Deserialize)]
|
||||||
|
struct SearchQueries {
|
||||||
|
/// An array of semantic search queries.
|
||||||
|
///
|
||||||
|
/// These queries will be used to search the user's codebase.
|
||||||
|
/// The function can only accept 4 queries, otherwise it will error.
|
||||||
|
/// As such, it's important that you limit the length of the search_queries array to 5 queries or less.
|
||||||
|
search_queries: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageModelTool for SearchQueries {
|
||||||
|
fn name() -> String {
|
||||||
|
"search_queries".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description() -> String {
|
||||||
|
"Generate semantic search queries based on context".to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::prompt_library::PromptStore;
|
|||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||||
use gpui::{Task, WeakView};
|
use gpui::{Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use std::sync::{atomic::AtomicBool, Arc};
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
@@ -56,6 +56,8 @@ impl SlashCommand for PromptSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: BufferSnapshot,
|
||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
@@ -95,6 +97,7 @@ impl SlashCommand for PromptSlashCommand {
|
|||||||
range,
|
range,
|
||||||
icon: IconName::Library,
|
icon: IconName::Library,
|
||||||
label: title,
|
label: title,
|
||||||
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: true,
|
run_commands_in_text: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,15 +7,13 @@ use anyhow::Result;
|
|||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||||
use feature_flags::FeatureFlag;
|
use feature_flags::FeatureFlag;
|
||||||
use gpui::{AppContext, Task, WeakView};
|
use gpui::{AppContext, Task, WeakView};
|
||||||
use language::{CodeLabel, LineEnding, LspAdapterDelegate};
|
use language::{CodeLabel, LspAdapterDelegate};
|
||||||
use semantic_index::SemanticDb;
|
use semantic_index::{LoadedSearchResult, SemanticDb};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
path::PathBuf,
|
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::{prelude::*, IconName};
|
use ui::{prelude::*, IconName};
|
||||||
use util::ResultExt;
|
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct SearchSlashCommandFeatureFlag;
|
pub(crate) struct SearchSlashCommandFeatureFlag;
|
||||||
@@ -60,6 +58,8 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: language::BufferSnapshot,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
@@ -101,66 +101,19 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
cx.spawn(|cx| async move {
|
cx.spawn(|cx| async move {
|
||||||
let results = project_index
|
let results = project_index
|
||||||
.read_with(&cx, |project_index, cx| {
|
.read_with(&cx, |project_index, cx| {
|
||||||
project_index.search(query.clone(), limit.unwrap_or(5), cx)
|
project_index.search(vec![query.clone()], limit.unwrap_or(5), cx)
|
||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut loaded_results = Vec::new();
|
let loaded_results = SemanticDb::load_results(results, &fs, &cx).await?;
|
||||||
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 output = cx
|
let output = cx
|
||||||
.background_executor()
|
.background_executor()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let mut text = format!("Search results for {query}:\n");
|
let mut text = format!("Search results for {query}:\n");
|
||||||
let mut sections = Vec::new();
|
let mut sections = Vec::new();
|
||||||
for (result, full_path, file_content) in loaded_results {
|
for loaded_result in &loaded_results {
|
||||||
let range_start = result.range.start.min(file_content.len());
|
add_search_result_section(loaded_result, &mut text, &mut sections);
|
||||||
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());
|
|
||||||
|
|
||||||
let section_start_ix = text.len();
|
|
||||||
text.push_str(&codeblock_fence_for_path(
|
|
||||||
Some(&result.path),
|
|
||||||
Some(start_row..end_row),
|
|
||||||
));
|
|
||||||
|
|
||||||
let mut excerpt =
|
|
||||||
file_content[start_line_byte_offset..end_line_byte_offset].to_string();
|
|
||||||
LineEnding::normalize(&mut excerpt);
|
|
||||||
text.push_str(&excerpt);
|
|
||||||
writeln!(text, "\n```\n").unwrap();
|
|
||||||
let section_end_ix = text.len() - 1;
|
|
||||||
sections.push(build_entry_output_section(
|
|
||||||
section_start_ix..section_end_ix,
|
|
||||||
Some(&full_path),
|
|
||||||
false,
|
|
||||||
Some(start_row + 1..end_row + 1),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let query = SharedString::from(query);
|
let query = SharedString::from(query);
|
||||||
@@ -168,6 +121,7 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
range: 0..text.len(),
|
range: 0..text.len(),
|
||||||
icon: IconName::MagnifyingGlass,
|
icon: IconName::MagnifyingGlass,
|
||||||
label: query,
|
label: query,
|
||||||
|
metadata: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
SlashCommandOutput {
|
SlashCommandOutput {
|
||||||
@@ -182,3 +136,35 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_search_result_section(
|
||||||
|
loaded_result: &LoadedSearchResult,
|
||||||
|
text: &mut String,
|
||||||
|
sections: &mut Vec<SlashCommandOutputSection<usize>>,
|
||||||
|
) {
|
||||||
|
let LoadedSearchResult {
|
||||||
|
path,
|
||||||
|
full_path,
|
||||||
|
excerpt_content,
|
||||||
|
row_range,
|
||||||
|
..
|
||||||
|
} = loaded_result;
|
||||||
|
let section_start_ix = text.len();
|
||||||
|
text.push_str(&codeblock_fence_for_path(
|
||||||
|
Some(&path),
|
||||||
|
Some(row_range.clone()),
|
||||||
|
));
|
||||||
|
|
||||||
|
text.push_str(&excerpt_content);
|
||||||
|
if !text.ends_with('\n') {
|
||||||
|
text.push('\n');
|
||||||
|
}
|
||||||
|
writeln!(text, "```\n").unwrap();
|
||||||
|
let section_end_ix = text.len() - 1;
|
||||||
|
sections.push(build_entry_output_section(
|
||||||
|
section_start_ix..section_end_ix,
|
||||||
|
Some(&full_path),
|
||||||
|
false,
|
||||||
|
Some(row_range.start() + 1..row_range.end() + 1),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context as _, Result};
|
|||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{Task, WeakView};
|
use gpui::{Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{path::Path, sync::atomic::AtomicBool};
|
use std::{path::Path, sync::atomic::AtomicBool};
|
||||||
use ui::{IconName, WindowContext};
|
use ui::{IconName, WindowContext};
|
||||||
@@ -41,6 +41,8 @@ impl SlashCommand for OutlineSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: BufferSnapshot,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
@@ -77,6 +79,7 @@ impl SlashCommand for OutlineSlashCommand {
|
|||||||
range: 0..outline_text.len(),
|
range: 0..outline_text.len(),
|
||||||
icon: IconName::ListTree,
|
icon: IconName::ListTree,
|
||||||
label: path.to_string_lossy().to_string().into(),
|
label: path.to_string_lossy().to_string().into(),
|
||||||
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
text: outline_text,
|
text: outline_text,
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
use super::{
|
use super::{file_command::append_buffer_to_output, SlashCommand, SlashCommandOutput};
|
||||||
diagnostics_command::write_single_file_diagnostics,
|
|
||||||
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
|
||||||
SlashCommand, SlashCommandOutput,
|
|
||||||
};
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use assistant_slash_command::ArgumentCompletion;
|
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use gpui::{Entity, Task, WeakView};
|
use gpui::{Entity, Task, WeakView};
|
||||||
use language::{BufferSnapshot, CodeLabel, HighlightId, LspAdapterDelegate};
|
use language::{BufferSnapshot, CodeLabel, HighlightId, LspAdapterDelegate};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Write,
|
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::{ActiveTheme, WindowContext};
|
use ui::{ActiveTheme, WindowContext};
|
||||||
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct TabSlashCommand;
|
pub(crate) struct TabSlashCommand;
|
||||||
@@ -131,6 +127,8 @@ impl SlashCommand for TabSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: BufferSnapshot,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
@@ -144,40 +142,11 @@ impl SlashCommand for TabSlashCommand {
|
|||||||
);
|
);
|
||||||
|
|
||||||
cx.background_executor().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
let mut sections = Vec::new();
|
let mut output = SlashCommandOutput::default();
|
||||||
let mut text = String::new();
|
|
||||||
let mut has_diagnostics = false;
|
|
||||||
for (full_path, buffer, _) in tab_items_search.await? {
|
for (full_path, buffer, _) in tab_items_search.await? {
|
||||||
let section_start_ix = text.len();
|
append_buffer_to_output(&buffer, full_path.as_deref(), &mut output).log_err();
|
||||||
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,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
Ok(output)
|
||||||
Ok(SlashCommandOutput {
|
|
||||||
text,
|
|
||||||
sections,
|
|
||||||
run_commands_in_text: has_diagnostics,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use assistant_slash_command::{
|
|||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
};
|
};
|
||||||
use gpui::{AppContext, Task, View, WeakView};
|
use gpui::{AppContext, Task, View, WeakView};
|
||||||
use language::{CodeLabel, LspAdapterDelegate};
|
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
||||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use workspace::{dock::Panel, Workspace};
|
use workspace::{dock::Panel, Workspace};
|
||||||
@@ -57,6 +57,8 @@ impl SlashCommand for TerminalSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: BufferSnapshot,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
@@ -91,6 +93,7 @@ impl SlashCommand for TerminalSlashCommand {
|
|||||||
range,
|
range,
|
||||||
icon: IconName::Terminal,
|
icon: IconName::Terminal,
|
||||||
label: "Terminal".into(),
|
label: "Terminal".into(),
|
||||||
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use assistant_slash_command::{
|
|||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
};
|
};
|
||||||
use gpui::{Task, WeakView};
|
use gpui::{Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
|
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
@@ -53,6 +53,8 @@ impl SlashCommand for WorkflowSlashCommand {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
_context_buffer: BufferSnapshot,
|
||||||
_workspace: WeakView<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
@@ -68,6 +70,7 @@ impl SlashCommand for WorkflowSlashCommand {
|
|||||||
range,
|
range,
|
||||||
icon: IconName::Route,
|
icon: IconName::Route,
|
||||||
label: "Workflow".into(),
|
label: "Workflow".into(),
|
||||||
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ pub struct SlashCommandSettings {
|
|||||||
/// Settings for the `/docs` slash command.
|
/// Settings for the `/docs` slash command.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub docs: DocsCommandSettings,
|
pub docs: DocsCommandSettings,
|
||||||
/// Settings for the `/project` slash command.
|
/// Settings for the `/cargo-workspace` slash command.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub project: ProjectCommandSettings,
|
pub cargo_workspace: CargoWorkspaceCommandSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Settings for the `/docs` slash command.
|
/// Settings for the `/docs` slash command.
|
||||||
@@ -23,10 +23,10 @@ pub struct DocsCommandSettings {
|
|||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Settings for the `/project` slash command.
|
/// Settings for the `/cargo-workspace` slash command.
|
||||||
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)]
|
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)]
|
||||||
pub struct ProjectCommandSettings {
|
pub struct CargoWorkspaceCommandSettings {
|
||||||
/// Whether `/project` is enabled.
|
/// Whether `/cargo-workspace` is enabled.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ impl TerminalInlineAssistant {
|
|||||||
messages,
|
messages,
|
||||||
tools: Vec::new(),
|
tools: Vec::new(),
|
||||||
stop: Vec::new(),
|
stop: Vec::new(),
|
||||||
temperature: 1.0,
|
temperature: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,7 +570,7 @@ impl Render for PromptEditor {
|
|||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.border_y_1()
|
.border_y_1()
|
||||||
.border_color(cx.theme().status().info_border)
|
.border_color(cx.theme().status().info_border)
|
||||||
.py_1p5()
|
.py_2()
|
||||||
.h_full()
|
.h_full()
|
||||||
.w_full()
|
.w_full()
|
||||||
.on_action(cx.listener(Self::confirm))
|
.on_action(cx.listener(Self::confirm))
|
||||||
@@ -949,12 +949,11 @@ impl PromptEditor {
|
|||||||
} else {
|
} else {
|
||||||
cx.theme().colors().text
|
cx.theme().colors().text
|
||||||
},
|
},
|
||||||
font_family: settings.ui_font.family.clone(),
|
font_family: settings.buffer_font.family.clone(),
|
||||||
font_features: settings.ui_font.features.clone(),
|
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||||
font_fallbacks: settings.ui_font.fallbacks.clone(),
|
font_size: settings.buffer_font_size.into(),
|
||||||
font_size: rems(0.875).into(),
|
font_weight: settings.buffer_font.weight,
|
||||||
font_weight: settings.ui_font.weight,
|
line_height: relative(settings.buffer_line_height.value()),
|
||||||
line_height: relative(1.3),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
EditorElement::new(
|
EditorElement::new(
|
||||||
@@ -1067,6 +1066,7 @@ impl Codegen {
|
|||||||
telemetry.report_assistant_event(
|
telemetry.report_assistant_event(
|
||||||
None,
|
None,
|
||||||
telemetry_events::AssistantKind::Inline,
|
telemetry_events::AssistantKind::Inline,
|
||||||
|
telemetry_events::AssistantPhase::Response,
|
||||||
model_telemetry_id,
|
model_telemetry_id,
|
||||||
response_latency,
|
response_latency,
|
||||||
error_message,
|
error_message,
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ impl WorkflowSuggestion {
|
|||||||
suggestion_range,
|
suggestion_range,
|
||||||
initial_prompt,
|
initial_prompt,
|
||||||
initial_transaction_id,
|
initial_transaction_id,
|
||||||
|
false,
|
||||||
Some(workspace.clone()),
|
Some(workspace.clone()),
|
||||||
Some(assistant_panel),
|
Some(assistant_panel),
|
||||||
cx,
|
cx,
|
||||||
|
|||||||
@@ -19,4 +19,5 @@ gpui.workspace = true
|
|||||||
language.workspace = true
|
language.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ mod slash_command_registry;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
|
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
|
||||||
use language::{CodeLabel, LspAdapterDelegate};
|
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
pub use slash_command_registry::*;
|
pub use slash_command_registry::*;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -77,6 +77,8 @@ pub trait SlashCommand: 'static + Send + Sync {
|
|||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
|
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
|
context_buffer: BufferSnapshot,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
// TODO: We're just using the `LspAdapterDelegate` here because that is
|
// TODO: We're just using the `LspAdapterDelegate` here because that is
|
||||||
// what the extension API is already expecting.
|
// 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,
|
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct SlashCommandOutput {
|
pub struct SlashCommandOutput {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub sections: Vec<SlashCommandOutputSection<usize>>,
|
pub sections: Vec<SlashCommandOutputSection<usize>>,
|
||||||
@@ -106,4 +108,11 @@ pub struct SlashCommandOutputSection<T> {
|
|||||||
pub range: Range<T>,
|
pub range: Range<T>,
|
||||||
pub icon: IconName,
|
pub icon: IconName,
|
||||||
pub label: SharedString,
|
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
|
derive_more.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
parking_lot.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
|
util.workspace = true
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ db.workspace = true
|
|||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
isahc.workspace = true
|
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
markdown_preview.workspace = true
|
markdown_preview.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use gpui::{
|
|||||||
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
|
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
|
||||||
SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext,
|
SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
use isahc::AsyncBody;
|
|
||||||
|
|
||||||
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
|
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
@@ -20,7 +19,7 @@ use smol::{fs, io::AsyncReadExt};
|
|||||||
use settings::{Settings, SettingsSources, SettingsStore};
|
use settings::{Settings, SettingsSources, SettingsStore};
|
||||||
use smol::{fs::File, process::Command};
|
use smol::{fs::File, process::Command};
|
||||||
|
|
||||||
use http_client::{HttpClient, HttpClientWithUrl};
|
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||||
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
|
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
|
||||||
use std::{
|
use std::{
|
||||||
env::{
|
env::{
|
||||||
@@ -244,29 +243,44 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
|
|||||||
let auto_updater = AutoUpdater::get(cx)?;
|
let auto_updater = AutoUpdater::get(cx)?;
|
||||||
let release_channel = ReleaseChannel::try_global(cx)?;
|
let release_channel = ReleaseChannel::try_global(cx)?;
|
||||||
|
|
||||||
if matches!(
|
match release_channel {
|
||||||
release_channel,
|
ReleaseChannel::Stable | ReleaseChannel::Preview => {
|
||||||
ReleaseChannel::Stable | ReleaseChannel::Preview
|
let auto_updater = auto_updater.read(cx);
|
||||||
) {
|
let current_version = auto_updater.current_version;
|
||||||
let auto_updater = auto_updater.read(cx);
|
let release_channel = release_channel.dev_name();
|
||||||
let release_channel = release_channel.dev_name();
|
let path = format!("/releases/{release_channel}/{current_version}");
|
||||||
let current_version = auto_updater.current_version;
|
let url = &auto_updater.http_client.build_url(&path);
|
||||||
let url = &auto_updater
|
cx.open_url(url);
|
||||||
.http_client
|
}
|
||||||
.build_url(&format!("/releases/{release_channel}/{current_version}"));
|
ReleaseChannel::Nightly => {
|
||||||
cx.open_url(url);
|
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
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||||
let release_channel = ReleaseChannel::global(cx);
|
let release_channel = ReleaseChannel::global(cx);
|
||||||
|
|
||||||
|
let url = match release_channel {
|
||||||
|
ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"),
|
||||||
|
ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(url) = url {
|
||||||
|
cx.open_url(url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let version = AppVersion::global(cx).to_string();
|
let version = AppVersion::global(cx).to_string();
|
||||||
|
|
||||||
let client = client::Client::global(cx).http_client();
|
let client = client::Client::global(cx).http_client();
|
||||||
let url = client.build_url(&format!(
|
let url = client.build_url(&format!(
|
||||||
"/api/release_notes/{}/{}",
|
"/api/release_notes/v2/{}/{}",
|
||||||
release_channel.dev_name(),
|
release_channel.dev_name(),
|
||||||
version
|
version
|
||||||
));
|
));
|
||||||
@@ -343,15 +357,17 @@ pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
|
|||||||
let should_show_notification = should_show_notification.await?;
|
let should_show_notification = should_show_notification.await?;
|
||||||
if should_show_notification {
|
if should_show_notification {
|
||||||
workspace.update(&mut cx, |workspace, cx| {
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
let workspace_handle = workspace.weak_handle();
|
||||||
workspace.show_notification(
|
workspace.show_notification(
|
||||||
NotificationId::unique::<UpdateNotification>(),
|
NotificationId::unique::<UpdateNotification>(),
|
||||||
cx,
|
cx,
|
||||||
|cx| cx.new_view(|_| UpdateNotification::new(version)),
|
|cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)),
|
||||||
);
|
);
|
||||||
updater
|
updater.update(cx, |updater, cx| {
|
||||||
.read(cx)
|
updater
|
||||||
.set_should_show_update_notification(false, cx)
|
.set_should_show_update_notification(false, cx)
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
|
});
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
div, DismissEvent, EventEmitter, InteractiveElement, IntoElement, ParentElement, Render,
|
div, DismissEvent, EventEmitter, InteractiveElement, IntoElement, ParentElement, Render,
|
||||||
SemanticVersion, StatefulInteractiveElement, Styled, ViewContext,
|
SemanticVersion, StatefulInteractiveElement, Styled, ViewContext, WeakView,
|
||||||
};
|
};
|
||||||
use menu::Cancel;
|
use menu::Cancel;
|
||||||
use release_channel::ReleaseChannel;
|
use release_channel::ReleaseChannel;
|
||||||
use workspace::ui::{h_flex, v_flex, Icon, IconName, Label, StyledExt};
|
use util::ResultExt;
|
||||||
|
use workspace::{
|
||||||
|
ui::{h_flex, v_flex, Icon, IconName, Label, StyledExt},
|
||||||
|
Workspace,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct UpdateNotification {
|
pub struct UpdateNotification {
|
||||||
version: SemanticVersion,
|
version: SemanticVersion,
|
||||||
|
workspace: WeakView<Workspace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for UpdateNotification {}
|
impl EventEmitter<DismissEvent> for UpdateNotification {}
|
||||||
@@ -41,7 +46,11 @@ impl Render for UpdateNotification {
|
|||||||
.child(Label::new("View the release notes"))
|
.child(Label::new("View the release notes"))
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
crate::view_release_notes(&Default::default(), cx);
|
this.workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
crate::view_release_notes_locally(workspace, cx);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
this.dismiss(&menu::Cancel, cx)
|
this.dismiss(&menu::Cancel, cx)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
@@ -49,8 +58,8 @@ impl Render for UpdateNotification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl UpdateNotification {
|
impl UpdateNotification {
|
||||||
pub fn new(version: SemanticVersion) -> Self {
|
pub fn new(version: SemanticVersion, workspace: WeakView<Workspace>) -> Self {
|
||||||
Self { version }
|
Self { version, workspace }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
pub fn dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
|
Element, EventEmitter, FocusableView, IntoElement, ParentElement, Render, StyledText,
|
||||||
ViewContext,
|
Subscription, ViewContext,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
@@ -90,17 +90,30 @@ impl Render for Breadcrumbs {
|
|||||||
ButtonLike::new("toggle outline view")
|
ButtonLike::new("toggle outline view")
|
||||||
.child(breadcrumbs_stack)
|
.child(breadcrumbs_stack)
|
||||||
.style(ButtonStyle::Transparent)
|
.style(ButtonStyle::Transparent)
|
||||||
.on_click(move |_, cx| {
|
.on_click({
|
||||||
if let Some(editor) = editor.upgrade() {
|
let editor = editor.clone();
|
||||||
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
|
move |_, cx| {
|
||||||
|
if let Some(editor) = editor.upgrade() {
|
||||||
|
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.tooltip(|cx| {
|
.tooltip(move |cx| {
|
||||||
Tooltip::for_action(
|
if let Some(editor) = editor.upgrade() {
|
||||||
"Show symbol outline",
|
let focus_handle = editor.read(cx).focus_handle(cx);
|
||||||
&editor::actions::ToggleOutline,
|
Tooltip::for_action_in(
|
||||||
cx,
|
"Show symbol outline",
|
||||||
)
|
&editor::actions::ToggleOutline,
|
||||||
|
&focus_handle,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Tooltip::for_action(
|
||||||
|
"Show symbol outline",
|
||||||
|
&editor::actions::ToggleOutline,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
None => element
|
None => element
|
||||||
|
|||||||
@@ -1200,6 +1200,7 @@ impl Room {
|
|||||||
room_id: self.id(),
|
room_id: self.id(),
|
||||||
worktrees: vec![],
|
worktrees: vec![],
|
||||||
dev_server_project_id: Some(dev_server_project_id.0),
|
dev_server_project_id: Some(dev_server_project_id.0),
|
||||||
|
is_ssh_project: false,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if let Some(project_id) = project.read(cx).remote_id() {
|
if let Some(project_id) = project.read(cx).remote_id() {
|
||||||
@@ -1210,6 +1211,7 @@ impl Room {
|
|||||||
room_id: self.id(),
|
room_id: self.id(),
|
||||||
worktrees: project.read(cx).worktree_metadata_protos(cx),
|
worktrees: project.read(cx).worktree_metadata_protos(cx),
|
||||||
dev_server_project_id: None,
|
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 gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
|
||||||
use language::proto::serialize_version;
|
use language::proto::serialize_version;
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::{self, AnyProtoClient, PeerId},
|
proto::{self, PeerId},
|
||||||
TypedEnvelope,
|
AnyProtoClient, TypedEnvelope,
|
||||||
};
|
};
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
use text::BufferId;
|
use text::BufferId;
|
||||||
@@ -66,7 +66,7 @@ impl ChannelBuffer {
|
|||||||
let capability = channel_store.read(cx).channel_capability(channel.id);
|
let capability = channel_store.read(cx).channel_capability(channel.id);
|
||||||
language::Buffer::remote(buffer_id, response.replica_id as u16, capability, base_text)
|
language::Buffer::remote(buffer_id, response.replica_id as u16, capability, base_text)
|
||||||
})?;
|
})?;
|
||||||
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;
|
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
|
||||||
|
|
||||||
let subscription = client.subscribe_to_entity(channel.id.0)?;
|
let subscription = client.subscribe_to_entity(channel.id.0)?;
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ impl ChannelBuffer {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
this.buffer
|
this.buffer
|
||||||
.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
|
.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
|
||||||
})??;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -171,11 +171,14 @@ impl ChannelBuffer {
|
|||||||
fn on_buffer_update(
|
fn on_buffer_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Model<language::Buffer>,
|
_: Model<language::Buffer>,
|
||||||
event: &language::Event,
|
event: &language::BufferEvent,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
language::Event::Operation(operation) => {
|
language::BufferEvent::Operation {
|
||||||
|
operation,
|
||||||
|
is_local: true,
|
||||||
|
} => {
|
||||||
if *ZED_ALWAYS_ACTIVE {
|
if *ZED_ALWAYS_ACTIVE {
|
||||||
if let language::Operation::UpdateSelections { selections, .. } = operation {
|
if let language::Operation::UpdateSelections { selections, .. } = operation {
|
||||||
if selections.is_empty() {
|
if selections.is_empty() {
|
||||||
@@ -191,7 +194,7 @@ impl ChannelBuffer {
|
|||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
language::Event::Edited => {
|
language::BufferEvent::Edited => {
|
||||||
cx.emit(ChannelBufferEvent::BufferEdited);
|
cx.emit(ChannelBufferEvent::BufferEdited);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use gpui::{
|
|||||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
|
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
|
||||||
};
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rpc::proto::AnyProtoClient;
|
use rpc::AnyProtoClient;
|
||||||
use std::{
|
use std::{
|
||||||
ops::{ControlFlow, Range},
|
ops::{ControlFlow, Range},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@@ -332,7 +332,7 @@ impl ChannelChat {
|
|||||||
.update(&mut cx, |chat, cx| {
|
.update(&mut cx, |chat, cx| {
|
||||||
if let Some(first_id) = chat.first_loaded_message_id() {
|
if let Some(first_id) = chat.first_loaded_message_id() {
|
||||||
if first_id <= 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);
|
let message_id = ChannelMessageId::Saved(message_id);
|
||||||
cursor.seek(&message_id, Bias::Left, &());
|
cursor.seek(&message_id, Bias::Left, &());
|
||||||
return ControlFlow::Break(
|
return ControlFlow::Break(
|
||||||
@@ -498,7 +498,7 @@ impl ChannelChat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn message(&self, ix: usize) -> &ChannelMessage {
|
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.seek(&Count(ix), Bias::Right, &());
|
||||||
cursor.item().unwrap()
|
cursor.item().unwrap()
|
||||||
}
|
}
|
||||||
@@ -515,13 +515,13 @@ impl ChannelChat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn messages_in_range(&self, range: Range<usize>) -> impl Iterator<Item = &ChannelMessage> {
|
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.seek(&Count(range.start), Bias::Right, &());
|
||||||
cursor.take(range.len())
|
cursor.take(range.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pending_messages(&self) -> impl Iterator<Item = &ChannelMessage> {
|
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.seek(&ChannelMessageId::Pending(0), Bias::Left, &());
|
||||||
cursor
|
cursor
|
||||||
}
|
}
|
||||||
@@ -589,11 +589,11 @@ impl ChannelChat {
|
|||||||
fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut ModelContext<Self>) {
|
fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut ModelContext<Self>) {
|
||||||
if let Some((first_message, last_message)) = messages.first().zip(messages.last()) {
|
if let Some((first_message, last_message)) = messages.first().zip(messages.last()) {
|
||||||
let nonces = messages
|
let nonces = messages
|
||||||
.cursor::<()>()
|
.cursor::<()>(&())
|
||||||
.map(|m| m.nonce)
|
.map(|m| m.nonce)
|
||||||
.collect::<HashSet<_>>();
|
.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 mut new_messages = old_cursor.slice(&first_message.id, Bias::Left, &());
|
||||||
let start_ix = old_cursor.start().1 .0;
|
let start_ix = old_cursor.start().1 .0;
|
||||||
let removed_messages = old_cursor.slice(&last_message.id, Bias::Right, &());
|
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>) {
|
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, &());
|
let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left, &());
|
||||||
if let Some(item) = cursor.item() {
|
if let Some(item) = cursor.item() {
|
||||||
if item.id == ChannelMessageId::Saved(id) {
|
if item.id == ChannelMessageId::Saved(id) {
|
||||||
@@ -685,7 +685,7 @@ impl ChannelChat {
|
|||||||
edited_at: Option<OffsetDateTime>,
|
edited_at: Option<OffsetDateTime>,
|
||||||
cx: &mut ModelContext<Self>,
|
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 mut messages = cursor.slice(&id, Bias::Left, &());
|
||||||
let ix = messages.summary().count;
|
let ix = messages.summary().count;
|
||||||
|
|
||||||
@@ -716,7 +716,7 @@ async fn messages_from_proto(
|
|||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Result<SumTree<ChannelMessage>> {
|
) -> Result<SumTree<ChannelMessage>> {
|
||||||
let messages = ChannelMessage::from_proto_vec(proto_messages, user_store, cx).await?;
|
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, &());
|
result.extend(messages, &());
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
@@ -808,7 +808,7 @@ pub fn mentions_to_proto(mentions: &[(Range<usize>, UserId)]) -> Vec<proto::Chat
|
|||||||
impl sum_tree::Item for ChannelMessage {
|
impl sum_tree::Item for ChannelMessage {
|
||||||
type Summary = ChannelMessageSummary;
|
type Summary = ChannelMessageSummary;
|
||||||
|
|
||||||
fn summary(&self) -> Self::Summary {
|
fn summary(&self, _cx: &()) -> Self::Summary {
|
||||||
ChannelMessageSummary {
|
ChannelMessageSummary {
|
||||||
max_id: self.id,
|
max_id: self.id,
|
||||||
count: 1,
|
count: 1,
|
||||||
@@ -825,6 +825,10 @@ impl Default for ChannelMessageId {
|
|||||||
impl sum_tree::Summary for ChannelMessageSummary {
|
impl sum_tree::Summary for ChannelMessageSummary {
|
||||||
type Context = ();
|
type Context = ();
|
||||||
|
|
||||||
|
fn zero(_cx: &Self::Context) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &Self, _: &()) {
|
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||||
self.max_id = summary.max_id;
|
self.max_id = summary.max_id;
|
||||||
self.count += summary.count;
|
self.count += summary.count;
|
||||||
@@ -832,6 +836,10 @@ impl sum_tree::Summary for ChannelMessageSummary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for ChannelMessageId {
|
impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for ChannelMessageId {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
|
||||||
debug_assert!(summary.max_id > *self);
|
debug_assert!(summary.max_id > *self);
|
||||||
*self = summary.max_id;
|
*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 {
|
impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for Count {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
|
||||||
self.0 += summary.count;
|
self.0 += summary.count;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1007,7 +1007,7 @@ impl ChannelStore {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(language::proto::deserialize_operation)
|
.map(language::proto::deserialize_operation)
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
buffer.apply_ops(incoming_operations, cx)?;
|
buffer.apply_ops(incoming_operations, cx);
|
||||||
anyhow::Ok(outgoing_operations)
|
anyhow::Ok(outgoing_operations)
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|||||||
@@ -18,12 +18,11 @@ test-support = ["clock/test-support", "collections/test-support", "gpui/test-sup
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
async-recursion = "0.3"
|
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"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
clock.workspace = true
|
clock.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
feature_flags.workspace = true
|
feature_flags.workspace = true
|
||||||
fs.workspace = true
|
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
@@ -34,7 +33,9 @@ parking_lot.workspace = true
|
|||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
release_channel.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
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.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 http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use proto::{AnyProtoClient, EntityMessageSubscriber, ProtoClient, ProtoMessageHandlerSet};
|
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use release_channel::{AppVersion, ReleaseChannel};
|
use release_channel::{AppVersion, ReleaseChannel};
|
||||||
use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage};
|
use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage};
|
||||||
@@ -241,8 +240,6 @@ pub enum EstablishConnectionError {
|
|||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Other(#[from] anyhow::Error),
|
Other(#[from] anyhow::Error),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Http(#[from] http_client::Error),
|
|
||||||
#[error("{0}")]
|
|
||||||
InvalidHeaderValue(#[from] async_tungstenite::tungstenite::http::header::InvalidHeaderValue),
|
InvalidHeaderValue(#[from] async_tungstenite::tungstenite::http::header::InvalidHeaderValue),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
@@ -530,19 +527,13 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn production(cx: &mut AppContext) -> Arc<Self> {
|
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 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,
|
&ClientSettings::get_global(cx).server_url,
|
||||||
Some(user_agent),
|
cx.http_client().proxy().cloned(),
|
||||||
ProxySettings::get_global(cx).proxy.clone(),
|
|
||||||
));
|
));
|
||||||
Self::new(clock, http.clone(), cx)
|
Self::new(clock, http, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> u64 {
|
pub fn id(&self) -> u64 {
|
||||||
@@ -1146,8 +1137,32 @@ impl Client {
|
|||||||
|
|
||||||
match url_scheme {
|
match url_scheme {
|
||||||
Https => {
|
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, _) =
|
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(
|
Ok(Connection::new(
|
||||||
stream
|
stream
|
||||||
.map_err(|error| anyhow!(error))
|
.map_err(|error| anyhow!(error))
|
||||||
@@ -1606,6 +1621,10 @@ impl ProtoClient for Client {
|
|||||||
fn message_handler_set(&self) -> &parking_lot::Mutex<ProtoMessageHandlerSet> {
|
fn message_handler_set(&self) -> &parking_lot::Mutex<ProtoMessageHandlerSet> {
|
||||||
&self.handler_set
|
&self.handler_set
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_via_collab(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ use std::io::Write;
|
|||||||
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||||
use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
|
use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
|
||||||
use telemetry_events::{
|
use telemetry_events::{
|
||||||
ActionEvent, AppEvent, AssistantEvent, AssistantKind, CallEvent, CpuEvent, EditEvent,
|
ActionEvent, AppEvent, AssistantEvent, AssistantKind, AssistantPhase, CallEvent, CpuEvent,
|
||||||
EditorEvent, Event, EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent,
|
EditEvent, EditorEvent, Event, EventRequestBody, EventWrapper, ExtensionEvent,
|
||||||
MemoryEvent, ReplEvent, SettingEvent,
|
InlineCompletionEvent, MemoryEvent, ReplEvent, SettingEvent,
|
||||||
};
|
};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
@@ -37,9 +37,10 @@ pub struct Telemetry {
|
|||||||
|
|
||||||
struct TelemetryState {
|
struct TelemetryState {
|
||||||
settings: TelemetrySettings,
|
settings: TelemetrySettings,
|
||||||
metrics_id: Option<Arc<str>>, // Per logged-in user
|
system_id: Option<Arc<str>>, // Per system
|
||||||
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
|
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
|
||||||
session_id: Option<String>, // Per app launch
|
session_id: Option<String>, // Per app launch
|
||||||
|
metrics_id: Option<Arc<str>>, // Per logged-in user
|
||||||
release_channel: Option<&'static str>,
|
release_channel: Option<&'static str>,
|
||||||
architecture: &'static str,
|
architecture: &'static str,
|
||||||
events_queue: Vec<EventWrapper>,
|
events_queue: Vec<EventWrapper>,
|
||||||
@@ -191,9 +192,10 @@ impl Telemetry {
|
|||||||
settings: *TelemetrySettings::get_global(cx),
|
settings: *TelemetrySettings::get_global(cx),
|
||||||
architecture: env::consts::ARCH,
|
architecture: env::consts::ARCH,
|
||||||
release_channel,
|
release_channel,
|
||||||
|
system_id: None,
|
||||||
installation_id: None,
|
installation_id: None,
|
||||||
metrics_id: None,
|
|
||||||
session_id: None,
|
session_id: None,
|
||||||
|
metrics_id: None,
|
||||||
events_queue: Vec::new(),
|
events_queue: Vec::new(),
|
||||||
flush_events_task: None,
|
flush_events_task: None,
|
||||||
log_file: None,
|
log_file: None,
|
||||||
@@ -283,11 +285,13 @@ impl Telemetry {
|
|||||||
|
|
||||||
pub fn start(
|
pub fn start(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
|
system_id: Option<String>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
session_id: String,
|
session_id: String,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) {
|
) {
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
|
state.system_id = system_id.map(|id| id.into());
|
||||||
state.installation_id = installation_id.map(|id| id.into());
|
state.installation_id = installation_id.map(|id| id.into());
|
||||||
state.session_id = Some(session_id);
|
state.session_id = Some(session_id);
|
||||||
state.app_version = release_channel::AppVersion::global(cx).to_string();
|
state.app_version = release_channel::AppVersion::global(cx).to_string();
|
||||||
@@ -304,7 +308,10 @@ impl Telemetry {
|
|||||||
|
|
||||||
let refresh_kind = ProcessRefreshKind::new().with_cpu().with_memory();
|
let refresh_kind = ProcessRefreshKind::new().with_cpu().with_memory();
|
||||||
let current_process = Pid::from_u32(std::process::id());
|
let current_process = Pid::from_u32(std::process::id());
|
||||||
system.refresh_process_specifics(current_process, refresh_kind);
|
system.refresh_processes_specifics(
|
||||||
|
sysinfo::ProcessesToUpdate::Some(&[current_process]),
|
||||||
|
refresh_kind,
|
||||||
|
);
|
||||||
|
|
||||||
// Waiting some amount of time before the first query is important to get a reasonable value
|
// Waiting some amount of time before the first query is important to get a reasonable value
|
||||||
// https://docs.rs/sysinfo/0.29.10/sysinfo/trait.ProcessExt.html#tymethod.cpu_usage
|
// https://docs.rs/sysinfo/0.29.10/sysinfo/trait.ProcessExt.html#tymethod.cpu_usage
|
||||||
@@ -314,7 +321,10 @@ impl Telemetry {
|
|||||||
smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await;
|
smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await;
|
||||||
|
|
||||||
let current_process = Pid::from_u32(std::process::id());
|
let current_process = Pid::from_u32(std::process::id());
|
||||||
system.refresh_process_specifics(current_process, refresh_kind);
|
system.refresh_processes_specifics(
|
||||||
|
sysinfo::ProcessesToUpdate::Some(&[current_process]),
|
||||||
|
refresh_kind,
|
||||||
|
);
|
||||||
let Some(process) = system.process(current_process) else {
|
let Some(process) = system.process(current_process) else {
|
||||||
log::error!(
|
log::error!(
|
||||||
"Failed to find own process {current_process:?} in system process table"
|
"Failed to find own process {current_process:?} in system process table"
|
||||||
@@ -385,6 +395,7 @@ impl Telemetry {
|
|||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
conversation_id: Option<String>,
|
conversation_id: Option<String>,
|
||||||
kind: AssistantKind,
|
kind: AssistantKind,
|
||||||
|
phase: AssistantPhase,
|
||||||
model: String,
|
model: String,
|
||||||
response_latency: Option<Duration>,
|
response_latency: Option<Duration>,
|
||||||
error_message: Option<String>,
|
error_message: Option<String>,
|
||||||
@@ -392,6 +403,7 @@ impl Telemetry {
|
|||||||
let event = Event::Assistant(AssistantEvent {
|
let event = Event::Assistant(AssistantEvent {
|
||||||
conversation_id,
|
conversation_id,
|
||||||
kind,
|
kind,
|
||||||
|
phase,
|
||||||
model: model.to_string(),
|
model: model.to_string(),
|
||||||
response_latency,
|
response_latency,
|
||||||
error_message,
|
error_message,
|
||||||
@@ -629,9 +641,10 @@ impl Telemetry {
|
|||||||
let state = this.state.lock();
|
let state = this.state.lock();
|
||||||
|
|
||||||
let request_body = EventRequestBody {
|
let request_body = EventRequestBody {
|
||||||
|
system_id: state.system_id.as_deref().map(Into::into),
|
||||||
installation_id: state.installation_id.as_deref().map(Into::into),
|
installation_id: state.installation_id.as_deref().map(Into::into),
|
||||||
metrics_id: state.metrics_id.as_deref().map(Into::into),
|
|
||||||
session_id: state.session_id.clone(),
|
session_id: state.session_id.clone(),
|
||||||
|
metrics_id: state.metrics_id.as_deref().map(Into::into),
|
||||||
is_staff: state.is_staff,
|
is_staff: state.is_staff,
|
||||||
app_version: state.app_version.clone(),
|
app_version: state.app_version.clone(),
|
||||||
os_name: state.os_name.clone(),
|
os_name: state.os_name.clone(),
|
||||||
@@ -703,6 +716,7 @@ mod tests {
|
|||||||
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
|
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
|
||||||
));
|
));
|
||||||
let http = FakeHttpClient::with_200_response();
|
let http = FakeHttpClient::with_200_response();
|
||||||
|
let system_id = Some("system_id".to_string());
|
||||||
let installation_id = Some("installation_id".to_string());
|
let installation_id = Some("installation_id".to_string());
|
||||||
let session_id = "session_id".to_string();
|
let session_id = "session_id".to_string();
|
||||||
|
|
||||||
@@ -710,7 +724,7 @@ mod tests {
|
|||||||
let telemetry = Telemetry::new(clock.clone(), http, cx);
|
let telemetry = Telemetry::new(clock.clone(), http, cx);
|
||||||
|
|
||||||
telemetry.state.lock().max_queue_size = 4;
|
telemetry.state.lock().max_queue_size = 4;
|
||||||
telemetry.start(installation_id, session_id, cx);
|
telemetry.start(system_id, installation_id, session_id, cx);
|
||||||
|
|
||||||
assert!(is_empty_state(&telemetry));
|
assert!(is_empty_state(&telemetry));
|
||||||
|
|
||||||
@@ -788,13 +802,14 @@ mod tests {
|
|||||||
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
|
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
|
||||||
));
|
));
|
||||||
let http = FakeHttpClient::with_200_response();
|
let http = FakeHttpClient::with_200_response();
|
||||||
|
let system_id = Some("system_id".to_string());
|
||||||
let installation_id = Some("installation_id".to_string());
|
let installation_id = Some("installation_id".to_string());
|
||||||
let session_id = "session_id".to_string();
|
let session_id = "session_id".to_string();
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let telemetry = Telemetry::new(clock.clone(), http, cx);
|
let telemetry = Telemetry::new(clock.clone(), http, cx);
|
||||||
telemetry.state.lock().max_queue_size = 4;
|
telemetry.state.lock().max_queue_size = 4;
|
||||||
telemetry.start(installation_id, session_id, cx);
|
telemetry.start(system_id, installation_id, session_id, cx);
|
||||||
|
|
||||||
assert!(is_empty_state(&telemetry));
|
assert!(is_empty_state(&telemetry));
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ use std::{
|
|||||||
|
|
||||||
pub use system_clock::*;
|
pub use system_clock::*;
|
||||||
|
|
||||||
|
pub const LOCAL_BRANCH_REPLICA_ID: u16 = u16::MAX;
|
||||||
|
|
||||||
/// A unique identifier for each distributed node.
|
/// A unique identifier for each distributed node.
|
||||||
pub type ReplicaId = u16;
|
pub type ReplicaId = u16;
|
||||||
|
|
||||||
@@ -25,7 +27,10 @@ pub struct Lamport {
|
|||||||
|
|
||||||
/// A [vector clock](https://en.wikipedia.org/wiki/Vector_clock).
|
/// A [vector clock](https://en.wikipedia.org/wiki/Vector_clock).
|
||||||
#[derive(Clone, Default, Hash, Eq, PartialEq)]
|
#[derive(Clone, Default, Hash, Eq, PartialEq)]
|
||||||
pub struct Global(SmallVec<[u32; 8]>);
|
pub struct Global {
|
||||||
|
values: SmallVec<[u32; 8]>,
|
||||||
|
local_branch_value: u32,
|
||||||
|
}
|
||||||
|
|
||||||
impl Global {
|
impl Global {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@@ -33,41 +38,51 @@ impl Global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, replica_id: ReplicaId) -> Seq {
|
pub fn get(&self, replica_id: ReplicaId) -> Seq {
|
||||||
self.0.get(replica_id as usize).copied().unwrap_or(0) as Seq
|
if replica_id == LOCAL_BRANCH_REPLICA_ID {
|
||||||
|
self.local_branch_value
|
||||||
|
} else {
|
||||||
|
self.values.get(replica_id as usize).copied().unwrap_or(0) as Seq
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe(&mut self, timestamp: Lamport) {
|
pub fn observe(&mut self, timestamp: Lamport) {
|
||||||
if timestamp.value > 0 {
|
if timestamp.value > 0 {
|
||||||
let new_len = timestamp.replica_id as usize + 1;
|
if timestamp.replica_id == LOCAL_BRANCH_REPLICA_ID {
|
||||||
if new_len > self.0.len() {
|
self.local_branch_value = cmp::max(self.local_branch_value, timestamp.value);
|
||||||
self.0.resize(new_len, 0);
|
} else {
|
||||||
}
|
let new_len = timestamp.replica_id as usize + 1;
|
||||||
|
if new_len > self.values.len() {
|
||||||
|
self.values.resize(new_len, 0);
|
||||||
|
}
|
||||||
|
|
||||||
let entry = &mut self.0[timestamp.replica_id as usize];
|
let entry = &mut self.values[timestamp.replica_id as usize];
|
||||||
*entry = cmp::max(*entry, timestamp.value);
|
*entry = cmp::max(*entry, timestamp.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn join(&mut self, other: &Self) {
|
pub fn join(&mut self, other: &Self) {
|
||||||
if other.0.len() > self.0.len() {
|
if other.values.len() > self.values.len() {
|
||||||
self.0.resize(other.0.len(), 0);
|
self.values.resize(other.values.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (left, right) in self.0.iter_mut().zip(&other.0) {
|
for (left, right) in self.values.iter_mut().zip(&other.values) {
|
||||||
*left = cmp::max(*left, *right);
|
*left = cmp::max(*left, *right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.local_branch_value = cmp::max(self.local_branch_value, other.local_branch_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn meet(&mut self, other: &Self) {
|
pub fn meet(&mut self, other: &Self) {
|
||||||
if other.0.len() > self.0.len() {
|
if other.values.len() > self.values.len() {
|
||||||
self.0.resize(other.0.len(), 0);
|
self.values.resize(other.values.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut new_len = 0;
|
let mut new_len = 0;
|
||||||
for (ix, (left, right)) in self
|
for (ix, (left, right)) in self
|
||||||
.0
|
.values
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(other.0.iter().chain(iter::repeat(&0)))
|
.zip(other.values.iter().chain(iter::repeat(&0)))
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
if *left == 0 {
|
if *left == 0 {
|
||||||
@@ -80,7 +95,8 @@ impl Global {
|
|||||||
new_len = ix + 1;
|
new_len = ix + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.0.resize(new_len, 0);
|
self.values.resize(new_len, 0);
|
||||||
|
self.local_branch_value = cmp::min(self.local_branch_value, other.local_branch_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observed(&self, timestamp: Lamport) -> bool {
|
pub fn observed(&self, timestamp: Lamport) -> bool {
|
||||||
@@ -88,34 +104,44 @@ impl Global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn observed_any(&self, other: &Self) -> bool {
|
pub fn observed_any(&self, other: &Self) -> bool {
|
||||||
self.0
|
self.values
|
||||||
.iter()
|
.iter()
|
||||||
.zip(other.0.iter())
|
.zip(other.values.iter())
|
||||||
.any(|(left, right)| *right > 0 && left >= right)
|
.any(|(left, right)| *right > 0 && left >= right)
|
||||||
|
|| (other.local_branch_value > 0 && self.local_branch_value >= other.local_branch_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observed_all(&self, other: &Self) -> bool {
|
pub fn observed_all(&self, other: &Self) -> bool {
|
||||||
let mut rhs = other.0.iter();
|
let mut rhs = other.values.iter();
|
||||||
self.0.iter().all(|left| match rhs.next() {
|
self.values.iter().all(|left| match rhs.next() {
|
||||||
Some(right) => left >= right,
|
Some(right) => left >= right,
|
||||||
None => true,
|
None => true,
|
||||||
}) && rhs.next().is_none()
|
}) && rhs.next().is_none()
|
||||||
|
&& self.local_branch_value >= other.local_branch_value
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn changed_since(&self, other: &Self) -> bool {
|
pub fn changed_since(&self, other: &Self) -> bool {
|
||||||
self.0.len() > other.0.len()
|
self.values.len() > other.values.len()
|
||||||
|| self
|
|| self
|
||||||
.0
|
.values
|
||||||
.iter()
|
.iter()
|
||||||
.zip(other.0.iter())
|
.zip(other.values.iter())
|
||||||
.any(|(left, right)| left > right)
|
.any(|(left, right)| left > right)
|
||||||
|
|| self.local_branch_value > other.local_branch_value
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = Lamport> + '_ {
|
pub fn iter(&self) -> impl Iterator<Item = Lamport> + '_ {
|
||||||
self.0.iter().enumerate().map(|(replica_id, seq)| Lamport {
|
self.values
|
||||||
replica_id: replica_id as ReplicaId,
|
.iter()
|
||||||
value: *seq,
|
.enumerate()
|
||||||
})
|
.map(|(replica_id, seq)| Lamport {
|
||||||
|
replica_id: replica_id as ReplicaId,
|
||||||
|
value: *seq,
|
||||||
|
})
|
||||||
|
.chain((self.local_branch_value > 0).then_some(Lamport {
|
||||||
|
replica_id: LOCAL_BRANCH_REPLICA_ID,
|
||||||
|
value: self.local_branch_value,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,6 +218,9 @@ impl fmt::Debug for Global {
|
|||||||
}
|
}
|
||||||
write!(f, "{}: {}", timestamp.replica_id, timestamp.value)?;
|
write!(f, "{}: {}", timestamp.replica_id, timestamp.value)?;
|
||||||
}
|
}
|
||||||
|
if self.local_branch_value > 0 {
|
||||||
|
write!(f, "<branch>: {}", self.local_branch_value)?;
|
||||||
|
}
|
||||||
write!(f, "}}")
|
write!(f, "}}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ futures.workspace = true
|
|||||||
google_ai.workspace = true
|
google_ai.workspace = true
|
||||||
hex.workspace = true
|
hex.workspace = true
|
||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
|
isahc_http_client.workspace = true
|
||||||
jsonwebtoken.workspace = true
|
jsonwebtoken.workspace = true
|
||||||
live_kit_server.workspace = true
|
live_kit_server.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ To use a different set of admin users, create `crates/collab/seed.json`.
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"admins": ["yourgithubhere"],
|
"admins": ["yourgithubhere"],
|
||||||
"channels": ["zed"],
|
"channels": ["zed"]
|
||||||
"number_of_users": 20
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -149,16 +149,6 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: google-ai
|
name: google-ai
|
||||||
key: api_key
|
key: api_key
|
||||||
- name: RUNPOD_API_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: runpod
|
|
||||||
key: api_key
|
|
||||||
- name: RUNPOD_API_SUMMARY_URL
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: runpod
|
|
||||||
key: summary
|
|
||||||
- name: BLOB_STORE_ACCESS_KEY
|
- name: BLOB_STORE_ACCESS_KEY
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
|
|||||||
@@ -8,6 +8,5 @@
|
|||||||
"JosephTLyons",
|
"JosephTLyons",
|
||||||
"rgbkrk"
|
"rgbkrk"
|
||||||
],
|
],
|
||||||
"channels": ["zed"],
|
"channels": ["zed"]
|
||||||
"number_of_users": 100
|
|
||||||
}
|
}
|
||||||
|
|||||||
602
crates/collab/seed/github_users.json
Normal file
602
crates/collab/seed/github_users.json
Normal file
@@ -0,0 +1,602 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"login": "mojombo",
|
||||||
|
"email": "tom@mojombo.com",
|
||||||
|
"created_at": "2007-10-20T05:24:19Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"login": "defunkt",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2007-10-20T05:24:19Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"login": "pjhyett",
|
||||||
|
"email": "pj@hyett.com",
|
||||||
|
"created_at": "2008-01-07T17:54:22Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"login": "wycats",
|
||||||
|
"email": "wycats@gmail.com",
|
||||||
|
"created_at": "2008-01-12T05:38:33Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"login": "ezmobius",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-12T07:51:46Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"login": "ivey",
|
||||||
|
"email": "ivey@gweezlebur.com",
|
||||||
|
"created_at": "2008-01-12T15:15:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"login": "evanphx",
|
||||||
|
"email": "evan@phx.io",
|
||||||
|
"created_at": "2008-01-12T16:46:24Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"login": "vanpelt",
|
||||||
|
"email": "vanpelt@wandb.com",
|
||||||
|
"created_at": "2008-01-13T05:57:18Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"login": "wayneeseguin",
|
||||||
|
"email": "wayneeseguin@gmail.com",
|
||||||
|
"created_at": "2008-01-13T06:02:21Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"login": "brynary",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-13T10:19:47Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"login": "kevinclark",
|
||||||
|
"email": "kevin.clark@gmail.com",
|
||||||
|
"created_at": "2008-01-13T18:33:26Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"login": "technoweenie",
|
||||||
|
"email": "technoweenie@hey.com",
|
||||||
|
"created_at": "2008-01-14T04:33:35Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"login": "macournoyer",
|
||||||
|
"email": "macournoyer@gmail.com",
|
||||||
|
"created_at": "2008-01-14T10:49:35Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"login": "takeo",
|
||||||
|
"email": "toby@takeo.email",
|
||||||
|
"created_at": "2008-01-14T11:25:49Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"login": "caged",
|
||||||
|
"email": "encytemedia@gmail.com",
|
||||||
|
"created_at": "2008-01-15T04:47:24Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26,
|
||||||
|
"login": "topfunky",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-15T05:40:05Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"login": "anotherjesse",
|
||||||
|
"email": "anotherjesse@gmail.com",
|
||||||
|
"created_at": "2008-01-15T07:49:30Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 28,
|
||||||
|
"login": "roland",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-15T08:12:51Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 29,
|
||||||
|
"login": "lukas",
|
||||||
|
"email": "lukas@wandb.com",
|
||||||
|
"created_at": "2008-01-15T12:50:02Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 30,
|
||||||
|
"login": "fanvsfan",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-15T14:15:23Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 31,
|
||||||
|
"login": "tomtt",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-15T15:44:31Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"login": "railsjitsu",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-16T04:57:23Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 34,
|
||||||
|
"login": "nitay",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-18T14:09:11Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 35,
|
||||||
|
"login": "kevwil",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-19T05:50:12Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 36,
|
||||||
|
"login": "KirinDave",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-19T08:01:02Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 37,
|
||||||
|
"login": "jamesgolick",
|
||||||
|
"email": "jamesgolick@gmail.com",
|
||||||
|
"created_at": "2008-01-19T22:52:30Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 38,
|
||||||
|
"login": "atmos",
|
||||||
|
"email": "atmos@atmos.org",
|
||||||
|
"created_at": "2008-01-22T09:14:11Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 44,
|
||||||
|
"login": "errfree",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-24T02:08:37Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 45,
|
||||||
|
"login": "mojodna",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-24T04:40:22Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 46,
|
||||||
|
"login": "bmizerany",
|
||||||
|
"email": "blake.mizerany@gmail.com",
|
||||||
|
"created_at": "2008-01-24T04:44:30Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 47,
|
||||||
|
"login": "jnewland",
|
||||||
|
"email": "jesse@jnewland.com",
|
||||||
|
"created_at": "2008-01-25T02:28:12Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 48,
|
||||||
|
"login": "joshknowles",
|
||||||
|
"email": "joshknowles@gmail.com",
|
||||||
|
"created_at": "2008-01-25T21:30:42Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 49,
|
||||||
|
"login": "hornbeck",
|
||||||
|
"email": "hornbeck@gmail.com",
|
||||||
|
"created_at": "2008-01-25T21:49:23Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 50,
|
||||||
|
"login": "jwhitmire",
|
||||||
|
"email": "jeff@jwhitmire.com",
|
||||||
|
"created_at": "2008-01-25T22:07:48Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 51,
|
||||||
|
"login": "elbowdonkey",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-25T22:08:20Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 52,
|
||||||
|
"login": "reinh",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-25T22:16:29Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 53,
|
||||||
|
"login": "knzai",
|
||||||
|
"email": "git@knz.ai",
|
||||||
|
"created_at": "2008-01-25T22:33:10Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 68,
|
||||||
|
"login": "bs",
|
||||||
|
"email": "yap@bri.tt",
|
||||||
|
"created_at": "2008-01-27T01:46:29Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 69,
|
||||||
|
"login": "rsanheim",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-27T07:09:47Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 70,
|
||||||
|
"login": "schacon",
|
||||||
|
"email": "schacon@gmail.com",
|
||||||
|
"created_at": "2008-01-27T17:19:28Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 71,
|
||||||
|
"login": "uggedal",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-27T22:18:57Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 72,
|
||||||
|
"login": "bruce",
|
||||||
|
"email": "brwcodes@gmail.com",
|
||||||
|
"created_at": "2008-01-28T07:16:45Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 73,
|
||||||
|
"login": "sam",
|
||||||
|
"email": "ssmoot@gmail.com",
|
||||||
|
"created_at": "2008-01-28T19:01:26Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 74,
|
||||||
|
"login": "mmower",
|
||||||
|
"email": "self@mattmower.com",
|
||||||
|
"created_at": "2008-01-28T19:47:50Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 75,
|
||||||
|
"login": "abhay",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-28T21:08:23Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 76,
|
||||||
|
"login": "rabble",
|
||||||
|
"email": "evan@protest.net",
|
||||||
|
"created_at": "2008-01-28T23:27:02Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 77,
|
||||||
|
"login": "benburkert",
|
||||||
|
"email": "ben@benburkert.com",
|
||||||
|
"created_at": "2008-01-28T23:44:14Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 78,
|
||||||
|
"login": "indirect",
|
||||||
|
"email": "andre@arko.net",
|
||||||
|
"created_at": "2008-01-29T07:59:27Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 79,
|
||||||
|
"login": "fearoffish",
|
||||||
|
"email": "me@fearof.fish",
|
||||||
|
"created_at": "2008-01-29T08:43:10Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 80,
|
||||||
|
"login": "ry",
|
||||||
|
"email": "ry@tinyclouds.org",
|
||||||
|
"created_at": "2008-01-29T08:50:34Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 81,
|
||||||
|
"login": "engineyard",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-29T09:51:30Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 82,
|
||||||
|
"login": "jsierles",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-29T11:10:25Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 83,
|
||||||
|
"login": "tweibley",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-29T13:52:07Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 84,
|
||||||
|
"login": "peimei",
|
||||||
|
"email": "james@railsjitsu.com",
|
||||||
|
"created_at": "2008-01-29T15:44:11Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 85,
|
||||||
|
"login": "brixen",
|
||||||
|
"email": "brixen@gmail.com",
|
||||||
|
"created_at": "2008-01-29T16:47:55Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 87,
|
||||||
|
"login": "tmornini",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-29T18:43:39Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 88,
|
||||||
|
"login": "outerim",
|
||||||
|
"email": "lee@outerim.com",
|
||||||
|
"created_at": "2008-01-29T18:48:32Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 89,
|
||||||
|
"login": "daksis",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-29T19:18:16Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 90,
|
||||||
|
"login": "sr",
|
||||||
|
"email": "me@simonrozet.com",
|
||||||
|
"created_at": "2008-01-29T20:37:53Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 91,
|
||||||
|
"login": "lifo",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-29T23:09:30Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 92,
|
||||||
|
"login": "rsl",
|
||||||
|
"email": "sconds@gmail.com",
|
||||||
|
"created_at": "2008-01-29T23:13:36Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 93,
|
||||||
|
"login": "imownbey",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-29T23:13:44Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 94,
|
||||||
|
"login": "dylanegan",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-29T23:15:18Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 95,
|
||||||
|
"login": "jm",
|
||||||
|
"email": "jeremymcanally@gmail.com",
|
||||||
|
"created_at": "2008-01-29T23:15:32Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 100,
|
||||||
|
"login": "kmarsh",
|
||||||
|
"email": "kevin.marsh@gmail.com",
|
||||||
|
"created_at": "2008-01-29T23:48:24Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 101,
|
||||||
|
"login": "jvantuyl",
|
||||||
|
"email": "jayson@aggressive.ly",
|
||||||
|
"created_at": "2008-01-30T01:11:50Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 102,
|
||||||
|
"login": "BrianTheCoder",
|
||||||
|
"email": "wbsmith83@gmail.com",
|
||||||
|
"created_at": "2008-01-30T02:22:32Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 103,
|
||||||
|
"login": "freeformz",
|
||||||
|
"email": "freeformz@gmail.com",
|
||||||
|
"created_at": "2008-01-30T06:19:57Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 104,
|
||||||
|
"login": "hassox",
|
||||||
|
"email": "dneighman@gmail.com",
|
||||||
|
"created_at": "2008-01-30T06:31:06Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 105,
|
||||||
|
"login": "automatthew",
|
||||||
|
"email": "automatthew@gmail.com",
|
||||||
|
"created_at": "2008-01-30T19:00:58Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 106,
|
||||||
|
"login": "queso",
|
||||||
|
"email": "Joshua.owens@gmail.com",
|
||||||
|
"created_at": "2008-01-30T19:48:45Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 107,
|
||||||
|
"login": "lancecarlson",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-30T19:53:29Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 108,
|
||||||
|
"login": "drnic",
|
||||||
|
"email": "drnicwilliams@gmail.com",
|
||||||
|
"created_at": "2008-01-30T23:19:18Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 109,
|
||||||
|
"login": "lukesutton",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-31T04:01:02Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 110,
|
||||||
|
"login": "danwrong",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-31T08:51:31Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 111,
|
||||||
|
"login": "HamptonMakes",
|
||||||
|
"email": "hampton@hamptoncatlin.com",
|
||||||
|
"created_at": "2008-01-31T17:03:51Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 112,
|
||||||
|
"login": "jfrost",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-31T22:14:27Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 113,
|
||||||
|
"login": "mattetti",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-01-31T22:56:31Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 114,
|
||||||
|
"login": "ctennis",
|
||||||
|
"email": "c@leb.tennis",
|
||||||
|
"created_at": "2008-01-31T23:43:14Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 115,
|
||||||
|
"login": "lawrencepit",
|
||||||
|
"email": "lawrence.pit@gmail.com",
|
||||||
|
"created_at": "2008-01-31T23:57:16Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 116,
|
||||||
|
"login": "marcjeanson",
|
||||||
|
"email": "github@marcjeanson.com",
|
||||||
|
"created_at": "2008-02-01T01:27:19Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 117,
|
||||||
|
"login": "grempe",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-02-01T04:12:42Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 118,
|
||||||
|
"login": "peterc",
|
||||||
|
"email": "git@peterc.org",
|
||||||
|
"created_at": "2008-02-02T01:00:36Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 119,
|
||||||
|
"login": "ministrycentered",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-02-02T03:50:26Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 120,
|
||||||
|
"login": "afarnham",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-02-02T05:11:03Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 121,
|
||||||
|
"login": "up_the_irons",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-02-02T10:59:51Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 122,
|
||||||
|
"login": "cristibalan",
|
||||||
|
"email": "cristibalan@gmail.com",
|
||||||
|
"created_at": "2008-02-02T11:29:45Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 123,
|
||||||
|
"login": "heavysixer",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-02-02T15:06:53Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 124,
|
||||||
|
"login": "brosner",
|
||||||
|
"email": "brosner@gmail.com",
|
||||||
|
"created_at": "2008-02-02T19:03:54Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 125,
|
||||||
|
"login": "danielmorrison",
|
||||||
|
"email": "daniel@collectiveidea.com",
|
||||||
|
"created_at": "2008-02-02T19:46:35Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 126,
|
||||||
|
"login": "danielharan",
|
||||||
|
"email": "chebuctonian@gmail.com",
|
||||||
|
"created_at": "2008-02-02T21:42:21Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 127,
|
||||||
|
"login": "kvnsmth",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-02-02T22:00:03Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 128,
|
||||||
|
"login": "collectiveidea",
|
||||||
|
"email": "info@collectiveidea.com",
|
||||||
|
"created_at": "2008-02-02T22:34:46Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 129,
|
||||||
|
"login": "canadaduane",
|
||||||
|
"email": "duane.johnson@gmail.com",
|
||||||
|
"created_at": "2008-02-02T23:25:39Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 130,
|
||||||
|
"login": "corasaurus-hex",
|
||||||
|
"email": "cora@sutton.me",
|
||||||
|
"created_at": "2008-02-03T04:20:22Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 131,
|
||||||
|
"login": "dstrelau",
|
||||||
|
"email": null,
|
||||||
|
"created_at": "2008-02-03T14:59:12Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 132,
|
||||||
|
"login": "sunny",
|
||||||
|
"email": "sunny@sunfox.org",
|
||||||
|
"created_at": "2008-02-03T15:43:43Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 133,
|
||||||
|
"login": "dkubb",
|
||||||
|
"email": "github@dan.kubb.ca",
|
||||||
|
"created_at": "2008-02-03T20:40:13Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 134,
|
||||||
|
"login": "jnicklas",
|
||||||
|
"email": "jonas@jnicklas.com",
|
||||||
|
"created_at": "2008-02-03T20:43:50Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 135,
|
||||||
|
"login": "richcollins",
|
||||||
|
"email": "richcollins@gmail.com",
|
||||||
|
"created_at": "2008-02-03T21:11:25Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -18,8 +18,8 @@ use sha2::{Digest, Sha256};
|
|||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
use telemetry_events::{
|
use telemetry_events::{
|
||||||
ActionEvent, AppEvent, AssistantEvent, CallEvent, CpuEvent, EditEvent, EditorEvent, Event,
|
ActionEvent, AppEvent, AssistantEvent, CallEvent, CpuEvent, EditEvent, EditorEvent, Event,
|
||||||
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, ReplEvent,
|
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, Panic,
|
||||||
SettingEvent,
|
ReplEvent, SettingEvent,
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -149,7 +149,8 @@ pub async fn post_crash(
|
|||||||
installation_id = %installation_id,
|
installation_id = %installation_id,
|
||||||
description = %description,
|
description = %description,
|
||||||
backtrace = %summary,
|
backtrace = %summary,
|
||||||
"crash report");
|
"crash report"
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
|
if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
|
||||||
let payload = slack::WebhookBody::new(|w| {
|
let payload = slack::WebhookBody::new(|w| {
|
||||||
@@ -295,10 +296,11 @@ pub async fn post_panic(
|
|||||||
version = %panic.app_version,
|
version = %panic.app_version,
|
||||||
os_name = %panic.os_name,
|
os_name = %panic.os_name,
|
||||||
os_version = %panic.os_version.clone().unwrap_or_default(),
|
os_version = %panic.os_version.clone().unwrap_or_default(),
|
||||||
installation_id = %panic.installation_id.unwrap_or_default(),
|
installation_id = %panic.installation_id.clone().unwrap_or_default(),
|
||||||
description = %panic.payload,
|
description = %panic.payload,
|
||||||
backtrace = %panic.backtrace.join("\n"),
|
backtrace = %panic.backtrace.join("\n"),
|
||||||
"panic report");
|
"panic report"
|
||||||
|
);
|
||||||
|
|
||||||
let backtrace = if panic.backtrace.len() > 25 {
|
let backtrace = if panic.backtrace.len() > 25 {
|
||||||
let total = panic.backtrace.len();
|
let total = panic.backtrace.len();
|
||||||
@@ -316,6 +318,11 @@ pub async fn post_panic(
|
|||||||
} else {
|
} else {
|
||||||
panic.backtrace.join("\n")
|
panic.backtrace.join("\n")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !report_to_slack(&panic) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let backtrace_with_summary = panic.payload + "\n" + &backtrace;
|
let backtrace_with_summary = panic.payload + "\n" + &backtrace;
|
||||||
|
|
||||||
if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
|
if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
|
||||||
@@ -356,6 +363,25 @@ pub async fn post_panic(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn report_to_slack(panic: &Panic) -> bool {
|
||||||
|
if panic.payload.contains("ERROR_SURFACE_LOST_KHR") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if panic.payload.contains("ERROR_INITIALIZATION_FAILED") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if panic
|
||||||
|
.payload
|
||||||
|
.contains("GPU has crashed, and no debug information is available")
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn post_events(
|
pub async fn post_events(
|
||||||
Extension(app): Extension<Arc<AppState>>,
|
Extension(app): Extension<Arc<AppState>>,
|
||||||
TypedHeader(ZedChecksumHeader(checksum)): TypedHeader<ZedChecksumHeader>,
|
TypedHeader(ZedChecksumHeader(checksum)): TypedHeader<ZedChecksumHeader>,
|
||||||
@@ -627,7 +653,9 @@ where
|
|||||||
|
|
||||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||||
pub struct EditorEventRow {
|
pub struct EditorEventRow {
|
||||||
|
system_id: String,
|
||||||
installation_id: String,
|
installation_id: String,
|
||||||
|
session_id: Option<String>,
|
||||||
metrics_id: String,
|
metrics_id: String,
|
||||||
operation: String,
|
operation: String,
|
||||||
app_version: String,
|
app_version: String,
|
||||||
@@ -647,7 +675,6 @@ pub struct EditorEventRow {
|
|||||||
historical_event: bool,
|
historical_event: bool,
|
||||||
architecture: String,
|
architecture: String,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
session_id: Option<String>,
|
|
||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
@@ -677,9 +704,10 @@ impl EditorEventRow {
|
|||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
architecture: body.architecture.clone(),
|
architecture: body.architecture.clone(),
|
||||||
|
system_id: body.system_id.clone().unwrap_or_default(),
|
||||||
installation_id: body.installation_id.clone().unwrap_or_default(),
|
installation_id: body.installation_id.clone().unwrap_or_default(),
|
||||||
metrics_id: body.metrics_id.clone().unwrap_or_default(),
|
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
|
metrics_id: body.metrics_id.clone().unwrap_or_default(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
time: time.timestamp_millis(),
|
time: time.timestamp_millis(),
|
||||||
operation: event.operation,
|
operation: event.operation,
|
||||||
@@ -699,6 +727,7 @@ impl EditorEventRow {
|
|||||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||||
pub struct InlineCompletionEventRow {
|
pub struct InlineCompletionEventRow {
|
||||||
installation_id: String,
|
installation_id: String,
|
||||||
|
session_id: Option<String>,
|
||||||
provider: String,
|
provider: String,
|
||||||
suggestion_accepted: bool,
|
suggestion_accepted: bool,
|
||||||
app_version: String,
|
app_version: String,
|
||||||
@@ -713,7 +742,6 @@ pub struct InlineCompletionEventRow {
|
|||||||
city: String,
|
city: String,
|
||||||
time: i64,
|
time: i64,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
session_id: Option<String>,
|
|
||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
@@ -834,6 +862,7 @@ pub struct AssistantEventRow {
|
|||||||
// AssistantEventRow
|
// AssistantEventRow
|
||||||
conversation_id: String,
|
conversation_id: String,
|
||||||
kind: String,
|
kind: String,
|
||||||
|
phase: String,
|
||||||
model: String,
|
model: String,
|
||||||
response_latency_in_ms: Option<i64>,
|
response_latency_in_ms: Option<i64>,
|
||||||
error_message: Option<String>,
|
error_message: Option<String>,
|
||||||
@@ -866,6 +895,7 @@ impl AssistantEventRow {
|
|||||||
time: time.timestamp_millis(),
|
time: time.timestamp_millis(),
|
||||||
conversation_id: event.conversation_id.unwrap_or_default(),
|
conversation_id: event.conversation_id.unwrap_or_default(),
|
||||||
kind: event.kind.to_string(),
|
kind: event.kind.to_string(),
|
||||||
|
phase: event.phase.to_string(),
|
||||||
model: event.model,
|
model: event.model,
|
||||||
response_latency_in_ms: event
|
response_latency_in_ms: event
|
||||||
.response_latency
|
.response_latency
|
||||||
@@ -878,6 +908,7 @@ impl AssistantEventRow {
|
|||||||
#[derive(Debug, clickhouse::Row, Serialize)]
|
#[derive(Debug, clickhouse::Row, Serialize)]
|
||||||
pub struct CpuEventRow {
|
pub struct CpuEventRow {
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
|
session_id: Option<String>,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
usage_as_percentage: f32,
|
usage_as_percentage: f32,
|
||||||
core_count: u32,
|
core_count: u32,
|
||||||
@@ -886,7 +917,6 @@ pub struct CpuEventRow {
|
|||||||
os_name: String,
|
os_name: String,
|
||||||
os_version: String,
|
os_version: String,
|
||||||
time: i64,
|
time: i64,
|
||||||
session_id: Option<String>,
|
|
||||||
// pub normalized_cpu_usage: f64, MATERIALIZED
|
// pub normalized_cpu_usage: f64, MATERIALIZED
|
||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ macro_rules! id_type {
|
|||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub fn from_proto(value: u64) -> Self {
|
pub fn from_proto(value: u64) -> Self {
|
||||||
|
debug_assert!(value != 0);
|
||||||
Self(value as i32)
|
Self(value as i32)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +105,7 @@ pub enum ChannelRole {
|
|||||||
/// Admin can read/write and change permissions.
|
/// Admin can read/write and change permissions.
|
||||||
#[sea_orm(string_value = "admin")]
|
#[sea_orm(string_value = "admin")]
|
||||||
Admin,
|
Admin,
|
||||||
/// Member can read/write, but not change pemissions.
|
/// Member can read/write, but not change permissions.
|
||||||
#[sea_orm(string_value = "member")]
|
#[sea_orm(string_value = "member")]
|
||||||
#[default]
|
#[default]
|
||||||
Member,
|
Member,
|
||||||
|
|||||||
@@ -689,9 +689,7 @@ impl Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut text_buffer = text::Buffer::new(0, text::BufferId::new(1).unwrap(), base_text);
|
let mut text_buffer = text::Buffer::new(0, text::BufferId::new(1).unwrap(), base_text);
|
||||||
text_buffer
|
text_buffer.apply_ops(operations.into_iter().filter_map(operation_from_wire));
|
||||||
.apply_ops(operations.into_iter().filter_map(operation_from_wire))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let base_text = text_buffer.text();
|
let base_text = text_buffer.text();
|
||||||
let epoch = buffer.epoch + 1;
|
let epoch = buffer.epoch + 1;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ impl Database {
|
|||||||
room_id: RoomId,
|
room_id: RoomId,
|
||||||
connection: ConnectionId,
|
connection: ConnectionId,
|
||||||
worktrees: &[proto::WorktreeMetadata],
|
worktrees: &[proto::WorktreeMetadata],
|
||||||
|
is_ssh_project: bool,
|
||||||
dev_server_project_id: Option<DevServerProjectId>,
|
dev_server_project_id: Option<DevServerProjectId>,
|
||||||
) -> Result<TransactionGuard<(ProjectId, proto::Room)>> {
|
) -> Result<TransactionGuard<(ProjectId, proto::Room)>> {
|
||||||
self.room_transaction(room_id, |tx| async move {
|
self.room_transaction(room_id, |tx| async move {
|
||||||
@@ -121,12 +122,14 @@ impl Database {
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let replica_id = if is_ssh_project { 1 } else { 0 };
|
||||||
|
|
||||||
project_collaborator::ActiveModel {
|
project_collaborator::ActiveModel {
|
||||||
project_id: ActiveValue::set(project.id),
|
project_id: ActiveValue::set(project.id),
|
||||||
connection_id: ActiveValue::set(connection.id as i32),
|
connection_id: ActiveValue::set(connection.id as i32),
|
||||||
connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
|
connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
|
||||||
user_id: ActiveValue::set(participant.user_id),
|
user_id: ActiveValue::set(participant.user_id),
|
||||||
replica_id: ActiveValue::set(ReplicaId(0)),
|
replica_id: ActiveValue::set(ReplicaId(replica_id)),
|
||||||
is_host: ActiveValue::set(true),
|
is_host: ActiveValue::set(true),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
@@ -282,7 +285,7 @@ impl Database {
|
|||||||
)
|
)
|
||||||
.one(&*tx)
|
.one(&*tx)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("no such project"))?;
|
.ok_or_else(|| anyhow!("no such project: {project_id}"))?;
|
||||||
|
|
||||||
// Update metadata.
|
// Update metadata.
|
||||||
worktree::Entity::update(worktree::ActiveModel {
|
worktree::Entity::update(worktree::ActiveModel {
|
||||||
|
|||||||
@@ -298,6 +298,12 @@ impl Database {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all feature flags.
|
||||||
|
pub async fn list_feature_flags(&self) -> Result<Vec<feature_flag::Model>> {
|
||||||
|
self.transaction(|tx| async move { Ok(feature_flag::Entity::find().all(&*tx).await?) })
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new feature flag.
|
/// Creates a new feature flag.
|
||||||
pub async fn create_user_flag(&self, flag: &str, enabled_for_all: bool) -> Result<FlagId> {
|
pub async fn create_user_flag(&self, flag: &str, enabled_for_all: bool) -> Result<FlagId> {
|
||||||
self.transaction(|tx| async move {
|
self.transaction(|tx| async move {
|
||||||
|
|||||||
@@ -96,16 +96,14 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
|||||||
text::BufferId::new(1).unwrap(),
|
text::BufferId::new(1).unwrap(),
|
||||||
buffer_response_b.base_text,
|
buffer_response_b.base_text,
|
||||||
);
|
);
|
||||||
buffer_b
|
buffer_b.apply_ops(buffer_response_b.operations.into_iter().map(|operation| {
|
||||||
.apply_ops(buffer_response_b.operations.into_iter().map(|operation| {
|
let operation = proto::deserialize_operation(operation).unwrap();
|
||||||
let operation = proto::deserialize_operation(operation).unwrap();
|
if let language::Operation::Buffer(operation) = operation {
|
||||||
if let language::Operation::Buffer(operation) = operation {
|
operation
|
||||||
operation
|
} else {
|
||||||
} else {
|
unreachable!()
|
||||||
unreachable!()
|
}
|
||||||
}
|
}));
|
||||||
}))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(buffer_b.text(), "hello, cruel world");
|
assert_eq!(buffer_b.text(), "hello, cruel world");
|
||||||
|
|
||||||
|
|||||||
@@ -540,18 +540,18 @@ async fn test_project_count(db: &Arc<Database>) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
|
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
|
||||||
|
|
||||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], None)
|
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, None)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
|
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
|
||||||
|
|
||||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], None)
|
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, None)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
||||||
|
|
||||||
// Projects shared by admins aren't counted.
|
// Projects shared by admins aren't counted.
|
||||||
db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], None)
|
db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false, None)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
||||||
|
|||||||
@@ -170,8 +170,6 @@ pub struct Config {
|
|||||||
pub anthropic_api_key: Option<Arc<str>>,
|
pub anthropic_api_key: Option<Arc<str>>,
|
||||||
pub anthropic_staff_api_key: Option<Arc<str>>,
|
pub anthropic_staff_api_key: Option<Arc<str>>,
|
||||||
pub llm_closed_beta_model_name: Option<Arc<str>>,
|
pub llm_closed_beta_model_name: Option<Arc<str>>,
|
||||||
pub runpod_api_key: Option<Arc<str>>,
|
|
||||||
pub runpod_api_summary_url: Option<Arc<str>>,
|
|
||||||
pub zed_client_checksum_seed: Option<String>,
|
pub zed_client_checksum_seed: Option<String>,
|
||||||
pub slack_panics_webhook: Option<String>,
|
pub slack_panics_webhook: Option<String>,
|
||||||
pub auto_join_channel_id: Option<ChannelId>,
|
pub auto_join_channel_id: Option<ChannelId>,
|
||||||
@@ -235,8 +233,6 @@ impl Config {
|
|||||||
stripe_api_key: None,
|
stripe_api_key: None,
|
||||||
stripe_price_id: None,
|
stripe_price_id: None,
|
||||||
supermaven_admin_api_key: None,
|
supermaven_admin_api_key: None,
|
||||||
runpod_api_key: None,
|
|
||||||
runpod_api_summary_url: None,
|
|
||||||
user_backfiller_github_access_token: None,
|
user_backfiller_github_access_token: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use chrono::{DateTime, Duration, Utc};
|
|||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase};
|
use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase};
|
||||||
use futures::{Stream, StreamExt as _};
|
use futures::{Stream, StreamExt as _};
|
||||||
use http_client::IsahcHttpClient;
|
use isahc_http_client::IsahcHttpClient;
|
||||||
use rpc::ListModelsResponse;
|
use rpc::ListModelsResponse;
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
|
proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
|
||||||
@@ -72,6 +72,7 @@ impl LlmState {
|
|||||||
let http_client = IsahcHttpClient::builder()
|
let http_client = IsahcHttpClient::builder()
|
||||||
.default_header("User-Agent", user_agent)
|
.default_header("User-Agent", user_agent)
|
||||||
.build()
|
.build()
|
||||||
|
.map(IsahcHttpClient::from)
|
||||||
.context("failed to construct http client")?;
|
.context("failed to construct http client")?;
|
||||||
|
|
||||||
let this = Self {
|
let this = Self {
|
||||||
@@ -399,42 +400,6 @@ async fn perform_completion(
|
|||||||
})
|
})
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
LanguageModelProvider::Zed => {
|
|
||||||
let api_key = state
|
|
||||||
.config
|
|
||||||
.runpod_api_key
|
|
||||||
.as_ref()
|
|
||||||
.context("no Qwen2-7B API key configured on the server")?;
|
|
||||||
let api_url = state
|
|
||||||
.config
|
|
||||||
.runpod_api_summary_url
|
|
||||||
.as_ref()
|
|
||||||
.context("no Qwen2-7B URL configured on the server")?;
|
|
||||||
let chunks = open_ai::stream_completion(
|
|
||||||
&state.http_client,
|
|
||||||
api_url,
|
|
||||||
api_key,
|
|
||||||
serde_json::from_str(params.provider_request.get())?,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
chunks
|
|
||||||
.map(|event| {
|
|
||||||
event.map(|chunk| {
|
|
||||||
let input_tokens =
|
|
||||||
chunk.usage.as_ref().map_or(0, |u| u.prompt_tokens) as usize;
|
|
||||||
let output_tokens =
|
|
||||||
chunk.usage.as_ref().map_or(0, |u| u.completion_tokens) as usize;
|
|
||||||
(
|
|
||||||
serde_json::to_vec(&chunk).unwrap(),
|
|
||||||
input_tokens,
|
|
||||||
output_tokens,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Response::new(Body::wrap_stream(TokenCountingStream {
|
Ok(Response::new(Body::wrap_stream(TokenCountingStream {
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ fn authorize_access_for_country(
|
|||||||
LanguageModelProvider::Anthropic => anthropic::is_supported_country(country_code),
|
LanguageModelProvider::Anthropic => anthropic::is_supported_country(country_code),
|
||||||
LanguageModelProvider::OpenAi => open_ai::is_supported_country(country_code),
|
LanguageModelProvider::OpenAi => open_ai::is_supported_country(country_code),
|
||||||
LanguageModelProvider::Google => google_ai::is_supported_country(country_code),
|
LanguageModelProvider::Google => google_ai::is_supported_country(country_code),
|
||||||
LanguageModelProvider::Zed => true,
|
|
||||||
};
|
};
|
||||||
if !is_country_supported_by_provider {
|
if !is_country_supported_by_provider {
|
||||||
Err(Error::http(
|
Err(Error::http(
|
||||||
@@ -213,7 +212,6 @@ mod tests {
|
|||||||
(LanguageModelProvider::Anthropic, "T1"), // Tor
|
(LanguageModelProvider::Anthropic, "T1"), // Tor
|
||||||
(LanguageModelProvider::OpenAi, "T1"), // Tor
|
(LanguageModelProvider::OpenAi, "T1"), // Tor
|
||||||
(LanguageModelProvider::Google, "T1"), // Tor
|
(LanguageModelProvider::Google, "T1"), // Tor
|
||||||
(LanguageModelProvider::Zed, "T1"), // Tor
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for (provider, country_code) in cases {
|
for (provider, country_code) in cases {
|
||||||
|
|||||||
@@ -40,15 +40,6 @@ pub async fn seed_database(_config: &Config, db: &mut LlmDatabase, _force: bool)
|
|||||||
price_per_million_input_tokens: 25, // $0.25/MTok
|
price_per_million_input_tokens: 25, // $0.25/MTok
|
||||||
price_per_million_output_tokens: 125, // $1.25/MTok
|
price_per_million_output_tokens: 125, // $1.25/MTok
|
||||||
},
|
},
|
||||||
ModelParams {
|
|
||||||
provider: LanguageModelProvider::Zed,
|
|
||||||
name: "Qwen/Qwen2-7B-Instruct".into(),
|
|
||||||
max_requests_per_minute: 5,
|
|
||||||
max_tokens_per_minute: 25_000, // These are arbitrary limits we've set to cap costs; we control this number
|
|
||||||
max_tokens_per_day: 300_000,
|
|
||||||
price_per_million_input_tokens: 25,
|
|
||||||
price_per_million_output_tokens: 125,
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ async fn test_initialize_providers(db: &mut LlmDatabase) {
|
|||||||
LanguageModelProvider::Anthropic,
|
LanguageModelProvider::Anthropic,
|
||||||
LanguageModelProvider::Google,
|
LanguageModelProvider::Google,
|
||||||
LanguageModelProvider::OpenAi,
|
LanguageModelProvider::OpenAi,
|
||||||
LanguageModelProvider::Zed
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ use chrono::Utc;
|
|||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
pub use connection_pool::{ConnectionPool, ZedVersion};
|
pub use connection_pool::{ConnectionPool, ZedVersion};
|
||||||
use core::fmt::{self, Debug, Formatter};
|
use core::fmt::{self, Debug, Formatter};
|
||||||
|
use http_client::HttpClient;
|
||||||
|
use isahc_http_client::IsahcHttpClient;
|
||||||
use open_ai::{OpenAiEmbeddingModel, OPEN_AI_API_URL};
|
use open_ai::{OpenAiEmbeddingModel, OPEN_AI_API_URL};
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi};
|
use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi};
|
||||||
@@ -45,7 +47,6 @@ use futures::{
|
|||||||
stream::FuturesUnordered,
|
stream::FuturesUnordered,
|
||||||
FutureExt, SinkExt, StreamExt, TryStreamExt,
|
FutureExt, SinkExt, StreamExt, TryStreamExt,
|
||||||
};
|
};
|
||||||
use http_client::IsahcHttpClient;
|
|
||||||
use prometheus::{register_int_gauge, IntGauge};
|
use prometheus::{register_int_gauge, IntGauge};
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::{
|
proto::{
|
||||||
@@ -139,7 +140,7 @@ struct Session {
|
|||||||
connection_pool: Arc<parking_lot::Mutex<ConnectionPool>>,
|
connection_pool: Arc<parking_lot::Mutex<ConnectionPool>>,
|
||||||
app_state: Arc<AppState>,
|
app_state: Arc<AppState>,
|
||||||
supermaven_client: Option<Arc<SupermavenAdminApi>>,
|
supermaven_client: Option<Arc<SupermavenAdminApi>>,
|
||||||
http_client: Arc<IsahcHttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
/// The GeoIP country code for the user.
|
/// The GeoIP country code for the user.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
geoip_country_code: Option<String>,
|
geoip_country_code: Option<String>,
|
||||||
@@ -473,9 +474,6 @@ impl Server {
|
|||||||
.add_request_handler(user_handler(
|
.add_request_handler(user_handler(
|
||||||
forward_read_only_project_request::<proto::GetReferences>,
|
forward_read_only_project_request::<proto::GetReferences>,
|
||||||
))
|
))
|
||||||
.add_request_handler(user_handler(
|
|
||||||
forward_read_only_project_request::<proto::SearchProject>,
|
|
||||||
))
|
|
||||||
.add_request_handler(user_handler(forward_find_search_candidates_request))
|
.add_request_handler(user_handler(forward_find_search_candidates_request))
|
||||||
.add_request_handler(user_handler(
|
.add_request_handler(user_handler(
|
||||||
forward_read_only_project_request::<proto::GetDocumentHighlights>,
|
forward_read_only_project_request::<proto::GetDocumentHighlights>,
|
||||||
@@ -957,7 +955,7 @@ impl Server {
|
|||||||
|
|
||||||
let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION"));
|
let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION"));
|
||||||
let http_client = match IsahcHttpClient::builder().default_header("User-Agent", user_agent).build() {
|
let http_client = match IsahcHttpClient::builder().default_header("User-Agent", user_agent).build() {
|
||||||
Ok(http_client) => Arc::new(http_client),
|
Ok(http_client) => Arc::new(IsahcHttpClient::from(http_client)),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::error!(?error, "failed to create HTTP client");
|
tracing::error!(?error, "failed to create HTTP client");
|
||||||
return;
|
return;
|
||||||
@@ -1996,6 +1994,7 @@ async fn share_project(
|
|||||||
RoomId::from_proto(request.room_id),
|
RoomId::from_proto(request.room_id),
|
||||||
session.connection_id,
|
session.connection_id,
|
||||||
&request.worktrees,
|
&request.worktrees,
|
||||||
|
request.is_ssh_project,
|
||||||
request
|
request
|
||||||
.dev_server_project_id
|
.dev_server_project_id
|
||||||
.map(DevServerProjectId::from_proto),
|
.map(DevServerProjectId::from_proto),
|
||||||
@@ -2296,7 +2295,7 @@ async fn list_remote_directory(
|
|||||||
let dev_server_connection_id = session
|
let dev_server_connection_id = session
|
||||||
.connection_pool()
|
.connection_pool()
|
||||||
.await
|
.await
|
||||||
.dev_server_connection_id_supporting(dev_server_id, ZedVersion::with_list_directory())?;
|
.online_dev_server_connection_id(dev_server_id)?;
|
||||||
|
|
||||||
session
|
session
|
||||||
.db()
|
.db()
|
||||||
@@ -2335,10 +2334,7 @@ async fn update_dev_server_project(
|
|||||||
let dev_server_connection_id = session
|
let dev_server_connection_id = session
|
||||||
.connection_pool()
|
.connection_pool()
|
||||||
.await
|
.await
|
||||||
.dev_server_connection_id_supporting(
|
.online_dev_server_connection_id(dev_server_project.dev_server_id)?;
|
||||||
dev_server_project.dev_server_id,
|
|
||||||
ZedVersion::with_list_directory(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
session.peer.send(
|
session.peer.send(
|
||||||
dev_server_connection_id,
|
dev_server_connection_id,
|
||||||
@@ -2948,40 +2944,6 @@ async fn forward_find_search_candidates_request(
|
|||||||
.await
|
.await
|
||||||
.host_for_read_only_project_request(project_id, session.connection_id, session.user_id())
|
.host_for_read_only_project_request(project_id, session.connection_id, session.user_id())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let host_version = session
|
|
||||||
.connection_pool()
|
|
||||||
.await
|
|
||||||
.connection(host_connection_id)
|
|
||||||
.map(|c| c.zed_version);
|
|
||||||
|
|
||||||
if host_version.is_some_and(|host_version| host_version < ZedVersion::with_search_candidates())
|
|
||||||
{
|
|
||||||
let query = request.query.ok_or_else(|| anyhow!("missing query"))?;
|
|
||||||
let search = proto::SearchProject {
|
|
||||||
project_id: project_id.to_proto(),
|
|
||||||
query: query.query,
|
|
||||||
regex: query.regex,
|
|
||||||
whole_word: query.whole_word,
|
|
||||||
case_sensitive: query.case_sensitive,
|
|
||||||
files_to_include: query.files_to_include,
|
|
||||||
files_to_exclude: query.files_to_exclude,
|
|
||||||
include_ignored: query.include_ignored,
|
|
||||||
};
|
|
||||||
|
|
||||||
let payload = session
|
|
||||||
.peer
|
|
||||||
.forward_request(session.connection_id, host_connection_id, search)
|
|
||||||
.await?;
|
|
||||||
return response.send(proto::FindSearchCandidatesResponse {
|
|
||||||
buffer_ids: payload
|
|
||||||
.locations
|
|
||||||
.into_iter()
|
|
||||||
.map(|loc| loc.buffer_id)
|
|
||||||
.collect(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let payload = session
|
let payload = session
|
||||||
.peer
|
.peer
|
||||||
.forward_request(session.connection_id, host_connection_id, request)
|
.forward_request(session.connection_id, host_connection_id, request)
|
||||||
|
|||||||
@@ -32,15 +32,7 @@ impl fmt::Display for ZedVersion {
|
|||||||
|
|
||||||
impl ZedVersion {
|
impl ZedVersion {
|
||||||
pub fn can_collaborate(&self) -> bool {
|
pub fn can_collaborate(&self) -> bool {
|
||||||
self.0 >= SemanticVersion::new(0, 134, 0)
|
self.0 >= SemanticVersion::new(0, 151, 0)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_list_directory() -> ZedVersion {
|
|
||||||
ZedVersion(SemanticVersion::new(0, 145, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_search_candidates() -> ZedVersion {
|
|
||||||
ZedVersion(SemanticVersion::new(0, 151, 0))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +161,16 @@ impl ConnectionPool {
|
|||||||
self.connected_dev_servers.get(&dev_server_id).copied()
|
self.connected_dev_servers.get(&dev_server_id).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn online_dev_server_connection_id(
|
||||||
|
&self,
|
||||||
|
dev_server_id: DevServerId,
|
||||||
|
) -> Result<ConnectionId> {
|
||||||
|
match self.connected_dev_servers.get(&dev_server_id) {
|
||||||
|
Some(cid) => Ok(*cid),
|
||||||
|
None => Err(anyhow!(proto::ErrorCode::DevServerOffline)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dev_server_connection_id_supporting(
|
pub fn dev_server_connection_id_supporting(
|
||||||
&self,
|
&self,
|
||||||
dev_server_id: DevServerId,
|
dev_server_id: DevServerId,
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ use anyhow::Context;
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use db::Database;
|
use db::Database;
|
||||||
use serde::{de::DeserializeOwned, Deserialize};
|
use serde::{de::DeserializeOwned, Deserialize};
|
||||||
use std::{fmt::Write, fs, path::Path};
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
|
|
||||||
|
/// A GitHub user.
|
||||||
|
///
|
||||||
|
/// This representation corresponds to the entries in the `seed/github_users.json` file.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct GithubUser {
|
struct GithubUser {
|
||||||
id: i32,
|
id: i32,
|
||||||
@@ -18,12 +21,10 @@ struct GithubUser {
|
|||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct SeedConfig {
|
struct SeedConfig {
|
||||||
// Which users to create as admins.
|
/// Which users to create as admins.
|
||||||
admins: Vec<String>,
|
admins: Vec<String>,
|
||||||
// Which channels to create (all admins are invited to all channels)
|
/// Which channels to create (all admins are invited to all channels).
|
||||||
channels: Vec<String>,
|
channels: Vec<String>,
|
||||||
// Number of random users to create from the Github API
|
|
||||||
number_of_users: Option<usize>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result<()> {
|
pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result<()> {
|
||||||
@@ -47,11 +48,21 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result
|
|||||||
let flag_names = ["remoting", "language-models"];
|
let flag_names = ["remoting", "language-models"];
|
||||||
let mut flags = Vec::new();
|
let mut flags = Vec::new();
|
||||||
|
|
||||||
|
let existing_feature_flags = db.list_feature_flags().await?;
|
||||||
|
|
||||||
for flag_name in flag_names {
|
for flag_name in flag_names {
|
||||||
|
if existing_feature_flags
|
||||||
|
.iter()
|
||||||
|
.any(|flag| flag.flag == flag_name)
|
||||||
|
{
|
||||||
|
log::info!("Flag {flag_name:?} already exists");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let flag = db
|
let flag = db
|
||||||
.create_user_flag(flag_name, false)
|
.create_user_flag(flag_name, false)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|_| panic!("failed to create flag: '{flag_name}'"));
|
.unwrap_or_else(|err| panic!("failed to create flag: '{flag_name}': {err}"));
|
||||||
flags.push(flag);
|
flags.push(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,44 +117,29 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Fix this later
|
let github_users_filepath = seed_path.parent().unwrap().join("seed/github_users.json");
|
||||||
if let Some(number_of_users) = seed_config.number_of_users {
|
let github_users: Vec<GithubUser> =
|
||||||
// Fetch 100 other random users from GitHub and insert them into the database
|
serde_json::from_str(&fs::read_to_string(github_users_filepath)?)?;
|
||||||
// (for testing autocompleters, etc.)
|
|
||||||
let mut user_count = db
|
for github_user in github_users {
|
||||||
.get_all_users(0, 200)
|
log::info!("Seeding {:?} from GitHub", github_user.login);
|
||||||
|
|
||||||
|
let user = db
|
||||||
|
.get_or_create_user_by_github_account(
|
||||||
|
&github_user.login,
|
||||||
|
github_user.id,
|
||||||
|
github_user.email.as_deref(),
|
||||||
|
github_user.created_at,
|
||||||
|
None,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.expect("failed to load users from db")
|
.expect("failed to insert user");
|
||||||
.len();
|
|
||||||
let mut last_user_id = None;
|
|
||||||
while user_count < number_of_users {
|
|
||||||
let mut uri = "https://api.github.com/users?per_page=100".to_string();
|
|
||||||
if let Some(last_user_id) = last_user_id {
|
|
||||||
write!(&mut uri, "&since={}", last_user_id).unwrap();
|
|
||||||
}
|
|
||||||
let users = fetch_github::<Vec<GithubUser>>(&client, &uri).await;
|
|
||||||
|
|
||||||
for github_user in users {
|
for flag in &flags {
|
||||||
last_user_id = Some(github_user.id);
|
db.add_user_flag(user.id, *flag).await.context(format!(
|
||||||
user_count += 1;
|
"Unable to enable flag '{}' for user '{}'",
|
||||||
let user = db
|
flag, user.id
|
||||||
.get_or_create_user_by_github_account(
|
))?;
|
||||||
&github_user.login,
|
|
||||||
github_user.id,
|
|
||||||
github_user.email.as_deref(),
|
|
||||||
github_user.created_at,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("failed to insert user");
|
|
||||||
|
|
||||||
for flag in &flags {
|
|
||||||
db.add_user_flag(user.id, *flag).await.context(format!(
|
|
||||||
"Unable to enable flag '{}' for user '{}'",
|
|
||||||
flag, user.id
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,6 @@ fn rust_lang() -> Arc<Language> {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ async fn test_channel_notes_participant_indices(
|
|||||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||||
|
|
||||||
// Clients A and B open the same file.
|
// Clients A and B open the same file.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user