Compare commits
129 Commits
buffer-ext
...
assistant-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23ff3fbeac | ||
|
|
1723713dc2 | ||
|
|
ca4980df02 | ||
|
|
5e6d1814e5 | ||
|
|
1b612108ba | ||
|
|
c3f47b8040 | ||
|
|
43e005e936 | ||
|
|
b43b800a54 | ||
|
|
eef44aff7f | ||
|
|
106ca5076f | ||
|
|
2cd9a88f53 | ||
|
|
a62e8f6396 | ||
|
|
2c8a6ee7cc | ||
|
|
9016de5d63 | ||
|
|
97f5fcf8e6 | ||
|
|
71b6f739cd | ||
|
|
30ef7e62bf | ||
|
|
97dc1d193f | ||
|
|
772bda54a2 | ||
|
|
fb7a7a564a | ||
|
|
826777a257 | ||
|
|
eda7e88fd4 | ||
|
|
a7977aa64d | ||
|
|
373a17acf4 | ||
|
|
425c8f8c3e | ||
|
|
84f2e0ee37 | ||
|
|
1a62396b1e | ||
|
|
3ac201e448 | ||
|
|
3b153a54c2 | ||
|
|
430ce073d2 | ||
|
|
a149a50946 | ||
|
|
f68f4ab982 | ||
|
|
aae26ee33d | ||
|
|
550ceec549 | ||
|
|
d4e10dfba3 | ||
|
|
2699fa8d4a | ||
|
|
8e30229ec9 | ||
|
|
2e72fd210a | ||
|
|
4d074fc737 | ||
|
|
fbb402ef12 | ||
|
|
56f9e4c7b3 | ||
|
|
8e45bf71ca | ||
|
|
db18f7a2b0 | ||
|
|
e7912370e6 | ||
|
|
51faf4a1cd | ||
|
|
bdca342cdc | ||
|
|
8cc6df573c | ||
|
|
7814dd0301 | ||
|
|
8a6c65c63b | ||
|
|
d3d3a093b4 | ||
|
|
06a13c2983 | ||
|
|
c28b22d1cf | ||
|
|
447a5d6e6e | ||
|
|
869a72bb3f | ||
|
|
ab7a7d3480 | ||
|
|
fc43b21e78 | ||
|
|
e6c4076ef0 | ||
|
|
7246a0f39c | ||
|
|
345efa4e36 | ||
|
|
5cdca6d8dd | ||
|
|
ccfd4b1887 | ||
|
|
ee8668ef45 | ||
|
|
ac5c35b3df | ||
|
|
0070635b4d | ||
|
|
3d69942f71 | ||
|
|
76603a5fc6 | ||
|
|
accff826ca | ||
|
|
27f09957c2 | ||
|
|
e88b48a9c7 | ||
|
|
d5003e1121 | ||
|
|
7c54965b11 | ||
|
|
10cfaecffa | ||
|
|
469dfe759c | ||
|
|
ecd1830793 | ||
|
|
ddaee2e8dd | ||
|
|
54b8232be2 | ||
|
|
a20c0eb626 | ||
|
|
c48584fb79 | ||
|
|
f1d21362fa | ||
|
|
4139a9a758 | ||
|
|
103f757c11 | ||
|
|
2165d52d3e | ||
|
|
c34fc5c6e5 | ||
|
|
5f0925fb5d | ||
|
|
d56e3d99b4 | ||
|
|
7d97855ed7 | ||
|
|
4160824b10 | ||
|
|
1285504b3e | ||
|
|
83192c29e8 | ||
|
|
a141415bd3 | ||
|
|
d315405be1 | ||
|
|
37b2f4b9d3 | ||
|
|
4441150809 | ||
|
|
d7c45ccf2f | ||
|
|
bc5ed1334f | ||
|
|
b54b3d6246 | ||
|
|
e7d18ef359 | ||
|
|
243629cce8 | ||
|
|
67f149a4bc | ||
|
|
e66ea9e5d4 | ||
|
|
01bb10f518 | ||
|
|
ca2cce79ed | ||
|
|
dea85099a2 | ||
|
|
b48c2c5846 | ||
|
|
f3769322ad | ||
|
|
2c9d07663a | ||
|
|
784c3093ae | ||
|
|
ba5c1322ce | ||
|
|
28fb1fd19b | ||
|
|
90b77e125a | ||
|
|
fb79346e6f | ||
|
|
761129e373 | ||
|
|
22db569adf | ||
|
|
2cae6f3e08 | ||
|
|
2baa704af7 | ||
|
|
e3d54b2211 | ||
|
|
f986513d0d | ||
|
|
02dfe08ce8 | ||
|
|
4e1bb68620 | ||
|
|
96a5daaf3f | ||
|
|
29a5def12c | ||
|
|
cdc3791544 | ||
|
|
524a1a6fec | ||
|
|
4f251429c7 | ||
|
|
6f337de440 | ||
|
|
d56fa25830 | ||
|
|
d5268c5197 | ||
|
|
40a00fb224 | ||
|
|
00c0a7254a |
23
.github/workflows/bump_nightly_tag.yml
vendored
Normal file
23
.github/workflows/bump_nightly_tag.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: Update Nightly Tag
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Fire every day at 7:00am UTC (Roughly before EU workday and after US workday)
|
||||||
|
- cron: "0 7 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-nightly-tag:
|
||||||
|
if: github.repository_owner == 'zed-industries'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Update nightly tag
|
||||||
|
run: |
|
||||||
|
git config user.name github-actions
|
||||||
|
git config user.email github-actions@github.com
|
||||||
|
git tag -f nightly
|
||||||
|
git push origin nightly --force
|
||||||
3
.github/workflows/bump_patch_version.yml
vendored
3
.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
|
||||||
|
|||||||
57
.github/workflows/ci.yml
vendored
57
.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`.
|
||||||
@@ -271,24 +271,20 @@ 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-2204
|
||||||
- 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
|
||||||
|
|
||||||
- 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') }}
|
||||||
@@ -343,7 +339,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,26 +350,9 @@ 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') }}
|
||||||
|
|||||||
9
.github/workflows/deploy_collab.yml
vendored
9
.github/workflows/deploy_collab.yml
vendored
@@ -61,8 +61,7 @@ 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: Add Rust to the PATH
|
||||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||||
@@ -75,9 +74,6 @@ 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 . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA
|
||||||
|
|
||||||
@@ -92,8 +88,7 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- publish
|
- publish
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
- deploy
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Sign into Kubernetes
|
- name: Sign into Kubernetes
|
||||||
|
|||||||
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}
|
||||||
|
|||||||
3
.github/workflows/randomized_tests.yml
vendored
3
.github/workflows/randomized_tests.yml
vendored
@@ -19,8 +19,7 @@ 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@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
||||||
|
|||||||
31
.github/workflows/release_nightly.yml
vendored
31
.github/workflows/release_nightly.yml
vendored
@@ -1,9 +1,6 @@
|
|||||||
name: Release Nightly
|
name: Release Nightly
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
|
||||||
# Fire every day at 7:00am UTC (Roughly before EU workday and after US workday)
|
|
||||||
- cron: "0 7 * * *"
|
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "nightly"
|
- "nightly"
|
||||||
@@ -100,8 +97,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-2204
|
||||||
- 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 +113,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
|
||||||
|
|
||||||
|
- 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 +150,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
|
||||||
|
|||||||
611
Cargo.lock
generated
611
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
63
Cargo.toml
63
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",
|
||||||
@@ -225,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" }
|
||||||
@@ -393,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"] }
|
||||||
@@ -415,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"
|
||||||
@@ -430,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"
|
||||||
|
|
||||||
@@ -489,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",
|
||||||
@@ -520,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"
|
||||||
|
|||||||
27
Dockerfile
27
Dockerfile
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -124,6 +124,7 @@
|
|||||||
"g i": "vim::InsertAtPrevious",
|
"g i": "vim::InsertAtPrevious",
|
||||||
"g ,": "vim::ChangeListNewer",
|
"g ,": "vim::ChangeListNewer",
|
||||||
"g ;": "vim::ChangeListOlder",
|
"g ;": "vim::ChangeListOlder",
|
||||||
|
"g q": "editor::Rewrap",
|
||||||
"shift-h": "vim::WindowTop",
|
"shift-h": "vim::WindowTop",
|
||||||
"shift-m": "vim::WindowMiddle",
|
"shift-m": "vim::WindowMiddle",
|
||||||
"shift-l": "vim::WindowBottom",
|
"shift-l": "vim::WindowBottom",
|
||||||
|
|||||||
@@ -111,6 +111,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 +318,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,
|
||||||
@@ -698,7 +714,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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -65,6 +65,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 +94,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,9 @@ 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, context_server_command, default_command, delta_command, diagnostics_command,
|
||||||
fetch_command, file_command, now_command, project_command, prompt_command, search_command,
|
docs_command, fetch_command, file_command, now_command, project_command, prompt_command,
|
||||||
symbols_command, tab_command, terminal_command, workflow_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 +58,7 @@ actions!(
|
|||||||
[
|
[
|
||||||
Assist,
|
Assist,
|
||||||
Split,
|
Split,
|
||||||
|
CopyCode,
|
||||||
CycleMessageRole,
|
CycleMessageRole,
|
||||||
QuoteSelection,
|
QuoteSelection,
|
||||||
InsertIntoEditor,
|
InsertIntoEditor,
|
||||||
@@ -367,6 +368,7 @@ 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(project_command::ProjectSlashCommand, true);
|
||||||
|
|||||||
@@ -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};
|
||||||
@@ -81,9 +83,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 +108,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);
|
||||||
@@ -1072,6 +1076,13 @@ impl AssistantPanel {
|
|||||||
self.show_updated_summary(&context_editor, cx);
|
self.show_updated_summary(&context_editor, cx);
|
||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
|
EditorEvent::SelectionsChanged { local } => {
|
||||||
|
if *local {
|
||||||
|
context_editor.update(cx, |this, cx| {
|
||||||
|
this.update_code_fence_blocks(cx);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
|
EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -1501,6 +1512,7 @@ pub struct ContextEditor {
|
|||||||
editor: View<Editor>,
|
editor: View<Editor>,
|
||||||
blocks: HashMap<MessageId, (MessageHeader, CustomBlockId)>,
|
blocks: HashMap<MessageId, (MessageHeader, CustomBlockId)>,
|
||||||
image_blocks: HashSet<CustomBlockId>,
|
image_blocks: HashSet<CustomBlockId>,
|
||||||
|
code_fence_blocks: HashSet<CustomBlockId>,
|
||||||
scroll_position: Option<ScrollPosition>,
|
scroll_position: Option<ScrollPosition>,
|
||||||
remote_id: Option<workspace::ViewId>,
|
remote_id: Option<workspace::ViewId>,
|
||||||
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
||||||
@@ -1569,6 +1581,7 @@ impl ContextEditor {
|
|||||||
lsp_adapter_delegate,
|
lsp_adapter_delegate,
|
||||||
blocks: Default::default(),
|
blocks: Default::default(),
|
||||||
image_blocks: Default::default(),
|
image_blocks: Default::default(),
|
||||||
|
code_fence_blocks: Default::default(),
|
||||||
scroll_position: None,
|
scroll_position: None,
|
||||||
remote_id: None,
|
remote_id: None,
|
||||||
fs,
|
fs,
|
||||||
@@ -1906,7 +1919,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,
|
||||||
@@ -3085,6 +3113,40 @@ 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)> {
|
||||||
|
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 text = snapshot
|
||||||
|
.text_for_range(surrounding_code_block_range)
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
(!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 +3165,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 +3173,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 +3349,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)) =
|
||||||
@@ -3634,6 +3716,54 @@ impl ContextEditor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_code_fence_blocks(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
let workspace = self.workspace.clone();
|
||||||
|
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
let Some((_, _, snapshot)) = buffer.as_singleton() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let old_blocks = std::mem::take(&mut self.code_fence_blocks);
|
||||||
|
editor.remove_blocks(old_blocks, None, cx);
|
||||||
|
|
||||||
|
let selection_head = editor.selections.newest::<usize>(cx).head();
|
||||||
|
if let Some(range) = find_surrounding_code_block(snapshot, selection_head) {
|
||||||
|
let start_row = snapshot.offset_to_point(range.start).row.saturating_sub(1);
|
||||||
|
let position = buffer.anchor_after(Point::new(start_row, 0));
|
||||||
|
let block = BlockProperties {
|
||||||
|
position,
|
||||||
|
height: 0,
|
||||||
|
style: BlockStyle::Sticky,
|
||||||
|
render: Box::new(move |_| {
|
||||||
|
h_flex()
|
||||||
|
.justify_end()
|
||||||
|
.child(
|
||||||
|
IconButton::new("copy-code", IconName::Copy)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.on_click({
|
||||||
|
let workspace = workspace.clone();
|
||||||
|
move |_, cx| {
|
||||||
|
workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
Self::copy_code(workspace, &CopyCode, cx);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}),
|
||||||
|
disposition: BlockDisposition::Above,
|
||||||
|
priority: 0,
|
||||||
|
};
|
||||||
|
let ids = editor.insert_blocks(vec![block], None, cx);
|
||||||
|
self.code_fence_blocks = HashSet::from_iter(ids);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
|
fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
|
||||||
self.context.update(cx, |context, cx| {
|
self.context.update(cx, |context, cx| {
|
||||||
let selections = self.editor.read(cx).selections.disjoint_anchors();
|
let selections = self.editor.read(cx).selections.disjoint_anchors();
|
||||||
@@ -4117,9 +4247,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 +4330,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 +5409,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 +5541,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 +5654,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -997,14 +1004,14 @@ 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(operation) => cx.emit(ContextEvent::Operation(
|
||||||
ContextOperation::BufferOperation(operation.clone()),
|
ContextOperation::BufferOperation(operation.clone()),
|
||||||
)),
|
)),
|
||||||
language::Event::Edited => {
|
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 +1832,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));
|
||||||
@@ -2126,6 +2134,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,
|
||||||
@@ -2977,6 +2986,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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1425,6 +1426,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,
|
||||||
|
|||||||
@@ -174,6 +174,18 @@ impl InlineAssistant {
|
|||||||
initial_prompt: Option<String>,
|
initial_prompt: Option<String>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
|
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||||
|
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||||
|
telemetry.report_assistant_event(
|
||||||
|
None,
|
||||||
|
telemetry_events::AssistantKind::Inline,
|
||||||
|
telemetry_events::AssistantPhase::Invoked,
|
||||||
|
model.telemetry_id(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
|
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||||
|
|
||||||
let mut selections = Vec::<Selection<Point>>::new();
|
let mut selections = Vec::<Selection<Point>>::new();
|
||||||
@@ -708,6 +720,22 @@ impl InlineAssistant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish_assist(&mut self, assist_id: InlineAssistId, undo: bool, cx: &mut WindowContext) {
|
pub fn finish_assist(&mut self, assist_id: InlineAssistId, undo: bool, cx: &mut WindowContext) {
|
||||||
|
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||||
|
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||||
|
telemetry.report_assistant_event(
|
||||||
|
None,
|
||||||
|
telemetry_events::AssistantKind::Inline,
|
||||||
|
if undo {
|
||||||
|
telemetry_events::AssistantPhase::Rejected
|
||||||
|
} else {
|
||||||
|
telemetry_events::AssistantPhase::Accepted
|
||||||
|
},
|
||||||
|
model.telemetry_id(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(assist) = self.assists.get(&assist_id) {
|
if let Some(assist) = self.assists.get(&assist_id) {
|
||||||
let assist_group_id = assist.group_id;
|
let assist_group_id = assist.group_id;
|
||||||
if self.assist_groups[&assist_group_id].linked {
|
if self.assist_groups[&assist_group_id].linked {
|
||||||
@@ -1465,7 +1493,7 @@ impl Render for PromptEditor {
|
|||||||
.border_y_1()
|
.border_y_1()
|
||||||
.border_color(cx.theme().status().info_border)
|
.border_color(cx.theme().status().info_border)
|
||||||
.size_full()
|
.size_full()
|
||||||
.py(cx.line_height() / 2.)
|
.py(cx.line_height() / 2.5)
|
||||||
.on_action(cx.listener(Self::confirm))
|
.on_action(cx.listener(Self::confirm))
|
||||||
.on_action(cx.listener(Self::cancel))
|
.on_action(cx.listener(Self::cancel))
|
||||||
.on_action(cx.listener(Self::move_up))
|
.on_action(cx.listener(Self::move_up))
|
||||||
@@ -1918,12 +1946,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: settings.ui_font_size.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(
|
||||||
@@ -2559,6 +2586,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,
|
||||||
@@ -3360,7 +3388,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)
|
)
|
||||||
.with_indents_query(
|
.with_indents_query(
|
||||||
r#"
|
r#"
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use workspace::Workspace;
|
|||||||
pub mod auto_command;
|
pub mod auto_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,
|
||||||
|
|||||||
@@ -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,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result};
|
|||||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{AppContext, Model, Task, WeakView};
|
use gpui::{AppContext, Model, Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use project::{Project, ProjectPath};
|
use project::{Project, ProjectPath};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
@@ -118,6 +118,8 @@ impl SlashCommand for ProjectSlashCommand {
|
|||||||
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,
|
||||||
@@ -140,6 +142,7 @@ impl SlashCommand for ProjectSlashCommand {
|
|||||||
range,
|
range,
|
||||||
icon: IconName::FileTree,
|
icon: IconName::FileTree,
|
||||||
label: "Project".into(),
|
label: "Project".into(),
|
||||||
|
metadata: None,
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,14 +8,12 @@ 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, LineEnding, 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,
|
||||||
@@ -105,52 +105,28 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
})?
|
})?
|
||||||
.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 LoadedSearchResult {
|
||||||
let range_start = result.range.start.min(file_content.len());
|
path,
|
||||||
let range_end = result.range.end.min(file_content.len());
|
range,
|
||||||
|
full_path,
|
||||||
let start_row = file_content[0..range_start].matches('\n').count() as u32;
|
file_content,
|
||||||
let end_row = file_content[0..range_end].matches('\n').count() as u32;
|
row_range,
|
||||||
let start_line_byte_offset = file_content[0..range_start]
|
} in loaded_results
|
||||||
.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();
|
let section_start_ix = text.len();
|
||||||
text.push_str(&codeblock_fence_for_path(
|
text.push_str(&codeblock_fence_for_path(
|
||||||
Some(&result.path),
|
Some(&path),
|
||||||
Some(start_row..end_row),
|
Some(row_range.clone()),
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut excerpt =
|
let mut excerpt = file_content[range].to_string();
|
||||||
file_content[start_line_byte_offset..end_line_byte_offset].to_string();
|
|
||||||
LineEnding::normalize(&mut excerpt);
|
LineEnding::normalize(&mut excerpt);
|
||||||
text.push_str(&excerpt);
|
text.push_str(&excerpt);
|
||||||
writeln!(text, "\n```\n").unwrap();
|
writeln!(text, "\n```\n").unwrap();
|
||||||
@@ -159,7 +135,7 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
section_start_ix..section_end_ix,
|
section_start_ix..section_end_ix,
|
||||||
Some(&full_path),
|
Some(&full_path),
|
||||||
false,
|
false,
|
||||||
Some(start_row + 1..end_row + 1),
|
Some(row_range.start() + 1..row_range.end() + 1),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +144,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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,19 +243,22 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -171,11 +171,11 @@ 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) => {
|
||||||
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 +191,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)
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ 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
|
||||||
@@ -34,7 +34,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))
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -154,11 +154,13 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: runpod
|
name: runpod
|
||||||
key: api_key
|
key: api_key
|
||||||
|
optional: true
|
||||||
- name: RUNPOD_API_SUMMARY_URL
|
- name: RUNPOD_API_SUMMARY_URL
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: runpod
|
name: runpod
|
||||||
key: summary
|
key: summary
|
||||||
|
optional: true
|
||||||
- name: BLOB_STORE_ACCESS_KEY
|
- name: BLOB_STORE_ACCESS_KEY
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
|
|||||||
@@ -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| {
|
||||||
@@ -627,7 +628,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 +650,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 +679,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 +702,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 +717,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 +837,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 +870,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
|
||||||
@@ -877,7 +882,9 @@ impl AssistantEventRow {
|
|||||||
|
|
||||||
#[derive(Debug, clickhouse::Row, Serialize)]
|
#[derive(Debug, clickhouse::Row, Serialize)]
|
||||||
pub struct CpuEventRow {
|
pub struct CpuEventRow {
|
||||||
|
system_id: Option<String>,
|
||||||
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 +893,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>,
|
||||||
@@ -915,6 +921,7 @@ impl CpuEventRow {
|
|||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
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(),
|
||||||
|
system_id: body.system_id.clone(),
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
@@ -938,6 +945,7 @@ pub struct MemoryEventRow {
|
|||||||
os_version: String,
|
os_version: String,
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
|
system_id: Option<String>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
session_id: Option<String>,
|
session_id: Option<String>,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
@@ -969,6 +977,7 @@ impl MemoryEventRow {
|
|||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
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(),
|
||||||
|
system_id: body.system_id.clone(),
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
@@ -992,6 +1001,7 @@ pub struct AppEventRow {
|
|||||||
os_version: String,
|
os_version: String,
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
|
system_id: Option<String>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
session_id: Option<String>,
|
session_id: Option<String>,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
@@ -1022,6 +1032,7 @@ impl AppEventRow {
|
|||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
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(),
|
||||||
|
system_id: body.system_id.clone(),
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
@@ -1044,6 +1055,7 @@ pub struct SettingEventRow {
|
|||||||
os_version: String,
|
os_version: String,
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
|
system_id: Option<String>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
session_id: Option<String>,
|
session_id: Option<String>,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
@@ -1074,6 +1086,7 @@ impl SettingEventRow {
|
|||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
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(),
|
||||||
|
system_id: body.system_id.clone(),
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
@@ -1097,6 +1110,7 @@ pub struct ExtensionEventRow {
|
|||||||
os_version: String,
|
os_version: String,
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
|
system_id: Option<String>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
session_id: Option<String>,
|
session_id: Option<String>,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
@@ -1132,6 +1146,7 @@ impl ExtensionEventRow {
|
|||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
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(),
|
||||||
|
system_id: body.system_id.clone(),
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
@@ -1222,6 +1237,7 @@ pub struct EditEventRow {
|
|||||||
os_version: String,
|
os_version: String,
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
|
system_id: Option<String>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
// Note: This column name has a typo in the ClickHouse table.
|
// Note: This column name has a typo in the ClickHouse table.
|
||||||
#[serde(rename = "sesssion_id")]
|
#[serde(rename = "sesssion_id")]
|
||||||
@@ -1259,6 +1275,7 @@ impl EditEventRow {
|
|||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
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(),
|
||||||
|
system_id: body.system_id.clone(),
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
|
|||||||
@@ -104,7 +104,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,
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>,
|
||||||
@@ -957,7 +958,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 +1997,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),
|
||||||
|
|||||||
@@ -63,6 +63,6 @@ fn rust_lang() -> Arc<Language> {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
|||||||
let active_call_a = cx_a.read(ActiveCall::global);
|
let active_call_a = cx_a.read(ActiveCall::global);
|
||||||
|
|
||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -552,7 +552,7 @@ async fn test_collaborating_with_code_actions(
|
|||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
let mut fake_language_servers = client_a
|
let mut fake_language_servers = client_a
|
||||||
.language_registry()
|
.language_registry()
|
||||||
.register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
|
.register_fake_lsp("Rust", FakeLspAdapter::default());
|
||||||
|
|
||||||
client_a
|
client_a
|
||||||
.fs()
|
.fs()
|
||||||
@@ -757,7 +757,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
|||||||
|
|
||||||
// Set up a fake language server.
|
// Set up a fake language server.
|
||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -982,7 +982,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||||||
cx_b.update(editor::init);
|
cx_b.update(editor::init);
|
||||||
|
|
||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
name: "the-language-server",
|
name: "the-language-server",
|
||||||
@@ -1268,7 +1268,7 @@ async fn test_on_input_format_from_host_to_guest(
|
|||||||
let active_call_a = cx_a.read(ActiveCall::global);
|
let active_call_a = cx_a.read(ActiveCall::global);
|
||||||
|
|
||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -1388,7 +1388,7 @@ async fn test_on_input_format_from_guest_to_host(
|
|||||||
let active_call_a = cx_a.read(ActiveCall::global);
|
let active_call_a = cx_a.read(ActiveCall::global);
|
||||||
|
|
||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -1524,6 +1524,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: false,
|
show_parameter_hints: false,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1538,6 +1539,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: false,
|
show_parameter_hints: false,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1545,7 +1547,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
|||||||
|
|
||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
client_b.language_registry().add(rust_lang());
|
client_b.language_registry().add(rust_lang());
|
||||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -1786,6 +1788,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
|||||||
show_type_hints: false,
|
show_type_hints: false,
|
||||||
show_parameter_hints: false,
|
show_parameter_hints: false,
|
||||||
show_other_hints: false,
|
show_other_hints: false,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1800,6 +1803,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: true,
|
show_parameter_hints: true,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1807,7 +1811,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
|||||||
|
|
||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
client_b.language_registry().add(rust_lang());
|
client_b.language_registry().add(rust_lang());
|
||||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
|
|||||||
@@ -2273,7 +2273,7 @@ async fn test_propagate_saves_and_fs_changes(
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
));
|
));
|
||||||
let javascript = Arc::new(Language::new(
|
let javascript = Arc::new(Language::new(
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
@@ -2284,7 +2284,7 @@ async fn test_propagate_saves_and_fs_changes(
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
));
|
));
|
||||||
for client in [&client_a, &client_b, &client_c] {
|
for client in [&client_a, &client_b, &client_c] {
|
||||||
client.language_registry().add(rust.clone());
|
client.language_registry().add(rust.clone());
|
||||||
@@ -3855,11 +3855,11 @@ async fn test_collaborating_with_diagnostics(
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)));
|
)));
|
||||||
let mut fake_language_servers = client_a
|
let mut fake_language_servers = client_a
|
||||||
.language_registry()
|
.language_registry()
|
||||||
.register_fake_lsp_adapter("Rust", Default::default());
|
.register_fake_lsp("Rust", Default::default());
|
||||||
|
|
||||||
// Share a project as client A
|
// Share a project as client A
|
||||||
client_a
|
client_a
|
||||||
@@ -4126,7 +4126,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
|
disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
|
||||||
@@ -4349,7 +4349,7 @@ async fn test_formatting_buffer(
|
|||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
let mut fake_language_servers = client_a
|
let mut fake_language_servers = client_a
|
||||||
.language_registry()
|
.language_registry()
|
||||||
.register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
|
.register_fake_lsp("Rust", FakeLspAdapter::default());
|
||||||
|
|
||||||
// Here we insert a fake tree with a directory that exists on disk. This is needed
|
// Here we insert a fake tree with a directory that exists on disk. This is needed
|
||||||
// because later we'll invoke a command, which requires passing a working directory
|
// because later we'll invoke a command, which requires passing a working directory
|
||||||
@@ -4458,9 +4458,9 @@ async fn test_prettier_formatting_buffer(
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)));
|
)));
|
||||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||||
"TypeScript",
|
"TypeScript",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
prettier_plugins: vec![test_plugin],
|
prettier_plugins: vec![test_plugin],
|
||||||
@@ -4576,7 +4576,7 @@ async fn test_definition(
|
|||||||
|
|
||||||
let mut fake_language_servers = client_a
|
let mut fake_language_servers = client_a
|
||||||
.language_registry()
|
.language_registry()
|
||||||
.register_fake_lsp_adapter("Rust", Default::default());
|
.register_fake_lsp("Rust", Default::default());
|
||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
|
|
||||||
client_a
|
client_a
|
||||||
@@ -4712,7 +4712,7 @@ async fn test_references(
|
|||||||
let active_call_a = cx_a.read(ActiveCall::global);
|
let active_call_a = cx_a.read(ActiveCall::global);
|
||||||
|
|
||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
name: "my-fake-lsp-adapter",
|
name: "my-fake-lsp-adapter",
|
||||||
@@ -4983,7 +4983,7 @@ async fn test_document_highlights(
|
|||||||
|
|
||||||
let mut fake_language_servers = client_a
|
let mut fake_language_servers = client_a
|
||||||
.language_registry()
|
.language_registry()
|
||||||
.register_fake_lsp_adapter("Rust", Default::default());
|
.register_fake_lsp("Rust", Default::default());
|
||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
|
|
||||||
let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
|
let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
|
||||||
@@ -5079,28 +5079,30 @@ async fn test_lsp_hover(
|
|||||||
|
|
||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
let language_server_names = ["rust-analyzer", "CrabLang-ls"];
|
let language_server_names = ["rust-analyzer", "CrabLang-ls"];
|
||||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
|
let mut language_servers = [
|
||||||
"Rust",
|
client_a.language_registry().register_fake_lsp(
|
||||||
FakeLspAdapter {
|
"Rust",
|
||||||
name: "rust-analyzer",
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
name: "rust-analyzer",
|
||||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
capabilities: lsp::ServerCapabilities {
|
||||||
..lsp::ServerCapabilities::default()
|
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||||
|
..lsp::ServerCapabilities::default()
|
||||||
|
},
|
||||||
|
..FakeLspAdapter::default()
|
||||||
},
|
},
|
||||||
..FakeLspAdapter::default()
|
),
|
||||||
},
|
client_a.language_registry().register_fake_lsp(
|
||||||
);
|
"Rust",
|
||||||
let _other_server = client_a.language_registry().register_fake_lsp_adapter(
|
FakeLspAdapter {
|
||||||
"Rust",
|
name: "CrabLang-ls",
|
||||||
FakeLspAdapter {
|
capabilities: lsp::ServerCapabilities {
|
||||||
name: "CrabLang-ls",
|
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||||
capabilities: lsp::ServerCapabilities {
|
..lsp::ServerCapabilities::default()
|
||||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
},
|
||||||
..lsp::ServerCapabilities::default()
|
..FakeLspAdapter::default()
|
||||||
},
|
},
|
||||||
..FakeLspAdapter::default()
|
),
|
||||||
},
|
];
|
||||||
);
|
|
||||||
|
|
||||||
let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
|
let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
|
||||||
let project_id = active_call_a
|
let project_id = active_call_a
|
||||||
@@ -5115,7 +5117,7 @@ async fn test_lsp_hover(
|
|||||||
|
|
||||||
let mut servers_with_hover_requests = HashMap::default();
|
let mut servers_with_hover_requests = HashMap::default();
|
||||||
for i in 0..language_server_names.len() {
|
for i in 0..language_server_names.len() {
|
||||||
let new_server = fake_language_servers.next().await.unwrap_or_else(|| {
|
let new_server = language_servers[i].next().await.unwrap_or_else(|| {
|
||||||
panic!(
|
panic!(
|
||||||
"Failed to get language server #{i} with name {}",
|
"Failed to get language server #{i} with name {}",
|
||||||
&language_server_names[i]
|
&language_server_names[i]
|
||||||
@@ -5260,7 +5262,7 @@ async fn test_project_symbols(
|
|||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
let mut fake_language_servers = client_a
|
let mut fake_language_servers = client_a
|
||||||
.language_registry()
|
.language_registry()
|
||||||
.register_fake_lsp_adapter("Rust", Default::default());
|
.register_fake_lsp("Rust", Default::default());
|
||||||
|
|
||||||
client_a
|
client_a
|
||||||
.fs()
|
.fs()
|
||||||
@@ -5362,7 +5364,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
|||||||
client_a.language_registry().add(rust_lang());
|
client_a.language_registry().add(rust_lang());
|
||||||
let mut fake_language_servers = client_a
|
let mut fake_language_servers = client_a
|
||||||
.language_registry()
|
.language_registry()
|
||||||
.register_fake_lsp_adapter("Rust", Default::default());
|
.register_fake_lsp("Rust", Default::default());
|
||||||
|
|
||||||
client_a
|
client_a
|
||||||
.fs()
|
.fs()
|
||||||
|
|||||||
@@ -1047,7 +1047,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)));
|
)));
|
||||||
client.language_registry().register_fake_lsp_adapter(
|
client.language_registry().register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
name: "the-fake-language-server",
|
name: "the-fake-language-server",
|
||||||
|
|||||||
@@ -228,10 +228,10 @@ impl MessageEditor {
|
|||||||
fn on_buffer_event(
|
fn on_buffer_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: Model<Buffer>,
|
buffer: Model<Buffer>,
|
||||||
event: &language::Event,
|
event: &language::BufferEvent,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if let language::Event::Reparsed | language::Event::Edited = event {
|
if let language::BufferEvent::Reparsed | language::BufferEvent::Edited = event {
|
||||||
let buffer = buffer.read(cx).snapshot();
|
let buffer = buffer.read(cx).snapshot();
|
||||||
self.mentions_task = Some(cx.spawn(|this, cx| async move {
|
self.mentions_task = Some(cx.spawn(|this, cx| async move {
|
||||||
cx.background_executor()
|
cx.background_executor()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ path = "src/context_servers.rs"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
|
command_palette_hooks.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ pub use registry::*;
|
|||||||
|
|
||||||
actions!(context_servers, [Restart]);
|
actions!(context_servers, [Restart]);
|
||||||
|
|
||||||
|
/// The namespace for the context servers actions.
|
||||||
|
const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers";
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
log::info!("initializing context server client");
|
log::info!("initializing context server client");
|
||||||
manager::init(cx);
|
manager::init(cx);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
//! and react to changes in settings.
|
//! and react to changes in settings.
|
||||||
|
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task};
|
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task};
|
||||||
use log;
|
use log;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
@@ -24,6 +25,7 @@ use settings::{Settings, SettingsSources, SettingsStore};
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::CONTEXT_SERVERS_NAMESPACE;
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{self, Client},
|
client::{self, Client},
|
||||||
types,
|
types,
|
||||||
@@ -148,26 +150,28 @@ impl ContextServerManager {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<anyhow::Result<()>> {
|
) -> Task<anyhow::Result<()>> {
|
||||||
let server_id = config.id.clone();
|
let server_id = config.id.clone();
|
||||||
let server_id2 = config.id.clone();
|
|
||||||
|
|
||||||
if self.servers.contains_key(&server_id) || self.pending_servers.contains(&server_id) {
|
if self.servers.contains_key(&server_id) || self.pending_servers.contains(&server_id) {
|
||||||
return Task::ready(Ok(()));
|
return Task::ready(Ok(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let task = cx.spawn(|this, mut cx| async move {
|
let task = {
|
||||||
let server = Arc::new(ContextServer::new(config));
|
let server_id = server_id.clone();
|
||||||
server.start(&cx).await?;
|
cx.spawn(|this, mut cx| async move {
|
||||||
this.update(&mut cx, |this, cx| {
|
let server = Arc::new(ContextServer::new(config));
|
||||||
this.servers.insert(server_id.clone(), server);
|
server.start(&cx).await?;
|
||||||
this.pending_servers.remove(&server_id);
|
this.update(&mut cx, |this, cx| {
|
||||||
cx.emit(Event::ServerStarted {
|
this.servers.insert(server_id.clone(), server);
|
||||||
server_id: server_id.clone(),
|
this.pending_servers.remove(&server_id);
|
||||||
});
|
cx.emit(Event::ServerStarted {
|
||||||
})?;
|
server_id: server_id.clone(),
|
||||||
Ok(())
|
});
|
||||||
});
|
})?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
self.pending_servers.insert(server_id2);
|
self.pending_servers.insert(server_id);
|
||||||
task
|
task
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,15 +247,20 @@ impl GlobalContextServerManager {
|
|||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
ContextServerSettings::register(cx);
|
ContextServerSettings::register(cx);
|
||||||
GlobalContextServerManager::register(cx);
|
GlobalContextServerManager::register(cx);
|
||||||
|
|
||||||
|
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||||
|
filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE);
|
||||||
|
});
|
||||||
|
|
||||||
cx.observe_global::<SettingsStore>(|cx| {
|
cx.observe_global::<SettingsStore>(|cx| {
|
||||||
let manager = ContextServerManager::global(cx);
|
let manager = ContextServerManager::global(cx);
|
||||||
cx.update_model(&manager, |manager, cx| {
|
cx.update_model(&manager, |manager, cx| {
|
||||||
let settings = ContextServerSettings::get_global(cx);
|
let settings = ContextServerSettings::get_global(cx);
|
||||||
let current_servers: HashMap<String, ServerConfig> = manager
|
let current_servers = manager
|
||||||
.servers()
|
.servers()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|server| (server.id.clone(), server.config.clone()))
|
.map(|server| (server.id.clone(), server.config.clone()))
|
||||||
.collect();
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
let new_servers = settings
|
let new_servers = settings
|
||||||
.servers
|
.servers
|
||||||
@@ -279,6 +288,15 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
for id in servers_to_remove {
|
for id in servers_to_remove {
|
||||||
manager.remove_server(&id, cx).detach_and_log_err(cx);
|
manager.remove_server(&id, cx).detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let has_any_context_servers = !manager.servers().is_empty();
|
||||||
|
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||||
|
if has_any_context_servers {
|
||||||
|
filter.show_namespace(CONTEXT_SERVERS_NAMESPACE);
|
||||||
|
} else {
|
||||||
|
filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE);
|
||||||
|
}
|
||||||
|
});
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|||||||
@@ -691,17 +691,17 @@ impl Copilot {
|
|||||||
fn handle_buffer_event(
|
fn handle_buffer_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: Model<Buffer>,
|
buffer: Model<Buffer>,
|
||||||
event: &language::Event,
|
event: &language::BufferEvent,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if let Ok(server) = self.server.as_running() {
|
if let Ok(server) = self.server.as_running() {
|
||||||
if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.entity_id())
|
if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.entity_id())
|
||||||
{
|
{
|
||||||
match event {
|
match event {
|
||||||
language::Event::Edited => {
|
language::BufferEvent::Edited => {
|
||||||
drop(registered_buffer.report_changes(&buffer, cx));
|
drop(registered_buffer.report_changes(&buffer, cx));
|
||||||
}
|
}
|
||||||
language::Event::Saved => {
|
language::BufferEvent::Saved => {
|
||||||
server
|
server
|
||||||
.lsp
|
.lsp
|
||||||
.notify::<lsp::notification::DidSaveTextDocument>(
|
.notify::<lsp::notification::DidSaveTextDocument>(
|
||||||
@@ -713,7 +713,8 @@ impl Copilot {
|
|||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
language::Event::FileHandleChanged | language::Event::LanguageChanged => {
|
language::BufferEvent::FileHandleChanged
|
||||||
|
| language::BufferEvent::LanguageChanged => {
|
||||||
let new_language_id = id_for_language(buffer.read(cx).language());
|
let new_language_id = id_for_language(buffer.read(cx).language());
|
||||||
let new_uri = uri_for_buffer(&buffer, cx);
|
let new_uri = uri_for_buffer(&buffer, cx);
|
||||||
if new_uri != registered_buffer.uri
|
if new_uri != registered_buffer.uri
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use crate::{Completion, Copilot};
|
use crate::{Completion, Copilot};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use editor::{Direction, InlineCompletionProvider};
|
use editor::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider};
|
||||||
use gpui::{AppContext, EntityId, Model, ModelContext, Task};
|
use gpui::{AppContext, EntityId, Model, ModelContext, Task};
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::{all_language_settings, AllLanguageSettings},
|
language_settings::{all_language_settings, AllLanguageSettings},
|
||||||
Buffer, OffsetRangeExt, ToOffset,
|
Buffer, OffsetRangeExt, ToOffset,
|
||||||
};
|
};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{ops::Range, path::Path, sync::Arc, time::Duration};
|
use std::{path::Path, sync::Arc, time::Duration};
|
||||||
|
|
||||||
pub const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
|
pub const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
|||||||
buffer: &Model<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
cursor_position: language::Anchor,
|
cursor_position: language::Anchor,
|
||||||
cx: &'a AppContext,
|
cx: &'a AppContext,
|
||||||
) -> Option<(&'a str, Option<Range<language::Anchor>>)> {
|
) -> Option<CompletionProposal> {
|
||||||
let buffer_id = buffer.entity_id();
|
let buffer_id = buffer.entity_id();
|
||||||
let buffer = buffer.read(cx);
|
let buffer = buffer.read(cx);
|
||||||
let completion = self.active_completion()?;
|
let completion = self.active_completion()?;
|
||||||
@@ -267,7 +267,14 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
|||||||
if completion_text.trim().is_empty() {
|
if completion_text.trim().is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some((completion_text, None))
|
Some(CompletionProposal {
|
||||||
|
inlays: vec![InlayProposal::Suggestion(
|
||||||
|
cursor_position.bias_right(buffer),
|
||||||
|
completion_text.into(),
|
||||||
|
)],
|
||||||
|
text: completion_text.into(),
|
||||||
|
delete_range: None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -11,16 +11,14 @@ pub use smol;
|
|||||||
pub use sqlez;
|
pub use sqlez;
|
||||||
pub use sqlez_macros;
|
pub use sqlez_macros;
|
||||||
|
|
||||||
use release_channel::ReleaseChannel;
|
|
||||||
pub use release_channel::RELEASE_CHANNEL;
|
pub use release_channel::RELEASE_CHANNEL;
|
||||||
use sqlez::domain::Migrator;
|
use sqlez::domain::Migrator;
|
||||||
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||||
use sqlez_macros::sql;
|
use sqlez_macros::sql;
|
||||||
use std::env;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::{atomic::Ordering, LazyLock};
|
||||||
use std::sync::LazyLock;
|
use std::{env, sync::atomic::AtomicBool};
|
||||||
use util::{maybe, ResultExt};
|
use util::{maybe, ResultExt};
|
||||||
|
|
||||||
const CONNECTION_INITIALIZE_QUERY: &str = sql!(
|
const CONNECTION_INITIALIZE_QUERY: &str = sql!(
|
||||||
@@ -47,16 +45,12 @@ pub static ALL_FILE_DB_FAILED: LazyLock<AtomicBool> = LazyLock::new(|| AtomicBoo
|
|||||||
/// This will retry a couple times if there are failures. If opening fails once, the db directory
|
/// This will retry a couple times if there are failures. If opening fails once, the db directory
|
||||||
/// is moved to a backup folder and a new one is created. If that fails, a shared in memory db is created.
|
/// is moved to a backup folder and a new one is created. If that fails, a shared in memory db is created.
|
||||||
/// In either case, static variables are set so that the user can be notified.
|
/// In either case, static variables are set so that the user can be notified.
|
||||||
pub async fn open_db<M: Migrator + 'static>(
|
pub async fn open_db<M: Migrator + 'static>(db_dir: &Path, scope: &str) -> ThreadSafeConnection<M> {
|
||||||
db_dir: &Path,
|
|
||||||
release_channel: &ReleaseChannel,
|
|
||||||
) -> ThreadSafeConnection<M> {
|
|
||||||
if *ZED_STATELESS {
|
if *ZED_STATELESS {
|
||||||
return open_fallback_db().await;
|
return open_fallback_db().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let release_channel_name = release_channel.dev_name();
|
let main_db_dir = db_dir.join(format!("0-{}", scope));
|
||||||
let main_db_dir = db_dir.join(Path::new(&format!("0-{}", release_channel_name)));
|
|
||||||
|
|
||||||
let connection = maybe!(async {
|
let connection = maybe!(async {
|
||||||
smol::fs::create_dir_all(&main_db_dir)
|
smol::fs::create_dir_all(&main_db_dir)
|
||||||
@@ -118,7 +112,7 @@ pub async fn open_test_db<M: Migrator>(db_name: &str) -> ThreadSafeConnection<M>
|
|||||||
/// Implements a basic DB wrapper for a given domain
|
/// Implements a basic DB wrapper for a given domain
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! define_connection {
|
macro_rules! define_connection {
|
||||||
(pub static ref $id:ident: $t:ident<()> = $migrations:expr;) => {
|
(pub static ref $id:ident: $t:ident<()> = $migrations:expr; $($global:ident)?) => {
|
||||||
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>);
|
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>);
|
||||||
|
|
||||||
impl ::std::ops::Deref for $t {
|
impl ::std::ops::Deref for $t {
|
||||||
@@ -139,18 +133,23 @@ macro_rules! define_connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub static $id: LazyLock<$t> = LazyLock::new(|| {
|
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
||||||
$t($crate::smol::block_on($crate::open_test_db(stringify!($id))))
|
$t($crate::smol::block_on($crate::open_test_db(stringify!($id))))
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(not(any(test, feature = "test-support")))]
|
#[cfg(not(any(test, feature = "test-support")))]
|
||||||
pub static $id: LazyLock<$t> = LazyLock::new(|| {
|
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
||||||
$t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)))
|
let db_dir = $crate::database_dir();
|
||||||
|
let scope = if false $(|| stringify!($global) == "global")? {
|
||||||
|
"global"
|
||||||
|
} else {
|
||||||
|
$crate::RELEASE_CHANNEL.dev_name()
|
||||||
|
};
|
||||||
|
$t($crate::smol::block_on($crate::open_db(db_dir, scope)))
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => {
|
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr; $($global:ident)?) => {
|
||||||
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<( $($d),+, $t )>);
|
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<( $($d),+, $t )>);
|
||||||
|
|
||||||
impl ::std::ops::Deref for $t {
|
impl ::std::ops::Deref for $t {
|
||||||
@@ -178,7 +177,13 @@ macro_rules! define_connection {
|
|||||||
|
|
||||||
#[cfg(not(any(test, feature = "test-support")))]
|
#[cfg(not(any(test, feature = "test-support")))]
|
||||||
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
||||||
$t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)))
|
let db_dir = $crate::database_dir();
|
||||||
|
let scope = if false $(|| stringify!($global) == "global")? {
|
||||||
|
"global"
|
||||||
|
} else {
|
||||||
|
$crate::RELEASE_CHANNEL.dev_name()
|
||||||
|
};
|
||||||
|
$t($crate::smol::block_on($crate::open_db(db_dir, scope)))
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -225,7 +230,11 @@ mod tests {
|
|||||||
.prefix("DbTests")
|
.prefix("DbTests")
|
||||||
.tempdir()
|
.tempdir()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let _bad_db = open_db::<BadDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
let _bad_db = open_db::<BadDB>(
|
||||||
|
tempdir.path(),
|
||||||
|
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test that DB exists but corrupted (causing recreate)
|
/// Test that DB exists but corrupted (causing recreate)
|
||||||
@@ -262,13 +271,19 @@ mod tests {
|
|||||||
.tempdir()
|
.tempdir()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
{
|
{
|
||||||
let corrupt_db =
|
let corrupt_db = open_db::<CorruptedDB>(
|
||||||
open_db::<CorruptedDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
tempdir.path(),
|
||||||
|
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
assert!(corrupt_db.persistent());
|
assert!(corrupt_db.persistent());
|
||||||
}
|
}
|
||||||
|
|
||||||
let good_db =
|
let good_db = open_db::<GoodDB>(
|
||||||
open_db::<GoodDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
tempdir.path(),
|
||||||
|
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
assert!(
|
assert!(
|
||||||
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -311,8 +326,11 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
{
|
{
|
||||||
// Setup the bad database
|
// Setup the bad database
|
||||||
let corrupt_db =
|
let corrupt_db = open_db::<CorruptedDB>(
|
||||||
open_db::<CorruptedDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
tempdir.path(),
|
||||||
|
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
assert!(corrupt_db.persistent());
|
assert!(corrupt_db.persistent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +341,7 @@ mod tests {
|
|||||||
let guard = thread::spawn(move || {
|
let guard = thread::spawn(move || {
|
||||||
let good_db = smol::block_on(open_db::<GoodDB>(
|
let good_db = smol::block_on(open_db::<GoodDB>(
|
||||||
tmp_path.as_path(),
|
tmp_path.as_path(),
|
||||||
&release_channel::ReleaseChannel::Dev,
|
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||||
));
|
));
|
||||||
assert!(
|
assert!(
|
||||||
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
||||||
|
|||||||
@@ -60,3 +60,33 @@ mod tests {
|
|||||||
assert_eq!(db.read_kvp("key-1").unwrap(), None);
|
assert_eq!(db.read_kvp("key-1").unwrap(), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
define_connection!(pub static ref GLOBAL_KEY_VALUE_STORE: GlobalKeyValueStore<()> =
|
||||||
|
&[sql!(
|
||||||
|
CREATE TABLE IF NOT EXISTS kv_store(
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL
|
||||||
|
) STRICT;
|
||||||
|
)];
|
||||||
|
global
|
||||||
|
);
|
||||||
|
|
||||||
|
impl GlobalKeyValueStore {
|
||||||
|
query! {
|
||||||
|
pub fn read_kvp(key: &str) -> Result<Option<String>> {
|
||||||
|
SELECT value FROM kv_store WHERE key = (?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query! {
|
||||||
|
pub async fn write_kvp(key: String, value: String) -> Result<()> {
|
||||||
|
INSERT OR REPLACE INTO kv_store(key, value) VALUES ((?), (?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query! {
|
||||||
|
pub async fn delete_kvp(key: String) -> Result<()> {
|
||||||
|
DELETE FROM kv_store WHERE key = (?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -645,37 +645,42 @@ impl Item for ProjectDiagnosticsEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn tab_content(&self, params: TabContentParams, _: &WindowContext) -> AnyElement {
|
fn tab_content(&self, params: TabContentParams, _: &WindowContext) -> AnyElement {
|
||||||
if self.summary.error_count == 0 && self.summary.warning_count == 0 {
|
h_flex()
|
||||||
Label::new("No problems")
|
.gap_1()
|
||||||
.color(params.text_color())
|
.when(
|
||||||
.into_any_element()
|
self.summary.error_count == 0 && self.summary.warning_count == 0,
|
||||||
} else {
|
|then| {
|
||||||
h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.when(self.summary.error_count > 0, |then| {
|
|
||||||
then.child(
|
then.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(Icon::new(IconName::XCircle).color(Color::Error))
|
.child(Icon::new(IconName::Check).color(Color::Success))
|
||||||
.child(
|
.child(Label::new("No problems").color(params.text_color())),
|
||||||
Label::new(self.summary.error_count.to_string())
|
|
||||||
.color(params.text_color()),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
.when(self.summary.warning_count > 0, |then| {
|
)
|
||||||
then.child(
|
.when(self.summary.error_count > 0, |then| {
|
||||||
h_flex()
|
then.child(
|
||||||
.gap_1()
|
h_flex()
|
||||||
.child(Icon::new(IconName::Warning).color(Color::Warning))
|
.gap_1()
|
||||||
.child(
|
.child(Icon::new(IconName::XCircle).color(Color::Error))
|
||||||
Label::new(self.summary.warning_count.to_string())
|
.child(
|
||||||
.color(params.text_color()),
|
Label::new(self.summary.error_count.to_string())
|
||||||
),
|
.color(params.text_color()),
|
||||||
)
|
),
|
||||||
})
|
)
|
||||||
.into_any_element()
|
})
|
||||||
}
|
.when(self.summary.warning_count > 0, |then| {
|
||||||
|
then.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(Icon::new(IconName::Warning).color(Color::Warning))
|
||||||
|
.child(
|
||||||
|
Label::new(self.summary.warning_count.to_string())
|
||||||
|
.color(params.text_color()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ schemars.workspace = true
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
similar.workspace = true
|
||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
snippet.workspace = true
|
snippet.workspace = true
|
||||||
|
|||||||
@@ -156,14 +156,14 @@ pub struct DeleteToPreviousWordStart {
|
|||||||
impl_actions!(
|
impl_actions!(
|
||||||
editor,
|
editor,
|
||||||
[
|
[
|
||||||
|
ComposeCompletion,
|
||||||
ConfirmCodeAction,
|
ConfirmCodeAction,
|
||||||
ConfirmCompletion,
|
ConfirmCompletion,
|
||||||
ComposeCompletion,
|
|
||||||
DeleteToNextWordEnd,
|
DeleteToNextWordEnd,
|
||||||
DeleteToPreviousWordStart,
|
DeleteToPreviousWordStart,
|
||||||
ExpandExcerpts,
|
ExpandExcerpts,
|
||||||
ExpandExcerptsUp,
|
|
||||||
ExpandExcerptsDown,
|
ExpandExcerptsDown,
|
||||||
|
ExpandExcerptsUp,
|
||||||
FoldAt,
|
FoldAt,
|
||||||
HandleInput,
|
HandleInput,
|
||||||
MoveDownByLines,
|
MoveDownByLines,
|
||||||
@@ -188,8 +188,8 @@ impl_actions!(
|
|||||||
gpui::actions!(
|
gpui::actions!(
|
||||||
editor,
|
editor,
|
||||||
[
|
[
|
||||||
AcceptPartialCopilotSuggestion,
|
|
||||||
AcceptInlineCompletion,
|
AcceptInlineCompletion,
|
||||||
|
AcceptPartialCopilotSuggestion,
|
||||||
AcceptPartialInlineCompletion,
|
AcceptPartialInlineCompletion,
|
||||||
AddSelectionAbove,
|
AddSelectionAbove,
|
||||||
AddSelectionBelow,
|
AddSelectionBelow,
|
||||||
@@ -210,10 +210,10 @@ gpui::actions!(
|
|||||||
ConvertToUpperCamelCase,
|
ConvertToUpperCamelCase,
|
||||||
ConvertToUpperCase,
|
ConvertToUpperCase,
|
||||||
Copy,
|
Copy,
|
||||||
|
CopyFileLocation,
|
||||||
CopyHighlightJson,
|
CopyHighlightJson,
|
||||||
CopyPath,
|
CopyPath,
|
||||||
CopyPermalinkToLine,
|
CopyPermalinkToLine,
|
||||||
CopyFileLocation,
|
|
||||||
CopyRelativePath,
|
CopyRelativePath,
|
||||||
Cut,
|
Cut,
|
||||||
CutToEndOfLine,
|
CutToEndOfLine,
|
||||||
@@ -232,10 +232,10 @@ gpui::actions!(
|
|||||||
Fold,
|
Fold,
|
||||||
FoldSelectedRanges,
|
FoldSelectedRanges,
|
||||||
Format,
|
Format,
|
||||||
GoToDefinition,
|
|
||||||
GoToDefinitionSplit,
|
|
||||||
GoToDeclaration,
|
GoToDeclaration,
|
||||||
GoToDeclarationSplit,
|
GoToDeclarationSplit,
|
||||||
|
GoToDefinition,
|
||||||
|
GoToDefinitionSplit,
|
||||||
GoToDiagnostic,
|
GoToDiagnostic,
|
||||||
GoToHunk,
|
GoToHunk,
|
||||||
GoToImplementation,
|
GoToImplementation,
|
||||||
@@ -273,9 +273,9 @@ gpui::actions!(
|
|||||||
NextScreen,
|
NextScreen,
|
||||||
OpenExcerpts,
|
OpenExcerpts,
|
||||||
OpenExcerptsSplit,
|
OpenExcerptsSplit,
|
||||||
|
OpenFile,
|
||||||
OpenPermalinkToLine,
|
OpenPermalinkToLine,
|
||||||
OpenUrl,
|
OpenUrl,
|
||||||
OpenFile,
|
|
||||||
Outdent,
|
Outdent,
|
||||||
PageDown,
|
PageDown,
|
||||||
PageUp,
|
PageUp,
|
||||||
@@ -289,17 +289,20 @@ gpui::actions!(
|
|||||||
ReverseLines,
|
ReverseLines,
|
||||||
RevertFile,
|
RevertFile,
|
||||||
RevertSelectedHunks,
|
RevertSelectedHunks,
|
||||||
|
Rewrap,
|
||||||
ScrollCursorBottom,
|
ScrollCursorBottom,
|
||||||
ScrollCursorCenter,
|
ScrollCursorCenter,
|
||||||
ScrollCursorTop,
|
|
||||||
ScrollCursorCenterTopBottom,
|
ScrollCursorCenterTopBottom,
|
||||||
|
ScrollCursorTop,
|
||||||
SelectAll,
|
SelectAll,
|
||||||
SelectAllMatches,
|
SelectAllMatches,
|
||||||
SelectDown,
|
SelectDown,
|
||||||
SelectLargerSyntaxNode,
|
|
||||||
SelectEnclosingSymbol,
|
SelectEnclosingSymbol,
|
||||||
|
SelectLargerSyntaxNode,
|
||||||
SelectLeft,
|
SelectLeft,
|
||||||
SelectLine,
|
SelectLine,
|
||||||
|
SelectPageDown,
|
||||||
|
SelectPageUp,
|
||||||
SelectRight,
|
SelectRight,
|
||||||
SelectSmallerSyntaxNode,
|
SelectSmallerSyntaxNode,
|
||||||
SelectToBeginning,
|
SelectToBeginning,
|
||||||
@@ -311,8 +314,6 @@ gpui::actions!(
|
|||||||
SelectToPreviousWordStart,
|
SelectToPreviousWordStart,
|
||||||
SelectToStartOfParagraph,
|
SelectToStartOfParagraph,
|
||||||
SelectUp,
|
SelectUp,
|
||||||
SelectPageDown,
|
|
||||||
SelectPageUp,
|
|
||||||
ShowCharacterPalette,
|
ShowCharacterPalette,
|
||||||
ShowInlineCompletion,
|
ShowInlineCompletion,
|
||||||
ShowSignatureHelp,
|
ShowSignatureHelp,
|
||||||
@@ -326,13 +327,13 @@ gpui::actions!(
|
|||||||
ToggleAutoSignatureHelp,
|
ToggleAutoSignatureHelp,
|
||||||
ToggleGitBlame,
|
ToggleGitBlame,
|
||||||
ToggleGitBlameInline,
|
ToggleGitBlameInline,
|
||||||
ToggleSelectionMenu,
|
|
||||||
ToggleHunkDiff,
|
ToggleHunkDiff,
|
||||||
|
ToggleIndentGuides,
|
||||||
ToggleInlayHints,
|
ToggleInlayHints,
|
||||||
ToggleInlineCompletions,
|
ToggleInlineCompletions,
|
||||||
ToggleLineNumbers,
|
ToggleLineNumbers,
|
||||||
ToggleRelativeLineNumbers,
|
ToggleRelativeLineNumbers,
|
||||||
ToggleIndentGuides,
|
ToggleSelectionMenu,
|
||||||
ToggleSoftWrap,
|
ToggleSoftWrap,
|
||||||
ToggleTabBar,
|
ToggleTabBar,
|
||||||
Transpose,
|
Transpose,
|
||||||
|
|||||||
@@ -127,7 +127,9 @@ impl DisplayMap {
|
|||||||
let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||||
|
|
||||||
let tab_size = Self::tab_size(&buffer, cx);
|
let tab_size = Self::tab_size(&buffer, cx);
|
||||||
let (inlay_map, snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
|
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||||
|
let crease_map = CreaseMap::new(&buffer_snapshot);
|
||||||
|
let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot);
|
||||||
let (fold_map, snapshot) = FoldMap::new(snapshot);
|
let (fold_map, snapshot) = FoldMap::new(snapshot);
|
||||||
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
|
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
|
||||||
let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
|
let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
|
||||||
@@ -138,7 +140,6 @@ impl DisplayMap {
|
|||||||
excerpt_header_height,
|
excerpt_header_height,
|
||||||
excerpt_footer_height,
|
excerpt_footer_height,
|
||||||
);
|
);
|
||||||
let crease_map = CreaseMap::default();
|
|
||||||
|
|
||||||
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
|
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
|
||||||
|
|
||||||
@@ -1645,7 +1646,7 @@ pub mod tests {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)
|
)
|
||||||
.with_highlights_query(
|
.with_highlights_query(
|
||||||
r#"
|
r#"
|
||||||
@@ -1750,7 +1751,7 @@ pub mod tests {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)
|
)
|
||||||
.with_highlights_query(
|
.with_highlights_query(
|
||||||
r#"
|
r#"
|
||||||
@@ -1833,7 +1834,7 @@ pub mod tests {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)
|
)
|
||||||
.with_highlights_query(
|
.with_highlights_query(
|
||||||
r#"
|
r#"
|
||||||
|
|||||||
@@ -389,10 +389,10 @@ impl BlockMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut transforms = self.transforms.borrow_mut();
|
let mut transforms = self.transforms.borrow_mut();
|
||||||
let mut new_transforms = SumTree::new();
|
let mut new_transforms = SumTree::default();
|
||||||
let old_row_count = transforms.summary().input_rows;
|
let old_row_count = transforms.summary().input_rows;
|
||||||
let new_row_count = wrap_snapshot.max_point().row() + 1;
|
let new_row_count = wrap_snapshot.max_point().row() + 1;
|
||||||
let mut cursor = transforms.cursor::<WrapRow>();
|
let mut cursor = transforms.cursor::<WrapRow>(&());
|
||||||
let mut last_block_ix = 0;
|
let mut last_block_ix = 0;
|
||||||
let mut blocks_in_edit = Vec::new();
|
let mut blocks_in_edit = Vec::new();
|
||||||
let mut edits = edits.into_iter().peekable();
|
let mut edits = edits.into_iter().peekable();
|
||||||
@@ -757,7 +757,7 @@ impl<'a> BlockMapReader<'a> {
|
|||||||
.unwrap_or(self.wrap_snapshot.max_point().row() + 1),
|
.unwrap_or(self.wrap_snapshot.max_point().row() + 1),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
|
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
|
||||||
cursor.seek(&start_wrap_row, Bias::Left, &());
|
cursor.seek(&start_wrap_row, Bias::Left, &());
|
||||||
while let Some(transform) = cursor.item() {
|
while let Some(transform) = cursor.item() {
|
||||||
if cursor.start().0 > end_wrap_row {
|
if cursor.start().0 > end_wrap_row {
|
||||||
@@ -950,7 +950,7 @@ impl BlockSnapshot {
|
|||||||
highlights: Highlights<'a>,
|
highlights: Highlights<'a>,
|
||||||
) -> BlockChunks<'a> {
|
) -> BlockChunks<'a> {
|
||||||
let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
|
let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
|
||||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
|
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||||
let input_end = {
|
let input_end = {
|
||||||
cursor.seek(&BlockRow(rows.end), Bias::Right, &());
|
cursor.seek(&BlockRow(rows.end), Bias::Right, &());
|
||||||
let overshoot = if cursor
|
let overshoot = if cursor
|
||||||
@@ -990,7 +990,7 @@ impl BlockSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn buffer_rows(&self, start_row: BlockRow) -> BlockBufferRows {
|
pub(super) fn buffer_rows(&self, start_row: BlockRow) -> BlockBufferRows {
|
||||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
|
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||||
cursor.seek(&start_row, Bias::Right, &());
|
cursor.seek(&start_row, Bias::Right, &());
|
||||||
let (output_start, input_start) = cursor.start();
|
let (output_start, input_start) = cursor.start();
|
||||||
let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) {
|
let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) {
|
||||||
@@ -1008,7 +1008,7 @@ impl BlockSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
|
pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
|
||||||
let mut cursor = self.transforms.cursor::<BlockRow>();
|
let mut cursor = self.transforms.cursor::<BlockRow>(&());
|
||||||
cursor.seek(&BlockRow(rows.start), Bias::Left, &());
|
cursor.seek(&BlockRow(rows.start), Bias::Left, &());
|
||||||
while cursor.start().0 < rows.start && cursor.end(&()).0 <= rows.start {
|
while cursor.start().0 < rows.start && cursor.end(&()).0 <= rows.start {
|
||||||
cursor.next(&());
|
cursor.next(&());
|
||||||
@@ -1050,7 +1050,7 @@ impl BlockSnapshot {
|
|||||||
let wrap_point = self
|
let wrap_point = self
|
||||||
.wrap_snapshot
|
.wrap_snapshot
|
||||||
.make_wrap_point(excerpt_range.start, Bias::Left);
|
.make_wrap_point(excerpt_range.start, Bias::Left);
|
||||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
|
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
|
||||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
|
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
|
||||||
while let Some(transform) = cursor.item() {
|
while let Some(transform) = cursor.item() {
|
||||||
if let Some(block) = transform.block.as_ref() {
|
if let Some(block) = transform.block.as_ref() {
|
||||||
@@ -1072,7 +1072,7 @@ impl BlockSnapshot {
|
|||||||
.wrap_snapshot
|
.wrap_snapshot
|
||||||
.make_wrap_point(excerpt_range.end, Bias::Left);
|
.make_wrap_point(excerpt_range.end, Bias::Left);
|
||||||
|
|
||||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
|
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
|
||||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
|
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
|
||||||
while let Some(transform) = cursor.item() {
|
while let Some(transform) = cursor.item() {
|
||||||
if let Some(block) = transform.block.as_ref() {
|
if let Some(block) = transform.block.as_ref() {
|
||||||
@@ -1102,7 +1102,7 @@ impl BlockSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn line_len(&self, row: BlockRow) -> u32 {
|
pub(super) fn line_len(&self, row: BlockRow) -> u32 {
|
||||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
|
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||||
cursor.seek(&BlockRow(row.0), Bias::Right, &());
|
cursor.seek(&BlockRow(row.0), Bias::Right, &());
|
||||||
if let Some(transform) = cursor.item() {
|
if let Some(transform) = cursor.item() {
|
||||||
let (output_start, input_start) = cursor.start();
|
let (output_start, input_start) = cursor.start();
|
||||||
@@ -1118,13 +1118,13 @@ impl BlockSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
|
pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
|
||||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
|
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||||
cursor.seek(&row, Bias::Right, &());
|
cursor.seek(&row, Bias::Right, &());
|
||||||
cursor.item().map_or(false, |t| t.block.is_some())
|
cursor.item().map_or(false, |t| t.block.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
|
pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
|
||||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
|
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||||
cursor.seek(&BlockRow(point.row), Bias::Right, &());
|
cursor.seek(&BlockRow(point.row), Bias::Right, &());
|
||||||
|
|
||||||
let max_input_row = WrapRow(self.transforms.summary().input_rows);
|
let max_input_row = WrapRow(self.transforms.summary().input_rows);
|
||||||
@@ -1172,7 +1172,7 @@ impl BlockSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
|
pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
|
||||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
|
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
|
||||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
|
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
|
||||||
if let Some(transform) = cursor.item() {
|
if let Some(transform) = cursor.item() {
|
||||||
debug_assert!(transform.is_isomorphic());
|
debug_assert!(transform.is_isomorphic());
|
||||||
@@ -1188,7 +1188,7 @@ impl BlockSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
|
pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
|
||||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
|
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||||
cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
|
cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
|
||||||
if let Some(transform) = cursor.item() {
|
if let Some(transform) = cursor.item() {
|
||||||
match transform.block.as_ref().map(|b| b.disposition()) {
|
match transform.block.as_ref().map(|b| b.disposition()) {
|
||||||
@@ -1368,6 +1368,10 @@ impl sum_tree::Item for Transform {
|
|||||||
impl sum_tree::Summary for TransformSummary {
|
impl sum_tree::Summary for TransformSummary {
|
||||||
type Context = ();
|
type Context = ();
|
||||||
|
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &Self, _: &()) {
|
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||||
self.input_rows += summary.input_rows;
|
self.input_rows += summary.input_rows;
|
||||||
self.output_rows += summary.output_rows;
|
self.output_rows += summary.output_rows;
|
||||||
@@ -1375,12 +1379,20 @@ impl sum_tree::Summary for TransformSummary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
self.0 += summary.input_rows;
|
self.0 += summary.input_rows;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
self.0 += summary.output_rows;
|
self.0 += summary.output_rows;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,19 +12,34 @@ use crate::FoldPlaceholder;
|
|||||||
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||||
pub struct CreaseId(usize);
|
pub struct CreaseId(usize);
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct CreaseMap {
|
pub struct CreaseMap {
|
||||||
snapshot: CreaseSnapshot,
|
snapshot: CreaseSnapshot,
|
||||||
next_id: CreaseId,
|
next_id: CreaseId,
|
||||||
id_to_range: HashMap<CreaseId, Range<Anchor>>,
|
id_to_range: HashMap<CreaseId, Range<Anchor>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
impl CreaseMap {
|
||||||
|
pub fn new(snapshot: &MultiBufferSnapshot) -> Self {
|
||||||
|
CreaseMap {
|
||||||
|
snapshot: CreaseSnapshot::new(snapshot),
|
||||||
|
next_id: CreaseId::default(),
|
||||||
|
id_to_range: HashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct CreaseSnapshot {
|
pub struct CreaseSnapshot {
|
||||||
creases: SumTree<CreaseItem>,
|
creases: SumTree<CreaseItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreaseSnapshot {
|
impl CreaseSnapshot {
|
||||||
|
pub fn new(snapshot: &MultiBufferSnapshot) -> Self {
|
||||||
|
CreaseSnapshot {
|
||||||
|
creases: SumTree::new(snapshot),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the first Crease starting on the specified buffer row.
|
/// Returns the first Crease starting on the specified buffer row.
|
||||||
pub fn query_row<'a>(
|
pub fn query_row<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
@@ -32,7 +47,7 @@ impl CreaseSnapshot {
|
|||||||
snapshot: &'a MultiBufferSnapshot,
|
snapshot: &'a MultiBufferSnapshot,
|
||||||
) -> Option<&'a Crease> {
|
) -> Option<&'a Crease> {
|
||||||
let start = snapshot.anchor_before(Point::new(row.0, 0));
|
let start = snapshot.anchor_before(Point::new(row.0, 0));
|
||||||
let mut cursor = self.creases.cursor::<ItemSummary>();
|
let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
|
||||||
cursor.seek(&start, Bias::Left, snapshot);
|
cursor.seek(&start, Bias::Left, snapshot);
|
||||||
while let Some(item) = cursor.item() {
|
while let Some(item) = cursor.item() {
|
||||||
match Ord::cmp(&item.crease.range.start.to_point(snapshot).row, &row.0) {
|
match Ord::cmp(&item.crease.range.start.to_point(snapshot).row, &row.0) {
|
||||||
@@ -56,7 +71,7 @@ impl CreaseSnapshot {
|
|||||||
snapshot: &'a MultiBufferSnapshot,
|
snapshot: &'a MultiBufferSnapshot,
|
||||||
) -> impl '_ + Iterator<Item = &'a Crease> {
|
) -> impl '_ + Iterator<Item = &'a Crease> {
|
||||||
let start = snapshot.anchor_before(Point::new(range.start.0, 0));
|
let start = snapshot.anchor_before(Point::new(range.start.0, 0));
|
||||||
let mut cursor = self.creases.cursor::<ItemSummary>();
|
let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
|
||||||
cursor.seek(&start, Bias::Left, snapshot);
|
cursor.seek(&start, Bias::Left, snapshot);
|
||||||
|
|
||||||
std::iter::from_fn(move || {
|
std::iter::from_fn(move || {
|
||||||
@@ -79,7 +94,7 @@ impl CreaseSnapshot {
|
|||||||
&self,
|
&self,
|
||||||
snapshot: &MultiBufferSnapshot,
|
snapshot: &MultiBufferSnapshot,
|
||||||
) -> Vec<(CreaseId, Range<Point>)> {
|
) -> Vec<(CreaseId, Range<Point>)> {
|
||||||
let mut cursor = self.creases.cursor::<ItemSummary>();
|
let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
|
|
||||||
cursor.next(snapshot);
|
cursor.next(snapshot);
|
||||||
@@ -194,8 +209,8 @@ impl CreaseMap {
|
|||||||
) -> Vec<CreaseId> {
|
) -> Vec<CreaseId> {
|
||||||
let mut new_ids = Vec::new();
|
let mut new_ids = Vec::new();
|
||||||
self.snapshot.creases = {
|
self.snapshot.creases = {
|
||||||
let mut new_creases = SumTree::new();
|
let mut new_creases = SumTree::new(snapshot);
|
||||||
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
|
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
|
||||||
for crease in creases {
|
for crease in creases {
|
||||||
new_creases.append(cursor.slice(&crease.range, Bias::Left, snapshot), snapshot);
|
new_creases.append(cursor.slice(&crease.range, Bias::Left, snapshot), snapshot);
|
||||||
|
|
||||||
@@ -227,8 +242,8 @@ impl CreaseMap {
|
|||||||
});
|
});
|
||||||
|
|
||||||
self.snapshot.creases = {
|
self.snapshot.creases = {
|
||||||
let mut new_creases = SumTree::new();
|
let mut new_creases = SumTree::new(snapshot);
|
||||||
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
|
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
|
||||||
|
|
||||||
for (id, range) in removals {
|
for (id, range) in removals {
|
||||||
new_creases.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
|
new_creases.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
|
||||||
@@ -264,6 +279,10 @@ impl Default for ItemSummary {
|
|||||||
impl sum_tree::Summary for ItemSummary {
|
impl sum_tree::Summary for ItemSummary {
|
||||||
type Context = MultiBufferSnapshot;
|
type Context = MultiBufferSnapshot;
|
||||||
|
|
||||||
|
fn zero(_cx: &Self::Context) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, other: &Self, _snapshot: &MultiBufferSnapshot) {
|
fn add_summary(&mut self, other: &Self, _snapshot: &MultiBufferSnapshot) {
|
||||||
self.range = other.range.clone();
|
self.range = other.range.clone();
|
||||||
}
|
}
|
||||||
@@ -303,7 +322,7 @@ mod test {
|
|||||||
let text = "line1\nline2\nline3\nline4\nline5";
|
let text = "line1\nline2\nline3\nline4\nline5";
|
||||||
let buffer = MultiBuffer::build_simple(text, cx);
|
let buffer = MultiBuffer::build_simple(text, cx);
|
||||||
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||||
let mut crease_map = CreaseMap::default();
|
let mut crease_map = CreaseMap::new(&buffer.read(cx).read(cx));
|
||||||
|
|
||||||
// Insert creases
|
// Insert creases
|
||||||
let creases = [
|
let creases = [
|
||||||
@@ -350,7 +369,7 @@ mod test {
|
|||||||
let text = "line1\nline2\nline3\nline4\nline5\nline6\nline7";
|
let text = "line1\nline2\nline3\nline4\nline5\nline6\nline7";
|
||||||
let buffer = MultiBuffer::build_simple(text, cx);
|
let buffer = MultiBuffer::build_simple(text, cx);
|
||||||
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||||
let mut crease_map = CreaseMap::default();
|
let mut crease_map = CreaseMap::new(&snapshot);
|
||||||
|
|
||||||
let creases = [
|
let creases = [
|
||||||
Crease::new(
|
Crease::new(
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ impl FoldPoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint {
|
pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint {
|
||||||
let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>();
|
let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
|
||||||
cursor.seek(&self, Bias::Right, &());
|
cursor.seek(&self, Bias::Right, &());
|
||||||
let overshoot = self.0 - cursor.start().0 .0;
|
let overshoot = self.0 - cursor.start().0 .0;
|
||||||
InlayPoint(cursor.start().1 .0 + overshoot)
|
InlayPoint(cursor.start().1 .0 + overshoot)
|
||||||
@@ -88,7 +88,7 @@ impl FoldPoint {
|
|||||||
pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset {
|
pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset {
|
||||||
let mut cursor = snapshot
|
let mut cursor = snapshot
|
||||||
.transforms
|
.transforms
|
||||||
.cursor::<(FoldPoint, TransformSummary)>();
|
.cursor::<(FoldPoint, TransformSummary)>(&());
|
||||||
cursor.seek(&self, Bias::Right, &());
|
cursor.seek(&self, Bias::Right, &());
|
||||||
let overshoot = self.0 - cursor.start().1.output.lines;
|
let overshoot = self.0 - cursor.start().1.output.lines;
|
||||||
let mut offset = cursor.start().1.output.len;
|
let mut offset = cursor.start().1.output.len;
|
||||||
@@ -105,6 +105,10 @@ impl FoldPoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
self.0 += &summary.output.lines;
|
self.0 += &summary.output.lines;
|
||||||
}
|
}
|
||||||
@@ -154,8 +158,8 @@ impl<'a> FoldMapWriter<'a> {
|
|||||||
folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(&a.range, &b.range, buffer));
|
folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(&a.range, &b.range, buffer));
|
||||||
|
|
||||||
self.0.snapshot.folds = {
|
self.0.snapshot.folds = {
|
||||||
let mut new_tree = SumTree::new();
|
let mut new_tree = SumTree::new(buffer);
|
||||||
let mut cursor = self.0.snapshot.folds.cursor::<FoldRange>();
|
let mut cursor = self.0.snapshot.folds.cursor::<FoldRange>(buffer);
|
||||||
for fold in folds {
|
for fold in folds {
|
||||||
new_tree.append(cursor.slice(&fold.range, Bias::Right, buffer), buffer);
|
new_tree.append(cursor.slice(&fold.range, Bias::Right, buffer), buffer);
|
||||||
new_tree.push(fold, buffer);
|
new_tree.push(fold, buffer);
|
||||||
@@ -202,8 +206,8 @@ impl<'a> FoldMapWriter<'a> {
|
|||||||
fold_ixs_to_delete.dedup();
|
fold_ixs_to_delete.dedup();
|
||||||
|
|
||||||
self.0.snapshot.folds = {
|
self.0.snapshot.folds = {
|
||||||
let mut cursor = self.0.snapshot.folds.cursor::<usize>();
|
let mut cursor = self.0.snapshot.folds.cursor::<usize>(buffer);
|
||||||
let mut folds = SumTree::new();
|
let mut folds = SumTree::new(buffer);
|
||||||
for fold_ix in fold_ixs_to_delete {
|
for fold_ix in fold_ixs_to_delete {
|
||||||
folds.append(cursor.slice(&fold_ix, Bias::Right, buffer), buffer);
|
folds.append(cursor.slice(&fold_ix, Bias::Right, buffer), buffer);
|
||||||
cursor.next(buffer);
|
cursor.next(buffer);
|
||||||
@@ -230,7 +234,7 @@ impl FoldMap {
|
|||||||
pub(crate) fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) {
|
pub(crate) fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) {
|
||||||
let this = Self {
|
let this = Self {
|
||||||
snapshot: FoldSnapshot {
|
snapshot: FoldSnapshot {
|
||||||
folds: Default::default(),
|
folds: SumTree::new(&inlay_snapshot.buffer),
|
||||||
transforms: SumTree::from_item(
|
transforms: SumTree::from_item(
|
||||||
Transform {
|
Transform {
|
||||||
summary: TransformSummary {
|
summary: TransformSummary {
|
||||||
@@ -314,8 +318,8 @@ impl FoldMap {
|
|||||||
} else {
|
} else {
|
||||||
let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable();
|
let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable();
|
||||||
|
|
||||||
let mut new_transforms = SumTree::<Transform>::new();
|
let mut new_transforms = SumTree::<Transform>::default();
|
||||||
let mut cursor = self.snapshot.transforms.cursor::<InlayOffset>();
|
let mut cursor = self.snapshot.transforms.cursor::<InlayOffset>(&());
|
||||||
cursor.seek(&InlayOffset(0), Bias::Right, &());
|
cursor.seek(&InlayOffset(0), Bias::Right, &());
|
||||||
|
|
||||||
while let Some(mut edit) = inlay_edits_iter.next() {
|
while let Some(mut edit) = inlay_edits_iter.next() {
|
||||||
@@ -367,7 +371,10 @@ impl FoldMap {
|
|||||||
let anchor = inlay_snapshot
|
let anchor = inlay_snapshot
|
||||||
.buffer
|
.buffer
|
||||||
.anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start));
|
.anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start));
|
||||||
let mut folds_cursor = self.snapshot.folds.cursor::<FoldRange>();
|
let mut folds_cursor = self
|
||||||
|
.snapshot
|
||||||
|
.folds
|
||||||
|
.cursor::<FoldRange>(&inlay_snapshot.buffer);
|
||||||
folds_cursor.seek(
|
folds_cursor.seek(
|
||||||
&FoldRange(anchor..Anchor::max()),
|
&FoldRange(anchor..Anchor::max()),
|
||||||
Bias::Left,
|
Bias::Left,
|
||||||
@@ -470,8 +477,8 @@ impl FoldMap {
|
|||||||
let mut old_transforms = self
|
let mut old_transforms = self
|
||||||
.snapshot
|
.snapshot
|
||||||
.transforms
|
.transforms
|
||||||
.cursor::<(InlayOffset, FoldOffset)>();
|
.cursor::<(InlayOffset, FoldOffset)>(&());
|
||||||
let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>();
|
let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>(&());
|
||||||
|
|
||||||
for mut edit in inlay_edits {
|
for mut edit in inlay_edits {
|
||||||
old_transforms.seek(&edit.old.start, Bias::Left, &());
|
old_transforms.seek(&edit.old.start, Bias::Left, &());
|
||||||
@@ -545,7 +552,7 @@ impl FoldSnapshot {
|
|||||||
pub fn text_summary_for_range(&self, range: Range<FoldPoint>) -> TextSummary {
|
pub fn text_summary_for_range(&self, range: Range<FoldPoint>) -> TextSummary {
|
||||||
let mut summary = TextSummary::default();
|
let mut summary = TextSummary::default();
|
||||||
|
|
||||||
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>();
|
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
|
||||||
cursor.seek(&range.start, Bias::Right, &());
|
cursor.seek(&range.start, Bias::Right, &());
|
||||||
if let Some(transform) = cursor.item() {
|
if let Some(transform) = cursor.item() {
|
||||||
let start_in_transform = range.start.0 - cursor.start().0 .0;
|
let start_in_transform = range.start.0 - cursor.start().0 .0;
|
||||||
@@ -594,7 +601,7 @@ impl FoldSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint {
|
pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint {
|
||||||
let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>();
|
let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(&());
|
||||||
cursor.seek(&point, Bias::Right, &());
|
cursor.seek(&point, Bias::Right, &());
|
||||||
if cursor.item().map_or(false, |t| t.is_fold()) {
|
if cursor.item().map_or(false, |t| t.is_fold()) {
|
||||||
if bias == Bias::Left || point == cursor.start().0 {
|
if bias == Bias::Left || point == cursor.start().0 {
|
||||||
@@ -631,7 +638,7 @@ impl FoldSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let fold_point = FoldPoint::new(start_row, 0);
|
let fold_point = FoldPoint::new(start_row, 0);
|
||||||
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>();
|
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
|
||||||
cursor.seek(&fold_point, Bias::Left, &());
|
cursor.seek(&fold_point, Bias::Left, &());
|
||||||
|
|
||||||
let overshoot = fold_point.0 - cursor.start().0 .0;
|
let overshoot = fold_point.0 - cursor.start().0 .0;
|
||||||
@@ -672,7 +679,7 @@ impl FoldSnapshot {
|
|||||||
{
|
{
|
||||||
let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer);
|
let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer);
|
||||||
let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
|
let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
|
||||||
let mut cursor = self.transforms.cursor::<InlayOffset>();
|
let mut cursor = self.transforms.cursor::<InlayOffset>(&());
|
||||||
cursor.seek(&inlay_offset, Bias::Right, &());
|
cursor.seek(&inlay_offset, Bias::Right, &());
|
||||||
cursor.item().map_or(false, |t| t.placeholder.is_some())
|
cursor.item().map_or(false, |t| t.placeholder.is_some())
|
||||||
}
|
}
|
||||||
@@ -681,7 +688,7 @@ impl FoldSnapshot {
|
|||||||
let mut inlay_point = self
|
let mut inlay_point = self
|
||||||
.inlay_snapshot
|
.inlay_snapshot
|
||||||
.to_inlay_point(Point::new(buffer_row.0, 0));
|
.to_inlay_point(Point::new(buffer_row.0, 0));
|
||||||
let mut cursor = self.transforms.cursor::<InlayPoint>();
|
let mut cursor = self.transforms.cursor::<InlayPoint>(&());
|
||||||
cursor.seek(&inlay_point, Bias::Right, &());
|
cursor.seek(&inlay_point, Bias::Right, &());
|
||||||
loop {
|
loop {
|
||||||
match cursor.item() {
|
match cursor.item() {
|
||||||
@@ -711,7 +718,7 @@ impl FoldSnapshot {
|
|||||||
language_aware: bool,
|
language_aware: bool,
|
||||||
highlights: Highlights<'a>,
|
highlights: Highlights<'a>,
|
||||||
) -> FoldChunks<'a> {
|
) -> FoldChunks<'a> {
|
||||||
let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>();
|
let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>(&());
|
||||||
transform_cursor.seek(&range.start, Bias::Right, &());
|
transform_cursor.seek(&range.start, Bias::Right, &());
|
||||||
|
|
||||||
let inlay_start = {
|
let inlay_start = {
|
||||||
@@ -766,7 +773,7 @@ impl FoldSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint {
|
pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint {
|
||||||
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>();
|
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
|
||||||
cursor.seek(&point, Bias::Right, &());
|
cursor.seek(&point, Bias::Right, &());
|
||||||
if let Some(transform) = cursor.item() {
|
if let Some(transform) = cursor.item() {
|
||||||
let transform_start = cursor.start().0 .0;
|
let transform_start = cursor.start().0 .0;
|
||||||
@@ -826,7 +833,7 @@ where
|
|||||||
let buffer = &inlay_snapshot.buffer;
|
let buffer = &inlay_snapshot.buffer;
|
||||||
let start = buffer.anchor_before(range.start.to_offset(buffer));
|
let start = buffer.anchor_before(range.start.to_offset(buffer));
|
||||||
let end = buffer.anchor_after(range.end.to_offset(buffer));
|
let end = buffer.anchor_after(range.end.to_offset(buffer));
|
||||||
let mut cursor = folds.filter::<_, usize>(move |summary| {
|
let mut cursor = folds.filter::<_, usize>(buffer, move |summary| {
|
||||||
let start_cmp = start.cmp(&summary.max_end, buffer);
|
let start_cmp = start.cmp(&summary.max_end, buffer);
|
||||||
let end_cmp = end.cmp(&summary.min_start, buffer);
|
let end_cmp = end.cmp(&summary.min_start, buffer);
|
||||||
|
|
||||||
@@ -945,6 +952,10 @@ impl sum_tree::Item for Transform {
|
|||||||
impl sum_tree::Summary for TransformSummary {
|
impl sum_tree::Summary for TransformSummary {
|
||||||
type Context = ();
|
type Context = ();
|
||||||
|
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, other: &Self, _: &()) {
|
fn add_summary(&mut self, other: &Self, _: &()) {
|
||||||
self.input += &other.input;
|
self.input += &other.input;
|
||||||
self.output += &other.output;
|
self.output += &other.output;
|
||||||
@@ -1028,6 +1039,10 @@ impl Default for FoldSummary {
|
|||||||
impl sum_tree::Summary for FoldSummary {
|
impl sum_tree::Summary for FoldSummary {
|
||||||
type Context = MultiBufferSnapshot;
|
type Context = MultiBufferSnapshot;
|
||||||
|
|
||||||
|
fn zero(_cx: &MultiBufferSnapshot) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
|
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
|
||||||
if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less {
|
if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less {
|
||||||
self.min_start = other.min_start;
|
self.min_start = other.min_start;
|
||||||
@@ -1052,6 +1067,10 @@ impl sum_tree::Summary for FoldSummary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange {
|
impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange {
|
||||||
|
fn zero(_cx: &MultiBufferSnapshot) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
|
fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
|
||||||
self.0.start = summary.start;
|
self.0.start = summary.start;
|
||||||
self.0.end = summary.end;
|
self.0.end = summary.end;
|
||||||
@@ -1065,6 +1084,10 @@ impl<'a> sum_tree::SeekTarget<'a, FoldSummary, FoldRange> for FoldRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
|
impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
|
||||||
|
fn zero(_cx: &MultiBufferSnapshot) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
|
fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
|
||||||
*self += summary.count;
|
*self += summary.count;
|
||||||
}
|
}
|
||||||
@@ -1196,7 +1219,7 @@ impl FoldOffset {
|
|||||||
pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint {
|
pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint {
|
||||||
let mut cursor = snapshot
|
let mut cursor = snapshot
|
||||||
.transforms
|
.transforms
|
||||||
.cursor::<(FoldOffset, TransformSummary)>();
|
.cursor::<(FoldOffset, TransformSummary)>(&());
|
||||||
cursor.seek(&self, Bias::Right, &());
|
cursor.seek(&self, Bias::Right, &());
|
||||||
let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) {
|
let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) {
|
||||||
Point::new(0, (self.0 - cursor.start().0 .0) as u32)
|
Point::new(0, (self.0 - cursor.start().0 .0) as u32)
|
||||||
@@ -1210,7 +1233,7 @@ impl FoldOffset {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset {
|
pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset {
|
||||||
let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>();
|
let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>(&());
|
||||||
cursor.seek(&self, Bias::Right, &());
|
cursor.seek(&self, Bias::Right, &());
|
||||||
let overshoot = self.0 - cursor.start().0 .0;
|
let overshoot = self.0 - cursor.start().0 .0;
|
||||||
InlayOffset(cursor.start().1 .0 + overshoot)
|
InlayOffset(cursor.start().1 .0 + overshoot)
|
||||||
@@ -1240,18 +1263,30 @@ impl Sub for FoldOffset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
self.0 += &summary.output.len;
|
self.0 += &summary.output.len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
self.0 += &summary.input.lines;
|
self.0 += &summary.input.lines;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
self.0 += &summary.input.len;
|
self.0 += &summary.input.len;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ struct TransformSummary {
|
|||||||
impl sum_tree::Summary for TransformSummary {
|
impl sum_tree::Summary for TransformSummary {
|
||||||
type Context = ();
|
type Context = ();
|
||||||
|
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, other: &Self, _: &()) {
|
fn add_summary(&mut self, other: &Self, _: &()) {
|
||||||
self.input += &other.input;
|
self.input += &other.input;
|
||||||
self.output += &other.output;
|
self.output += &other.output;
|
||||||
@@ -137,6 +141,10 @@ impl SubAssign for InlayOffset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
self.0 += &summary.output.len;
|
self.0 += &summary.output.len;
|
||||||
}
|
}
|
||||||
@@ -162,18 +170,30 @@ impl Sub for InlayPoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
self.0 += &summary.output.lines;
|
self.0 += &summary.output.lines;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
*self += &summary.input.len;
|
*self += &summary.input.len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
*self += &summary.input.lines;
|
*self += &summary.input.lines;
|
||||||
}
|
}
|
||||||
@@ -475,8 +495,8 @@ impl InlayMap {
|
|||||||
(snapshot.clone(), Vec::new())
|
(snapshot.clone(), Vec::new())
|
||||||
} else {
|
} else {
|
||||||
let mut inlay_edits = Patch::default();
|
let mut inlay_edits = Patch::default();
|
||||||
let mut new_transforms = SumTree::new();
|
let mut new_transforms = SumTree::default();
|
||||||
let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>();
|
let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>(&());
|
||||||
let mut buffer_edits_iter = buffer_edits.iter().peekable();
|
let mut buffer_edits_iter = buffer_edits.iter().peekable();
|
||||||
while let Some(buffer_edit) = buffer_edits_iter.next() {
|
while let Some(buffer_edit) = buffer_edits_iter.next() {
|
||||||
new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
|
new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
|
||||||
@@ -693,7 +713,7 @@ impl InlaySnapshot {
|
|||||||
pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
|
pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
|
||||||
let mut cursor = self
|
let mut cursor = self
|
||||||
.transforms
|
.transforms
|
||||||
.cursor::<(InlayOffset, (InlayPoint, usize))>();
|
.cursor::<(InlayOffset, (InlayPoint, usize))>(&());
|
||||||
cursor.seek(&offset, Bias::Right, &());
|
cursor.seek(&offset, Bias::Right, &());
|
||||||
let overshoot = offset.0 - cursor.start().0 .0;
|
let overshoot = offset.0 - cursor.start().0 .0;
|
||||||
match cursor.item() {
|
match cursor.item() {
|
||||||
@@ -723,7 +743,7 @@ impl InlaySnapshot {
|
|||||||
pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
|
pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
|
||||||
let mut cursor = self
|
let mut cursor = self
|
||||||
.transforms
|
.transforms
|
||||||
.cursor::<(InlayPoint, (InlayOffset, Point))>();
|
.cursor::<(InlayPoint, (InlayOffset, Point))>(&());
|
||||||
cursor.seek(&point, Bias::Right, &());
|
cursor.seek(&point, Bias::Right, &());
|
||||||
let overshoot = point.0 - cursor.start().0 .0;
|
let overshoot = point.0 - cursor.start().0 .0;
|
||||||
match cursor.item() {
|
match cursor.item() {
|
||||||
@@ -741,9 +761,8 @@ impl InlaySnapshot {
|
|||||||
None => self.len(),
|
None => self.len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
|
pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
|
||||||
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
|
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
|
||||||
cursor.seek(&point, Bias::Right, &());
|
cursor.seek(&point, Bias::Right, &());
|
||||||
match cursor.item() {
|
match cursor.item() {
|
||||||
Some(Transform::Isomorphic(_)) => {
|
Some(Transform::Isomorphic(_)) => {
|
||||||
@@ -754,9 +773,8 @@ impl InlaySnapshot {
|
|||||||
None => self.buffer.max_point(),
|
None => self.buffer.max_point(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
|
pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
|
||||||
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
|
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
|
||||||
cursor.seek(&offset, Bias::Right, &());
|
cursor.seek(&offset, Bias::Right, &());
|
||||||
match cursor.item() {
|
match cursor.item() {
|
||||||
Some(Transform::Isomorphic(_)) => {
|
Some(Transform::Isomorphic(_)) => {
|
||||||
@@ -769,7 +787,7 @@ impl InlaySnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
|
pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
|
||||||
let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>();
|
let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>(&());
|
||||||
cursor.seek(&offset, Bias::Left, &());
|
cursor.seek(&offset, Bias::Left, &());
|
||||||
loop {
|
loop {
|
||||||
match cursor.item() {
|
match cursor.item() {
|
||||||
@@ -801,9 +819,8 @@ impl InlaySnapshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
|
pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
|
||||||
let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>();
|
let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(&());
|
||||||
cursor.seek(&point, Bias::Left, &());
|
cursor.seek(&point, Bias::Left, &());
|
||||||
loop {
|
loop {
|
||||||
match cursor.item() {
|
match cursor.item() {
|
||||||
@@ -837,7 +854,7 @@ impl InlaySnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
|
pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
|
||||||
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
|
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
|
||||||
cursor.seek(&point, Bias::Left, &());
|
cursor.seek(&point, Bias::Left, &());
|
||||||
loop {
|
loop {
|
||||||
match cursor.item() {
|
match cursor.item() {
|
||||||
@@ -934,7 +951,7 @@ impl InlaySnapshot {
|
|||||||
pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
|
pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
|
||||||
let mut summary = TextSummary::default();
|
let mut summary = TextSummary::default();
|
||||||
|
|
||||||
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
|
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
|
||||||
cursor.seek(&range.start, Bias::Right, &());
|
cursor.seek(&range.start, Bias::Right, &());
|
||||||
|
|
||||||
let overshoot = range.start.0 - cursor.start().0 .0;
|
let overshoot = range.start.0 - cursor.start().0 .0;
|
||||||
@@ -982,7 +999,7 @@ impl InlaySnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn buffer_rows(&self, row: u32) -> InlayBufferRows<'_> {
|
pub fn buffer_rows(&self, row: u32) -> InlayBufferRows<'_> {
|
||||||
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
|
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
|
||||||
let inlay_point = InlayPoint::new(row, 0);
|
let inlay_point = InlayPoint::new(row, 0);
|
||||||
cursor.seek(&inlay_point, Bias::Left, &());
|
cursor.seek(&inlay_point, Bias::Left, &());
|
||||||
|
|
||||||
@@ -1024,7 +1041,7 @@ impl InlaySnapshot {
|
|||||||
language_aware: bool,
|
language_aware: bool,
|
||||||
highlights: Highlights<'a>,
|
highlights: Highlights<'a>,
|
||||||
) -> InlayChunks<'a> {
|
) -> InlayChunks<'a> {
|
||||||
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
|
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
|
||||||
cursor.seek(&range.start, Bias::Right, &());
|
cursor.seek(&range.start, Bias::Right, &());
|
||||||
|
|
||||||
let mut highlight_endpoints = Vec::new();
|
let mut highlight_endpoints = Vec::new();
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ impl WrapMap {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let old_rows = self.snapshot.transforms.summary().output.lines.row + 1;
|
let old_rows = self.snapshot.transforms.summary().output.lines.row + 1;
|
||||||
self.snapshot.transforms = SumTree::new();
|
self.snapshot.transforms = SumTree::default();
|
||||||
let summary = self.snapshot.tab_snapshot.text_summary();
|
let summary = self.snapshot.tab_snapshot.text_summary();
|
||||||
if !summary.lines.is_zero() {
|
if !summary.lines.is_zero() {
|
||||||
self.snapshot
|
self.snapshot
|
||||||
@@ -303,7 +303,7 @@ impl WrapMap {
|
|||||||
|
|
||||||
impl WrapSnapshot {
|
impl WrapSnapshot {
|
||||||
fn new(tab_snapshot: TabSnapshot) -> Self {
|
fn new(tab_snapshot: TabSnapshot) -> Self {
|
||||||
let mut transforms = SumTree::new();
|
let mut transforms = SumTree::default();
|
||||||
let extent = tab_snapshot.text_summary();
|
let extent = tab_snapshot.text_summary();
|
||||||
if !extent.lines.is_zero() {
|
if !extent.lines.is_zero() {
|
||||||
transforms.push(Transform::isomorphic(extent), &());
|
transforms.push(Transform::isomorphic(extent), &());
|
||||||
@@ -324,7 +324,7 @@ impl WrapSnapshot {
|
|||||||
if tab_edits.is_empty() {
|
if tab_edits.is_empty() {
|
||||||
new_transforms = self.transforms.clone();
|
new_transforms = self.transforms.clone();
|
||||||
} else {
|
} else {
|
||||||
let mut old_cursor = self.transforms.cursor::<TabPoint>();
|
let mut old_cursor = self.transforms.cursor::<TabPoint>(&());
|
||||||
|
|
||||||
let mut tab_edits_iter = tab_edits.iter().peekable();
|
let mut tab_edits_iter = tab_edits.iter().peekable();
|
||||||
new_transforms =
|
new_transforms =
|
||||||
@@ -424,7 +424,7 @@ impl WrapSnapshot {
|
|||||||
new_transforms = self.transforms.clone();
|
new_transforms = self.transforms.clone();
|
||||||
} else {
|
} else {
|
||||||
let mut row_edits = row_edits.into_iter().peekable();
|
let mut row_edits = row_edits.into_iter().peekable();
|
||||||
let mut old_cursor = self.transforms.cursor::<TabPoint>();
|
let mut old_cursor = self.transforms.cursor::<TabPoint>(&());
|
||||||
|
|
||||||
new_transforms = old_cursor.slice(
|
new_transforms = old_cursor.slice(
|
||||||
&TabPoint::new(row_edits.peek().unwrap().old_rows.start, 0),
|
&TabPoint::new(row_edits.peek().unwrap().old_rows.start, 0),
|
||||||
@@ -537,8 +537,8 @@ impl WrapSnapshot {
|
|||||||
|
|
||||||
fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &WrapSnapshot) -> Patch<u32> {
|
fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &WrapSnapshot) -> Patch<u32> {
|
||||||
let mut wrap_edits = Vec::new();
|
let mut wrap_edits = Vec::new();
|
||||||
let mut old_cursor = self.transforms.cursor::<TransformSummary>();
|
let mut old_cursor = self.transforms.cursor::<TransformSummary>(&());
|
||||||
let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>();
|
let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>(&());
|
||||||
for mut tab_edit in tab_edits.iter().cloned() {
|
for mut tab_edit in tab_edits.iter().cloned() {
|
||||||
tab_edit.old.start.0.column = 0;
|
tab_edit.old.start.0.column = 0;
|
||||||
tab_edit.old.end.0 += Point::new(1, 0);
|
tab_edit.old.end.0 += Point::new(1, 0);
|
||||||
@@ -579,7 +579,7 @@ impl WrapSnapshot {
|
|||||||
) -> WrapChunks<'a> {
|
) -> WrapChunks<'a> {
|
||||||
let output_start = WrapPoint::new(rows.start, 0);
|
let output_start = WrapPoint::new(rows.start, 0);
|
||||||
let output_end = WrapPoint::new(rows.end, 0);
|
let output_end = WrapPoint::new(rows.end, 0);
|
||||||
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>();
|
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||||
transforms.seek(&output_start, Bias::Right, &());
|
transforms.seek(&output_start, Bias::Right, &());
|
||||||
let mut input_start = TabPoint(transforms.start().1 .0);
|
let mut input_start = TabPoint(transforms.start().1 .0);
|
||||||
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
|
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
|
||||||
@@ -606,7 +606,7 @@ impl WrapSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn line_len(&self, row: u32) -> u32 {
|
pub fn line_len(&self, row: u32) -> u32 {
|
||||||
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
|
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||||
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left, &());
|
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left, &());
|
||||||
if cursor
|
if cursor
|
||||||
.item()
|
.item()
|
||||||
@@ -626,7 +626,7 @@ impl WrapSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
|
pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
|
||||||
let mut cursor = self.transforms.cursor::<WrapPoint>();
|
let mut cursor = self.transforms.cursor::<WrapPoint>(&());
|
||||||
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &());
|
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &());
|
||||||
cursor.item().and_then(|transform| {
|
cursor.item().and_then(|transform| {
|
||||||
if transform.is_isomorphic() {
|
if transform.is_isomorphic() {
|
||||||
@@ -642,7 +642,7 @@ impl WrapSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn buffer_rows(&self, start_row: u32) -> WrapBufferRows {
|
pub fn buffer_rows(&self, start_row: u32) -> WrapBufferRows {
|
||||||
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>();
|
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||||
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
|
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
|
||||||
let mut input_row = transforms.start().1.row();
|
let mut input_row = transforms.start().1.row();
|
||||||
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
|
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
|
||||||
@@ -662,7 +662,7 @@ impl WrapSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint {
|
pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint {
|
||||||
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
|
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||||
cursor.seek(&point, Bias::Right, &());
|
cursor.seek(&point, Bias::Right, &());
|
||||||
let mut tab_point = cursor.start().1 .0;
|
let mut tab_point = cursor.start().1 .0;
|
||||||
if cursor.item().map_or(false, |t| t.is_isomorphic()) {
|
if cursor.item().map_or(false, |t| t.is_isomorphic()) {
|
||||||
@@ -680,14 +680,14 @@ impl WrapSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint {
|
pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint {
|
||||||
let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>();
|
let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>(&());
|
||||||
cursor.seek(&point, Bias::Right, &());
|
cursor.seek(&point, Bias::Right, &());
|
||||||
WrapPoint(cursor.start().1 .0 + (point.0 - cursor.start().0 .0))
|
WrapPoint(cursor.start().1 .0 + (point.0 - cursor.start().0 .0))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint {
|
pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint {
|
||||||
if bias == Bias::Left {
|
if bias == Bias::Left {
|
||||||
let mut cursor = self.transforms.cursor::<WrapPoint>();
|
let mut cursor = self.transforms.cursor::<WrapPoint>(&());
|
||||||
cursor.seek(&point, Bias::Right, &());
|
cursor.seek(&point, Bias::Right, &());
|
||||||
if cursor.item().map_or(false, |t| !t.is_isomorphic()) {
|
if cursor.item().map_or(false, |t| !t.is_isomorphic()) {
|
||||||
point = *cursor.start();
|
point = *cursor.start();
|
||||||
@@ -705,7 +705,7 @@ impl WrapSnapshot {
|
|||||||
|
|
||||||
*point.column_mut() = 0;
|
*point.column_mut() = 0;
|
||||||
|
|
||||||
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
|
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||||
cursor.seek(&point, Bias::Right, &());
|
cursor.seek(&point, Bias::Right, &());
|
||||||
if cursor.item().is_none() {
|
if cursor.item().is_none() {
|
||||||
cursor.prev(&());
|
cursor.prev(&());
|
||||||
@@ -725,7 +725,7 @@ impl WrapSnapshot {
|
|||||||
pub fn next_row_boundary(&self, mut point: WrapPoint) -> Option<u32> {
|
pub fn next_row_boundary(&self, mut point: WrapPoint) -> Option<u32> {
|
||||||
point.0 += Point::new(1, 0);
|
point.0 += Point::new(1, 0);
|
||||||
|
|
||||||
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
|
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||||
cursor.seek(&point, Bias::Right, &());
|
cursor.seek(&point, Bias::Right, &());
|
||||||
while let Some(transform) = cursor.item() {
|
while let Some(transform) = cursor.item() {
|
||||||
if transform.is_isomorphic() && cursor.start().1.column() == 0 {
|
if transform.is_isomorphic() && cursor.start().1.column() == 0 {
|
||||||
@@ -747,7 +747,7 @@ impl WrapSnapshot {
|
|||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut transforms = self.transforms.cursor::<()>().peekable();
|
let mut transforms = self.transforms.cursor::<()>(&()).peekable();
|
||||||
while let Some(transform) = transforms.next() {
|
while let Some(transform) = transforms.next() {
|
||||||
if let Some(next_transform) = transforms.peek() {
|
if let Some(next_transform) = transforms.peek() {
|
||||||
assert!(transform.is_isomorphic() != next_transform.is_isomorphic());
|
assert!(transform.is_isomorphic() != next_transform.is_isomorphic());
|
||||||
@@ -982,6 +982,10 @@ impl WrapPoint {
|
|||||||
impl sum_tree::Summary for TransformSummary {
|
impl sum_tree::Summary for TransformSummary {
|
||||||
type Context = ();
|
type Context = ();
|
||||||
|
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, other: &Self, _: &()) {
|
fn add_summary(&mut self, other: &Self, _: &()) {
|
||||||
self.input += &other.input;
|
self.input += &other.input;
|
||||||
self.output += &other.output;
|
self.output += &other.output;
|
||||||
@@ -989,6 +993,10 @@ impl sum_tree::Summary for TransformSummary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
self.0 += summary.input.lines;
|
self.0 += summary.input.lines;
|
||||||
}
|
}
|
||||||
@@ -1001,6 +1009,10 @@ impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for TabPoi
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
self.0 += summary.output.lines;
|
self.0 += summary.output.lines;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ use language::{
|
|||||||
};
|
};
|
||||||
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
|
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
|
||||||
use linked_editing_ranges::refresh_linked_ranges;
|
use linked_editing_ranges::refresh_linked_ranges;
|
||||||
|
use similar::{ChangeTag, TextDiff};
|
||||||
use task::{ResolvedTask, TaskTemplate, TaskVariables};
|
use task::{ResolvedTask, TaskTemplate, TaskVariables};
|
||||||
|
|
||||||
use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
|
use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
|
||||||
@@ -411,8 +412,37 @@ impl Default for EditorStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn make_inlay_hints_style(cx: &WindowContext) -> HighlightStyle {
|
||||||
|
let show_background = all_language_settings(None, cx)
|
||||||
|
.language(None)
|
||||||
|
.inlay_hints
|
||||||
|
.show_background;
|
||||||
|
|
||||||
|
HighlightStyle {
|
||||||
|
color: Some(cx.theme().status().hint),
|
||||||
|
background_color: show_background.then(|| cx.theme().status().hint_background),
|
||||||
|
..HighlightStyle::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type CompletionId = usize;
|
type CompletionId = usize;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct CompletionState {
|
||||||
|
// render_inlay_ids represents the inlay hints that are inserted
|
||||||
|
// for rendering the inline completions. They may be discontinuous
|
||||||
|
// in the event that the completion provider returns some intersection
|
||||||
|
// with the existing content.
|
||||||
|
render_inlay_ids: Vec<InlayId>,
|
||||||
|
// text is the resulting rope that is inserted when the user accepts a completion.
|
||||||
|
text: Rope,
|
||||||
|
// position is the position of the cursor when the completion was triggered.
|
||||||
|
position: multi_buffer::Anchor,
|
||||||
|
// delete_range is the range of text that this completion state covers.
|
||||||
|
// if the completion is accepted, this range should be deleted.
|
||||||
|
delete_range: Option<Range<multi_buffer::Anchor>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
|
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
|
||||||
struct EditorActionId(usize);
|
struct EditorActionId(usize);
|
||||||
|
|
||||||
@@ -556,7 +586,7 @@ pub struct Editor {
|
|||||||
gutter_hovered: bool,
|
gutter_hovered: bool,
|
||||||
hovered_link_state: Option<HoveredLinkState>,
|
hovered_link_state: Option<HoveredLinkState>,
|
||||||
inline_completion_provider: Option<RegisteredInlineCompletionProvider>,
|
inline_completion_provider: Option<RegisteredInlineCompletionProvider>,
|
||||||
active_inline_completion: Option<(Inlay, Option<Range<Anchor>>)>,
|
active_inline_completion: Option<CompletionState>,
|
||||||
// enable_inline_completions is a switch that Vim can use to disable
|
// enable_inline_completions is a switch that Vim can use to disable
|
||||||
// inline completions based on its mode.
|
// inline completions based on its mode.
|
||||||
enable_inline_completions: bool,
|
enable_inline_completions: bool,
|
||||||
@@ -1887,7 +1917,9 @@ impl Editor {
|
|||||||
linked_editing_range_task: Default::default(),
|
linked_editing_range_task: Default::default(),
|
||||||
pending_rename: Default::default(),
|
pending_rename: Default::default(),
|
||||||
searchable: true,
|
searchable: true,
|
||||||
cursor_shape: Default::default(),
|
cursor_shape: EditorSettings::get_global(cx)
|
||||||
|
.cursor_shape
|
||||||
|
.unwrap_or_default(),
|
||||||
current_line_highlight: None,
|
current_line_highlight: None,
|
||||||
autoindent_mode: Some(AutoindentMode::EachLine),
|
autoindent_mode: Some(AutoindentMode::EachLine),
|
||||||
collapse_matches: false,
|
collapse_matches: false,
|
||||||
@@ -5068,7 +5100,7 @@ impl Editor {
|
|||||||
_: &AcceptInlineCompletion,
|
_: &AcceptInlineCompletion,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
let Some((completion, delete_range)) = self.take_active_inline_completion(cx) else {
|
let Some(completion) = self.take_active_inline_completion(cx) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let Some(provider) = self.inline_completion_provider() {
|
if let Some(provider) = self.inline_completion_provider() {
|
||||||
@@ -5080,7 +5112,7 @@ impl Editor {
|
|||||||
text: completion.text.to_string().into(),
|
text: completion.text.to_string().into(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(range) = delete_range {
|
if let Some(range) = completion.delete_range {
|
||||||
self.change_selections(None, cx, |s| s.select_ranges([range]))
|
self.change_selections(None, cx, |s| s.select_ranges([range]))
|
||||||
}
|
}
|
||||||
self.insert_with_autoindent_mode(&completion.text.to_string(), None, cx);
|
self.insert_with_autoindent_mode(&completion.text.to_string(), None, cx);
|
||||||
@@ -5094,7 +5126,7 @@ impl Editor {
|
|||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if self.selections.count() == 1 && self.has_active_inline_completion(cx) {
|
if self.selections.count() == 1 && self.has_active_inline_completion(cx) {
|
||||||
if let Some((completion, delete_range)) = self.take_active_inline_completion(cx) {
|
if let Some(completion) = self.take_active_inline_completion(cx) {
|
||||||
let mut partial_completion = completion
|
let mut partial_completion = completion
|
||||||
.text
|
.text
|
||||||
.chars()
|
.chars()
|
||||||
@@ -5115,7 +5147,7 @@ impl Editor {
|
|||||||
text: partial_completion.clone().into(),
|
text: partial_completion.clone().into(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(range) = delete_range {
|
if let Some(range) = completion.delete_range {
|
||||||
self.change_selections(None, cx, |s| s.select_ranges([range]))
|
self.change_selections(None, cx, |s| s.select_ranges([range]))
|
||||||
}
|
}
|
||||||
self.insert_with_autoindent_mode(&partial_completion, None, cx);
|
self.insert_with_autoindent_mode(&partial_completion, None, cx);
|
||||||
@@ -5141,7 +5173,7 @@ impl Editor {
|
|||||||
pub fn has_active_inline_completion(&self, cx: &AppContext) -> bool {
|
pub fn has_active_inline_completion(&self, cx: &AppContext) -> bool {
|
||||||
if let Some(completion) = self.active_inline_completion.as_ref() {
|
if let Some(completion) = self.active_inline_completion.as_ref() {
|
||||||
let buffer = self.buffer.read(cx).read(cx);
|
let buffer = self.buffer.read(cx).read(cx);
|
||||||
completion.0.position.is_valid(&buffer)
|
completion.position.is_valid(&buffer)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -5150,14 +5182,15 @@ impl Editor {
|
|||||||
fn take_active_inline_completion(
|
fn take_active_inline_completion(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<(Inlay, Option<Range<Anchor>>)> {
|
) -> Option<CompletionState> {
|
||||||
let completion = self.active_inline_completion.take()?;
|
let completion = self.active_inline_completion.take()?;
|
||||||
|
let render_inlay_ids = completion.render_inlay_ids.clone();
|
||||||
self.display_map.update(cx, |map, cx| {
|
self.display_map.update(cx, |map, cx| {
|
||||||
map.splice_inlays(vec![completion.0.id], Default::default(), cx);
|
map.splice_inlays(render_inlay_ids, Default::default(), cx);
|
||||||
});
|
});
|
||||||
let buffer = self.buffer.read(cx).read(cx);
|
let buffer = self.buffer.read(cx).read(cx);
|
||||||
|
|
||||||
if completion.0.position.is_valid(&buffer) {
|
if completion.position.is_valid(&buffer) {
|
||||||
Some(completion)
|
Some(completion)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -5178,31 +5211,50 @@ impl Editor {
|
|||||||
if let Some((buffer, cursor_buffer_position)) =
|
if let Some((buffer, cursor_buffer_position)) =
|
||||||
self.buffer.read(cx).text_anchor_for_position(cursor, cx)
|
self.buffer.read(cx).text_anchor_for_position(cursor, cx)
|
||||||
{
|
{
|
||||||
if let Some((text, text_anchor_range)) =
|
if let Some(proposal) =
|
||||||
provider.active_completion_text(&buffer, cursor_buffer_position, cx)
|
provider.active_completion_text(&buffer, cursor_buffer_position, cx)
|
||||||
{
|
{
|
||||||
let text = Rope::from(text);
|
|
||||||
let mut to_remove = Vec::new();
|
let mut to_remove = Vec::new();
|
||||||
if let Some(completion) = self.active_inline_completion.take() {
|
if let Some(completion) = self.active_inline_completion.take() {
|
||||||
to_remove.push(completion.0.id);
|
to_remove.extend(completion.render_inlay_ids.iter());
|
||||||
}
|
}
|
||||||
|
|
||||||
let completion_inlay =
|
let to_add = proposal
|
||||||
Inlay::suggestion(post_inc(&mut self.next_inlay_id), cursor, text);
|
.inlays
|
||||||
|
.iter()
|
||||||
|
.filter_map(|inlay| {
|
||||||
|
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||||
|
let id = post_inc(&mut self.next_inlay_id);
|
||||||
|
match inlay {
|
||||||
|
InlayProposal::Hint(position, hint) => {
|
||||||
|
let position =
|
||||||
|
snapshot.anchor_in_excerpt(excerpt_id, *position)?;
|
||||||
|
Some(Inlay::hint(id, position, hint))
|
||||||
|
}
|
||||||
|
InlayProposal::Suggestion(position, text) => {
|
||||||
|
let position =
|
||||||
|
snapshot.anchor_in_excerpt(excerpt_id, *position)?;
|
||||||
|
Some(Inlay::suggestion(id, position, text.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
let multibuffer_anchor_range = text_anchor_range.and_then(|range| {
|
self.active_inline_completion = Some(CompletionState {
|
||||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
position: cursor,
|
||||||
Some(
|
text: proposal.text,
|
||||||
snapshot.anchor_in_excerpt(excerpt_id, range.start)?
|
delete_range: proposal.delete_range.and_then(|range| {
|
||||||
..snapshot.anchor_in_excerpt(excerpt_id, range.end)?,
|
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||||
)
|
let start = snapshot.anchor_in_excerpt(excerpt_id, range.start);
|
||||||
|
let end = snapshot.anchor_in_excerpt(excerpt_id, range.end);
|
||||||
|
Some(start?..end?)
|
||||||
|
}),
|
||||||
|
render_inlay_ids: to_add.iter().map(|i| i.id).collect(),
|
||||||
});
|
});
|
||||||
self.active_inline_completion =
|
|
||||||
Some((completion_inlay.clone(), multibuffer_anchor_range));
|
|
||||||
|
|
||||||
self.display_map.update(cx, move |map, cx| {
|
self.display_map
|
||||||
map.splice_inlays(to_remove, vec![completion_inlay], cx)
|
.update(cx, move |map, cx| map.splice_inlays(to_remove, to_add, cx));
|
||||||
});
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -6659,6 +6711,161 @@ impl Editor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rewrap(&mut self, _: &Rewrap, cx: &mut ViewContext<Self>) {
|
||||||
|
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||||
|
let selections = self.selections.all::<Point>(cx);
|
||||||
|
let mut selections = selections.iter().peekable();
|
||||||
|
|
||||||
|
let mut edits = Vec::new();
|
||||||
|
let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
|
||||||
|
|
||||||
|
while let Some(selection) = selections.next() {
|
||||||
|
let mut start_row = selection.start.row;
|
||||||
|
let mut end_row = selection.end.row;
|
||||||
|
|
||||||
|
// Skip selections that overlap with a range that has already been rewrapped.
|
||||||
|
let selection_range = start_row..end_row;
|
||||||
|
if rewrapped_row_ranges
|
||||||
|
.iter()
|
||||||
|
.any(|range| range.overlaps(&selection_range))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut should_rewrap = false;
|
||||||
|
|
||||||
|
if let Some(language_scope) = buffer.language_scope_at(selection.head()) {
|
||||||
|
match language_scope.language_name().0.as_ref() {
|
||||||
|
"Markdown" | "Plain Text" => {
|
||||||
|
should_rewrap = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let row = selection.head().row;
|
||||||
|
let indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
|
||||||
|
let indent_end = Point::new(row, indent_size.len);
|
||||||
|
|
||||||
|
let mut line_prefix = indent_size.chars().collect::<String>();
|
||||||
|
|
||||||
|
if let Some(comment_prefix) =
|
||||||
|
buffer
|
||||||
|
.language_scope_at(selection.head())
|
||||||
|
.and_then(|language| {
|
||||||
|
language
|
||||||
|
.line_comment_prefixes()
|
||||||
|
.iter()
|
||||||
|
.find(|prefix| buffer.contains_str_at(indent_end, prefix))
|
||||||
|
.cloned()
|
||||||
|
})
|
||||||
|
{
|
||||||
|
line_prefix.push_str(&comment_prefix);
|
||||||
|
should_rewrap = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.is_empty() {
|
||||||
|
'expand_upwards: while start_row > 0 {
|
||||||
|
let prev_row = start_row - 1;
|
||||||
|
if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
|
||||||
|
&& buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
|
||||||
|
{
|
||||||
|
start_row = prev_row;
|
||||||
|
} else {
|
||||||
|
break 'expand_upwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
'expand_downwards: while end_row < buffer.max_point().row {
|
||||||
|
let next_row = end_row + 1;
|
||||||
|
if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
|
||||||
|
&& buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
|
||||||
|
{
|
||||||
|
end_row = next_row;
|
||||||
|
} else {
|
||||||
|
break 'expand_downwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !should_rewrap {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = Point::new(start_row, 0);
|
||||||
|
let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
|
||||||
|
let selection_text = buffer.text_for_range(start..end).collect::<String>();
|
||||||
|
let unwrapped_text = selection_text
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.strip_prefix(&line_prefix).unwrap())
|
||||||
|
.join(" ");
|
||||||
|
let wrap_column = buffer
|
||||||
|
.settings_at(Point::new(start_row, 0), cx)
|
||||||
|
.preferred_line_length as usize;
|
||||||
|
let mut wrapped_text = String::new();
|
||||||
|
let mut current_line = line_prefix.clone();
|
||||||
|
for word in unwrapped_text.split_whitespace() {
|
||||||
|
if current_line.len() + word.len() >= wrap_column {
|
||||||
|
wrapped_text.push_str(¤t_line);
|
||||||
|
wrapped_text.push('\n');
|
||||||
|
current_line.truncate(line_prefix.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
if current_line.len() > line_prefix.len() {
|
||||||
|
current_line.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
current_line.push_str(word);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !current_line.is_empty() {
|
||||||
|
wrapped_text.push_str(¤t_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
let diff = TextDiff::from_lines(&selection_text, &wrapped_text);
|
||||||
|
let mut offset = start.to_offset(&buffer);
|
||||||
|
let mut moved_since_edit = true;
|
||||||
|
|
||||||
|
for change in diff.iter_all_changes() {
|
||||||
|
let value = change.value();
|
||||||
|
match change.tag() {
|
||||||
|
ChangeTag::Equal => {
|
||||||
|
offset += value.len();
|
||||||
|
moved_since_edit = true;
|
||||||
|
}
|
||||||
|
ChangeTag::Delete => {
|
||||||
|
let start = buffer.anchor_after(offset);
|
||||||
|
let end = buffer.anchor_before(offset + value.len());
|
||||||
|
|
||||||
|
if moved_since_edit {
|
||||||
|
edits.push((start..end, String::new()));
|
||||||
|
} else {
|
||||||
|
edits.last_mut().unwrap().0.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += value.len();
|
||||||
|
moved_since_edit = false;
|
||||||
|
}
|
||||||
|
ChangeTag::Insert => {
|
||||||
|
if moved_since_edit {
|
||||||
|
let anchor = buffer.anchor_after(offset);
|
||||||
|
edits.push((anchor..anchor, value.to_string()));
|
||||||
|
} else {
|
||||||
|
edits.last_mut().unwrap().1.push_str(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
moved_since_edit = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rewrapped_row_ranges.push(start_row..=end_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.buffer
|
||||||
|
.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
|
pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||||
@@ -9840,9 +10047,8 @@ impl Editor {
|
|||||||
syntax: cx.editor_style.syntax.clone(),
|
syntax: cx.editor_style.syntax.clone(),
|
||||||
status: cx.editor_style.status.clone(),
|
status: cx.editor_style.status.clone(),
|
||||||
inlay_hints_style: HighlightStyle {
|
inlay_hints_style: HighlightStyle {
|
||||||
color: Some(cx.theme().status().hint),
|
|
||||||
font_weight: Some(FontWeight::BOLD),
|
font_weight: Some(FontWeight::BOLD),
|
||||||
..HighlightStyle::default()
|
..make_inlay_hints_style(cx)
|
||||||
},
|
},
|
||||||
suggestions_style: HighlightStyle {
|
suggestions_style: HighlightStyle {
|
||||||
color: Some(cx.theme().status().predictive),
|
color: Some(cx.theme().status().predictive),
|
||||||
@@ -11628,6 +11834,9 @@ impl Editor {
|
|||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
let editor_settings = EditorSettings::get_global(cx);
|
let editor_settings = EditorSettings::get_global(cx);
|
||||||
|
if let Some(cursor_shape) = editor_settings.cursor_shape {
|
||||||
|
self.cursor_shape = cursor_shape;
|
||||||
|
}
|
||||||
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
|
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
|
||||||
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
|
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
|
||||||
|
|
||||||
@@ -12795,10 +13004,7 @@ impl Render for Editor {
|
|||||||
scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
|
scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
|
||||||
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: make_inlay_hints_style(cx),
|
||||||
color: Some(cx.theme().status().hint),
|
|
||||||
..HighlightStyle::default()
|
|
||||||
},
|
|
||||||
suggestions_style: HighlightStyle {
|
suggestions_style: HighlightStyle {
|
||||||
color: Some(cx.theme().status().predictive),
|
color: Some(cx.theme().status().predictive),
|
||||||
..HighlightStyle::default()
|
..HighlightStyle::default()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use gpui::AppContext;
|
use gpui::AppContext;
|
||||||
|
use language::CursorShape;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{Settings, SettingsSources};
|
use settings::{Settings, SettingsSources};
|
||||||
@@ -6,6 +7,7 @@ use settings::{Settings, SettingsSources};
|
|||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct EditorSettings {
|
pub struct EditorSettings {
|
||||||
pub cursor_blink: bool,
|
pub cursor_blink: bool,
|
||||||
|
pub cursor_shape: Option<CursorShape>,
|
||||||
pub current_line_highlight: CurrentLineHighlight,
|
pub current_line_highlight: CurrentLineHighlight,
|
||||||
pub hover_popover_enabled: bool,
|
pub hover_popover_enabled: bool,
|
||||||
pub show_completions_on_input: bool,
|
pub show_completions_on_input: bool,
|
||||||
@@ -177,6 +179,11 @@ pub struct EditorSettingsContent {
|
|||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
pub cursor_blink: Option<bool>,
|
pub cursor_blink: Option<bool>,
|
||||||
|
/// Cursor shape for the default editor.
|
||||||
|
/// Can be "bar", "block", "underscore", or "hollow".
|
||||||
|
///
|
||||||
|
/// Default: None
|
||||||
|
pub cursor_shape: Option<CursorShape>,
|
||||||
/// How to highlight the current line in the editor.
|
/// How to highlight the current line in the editor.
|
||||||
///
|
///
|
||||||
/// Default: all
|
/// Default: all
|
||||||
|
|||||||
@@ -2322,7 +2322,7 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) {
|
|||||||
let language = Arc::new(
|
let language = Arc::new(
|
||||||
Language::new(
|
Language::new(
|
||||||
LanguageConfig::default(),
|
LanguageConfig::default(),
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)
|
)
|
||||||
.with_indents_query(r#"(_ "(" ")" @end) @indent"#)
|
.with_indents_query(r#"(_ "(" ")" @end) @indent"#)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@@ -2370,7 +2370,7 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) {
|
|||||||
let language = Arc::new(
|
let language = Arc::new(
|
||||||
Language::new(
|
Language::new(
|
||||||
LanguageConfig::default(),
|
LanguageConfig::default(),
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)
|
)
|
||||||
.with_indents_query(r#"(_ "(" ")" @end) @indent"#)
|
.with_indents_query(r#"(_ "(" ")" @end) @indent"#)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@@ -2524,7 +2524,7 @@ async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAp
|
|||||||
let language = Arc::new(
|
let language = Arc::new(
|
||||||
Language::new(
|
Language::new(
|
||||||
LanguageConfig::default(),
|
LanguageConfig::default(),
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)
|
)
|
||||||
.with_indents_query(r#"(_ "(" ")" @end) @indent"#)
|
.with_indents_query(r#"(_ "(" ")" @end) @indent"#)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@@ -2585,7 +2585,7 @@ async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
|
|||||||
let language = Arc::new(
|
let language = Arc::new(
|
||||||
Language::new(
|
Language::new(
|
||||||
LanguageConfig::default(),
|
LanguageConfig::default(),
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)
|
)
|
||||||
.with_indents_query(r#"(_ "{" "}" @end) @indent"#)
|
.with_indents_query(r#"(_ "{" "}" @end) @indent"#)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@@ -3979,6 +3979,278 @@ fn test_transpose(cx: &mut TestAppContext) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_rewrap(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
|
let mut cx = EditorTestContext::new(cx).await;
|
||||||
|
|
||||||
|
{
|
||||||
|
let language = Arc::new(Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
line_comments: vec!["// ".into()],
|
||||||
|
..LanguageConfig::default()
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||||
|
|
||||||
|
let unwrapped_text = indoc! {"
|
||||||
|
// ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
|
||||||
|
"};
|
||||||
|
|
||||||
|
let wrapped_text = indoc! {"
|
||||||
|
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||||
|
// purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
|
||||||
|
// auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
|
||||||
|
// tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
|
||||||
|
// Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
|
||||||
|
// vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
|
||||||
|
// et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
|
||||||
|
// dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
|
||||||
|
// viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
|
||||||
|
// porttitor id. Aliquam id accumsan eros.ˇ
|
||||||
|
"};
|
||||||
|
|
||||||
|
cx.set_state(unwrapped_text);
|
||||||
|
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||||
|
cx.assert_editor_state(wrapped_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that rewrapping works inside of a selection
|
||||||
|
{
|
||||||
|
let language = Arc::new(Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
line_comments: vec!["// ".into()],
|
||||||
|
..LanguageConfig::default()
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||||
|
|
||||||
|
let unwrapped_text = indoc! {"
|
||||||
|
«// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
|
||||||
|
"};
|
||||||
|
|
||||||
|
let wrapped_text = indoc! {"
|
||||||
|
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||||
|
// purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
|
||||||
|
// auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
|
||||||
|
// tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
|
||||||
|
// Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
|
||||||
|
// vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
|
||||||
|
// et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
|
||||||
|
// dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
|
||||||
|
// viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
|
||||||
|
// porttitor id. Aliquam id accumsan eros.ˇ
|
||||||
|
"};
|
||||||
|
|
||||||
|
cx.set_state(unwrapped_text);
|
||||||
|
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||||
|
cx.assert_editor_state(wrapped_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that cursors that expand to the same region are collapsed.
|
||||||
|
{
|
||||||
|
let language = Arc::new(Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
line_comments: vec!["// ".into()],
|
||||||
|
..LanguageConfig::default()
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||||
|
|
||||||
|
let unwrapped_text = indoc! {"
|
||||||
|
// ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
|
// ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
|
||||||
|
// ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
|
||||||
|
// ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
|
||||||
|
"};
|
||||||
|
|
||||||
|
let wrapped_text = indoc! {"
|
||||||
|
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||||
|
// purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
|
||||||
|
// auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
|
||||||
|
// tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
|
||||||
|
// Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
|
||||||
|
// vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
|
||||||
|
// et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
|
||||||
|
// dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
|
||||||
|
// viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
|
||||||
|
// porttitor id. Aliquam id accumsan eros.ˇˇˇˇ
|
||||||
|
"};
|
||||||
|
|
||||||
|
cx.set_state(unwrapped_text);
|
||||||
|
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||||
|
cx.assert_editor_state(wrapped_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that non-contiguous selections are treated separately.
|
||||||
|
{
|
||||||
|
let language = Arc::new(Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
line_comments: vec!["// ".into()],
|
||||||
|
..LanguageConfig::default()
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||||
|
|
||||||
|
let unwrapped_text = indoc! {"
|
||||||
|
// ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
|
// ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
|
||||||
|
//
|
||||||
|
// ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
|
||||||
|
// ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
|
||||||
|
"};
|
||||||
|
|
||||||
|
let wrapped_text = indoc! {"
|
||||||
|
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||||
|
// purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
|
||||||
|
// auctor, eu lacinia sapien scelerisque.ˇˇ
|
||||||
|
//
|
||||||
|
// Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
|
||||||
|
// tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
|
||||||
|
// blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
|
||||||
|
// molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
|
||||||
|
// nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
|
||||||
|
// porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
|
||||||
|
// vulputate turpis porttitor id. Aliquam id accumsan eros.ˇˇ
|
||||||
|
"};
|
||||||
|
|
||||||
|
cx.set_state(unwrapped_text);
|
||||||
|
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||||
|
cx.assert_editor_state(wrapped_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that different comment prefixes are supported.
|
||||||
|
{
|
||||||
|
let language = Arc::new(Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
line_comments: vec!["# ".into()],
|
||||||
|
..LanguageConfig::default()
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||||
|
|
||||||
|
let unwrapped_text = indoc! {"
|
||||||
|
# ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
|
||||||
|
"};
|
||||||
|
|
||||||
|
let wrapped_text = indoc! {"
|
||||||
|
# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||||
|
# purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
|
||||||
|
# eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
|
||||||
|
# hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
|
||||||
|
# lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
|
||||||
|
# amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
|
||||||
|
# in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
|
||||||
|
# adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
|
||||||
|
# Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
|
||||||
|
# accumsan eros.ˇ
|
||||||
|
"};
|
||||||
|
|
||||||
|
cx.set_state(unwrapped_text);
|
||||||
|
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||||
|
cx.assert_editor_state(wrapped_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that rewrapping is ignored outside of comments in most languages.
|
||||||
|
{
|
||||||
|
let language = Arc::new(Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
line_comments: vec!["// ".into(), "/// ".into()],
|
||||||
|
..LanguageConfig::default()
|
||||||
|
},
|
||||||
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
|
));
|
||||||
|
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||||
|
|
||||||
|
let unwrapped_text = indoc! {"
|
||||||
|
/// Adds two numbers.
|
||||||
|
/// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
|
||||||
|
fn add(a: u32, b: u32) -> u32 {
|
||||||
|
a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
|
||||||
|
}
|
||||||
|
"};
|
||||||
|
|
||||||
|
let wrapped_text = indoc! {"
|
||||||
|
/// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
|
/// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
|
||||||
|
fn add(a: u32, b: u32) -> u32 {
|
||||||
|
a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
|
||||||
|
}
|
||||||
|
"};
|
||||||
|
|
||||||
|
cx.set_state(unwrapped_text);
|
||||||
|
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||||
|
cx.assert_editor_state(wrapped_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that rewrapping works in Markdown and Plain Text languages.
|
||||||
|
{
|
||||||
|
let markdown_language = Arc::new(Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
name: "Markdown".into(),
|
||||||
|
..LanguageConfig::default()
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
|
||||||
|
|
||||||
|
let unwrapped_text = indoc! {"
|
||||||
|
# Hello
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
|
||||||
|
"};
|
||||||
|
|
||||||
|
let wrapped_text = indoc! {"
|
||||||
|
# Hello
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||||
|
purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
|
||||||
|
eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
|
||||||
|
hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
|
||||||
|
lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
|
||||||
|
nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
|
||||||
|
Integer sit amet scelerisque nisi.ˇ
|
||||||
|
"};
|
||||||
|
|
||||||
|
cx.set_state(unwrapped_text);
|
||||||
|
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||||
|
cx.assert_editor_state(wrapped_text);
|
||||||
|
|
||||||
|
let plaintext_language = Arc::new(Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
name: "Plain Text".into(),
|
||||||
|
..LanguageConfig::default()
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
|
||||||
|
|
||||||
|
let unwrapped_text = indoc! {"
|
||||||
|
Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
|
||||||
|
"};
|
||||||
|
|
||||||
|
let wrapped_text = indoc! {"
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
|
||||||
|
purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
|
||||||
|
eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
|
||||||
|
hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
|
||||||
|
lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
|
||||||
|
nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
|
||||||
|
Integer sit amet scelerisque nisi.ˇ
|
||||||
|
"};
|
||||||
|
|
||||||
|
cx.set_state(unwrapped_text);
|
||||||
|
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
|
||||||
|
cx.assert_editor_state(wrapped_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_clipboard(cx: &mut gpui::TestAppContext) {
|
async fn test_clipboard(cx: &mut gpui::TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
@@ -4072,7 +4344,7 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
|
|||||||
let mut cx = EditorTestContext::new(cx).await;
|
let mut cx = EditorTestContext::new(cx).await;
|
||||||
let language = Arc::new(Language::new(
|
let language = Arc::new(Language::new(
|
||||||
LanguageConfig::default(),
|
LanguageConfig::default(),
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
));
|
));
|
||||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||||
|
|
||||||
@@ -4783,7 +5055,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
|
|||||||
|
|
||||||
let language = Arc::new(Language::new(
|
let language = Arc::new(Language::new(
|
||||||
LanguageConfig::default(),
|
LanguageConfig::default(),
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
));
|
));
|
||||||
|
|
||||||
let text = r#"
|
let text = r#"
|
||||||
@@ -4992,7 +5264,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)
|
)
|
||||||
.with_indents_query(
|
.with_indents_query(
|
||||||
r#"
|
r#"
|
||||||
@@ -5085,7 +5357,7 @@ async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
|
|||||||
autoclose_before: "})]".to_string(),
|
autoclose_before: "})]".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
));
|
));
|
||||||
|
|
||||||
cx.language_registry().add(language.clone());
|
cx.language_registry().add(language.clone());
|
||||||
@@ -5257,7 +5529,7 @@ async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestA
|
|||||||
autoclose_before: "})]".to_string(),
|
autoclose_before: "})]".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
));
|
));
|
||||||
|
|
||||||
cx.language_registry().add(language.clone());
|
cx.language_registry().add(language.clone());
|
||||||
@@ -5397,7 +5669,7 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
|
|||||||
autoclose_before: "})]>".into(),
|
autoclose_before: "})]>".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_typescript::language_tsx()),
|
Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
|
||||||
));
|
));
|
||||||
|
|
||||||
cx.language_registry().add(html_language.clone());
|
cx.language_registry().add(html_language.clone());
|
||||||
@@ -5572,7 +5844,7 @@ async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
|
|||||||
autoclose_before: "})]>".into(),
|
autoclose_before: "})]>".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)
|
)
|
||||||
.with_override_query("(string_literal) @string")
|
.with_override_query("(string_literal) @string")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@@ -5677,7 +5949,7 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
));
|
));
|
||||||
|
|
||||||
let text = r#"
|
let text = r#"
|
||||||
@@ -5826,7 +6098,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
|
|||||||
autoclose_before: "}".to_string(),
|
autoclose_before: "}".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
));
|
));
|
||||||
|
|
||||||
let text = r#"
|
let text = r#"
|
||||||
@@ -5953,7 +6225,7 @@ async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppC
|
|||||||
autoclose_before: "})]".to_string(),
|
autoclose_before: "})]".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
));
|
));
|
||||||
|
|
||||||
cx.language_registry().add(language.clone());
|
cx.language_registry().add(language.clone());
|
||||||
@@ -6023,7 +6295,7 @@ async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
|
|||||||
|
|
||||||
let language = Arc::new(Language::new(
|
let language = Arc::new(Language::new(
|
||||||
LanguageConfig::default(),
|
LanguageConfig::default(),
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
));
|
));
|
||||||
|
|
||||||
let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
|
let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
|
||||||
@@ -6205,7 +6477,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
|||||||
|
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
language_registry.add(rust_lang());
|
language_registry.add(rust_lang());
|
||||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -6361,7 +6633,7 @@ async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
|
|||||||
|
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
language_registry.add(rust_lang());
|
language_registry.add(rust_lang());
|
||||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -6557,7 +6829,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
|||||||
|
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
language_registry.add(rust_lang());
|
language_registry.add(rust_lang());
|
||||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -6698,7 +6970,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
|
|||||||
},
|
},
|
||||||
..LanguageConfig::default()
|
..LanguageConfig::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)));
|
)));
|
||||||
update_test_language_settings(cx, |settings| {
|
update_test_language_settings(cx, |settings| {
|
||||||
// Enable Prettier formatting for the same buffer, and ensure
|
// Enable Prettier formatting for the same buffer, and ensure
|
||||||
@@ -6708,7 +6980,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
|
|||||||
..PrettierSettings::default()
|
..PrettierSettings::default()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -7033,7 +7305,7 @@ async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
|
|||||||
autoclose_before: "})]".to_string(),
|
autoclose_before: "})]".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
);
|
);
|
||||||
let language = Arc::new(language);
|
let language = Arc::new(language);
|
||||||
|
|
||||||
@@ -7175,7 +7447,7 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui:
|
|||||||
autoclose_before: "})]".to_string(),
|
autoclose_before: "})]".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
);
|
);
|
||||||
let language = Arc::new(language);
|
let language = Arc::new(language);
|
||||||
|
|
||||||
@@ -7968,7 +8240,7 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
|
|||||||
line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
|
line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
));
|
));
|
||||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||||
|
|
||||||
@@ -8089,7 +8361,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
|
|||||||
line_comments: vec!["// ".into()],
|
line_comments: vec!["// ".into()],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut cx = EditorTestContext::new(cx).await;
|
let mut cx = EditorTestContext::new(cx).await;
|
||||||
@@ -8242,7 +8514,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
|
|||||||
line_comments: vec!["// ".into()],
|
line_comments: vec!["// ".into()],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_typescript::language_tsx()),
|
Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
|
||||||
));
|
));
|
||||||
|
|
||||||
cx.language_registry().add(html_language.clone());
|
cx.language_registry().add(html_language.clone());
|
||||||
@@ -8650,7 +8922,7 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)
|
)
|
||||||
.with_indents_query("")
|
.with_indents_query("")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@@ -9486,9 +9758,9 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)));
|
)));
|
||||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -9599,9 +9871,9 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)));
|
)));
|
||||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
name: language_server_name,
|
name: language_server_name,
|
||||||
@@ -9832,7 +10104,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
|
|||||||
.collect(),
|
.collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_typescript::language_tsx()),
|
Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
|
||||||
)
|
)
|
||||||
.with_override_query("(jsx_self_closing_element) @element")
|
.with_override_query("(jsx_self_closing_element) @element")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@@ -9935,7 +10207,7 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)));
|
)));
|
||||||
update_test_language_settings(cx, |settings| {
|
update_test_language_settings(cx, |settings| {
|
||||||
settings.defaults.prettier = Some(PrettierSettings {
|
settings.defaults.prettier = Some(PrettierSettings {
|
||||||
@@ -9945,7 +10217,7 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let test_plugin = "test_plugin";
|
let test_plugin = "test_plugin";
|
||||||
let _ = language_registry.register_fake_lsp_adapter(
|
let _ = language_registry.register_fake_lsp(
|
||||||
"TypeScript",
|
"TypeScript",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
prettier_plugins: vec![test_plugin],
|
prettier_plugins: vec![test_plugin],
|
||||||
@@ -13652,7 +13924,7 @@ pub(crate) fn rust_lang() -> Arc<Language> {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,7 @@ impl EditorElement {
|
|||||||
register_action(view, cx, Editor::move_line_up);
|
register_action(view, cx, Editor::move_line_up);
|
||||||
register_action(view, cx, Editor::move_line_down);
|
register_action(view, cx, Editor::move_line_down);
|
||||||
register_action(view, cx, Editor::transpose);
|
register_action(view, cx, Editor::transpose);
|
||||||
|
register_action(view, cx, Editor::rewrap);
|
||||||
register_action(view, cx, Editor::cut);
|
register_action(view, cx, Editor::cut);
|
||||||
register_action(view, cx, Editor::copy);
|
register_action(view, cx, Editor::copy);
|
||||||
register_action(view, cx, Editor::paste);
|
register_action(view, cx, Editor::paste);
|
||||||
@@ -2078,13 +2079,13 @@ impl EditorElement {
|
|||||||
.id(("path excerpt header", EntityId::from(block_id)))
|
.id(("path excerpt header", EntityId::from(block_id)))
|
||||||
.w_full()
|
.w_full()
|
||||||
.px(header_padding)
|
.px(header_padding)
|
||||||
|
.pt(header_padding)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
|
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
|
||||||
.id("path header block")
|
.id("path header block")
|
||||||
.h(2. * cx.line_height())
|
.h(2. * cx.line_height())
|
||||||
.pl(gpui::px(12.))
|
.px(gpui::px(12.))
|
||||||
.pr(gpui::px(8.))
|
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.shadow_md()
|
.shadow_md()
|
||||||
.border_1()
|
.border_1()
|
||||||
|
|||||||
@@ -37,12 +37,20 @@ impl sum_tree::Item for GitBlameEntry {
|
|||||||
impl sum_tree::Summary for GitBlameEntrySummary {
|
impl sum_tree::Summary for GitBlameEntrySummary {
|
||||||
type Context = ();
|
type Context = ();
|
||||||
|
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &Self, _cx: &()) {
|
fn add_summary(&mut self, summary: &Self, _cx: &()) {
|
||||||
self.rows += summary.rows;
|
self.rows += summary.rows;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, GitBlameEntrySummary> for u32 {
|
impl<'a> sum_tree::Dimension<'a, GitBlameEntrySummary> for u32 {
|
||||||
|
fn zero(_cx: &()) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &'a GitBlameEntrySummary, _cx: &()) {
|
fn add_summary(&mut self, summary: &'a GitBlameEntrySummary, _cx: &()) {
|
||||||
*self += summary.rows;
|
*self += summary.rows;
|
||||||
}
|
}
|
||||||
@@ -121,12 +129,12 @@ impl GitBlame {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let buffer_subscriptions = cx.subscribe(&buffer, |this, buffer, event, cx| match event {
|
let buffer_subscriptions = cx.subscribe(&buffer, |this, buffer, event, cx| match event {
|
||||||
language::Event::DirtyChanged => {
|
language::BufferEvent::DirtyChanged => {
|
||||||
if !buffer.read(cx).is_dirty() {
|
if !buffer.read(cx).is_dirty() {
|
||||||
this.generate(cx);
|
this.generate(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
language::Event::Edited => {
|
language::BufferEvent::Edited => {
|
||||||
this.regenerate_on_edit(cx);
|
this.regenerate_on_edit(cx);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -191,7 +199,7 @@ impl GitBlame {
|
|||||||
) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
|
) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
|
||||||
self.sync(cx);
|
self.sync(cx);
|
||||||
|
|
||||||
let mut cursor = self.entries.cursor::<u32>();
|
let mut cursor = self.entries.cursor::<u32>(&());
|
||||||
rows.into_iter().map(move |row| {
|
rows.into_iter().map(move |row| {
|
||||||
let row = row?;
|
let row = row?;
|
||||||
cursor.seek_forward(&row.0, Bias::Right, &());
|
cursor.seek_forward(&row.0, Bias::Right, &());
|
||||||
@@ -249,8 +257,8 @@ impl GitBlame {
|
|||||||
})
|
})
|
||||||
.peekable();
|
.peekable();
|
||||||
|
|
||||||
let mut new_entries = SumTree::new();
|
let mut new_entries = SumTree::default();
|
||||||
let mut cursor = self.entries.cursor::<u32>();
|
let mut cursor = self.entries.cursor::<u32>(&());
|
||||||
|
|
||||||
while let Some(mut edit) = row_edits.next() {
|
while let Some(mut edit) = row_edits.next() {
|
||||||
while let Some(next_edit) = row_edits.peek() {
|
while let Some(next_edit) = row_edits.peek() {
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)
|
)
|
||||||
.with_brackets_query(indoc! {r#"
|
.with_brackets_query(indoc! {r#"
|
||||||
("{" @open "}" @close)
|
("{" @open "}" @close)
|
||||||
|
|||||||
@@ -1205,6 +1205,7 @@ mod tests {
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: true,
|
show_parameter_hints: true,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1337,6 +1337,7 @@ mod tests {
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: true,
|
show_parameter_hints: true,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ impl InlayHintCache {
|
|||||||
/// If needed, queries LSP for new inlay hints, using the invalidation strategy given.
|
/// If needed, queries LSP for new inlay hints, using the invalidation strategy given.
|
||||||
/// To reduce inlay hint jumping, attempts to query a visible range of the editor(s) first,
|
/// To reduce inlay hint jumping, attempts to query a visible range of the editor(s) first,
|
||||||
/// followed by the delayed queries of the same range above and below the visible one.
|
/// followed by the delayed queries of the same range above and below the visible one.
|
||||||
/// This way, concequent refresh invocations are less likely to trigger LSP queries for the invisible ranges.
|
/// This way, subsequent refresh invocations are less likely to trigger LSP queries for the invisible ranges.
|
||||||
pub(super) fn spawn_hint_refresh(
|
pub(super) fn spawn_hint_refresh(
|
||||||
&mut self,
|
&mut self,
|
||||||
reason_description: &'static str,
|
reason_description: &'static str,
|
||||||
@@ -1296,6 +1296,7 @@ pub mod tests {
|
|||||||
show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
|
show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
|
||||||
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
|
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
|
||||||
show_other_hints: allowed_hint_kinds.contains(&None),
|
show_other_hints: allowed_hint_kinds.contains(&None),
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1428,6 +1429,7 @@ pub mod tests {
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: true,
|
show_parameter_hints: true,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1547,6 +1549,7 @@ pub mod tests {
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: true,
|
show_parameter_hints: true,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1575,9 +1578,9 @@ pub mod tests {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)));
|
)));
|
||||||
let fake_servers = language_registry.register_fake_lsp_adapter(
|
let fake_servers = language_registry.register_fake_lsp(
|
||||||
name,
|
name,
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
name,
|
name,
|
||||||
@@ -1777,6 +1780,7 @@ pub mod tests {
|
|||||||
show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
|
show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
|
||||||
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
|
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
|
||||||
show_other_hints: allowed_hint_kinds.contains(&None),
|
show_other_hints: allowed_hint_kinds.contains(&None),
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1941,6 +1945,7 @@ pub mod tests {
|
|||||||
show_parameter_hints: new_allowed_hint_kinds
|
show_parameter_hints: new_allowed_hint_kinds
|
||||||
.contains(&Some(InlayHintKind::Parameter)),
|
.contains(&Some(InlayHintKind::Parameter)),
|
||||||
show_other_hints: new_allowed_hint_kinds.contains(&None),
|
show_other_hints: new_allowed_hint_kinds.contains(&None),
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
cx.executor().run_until_parked();
|
cx.executor().run_until_parked();
|
||||||
@@ -1987,6 +1992,7 @@ pub mod tests {
|
|||||||
show_parameter_hints: another_allowed_hint_kinds
|
show_parameter_hints: another_allowed_hint_kinds
|
||||||
.contains(&Some(InlayHintKind::Parameter)),
|
.contains(&Some(InlayHintKind::Parameter)),
|
||||||
show_other_hints: another_allowed_hint_kinds.contains(&None),
|
show_other_hints: another_allowed_hint_kinds.contains(&None),
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
cx.executor().run_until_parked();
|
cx.executor().run_until_parked();
|
||||||
@@ -2047,6 +2053,7 @@ pub mod tests {
|
|||||||
show_parameter_hints: final_allowed_hint_kinds
|
show_parameter_hints: final_allowed_hint_kinds
|
||||||
.contains(&Some(InlayHintKind::Parameter)),
|
.contains(&Some(InlayHintKind::Parameter)),
|
||||||
show_other_hints: final_allowed_hint_kinds.contains(&None),
|
show_other_hints: final_allowed_hint_kinds.contains(&None),
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
cx.executor().run_until_parked();
|
cx.executor().run_until_parked();
|
||||||
@@ -2122,6 +2129,7 @@ pub mod tests {
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: true,
|
show_parameter_hints: true,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2256,6 +2264,7 @@ pub mod tests {
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: true,
|
show_parameter_hints: true,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2273,7 +2282,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
language_registry.add(crate::editor_tests::rust_lang());
|
language_registry.add(crate::editor_tests::rust_lang());
|
||||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -2551,6 +2560,7 @@ pub mod tests {
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: true,
|
show_parameter_hints: true,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2569,7 +2579,7 @@ pub mod tests {
|
|||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
let language = crate::editor_tests::rust_lang();
|
let language = crate::editor_tests::rust_lang();
|
||||||
language_registry.add(language);
|
language_registry.add(language);
|
||||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -2902,6 +2912,7 @@ pub mod tests {
|
|||||||
show_type_hints: false,
|
show_type_hints: false,
|
||||||
show_parameter_hints: false,
|
show_parameter_hints: false,
|
||||||
show_other_hints: false,
|
show_other_hints: false,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2919,7 +2930,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
language_registry.add(crate::editor_tests::rust_lang());
|
language_registry.add(crate::editor_tests::rust_lang());
|
||||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -3096,6 +3107,7 @@ pub mod tests {
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: true,
|
show_parameter_hints: true,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
cx.executor().run_until_parked();
|
cx.executor().run_until_parked();
|
||||||
@@ -3131,6 +3143,7 @@ pub mod tests {
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: true,
|
show_parameter_hints: true,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3148,7 +3161,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
language_registry.add(crate::editor_tests::rust_lang());
|
language_registry.add(crate::editor_tests::rust_lang());
|
||||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
@@ -3225,6 +3238,7 @@ pub mod tests {
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: true,
|
show_parameter_hints: true,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3305,6 +3319,7 @@ pub mod tests {
|
|||||||
show_type_hints: true,
|
show_type_hints: true,
|
||||||
show_parameter_hints: true,
|
show_parameter_hints: true,
|
||||||
show_other_hints: true,
|
show_other_hints: true,
|
||||||
|
show_background: false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
cx.executor().run_until_parked();
|
cx.executor().run_until_parked();
|
||||||
@@ -3389,7 +3404,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
language_registry.add(crate::editor_tests::rust_lang());
|
language_registry.add(crate::editor_tests::rust_lang());
|
||||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
"Rust",
|
"Rust",
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities: lsp::ServerCapabilities {
|
capabilities: lsp::ServerCapabilities {
|
||||||
|
|||||||
@@ -2,6 +2,18 @@ use crate::Direction;
|
|||||||
use gpui::{AppContext, Model, ModelContext};
|
use gpui::{AppContext, Model, ModelContext};
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
use text::{Anchor, Rope};
|
||||||
|
|
||||||
|
pub enum InlayProposal {
|
||||||
|
Hint(Anchor, project::InlayHint),
|
||||||
|
Suggestion(Anchor, Rope),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CompletionProposal {
|
||||||
|
pub inlays: Vec<InlayProposal>,
|
||||||
|
pub text: Rope,
|
||||||
|
pub delete_range: Option<Range<Anchor>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait InlineCompletionProvider: 'static + Sized {
|
pub trait InlineCompletionProvider: 'static + Sized {
|
||||||
fn name() -> &'static str;
|
fn name() -> &'static str;
|
||||||
@@ -32,7 +44,7 @@ pub trait InlineCompletionProvider: 'static + Sized {
|
|||||||
buffer: &Model<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
cursor_position: language::Anchor,
|
cursor_position: language::Anchor,
|
||||||
cx: &'a AppContext,
|
cx: &'a AppContext,
|
||||||
) -> Option<(&'a str, Option<Range<language::Anchor>>)>;
|
) -> Option<CompletionProposal>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait InlineCompletionProviderHandle {
|
pub trait InlineCompletionProviderHandle {
|
||||||
@@ -63,7 +75,7 @@ pub trait InlineCompletionProviderHandle {
|
|||||||
buffer: &Model<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
cursor_position: language::Anchor,
|
cursor_position: language::Anchor,
|
||||||
cx: &'a AppContext,
|
cx: &'a AppContext,
|
||||||
) -> Option<(&'a str, Option<Range<language::Anchor>>)>;
|
) -> Option<CompletionProposal>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> InlineCompletionProviderHandle for Model<T>
|
impl<T> InlineCompletionProviderHandle for Model<T>
|
||||||
@@ -118,7 +130,7 @@ where
|
|||||||
buffer: &Model<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
cursor_position: language::Anchor,
|
cursor_position: language::Anchor,
|
||||||
cx: &'a AppContext,
|
cx: &'a AppContext,
|
||||||
) -> Option<(&'a str, Option<Range<language::Anchor>>)> {
|
) -> Option<CompletionProposal> {
|
||||||
self.read(cx)
|
self.read(cx)
|
||||||
.active_completion_text(buffer, cursor_position, cx)
|
.active_completion_text(buffer, cursor_position, cx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1599,7 +1599,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use ui::{px, Pixels};
|
use ui::{px, Pixels};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ScrollDirection {
|
||||||
|
Upwards,
|
||||||
|
Downwards,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScrollDirection {
|
||||||
|
pub fn is_upwards(&self) -> bool {
|
||||||
|
matches!(self, ScrollDirection::Upwards)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
pub enum ScrollAmount {
|
pub enum ScrollAmount {
|
||||||
// Scroll N lines (positive is towards the end of the document)
|
// Scroll N lines (positive is towards the end of the document)
|
||||||
@@ -15,7 +27,7 @@ impl ScrollAmount {
|
|||||||
Self::Line(count) => *count,
|
Self::Line(count) => *count,
|
||||||
Self::Page(count) => {
|
Self::Page(count) => {
|
||||||
// for full pages subtract one to leave an anchor line
|
// for full pages subtract one to leave an anchor line
|
||||||
if count.abs() == 1.0 {
|
if self.is_full_page() {
|
||||||
visible_line_count -= 1.0
|
visible_line_count -= 1.0
|
||||||
}
|
}
|
||||||
(visible_line_count * count).trunc()
|
(visible_line_count * count).trunc()
|
||||||
@@ -29,4 +41,19 @@ impl ScrollAmount {
|
|||||||
ScrollAmount::Page(x) => px(height.0 * x),
|
ScrollAmount::Page(x) => px(height.0 * x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_full_page(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ScrollAmount::Page(count) if count.abs() == 1.0 => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn direction(&self) -> ScrollDirection {
|
||||||
|
match self {
|
||||||
|
Self::Line(amount) if amount.is_sign_positive() => ScrollDirection::Downwards,
|
||||||
|
Self::Page(amount) if amount.is_sign_positive() => ScrollDirection::Downwards,
|
||||||
|
_ => ScrollDirection::Upwards,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ impl EditorLspTestContext {
|
|||||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||||
|
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
let mut fake_servers = language_registry.register_fake_lsp(
|
||||||
language.name(),
|
language.name(),
|
||||||
FakeLspAdapter {
|
FakeLspAdapter {
|
||||||
capabilities,
|
capabilities,
|
||||||
@@ -125,7 +125,7 @@ impl EditorLspTestContext {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_rust::language()),
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||||
)
|
)
|
||||||
.with_queries(LanguageQueries {
|
.with_queries(LanguageQueries {
|
||||||
indents: Some(Cow::from(indoc! {r#"
|
indents: Some(Cow::from(indoc! {r#"
|
||||||
@@ -184,7 +184,7 @@ impl EditorLspTestContext {
|
|||||||
word_characters,
|
word_characters,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(tree_sitter_typescript::language_typescript()),
|
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
|
||||||
)
|
)
|
||||||
.with_queries(LanguageQueries {
|
.with_queries(LanguageQueries {
|
||||||
brackets: Some(Cow::from(indoc! {r#"
|
brackets: Some(Cow::from(indoc! {r#"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user