Compare commits
57 Commits
multibuffe
...
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 |
2
.github/workflows/bump_nightly_tag.yml
vendored
2
.github/workflows/bump_nightly_tag.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
3
.github/workflows/bump_patch_version.yml
vendored
3
.github/workflows/bump_patch_version.yml
vendored
@@ -15,8 +15,7 @@ concurrency:
|
||||
jobs:
|
||||
bump_patch_version:
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
|
||||
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -39,16 +39,7 @@ jobs:
|
||||
run: git clean -df
|
||||
|
||||
- name: Check spelling
|
||||
run: |
|
||||
if ! cargo install --list | grep "typos-cli v$TYPOS_CLI_VERSION" > /dev/null; then
|
||||
echo "Installing typos-cli@$TYPOS_CLI_VERSION..."
|
||||
cargo install "typos-cli@$TYPOS_CLI_VERSION"
|
||||
else
|
||||
echo "typos-cli@$TYPOS_CLI_VERSION is already installed."
|
||||
fi
|
||||
typos
|
||||
env:
|
||||
TYPOS_CLI_VERSION: "1.23.3"
|
||||
run: script/check-spelling
|
||||
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
@@ -110,7 +101,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
name: (Linux) Run Clippy and tests
|
||||
runs-on:
|
||||
- hosted-linux-x86-1
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
@@ -120,6 +111,12 @@ jobs:
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "buildjet"
|
||||
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux
|
||||
|
||||
@@ -147,6 +144,7 @@ jobs:
|
||||
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "github"
|
||||
|
||||
- name: cargo clippy
|
||||
# Windows can't run shell scripts, so we need to use `cargo xtask`.
|
||||
@@ -273,7 +271,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
name: Create a Linux bundle
|
||||
runs-on:
|
||||
- hosted-linux-x86-1
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
needs: [linux_tests]
|
||||
env:
|
||||
@@ -288,9 +286,6 @@ jobs:
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Determine version and release channel
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
@@ -344,7 +339,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
name: Create arm64 Linux bundle
|
||||
runs-on:
|
||||
- hosted-linux-arm-1
|
||||
- buildjet-16vcpu-ubuntu-2204-arm
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
needs: [linux_tests]
|
||||
env:
|
||||
@@ -359,9 +354,6 @@ jobs:
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Determine version and release channel
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
|
||||
6
.github/workflows/deploy_collab.yml
vendored
6
.github/workflows/deploy_collab.yml
vendored
@@ -61,8 +61,7 @@ jobs:
|
||||
- style
|
||||
- tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
@@ -89,8 +88,7 @@ jobs:
|
||||
needs:
|
||||
- publish
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
|
||||
steps:
|
||||
- name: Sign into Kubernetes
|
||||
|
||||
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
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "github"
|
||||
|
||||
- name: Configure linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
|
||||
3
.github/workflows/randomized_tests.yml
vendored
3
.github/workflows/randomized_tests.yml
vendored
@@ -19,8 +19,7 @@ jobs:
|
||||
tests:
|
||||
name: Run randomized tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- randomized-tests
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
||||
|
||||
9
.github/workflows/release_nightly.yml
vendored
9
.github/workflows/release_nightly.yml
vendored
@@ -97,8 +97,7 @@ jobs:
|
||||
name: Create a Linux *.tar.gz bundle for x86
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
needs: tests
|
||||
env:
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
@@ -114,6 +113,12 @@ jobs:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Set release channel to nightly
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
258
Cargo.lock
generated
258
Cargo.lock
generated
@@ -263,9 +263,9 @@ checksum = "34cd60c5e3152cef0a592f1b296f1cc93715d89d2551d85315828c3a09575ff4"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.86"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
@@ -402,6 +402,7 @@ dependencies = [
|
||||
"indoc",
|
||||
"language",
|
||||
"language_model",
|
||||
"languages",
|
||||
"log",
|
||||
"markdown",
|
||||
"menu",
|
||||
@@ -436,6 +437,7 @@ dependencies = [
|
||||
"text",
|
||||
"theme",
|
||||
"toml 0.8.19",
|
||||
"tree-sitter-md",
|
||||
"ui",
|
||||
"unindent",
|
||||
"util",
|
||||
@@ -876,6 +878,20 @@ version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "async-tls"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustls 0.20.9",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"webpki",
|
||||
"webpki-roots 0.22.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.81"
|
||||
@@ -893,8 +909,8 @@ version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1e9efbe14612da0a19fb983059a0b621e9cf6225d7018ecab4f9988215540dc"
|
||||
dependencies = [
|
||||
"async-native-tls",
|
||||
"async-std",
|
||||
"async-tls",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"log",
|
||||
@@ -981,7 +997,6 @@ dependencies = [
|
||||
"editor",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"isahc",
|
||||
"log",
|
||||
"markdown_preview",
|
||||
"menu",
|
||||
@@ -1049,7 +1064,7 @@ dependencies = [
|
||||
"fastrand 2.1.1",
|
||||
"hex",
|
||||
"http 0.2.12",
|
||||
"ring",
|
||||
"ring 0.17.8",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -1218,7 +1233,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"p256",
|
||||
"percent-encoding",
|
||||
"ring",
|
||||
"ring 0.17.8",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"time",
|
||||
@@ -1331,7 +1346,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"rustls",
|
||||
"rustls 0.21.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
@@ -2405,6 +2420,8 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"release_channel",
|
||||
"rpc",
|
||||
"rustls 0.20.9",
|
||||
"rustls-native-certs 0.8.0",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2553,6 +2570,7 @@ dependencies = [
|
||||
"http_client",
|
||||
"hyper",
|
||||
"indoc",
|
||||
"isahc_http_client",
|
||||
"jsonwebtoken",
|
||||
"language",
|
||||
"language_model",
|
||||
@@ -4000,6 +4018,34 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "evals"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"env_logger",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"git",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"isahc_http_client",
|
||||
"language",
|
||||
"languages",
|
||||
"node_runtime",
|
||||
"open_ai",
|
||||
"project",
|
||||
"semantic_index",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
@@ -4083,6 +4129,7 @@ dependencies = [
|
||||
"http_client",
|
||||
"indexed_docs",
|
||||
"isahc",
|
||||
"isahc_http_client",
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
@@ -4121,7 +4168,7 @@ dependencies = [
|
||||
"env_logger",
|
||||
"extension",
|
||||
"fs",
|
||||
"http_client",
|
||||
"isahc_http_client",
|
||||
"language",
|
||||
"log",
|
||||
"rpc",
|
||||
@@ -4368,7 +4415,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"nanorand",
|
||||
"spin",
|
||||
"spin 0.9.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4877,7 +4924,6 @@ dependencies = [
|
||||
"git",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"isahc",
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -5510,12 +5556,11 @@ dependencies = [
|
||||
"anyhow",
|
||||
"derive_more",
|
||||
"futures 0.3.30",
|
||||
"futures-lite 1.13.0",
|
||||
"http 1.1.0",
|
||||
"isahc",
|
||||
"http 0.2.12",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -5577,8 +5622,8 @@ dependencies = [
|
||||
"http 0.2.12",
|
||||
"hyper",
|
||||
"log",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls 0.21.12",
|
||||
"rustls-native-certs 0.6.3",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
]
|
||||
@@ -5990,6 +6035,17 @@ dependencies = [
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "isahc_http_client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures 0.3.30",
|
||||
"http_client",
|
||||
"isahc",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
@@ -6094,7 +6150,7 @@ dependencies = [
|
||||
"base64 0.21.7",
|
||||
"js-sys",
|
||||
"pem",
|
||||
"ring",
|
||||
"ring 0.17.8",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_asn1",
|
||||
@@ -6345,7 +6401,7 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin",
|
||||
"spin 0.9.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7005,32 +7061,6 @@ dependencies = [
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multi_buffer2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clock",
|
||||
"collections",
|
||||
"ctor",
|
||||
"env_logger",
|
||||
"futures 0.3.30",
|
||||
"git",
|
||||
"gpui",
|
||||
"itertools 0.13.0",
|
||||
"language",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"sum_tree",
|
||||
"text",
|
||||
"theme",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multimap"
|
||||
version = "0.8.3"
|
||||
@@ -7482,7 +7512,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"futures 0.3.30",
|
||||
"http_client",
|
||||
"isahc",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -9174,7 +9203,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
@@ -9238,6 +9267,21 @@ dependencies = [
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin 0.5.2",
|
||||
"untrusted 0.7.1",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
@@ -9248,8 +9292,8 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.2.15",
|
||||
"libc",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"spin 0.9.8",
|
||||
"untrusted 0.9.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -9405,7 +9449,7 @@ dependencies = [
|
||||
"futures 0.3.30",
|
||||
"glob",
|
||||
"rand 0.8.5",
|
||||
"ring",
|
||||
"ring 0.17.8",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand 3.1.0",
|
||||
@@ -9526,6 +9570,18 @@ dependencies = [
|
||||
"rustix 0.38.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.20.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring 0.16.20",
|
||||
"sct",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.12"
|
||||
@@ -9533,7 +9589,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"ring 0.17.8",
|
||||
"rustls-webpki",
|
||||
"sct",
|
||||
]
|
||||
@@ -9545,7 +9601,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile 2.1.3",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
@@ -9559,14 +9628,30 @@ dependencies = [
|
||||
"base64 0.21.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.101.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
"ring 0.17.8",
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9680,8 +9765,8 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
"ring 0.17.8",
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9877,6 +9962,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"heed",
|
||||
"http_client",
|
||||
"isahc_http_client",
|
||||
"language",
|
||||
"language_model",
|
||||
"languages",
|
||||
@@ -10436,6 +10522,12 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
@@ -10558,8 +10650,8 @@ dependencies = [
|
||||
"paste",
|
||||
"percent-encoding",
|
||||
"rust_decimal",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"rustls 0.21.12",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
@@ -10572,7 +10664,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
"webpki-roots",
|
||||
"webpki-roots 0.25.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11084,17 +11176,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.30.13"
|
||||
version = "0.31.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3"
|
||||
checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"once_cell",
|
||||
"rayon",
|
||||
"windows 0.52.0",
|
||||
"windows 0.54.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11705,7 +11796,7 @@ version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"rustls 0.21.12",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -12232,7 +12323,6 @@ dependencies = [
|
||||
"http 0.2.12",
|
||||
"httparse",
|
||||
"log",
|
||||
"native-tls",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
@@ -12417,6 +12507,12 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -13271,6 +13367,25 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
|
||||
dependencies = [
|
||||
"ring 0.17.8",
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
|
||||
dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.4"
|
||||
@@ -13442,16 +13557,6 @@ dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
|
||||
dependencies = [
|
||||
"windows-core 0.52.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.54.0"
|
||||
@@ -14315,6 +14420,7 @@ dependencies = [
|
||||
"inline_completion_button",
|
||||
"install_cli",
|
||||
"isahc",
|
||||
"isahc_http_client",
|
||||
"journal",
|
||||
"language",
|
||||
"language_model",
|
||||
@@ -14542,7 +14648,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_ruff"
|
||||
version = "0.0.2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -14557,7 +14663,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_svelte"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -27,6 +27,7 @@ members = [
|
||||
"crates/diagnostics",
|
||||
"crates/docs_preprocessor",
|
||||
"crates/editor",
|
||||
"crates/evals",
|
||||
"crates/extension",
|
||||
"crates/extension_api",
|
||||
"crates/extension_cli",
|
||||
@@ -51,6 +52,7 @@ members = [
|
||||
"crates/indexed_docs",
|
||||
"crates/inline_completion_button",
|
||||
"crates/install_cli",
|
||||
"crates/isahc_http_client",
|
||||
"crates/journal",
|
||||
"crates/language",
|
||||
"crates/language_model",
|
||||
@@ -65,7 +67,6 @@ members = [
|
||||
"crates/media",
|
||||
"crates/menu",
|
||||
"crates/multi_buffer",
|
||||
"crates/multi_buffer2",
|
||||
"crates/node_runtime",
|
||||
"crates/notifications",
|
||||
"crates/ollama",
|
||||
@@ -226,6 +227,7 @@ image_viewer = { path = "crates/image_viewer" }
|
||||
indexed_docs = { path = "crates/indexed_docs" }
|
||||
inline_completion_button = { path = "crates/inline_completion_button" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
isahc_http_client = { path = "crates/isahc_http_client" }
|
||||
journal = { path = "crates/journal" }
|
||||
language = { path = "crates/language" }
|
||||
language_model = { path = "crates/language_model" }
|
||||
@@ -394,6 +396,8 @@ runtimelib = { version = "0.15", default-features = false, features = [
|
||||
] }
|
||||
rustc-demangle = "0.1.23"
|
||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||
rustls = "0.20.3"
|
||||
rustls-native-certs = "0.8.0"
|
||||
schemars = { version = "0.8", features = ["impl_json_schema"] }
|
||||
semver = "1.0"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
@@ -416,7 +420,7 @@ strsim = "0.11"
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
subtle = "2.5.0"
|
||||
sys-locale = "0.3.1"
|
||||
sysinfo = "0.30.7"
|
||||
sysinfo = "0.31.0"
|
||||
tempfile = "3.9.0"
|
||||
thiserror = "1.0.29"
|
||||
tiktoken-rs = "0.5.9"
|
||||
@@ -490,7 +494,6 @@ features = [
|
||||
"implement",
|
||||
"Foundation_Numerics",
|
||||
"Storage",
|
||||
"System",
|
||||
"System_Threading",
|
||||
"UI_ViewManagement",
|
||||
"Wdk_System_SystemServices",
|
||||
@@ -521,6 +524,7 @@ features = [
|
||||
"Win32_UI_Input_Ime",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_Shell_Common",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
]
|
||||
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
{
|
||||
"context": "AssistantPanel",
|
||||
"bindings": {
|
||||
"ctrl-k c": "assistant::CopyCode",
|
||||
"ctrl-g": "search::SelectNextMatch",
|
||||
"ctrl-shift-g": "search::SelectPrevMatch",
|
||||
"alt-m": "assistant::ToggleModelSelector",
|
||||
|
||||
@@ -188,6 +188,7 @@
|
||||
{
|
||||
"context": "AssistantPanel",
|
||||
"bindings": {
|
||||
"cmd-k c": "assistant::CopyCode",
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-g": "search::SelectPrevMatch",
|
||||
"alt-m": "assistant::ToggleModelSelector",
|
||||
|
||||
@@ -318,6 +318,10 @@
|
||||
"show_parameter_hints": true,
|
||||
// Corresponds to null/None LSP hint type value.
|
||||
"show_other_hints": true,
|
||||
// Whether to show a background for inlay hints.
|
||||
//
|
||||
// If set to `true`, the background will use the `hint.background` color from the current theme.
|
||||
"show_background": false,
|
||||
// Time to wait after editing the buffer, before requesting the hints,
|
||||
// set to 0 to disable debouncing.
|
||||
"edit_debounce_ms": 700,
|
||||
|
||||
@@ -94,9 +94,11 @@ editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
language_model = { workspace = true, features = ["test-support"] }
|
||||
languages = { workspace = true, features = ["test-support"] }
|
||||
log.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
text = { workspace = true, features = ["test-support"] }
|
||||
tree-sitter-md.workspace = true
|
||||
unindent.workspace = true
|
||||
|
||||
@@ -58,6 +58,7 @@ actions!(
|
||||
[
|
||||
Assist,
|
||||
Split,
|
||||
CopyCode,
|
||||
CycleMessageRole,
|
||||
QuoteSelection,
|
||||
InsertIntoEditor,
|
||||
|
||||
@@ -12,11 +12,11 @@ use crate::{
|
||||
slash_command_picker,
|
||||
terminal_inline_assistant::TerminalInlineAssistant,
|
||||
Assist, CacheStatus, ConfirmCommand, Content, Context, ContextEvent, ContextId, ContextStore,
|
||||
ContextStoreEvent, CycleMessageRole, DeployHistory, DeployPromptLibrary, InlineAssistId,
|
||||
InlineAssistant, InsertDraggedFiles, InsertIntoEditor, Message, MessageId, MessageMetadata,
|
||||
MessageStatus, ModelPickerDelegate, ModelSelector, NewContext, PendingSlashCommand,
|
||||
PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, SavedContextMetadata, Split,
|
||||
ToggleFocus, ToggleModelSelector, WorkflowStepResolution,
|
||||
ContextStoreEvent, CopyCode, CycleMessageRole, DeployHistory, DeployPromptLibrary,
|
||||
InlineAssistId, InlineAssistant, InsertDraggedFiles, InsertIntoEditor, Message, MessageId,
|
||||
MessageMetadata, MessageStatus, ModelPickerDelegate, ModelSelector, NewContext,
|
||||
PendingSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata,
|
||||
SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector, WorkflowStepResolution,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
|
||||
@@ -45,7 +45,8 @@ use gpui::{
|
||||
};
|
||||
use indexed_docs::IndexedDocsStore;
|
||||
use language::{
|
||||
language_settings::SoftWrap, Capability, LanguageRegistry, LspAdapterDelegate, Point, ToOffset,
|
||||
language_settings::SoftWrap, BufferSnapshot, Capability, LanguageRegistry, LspAdapterDelegate,
|
||||
ToOffset,
|
||||
};
|
||||
use language_model::{
|
||||
provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
|
||||
@@ -54,8 +55,9 @@ use language_model::{
|
||||
use language_model::{LanguageModelImage, LanguageModelToolUse};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::lsp_store::ProjectLspAdapterDelegate;
|
||||
use project::lsp_store::LocalLspAdapterDelegate;
|
||||
use project::{Project, Worktree};
|
||||
use rope::Point;
|
||||
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{update_settings_file, Settings};
|
||||
@@ -81,9 +83,10 @@ use util::{maybe, ResultExt};
|
||||
use workspace::{
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
item::{self, FollowableItem, Item, ItemHandle},
|
||||
notifications::NotificationId,
|
||||
pane::{self, SaveIntent},
|
||||
searchable::{SearchEvent, SearchableItem},
|
||||
DraggedSelection, Pane, Save, ShowConfiguration, ToggleZoom, ToolbarItemEvent,
|
||||
DraggedSelection, Pane, Save, ShowConfiguration, Toast, ToggleZoom, ToolbarItemEvent,
|
||||
ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
};
|
||||
use workspace::{searchable::SearchableItemHandle, DraggedTab};
|
||||
@@ -105,6 +108,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
.register_action(AssistantPanel::inline_assist)
|
||||
.register_action(ContextEditor::quote_selection)
|
||||
.register_action(ContextEditor::insert_selection)
|
||||
.register_action(ContextEditor::copy_code)
|
||||
.register_action(ContextEditor::insert_dragged_files)
|
||||
.register_action(AssistantPanel::show_configuration)
|
||||
.register_action(AssistantPanel::create_new_context);
|
||||
@@ -1072,6 +1076,13 @@ impl AssistantPanel {
|
||||
self.show_updated_summary(&context_editor, cx);
|
||||
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),
|
||||
_ => {}
|
||||
}
|
||||
@@ -1501,6 +1512,7 @@ pub struct ContextEditor {
|
||||
editor: View<Editor>,
|
||||
blocks: HashMap<MessageId, (MessageHeader, CustomBlockId)>,
|
||||
image_blocks: HashSet<CustomBlockId>,
|
||||
code_fence_blocks: HashSet<CustomBlockId>,
|
||||
scroll_position: Option<ScrollPosition>,
|
||||
remote_id: Option<workspace::ViewId>,
|
||||
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
||||
@@ -1569,6 +1581,7 @@ impl ContextEditor {
|
||||
lsp_adapter_delegate,
|
||||
blocks: Default::default(),
|
||||
image_blocks: Default::default(),
|
||||
code_fence_blocks: Default::default(),
|
||||
scroll_position: None,
|
||||
remote_id: None,
|
||||
fs,
|
||||
@@ -3100,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(
|
||||
workspace: &mut Workspace,
|
||||
_: &InsertIntoEditor,
|
||||
@@ -3118,17 +3165,7 @@ impl ContextEditor {
|
||||
return;
|
||||
};
|
||||
|
||||
let context_editor = context_editor_view.read(cx).editor.read(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() {
|
||||
if let Some((text, _)) = Self::get_selection_or_code_block(&context_editor_view, cx) {
|
||||
active_editor_view.update(cx, |editor, cx| {
|
||||
editor.insert(&text, cx);
|
||||
editor.focus(cx);
|
||||
@@ -3136,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(
|
||||
workspace: &mut Workspace,
|
||||
action: &InsertDraggedFiles,
|
||||
@@ -3282,7 +3349,7 @@ impl ContextEditor {
|
||||
|
||||
let fence = codeblock_fence_for_path(
|
||||
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)) =
|
||||
@@ -3649,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>) {
|
||||
self.context.update(cx, |context, cx| {
|
||||
let selections = self.editor.read(cx).selections.disjoint_anchors();
|
||||
@@ -4215,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(
|
||||
editor: WeakView<Editor>,
|
||||
icon: IconName,
|
||||
@@ -5384,18 +5541,16 @@ fn make_lsp_adapter_delegate(
|
||||
let worktree = project
|
||||
.worktrees(cx)
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
|
||||
let fs = if project.is_local() {
|
||||
Some(project.fs().clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
.ok_or_else(|| anyhow!("no worktrees when constructing LocalLspAdapterDelegate"))?;
|
||||
let http_client = project.client().http_client().clone();
|
||||
project.lsp_store().update(cx, |lsp_store, cx| {
|
||||
Ok(
|
||||
ProjectLspAdapterDelegate::new(lsp_store, &worktree, http_client, fs, None, cx)
|
||||
as Arc<dyn LspAdapterDelegate>,
|
||||
)
|
||||
Ok(LocalLspAdapterDelegate::new(
|
||||
lsp_store,
|
||||
&worktree,
|
||||
http_client,
|
||||
project.fs().clone(),
|
||||
cx,
|
||||
) as Arc<dyn LspAdapterDelegate>)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -5499,3 +5654,85 @@ fn configuration_error(cx: &AppContext) -> Option<ConfigurationError> {
|
||||
|
||||
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,7 +46,7 @@ use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use telemetry_events::AssistantKind;
|
||||
use telemetry_events::{AssistantKind, AssistantPhase};
|
||||
use text::BufferSnapshot;
|
||||
use util::{post_inc, ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
@@ -2134,6 +2134,7 @@ impl Context {
|
||||
telemetry.report_assistant_event(
|
||||
Some(this.id.0.clone()),
|
||||
AssistantKind::Panel,
|
||||
AssistantPhase::Response,
|
||||
model.telemetry_id(),
|
||||
response_latency,
|
||||
error_message,
|
||||
|
||||
@@ -174,6 +174,18 @@ impl InlineAssistant {
|
||||
initial_prompt: Option<String>,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||
telemetry.report_assistant_event(
|
||||
None,
|
||||
telemetry_events::AssistantKind::Inline,
|
||||
telemetry_events::AssistantPhase::Invoked,
|
||||
model.telemetry_id(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||
|
||||
let mut selections = Vec::<Selection<Point>>::new();
|
||||
@@ -708,6 +720,22 @@ impl InlineAssistant {
|
||||
}
|
||||
|
||||
pub fn finish_assist(&mut self, assist_id: InlineAssistId, undo: bool, cx: &mut WindowContext) {
|
||||
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||
telemetry.report_assistant_event(
|
||||
None,
|
||||
telemetry_events::AssistantKind::Inline,
|
||||
if undo {
|
||||
telemetry_events::AssistantPhase::Rejected
|
||||
} else {
|
||||
telemetry_events::AssistantPhase::Accepted
|
||||
},
|
||||
model.telemetry_id(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(assist) = self.assists.get(&assist_id) {
|
||||
let assist_group_id = assist.group_id;
|
||||
if self.assist_groups[&assist_group_id].linked {
|
||||
@@ -2558,6 +2586,7 @@ impl Codegen {
|
||||
telemetry.report_assistant_event(
|
||||
None,
|
||||
telemetry_events::AssistantKind::Inline,
|
||||
telemetry_events::AssistantPhase::Response,
|
||||
model_telemetry_id,
|
||||
response_latency,
|
||||
error_message,
|
||||
|
||||
@@ -921,10 +921,8 @@ impl PromptLibrary {
|
||||
scrollbar_width: Pixels::ZERO,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
status: cx.theme().status().clone(),
|
||||
inlay_hints_style: HighlightStyle {
|
||||
color: Some(cx.theme().status().hint),
|
||||
..HighlightStyle::default()
|
||||
},
|
||||
inlay_hints_style:
|
||||
editor::make_inlay_hints_style(cx),
|
||||
suggestions_style: HighlightStyle {
|
||||
color: Some(cx.theme().status().predictive),
|
||||
..HighlightStyle::default()
|
||||
|
||||
@@ -8,7 +8,7 @@ use project::{PathMatchCandidateSet, Project};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt::Write,
|
||||
ops::Range,
|
||||
ops::{Range, RangeInclusive},
|
||||
path::{Path, PathBuf},
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
@@ -342,7 +342,10 @@ fn collect_files(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32>>) -> String {
|
||||
pub fn codeblock_fence_for_path(
|
||||
path: Option<&Path>,
|
||||
row_range: Option<RangeInclusive<u32>>,
|
||||
) -> String {
|
||||
let mut text = String::new();
|
||||
write!(text, "```").unwrap();
|
||||
|
||||
@@ -357,7 +360,7 @@ pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32
|
||||
}
|
||||
|
||||
if let Some(row_range) = row_range {
|
||||
write!(text, ":{}-{}", row_range.start + 1, row_range.end + 1).unwrap();
|
||||
write!(text, ":{}-{}", row_range.start() + 1, row_range.end() + 1).unwrap();
|
||||
}
|
||||
|
||||
text.push('\n');
|
||||
|
||||
@@ -8,14 +8,12 @@ use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
|
||||
use feature_flags::FeatureFlag;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use language::{CodeLabel, LineEnding, LspAdapterDelegate};
|
||||
use semantic_index::SemanticDb;
|
||||
use semantic_index::{LoadedSearchResult, SemanticDb};
|
||||
use std::{
|
||||
fmt::Write,
|
||||
path::PathBuf,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use ui::{prelude::*, IconName};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct SearchSlashCommandFeatureFlag;
|
||||
@@ -107,52 +105,28 @@ impl SlashCommand for SearchSlashCommand {
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let mut loaded_results = Vec::new();
|
||||
for result in results {
|
||||
let (full_path, file_content) =
|
||||
result.worktree.read_with(&cx, |worktree, _cx| {
|
||||
let entry_abs_path = worktree.abs_path().join(&result.path);
|
||||
let mut entry_full_path = PathBuf::from(worktree.root_name());
|
||||
entry_full_path.push(&result.path);
|
||||
let file_content = async {
|
||||
let entry_abs_path = entry_abs_path;
|
||||
fs.load(&entry_abs_path).await
|
||||
};
|
||||
(entry_full_path, file_content)
|
||||
})?;
|
||||
if let Some(file_content) = file_content.await.log_err() {
|
||||
loaded_results.push((result, full_path, file_content));
|
||||
}
|
||||
}
|
||||
let loaded_results = SemanticDb::load_results(results, &fs, &cx).await?;
|
||||
|
||||
let output = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
let mut text = format!("Search results for {query}:\n");
|
||||
let mut sections = Vec::new();
|
||||
for (result, full_path, file_content) in loaded_results {
|
||||
let range_start = result.range.start.min(file_content.len());
|
||||
let range_end = result.range.end.min(file_content.len());
|
||||
|
||||
let start_row = file_content[0..range_start].matches('\n').count() as u32;
|
||||
let end_row = file_content[0..range_end].matches('\n').count() as u32;
|
||||
let start_line_byte_offset = file_content[0..range_start]
|
||||
.rfind('\n')
|
||||
.map(|pos| pos + 1)
|
||||
.unwrap_or_default();
|
||||
let end_line_byte_offset = file_content[range_end..]
|
||||
.find('\n')
|
||||
.map(|pos| range_end + pos)
|
||||
.unwrap_or_else(|| file_content.len());
|
||||
|
||||
for LoadedSearchResult {
|
||||
path,
|
||||
range,
|
||||
full_path,
|
||||
file_content,
|
||||
row_range,
|
||||
} in loaded_results
|
||||
{
|
||||
let section_start_ix = text.len();
|
||||
text.push_str(&codeblock_fence_for_path(
|
||||
Some(&result.path),
|
||||
Some(start_row..end_row),
|
||||
Some(&path),
|
||||
Some(row_range.clone()),
|
||||
));
|
||||
|
||||
let mut excerpt =
|
||||
file_content[start_line_byte_offset..end_line_byte_offset].to_string();
|
||||
let mut excerpt = file_content[range].to_string();
|
||||
LineEnding::normalize(&mut excerpt);
|
||||
text.push_str(&excerpt);
|
||||
writeln!(text, "\n```\n").unwrap();
|
||||
@@ -161,7 +135,7 @@ impl SlashCommand for SearchSlashCommand {
|
||||
section_start_ix..section_end_ix,
|
||||
Some(&full_path),
|
||||
false,
|
||||
Some(start_row + 1..end_row + 1),
|
||||
Some(row_range.start() + 1..row_range.end() + 1),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -570,7 +570,7 @@ impl Render for PromptEditor {
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border_y_1()
|
||||
.border_color(cx.theme().status().info_border)
|
||||
.py_1p5()
|
||||
.py_2()
|
||||
.h_full()
|
||||
.w_full()
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
@@ -949,12 +949,11 @@ impl PromptEditor {
|
||||
} else {
|
||||
cx.theme().colors().text
|
||||
},
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features.clone(),
|
||||
font_fallbacks: settings.ui_font.fallbacks.clone(),
|
||||
font_size: rems(0.875).into(),
|
||||
font_weight: settings.ui_font.weight,
|
||||
line_height: relative(1.3),
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: settings.buffer_font_size.into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
..Default::default()
|
||||
};
|
||||
EditorElement::new(
|
||||
@@ -1067,6 +1066,7 @@ impl Codegen {
|
||||
telemetry.report_assistant_event(
|
||||
None,
|
||||
telemetry_events::AssistantKind::Inline,
|
||||
telemetry_events::AssistantPhase::Response,
|
||||
model_telemetry_id,
|
||||
response_latency,
|
||||
error_message,
|
||||
|
||||
@@ -19,7 +19,6 @@ db.workspace = true
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc.workspace = true
|
||||
log.workspace = true
|
||||
markdown_preview.workspace = true
|
||||
menu.workspace = true
|
||||
|
||||
@@ -9,7 +9,6 @@ use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
|
||||
SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use isahc::AsyncBody;
|
||||
|
||||
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
|
||||
use schemars::JsonSchema;
|
||||
@@ -20,7 +19,7 @@ use smol::{fs, io::AsyncReadExt};
|
||||
use settings::{Settings, SettingsSources, SettingsStore};
|
||||
use smol::{fs::File, process::Command};
|
||||
|
||||
use http_client::{HttpClient, HttpClientWithUrl};
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
|
||||
use std::{
|
||||
env::{
|
||||
@@ -244,19 +243,22 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
|
||||
let auto_updater = AutoUpdater::get(cx)?;
|
||||
let release_channel = ReleaseChannel::try_global(cx)?;
|
||||
|
||||
if matches!(
|
||||
release_channel,
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview
|
||||
) {
|
||||
let auto_updater = auto_updater.read(cx);
|
||||
let release_channel = release_channel.dev_name();
|
||||
let current_version = auto_updater.current_version;
|
||||
let url = &auto_updater
|
||||
.http_client
|
||||
.build_url(&format!("/releases/{release_channel}/{current_version}"));
|
||||
cx.open_url(url);
|
||||
match release_channel {
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview => {
|
||||
let auto_updater = auto_updater.read(cx);
|
||||
let current_version = auto_updater.current_version;
|
||||
let release_channel = release_channel.dev_name();
|
||||
let path = format!("/releases/{release_channel}/{current_version}");
|
||||
let url = &auto_updater.http_client.build_url(&path);
|
||||
cx.open_url(url);
|
||||
}
|
||||
ReleaseChannel::Nightly => {
|
||||
cx.open_url("https://github.com/zed-industries/zed/commits/nightly/");
|
||||
}
|
||||
ReleaseChannel::Dev => {
|
||||
cx.open_url("https://github.com/zed-industries/zed/commits/main/");
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
@@ -332,7 +332,7 @@ impl ChannelChat {
|
||||
.update(&mut cx, |chat, cx| {
|
||||
if let Some(first_id) = chat.first_loaded_message_id() {
|
||||
if first_id <= message_id {
|
||||
let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>();
|
||||
let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>(&());
|
||||
let message_id = ChannelMessageId::Saved(message_id);
|
||||
cursor.seek(&message_id, Bias::Left, &());
|
||||
return ControlFlow::Break(
|
||||
@@ -498,7 +498,7 @@ impl ChannelChat {
|
||||
}
|
||||
|
||||
pub fn message(&self, ix: usize) -> &ChannelMessage {
|
||||
let mut cursor = self.messages.cursor::<Count>();
|
||||
let mut cursor = self.messages.cursor::<Count>(&());
|
||||
cursor.seek(&Count(ix), Bias::Right, &());
|
||||
cursor.item().unwrap()
|
||||
}
|
||||
@@ -515,13 +515,13 @@ impl ChannelChat {
|
||||
}
|
||||
|
||||
pub fn messages_in_range(&self, range: Range<usize>) -> impl Iterator<Item = &ChannelMessage> {
|
||||
let mut cursor = self.messages.cursor::<Count>();
|
||||
let mut cursor = self.messages.cursor::<Count>(&());
|
||||
cursor.seek(&Count(range.start), Bias::Right, &());
|
||||
cursor.take(range.len())
|
||||
}
|
||||
|
||||
pub fn pending_messages(&self) -> impl Iterator<Item = &ChannelMessage> {
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>();
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>(&());
|
||||
cursor.seek(&ChannelMessageId::Pending(0), Bias::Left, &());
|
||||
cursor
|
||||
}
|
||||
@@ -589,11 +589,11 @@ impl ChannelChat {
|
||||
fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut ModelContext<Self>) {
|
||||
if let Some((first_message, last_message)) = messages.first().zip(messages.last()) {
|
||||
let nonces = messages
|
||||
.cursor::<()>()
|
||||
.cursor::<()>(&())
|
||||
.map(|m| m.nonce)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let mut old_cursor = self.messages.cursor::<(ChannelMessageId, Count)>();
|
||||
let mut old_cursor = self.messages.cursor::<(ChannelMessageId, Count)>(&());
|
||||
let mut new_messages = old_cursor.slice(&first_message.id, Bias::Left, &());
|
||||
let start_ix = old_cursor.start().1 .0;
|
||||
let removed_messages = old_cursor.slice(&last_message.id, Bias::Right, &());
|
||||
@@ -646,7 +646,7 @@ impl ChannelChat {
|
||||
}
|
||||
|
||||
fn message_removed(&mut self, id: u64, cx: &mut ModelContext<Self>) {
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>();
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>(&());
|
||||
let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left, &());
|
||||
if let Some(item) = cursor.item() {
|
||||
if item.id == ChannelMessageId::Saved(id) {
|
||||
@@ -685,7 +685,7 @@ impl ChannelChat {
|
||||
edited_at: Option<OffsetDateTime>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>();
|
||||
let mut cursor = self.messages.cursor::<ChannelMessageId>(&());
|
||||
let mut messages = cursor.slice(&id, Bias::Left, &());
|
||||
let ix = messages.summary().count;
|
||||
|
||||
@@ -716,7 +716,7 @@ async fn messages_from_proto(
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<SumTree<ChannelMessage>> {
|
||||
let messages = ChannelMessage::from_proto_vec(proto_messages, user_store, cx).await?;
|
||||
let mut result = SumTree::new();
|
||||
let mut result = SumTree::default();
|
||||
result.extend(messages, &());
|
||||
Ok(result)
|
||||
}
|
||||
@@ -825,6 +825,10 @@ impl Default for ChannelMessageId {
|
||||
impl sum_tree::Summary for ChannelMessageSummary {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &Self::Context) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||
self.max_id = summary.max_id;
|
||||
self.count += summary.count;
|
||||
@@ -832,6 +836,10 @@ impl sum_tree::Summary for ChannelMessageSummary {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for ChannelMessageId {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
|
||||
debug_assert!(summary.max_id > *self);
|
||||
*self = summary.max_id;
|
||||
@@ -839,6 +847,10 @@ impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for ChannelMessageId {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for Count {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
|
||||
self.0 += summary.count;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ test-support = ["clock/test-support", "collections/test-support", "gpui/test-sup
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-recursion = "0.3"
|
||||
async-tungstenite = { workspace = true, features = ["async-std", "async-native-tls"] }
|
||||
async-tungstenite = { workspace = true, features = ["async-std", "async-tls"] }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
@@ -35,6 +35,8 @@ postage.workspace = true
|
||||
rand.workspace = true
|
||||
release_channel.workspace = true
|
||||
rpc = { workspace = true, features = ["gpui"] }
|
||||
rustls.workspace = true
|
||||
rustls-native-certs.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -240,8 +240,6 @@ pub enum EstablishConnectionError {
|
||||
#[error("{0}")]
|
||||
Other(#[from] anyhow::Error),
|
||||
#[error("{0}")]
|
||||
Http(#[from] http_client::Error),
|
||||
#[error("{0}")]
|
||||
InvalidHeaderValue(#[from] async_tungstenite::tungstenite::http::header::InvalidHeaderValue),
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
@@ -529,19 +527,13 @@ impl Client {
|
||||
}
|
||||
|
||||
pub fn production(cx: &mut AppContext) -> Arc<Self> {
|
||||
let user_agent = format!(
|
||||
"Zed/{} ({}; {})",
|
||||
AppVersion::global(cx),
|
||||
std::env::consts::OS,
|
||||
std::env::consts::ARCH
|
||||
);
|
||||
let clock = Arc::new(clock::RealSystemClock);
|
||||
let http = Arc::new(HttpClientWithUrl::new(
|
||||
let http = Arc::new(HttpClientWithUrl::new_uri(
|
||||
cx.http_client(),
|
||||
&ClientSettings::get_global(cx).server_url,
|
||||
Some(user_agent),
|
||||
ProxySettings::get_global(cx).proxy.clone(),
|
||||
cx.http_client().proxy().cloned(),
|
||||
));
|
||||
Self::new(clock, http.clone(), cx)
|
||||
Self::new(clock, http, cx)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u64 {
|
||||
@@ -1145,8 +1137,32 @@ impl Client {
|
||||
|
||||
match url_scheme {
|
||||
Https => {
|
||||
let client_config = {
|
||||
let mut root_store = rustls::RootCertStore::empty();
|
||||
|
||||
let root_certs = rustls_native_certs::load_native_certs();
|
||||
for error in root_certs.errors {
|
||||
log::warn!("error loading native certs: {:?}", error);
|
||||
}
|
||||
root_store.add_parsable_certificates(
|
||||
&root_certs
|
||||
.certs
|
||||
.into_iter()
|
||||
.map(|cert| cert.as_ref().to_owned())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth()
|
||||
};
|
||||
let (stream, _) =
|
||||
async_tungstenite::async_std::client_async_tls(request, stream).await?;
|
||||
async_tungstenite::async_tls::client_async_tls_with_connector(
|
||||
request,
|
||||
stream,
|
||||
Some(client_config.into()),
|
||||
)
|
||||
.await?;
|
||||
Ok(Connection::new(
|
||||
stream
|
||||
.map_err(|error| anyhow!(error))
|
||||
|
||||
@@ -16,9 +16,9 @@ use std::io::Write;
|
||||
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
|
||||
use telemetry_events::{
|
||||
ActionEvent, AppEvent, AssistantEvent, AssistantKind, CallEvent, CpuEvent, EditEvent,
|
||||
EditorEvent, Event, EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent,
|
||||
MemoryEvent, ReplEvent, SettingEvent,
|
||||
ActionEvent, AppEvent, AssistantEvent, AssistantKind, AssistantPhase, CallEvent, CpuEvent,
|
||||
EditEvent, EditorEvent, Event, EventRequestBody, EventWrapper, ExtensionEvent,
|
||||
InlineCompletionEvent, MemoryEvent, ReplEvent, SettingEvent,
|
||||
};
|
||||
use tempfile::NamedTempFile;
|
||||
#[cfg(not(debug_assertions))]
|
||||
@@ -37,9 +37,10 @@ pub struct Telemetry {
|
||||
|
||||
struct TelemetryState {
|
||||
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)
|
||||
session_id: Option<String>, // Per app launch
|
||||
metrics_id: Option<Arc<str>>, // Per logged-in user
|
||||
release_channel: Option<&'static str>,
|
||||
architecture: &'static str,
|
||||
events_queue: Vec<EventWrapper>,
|
||||
@@ -191,9 +192,10 @@ impl Telemetry {
|
||||
settings: *TelemetrySettings::get_global(cx),
|
||||
architecture: env::consts::ARCH,
|
||||
release_channel,
|
||||
system_id: None,
|
||||
installation_id: None,
|
||||
metrics_id: None,
|
||||
session_id: None,
|
||||
metrics_id: None,
|
||||
events_queue: Vec::new(),
|
||||
flush_events_task: None,
|
||||
log_file: None,
|
||||
@@ -283,11 +285,13 @@ impl Telemetry {
|
||||
|
||||
pub fn start(
|
||||
self: &Arc<Self>,
|
||||
system_id: Option<String>,
|
||||
installation_id: Option<String>,
|
||||
session_id: String,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
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.session_id = Some(session_id);
|
||||
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 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
|
||||
// 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;
|
||||
|
||||
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 {
|
||||
log::error!(
|
||||
"Failed to find own process {current_process:?} in system process table"
|
||||
@@ -385,6 +395,7 @@ impl Telemetry {
|
||||
self: &Arc<Self>,
|
||||
conversation_id: Option<String>,
|
||||
kind: AssistantKind,
|
||||
phase: AssistantPhase,
|
||||
model: String,
|
||||
response_latency: Option<Duration>,
|
||||
error_message: Option<String>,
|
||||
@@ -392,6 +403,7 @@ impl Telemetry {
|
||||
let event = Event::Assistant(AssistantEvent {
|
||||
conversation_id,
|
||||
kind,
|
||||
phase,
|
||||
model: model.to_string(),
|
||||
response_latency,
|
||||
error_message,
|
||||
@@ -629,9 +641,10 @@ impl Telemetry {
|
||||
let state = this.state.lock();
|
||||
|
||||
let request_body = EventRequestBody {
|
||||
system_id: state.system_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(),
|
||||
metrics_id: state.metrics_id.as_deref().map(Into::into),
|
||||
is_staff: state.is_staff,
|
||||
app_version: state.app_version.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(),
|
||||
));
|
||||
let http = FakeHttpClient::with_200_response();
|
||||
let system_id = Some("system_id".to_string());
|
||||
let installation_id = Some("installation_id".to_string());
|
||||
let session_id = "session_id".to_string();
|
||||
|
||||
@@ -710,7 +724,7 @@ mod tests {
|
||||
let telemetry = Telemetry::new(clock.clone(), http, cx);
|
||||
|
||||
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));
|
||||
|
||||
@@ -788,13 +802,14 @@ mod tests {
|
||||
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
|
||||
));
|
||||
let http = FakeHttpClient::with_200_response();
|
||||
let system_id = Some("system_id".to_string());
|
||||
let installation_id = Some("installation_id".to_string());
|
||||
let session_id = "session_id".to_string();
|
||||
|
||||
cx.update(|cx| {
|
||||
let telemetry = Telemetry::new(clock.clone(), http, cx);
|
||||
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));
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ futures.workspace = true
|
||||
google_ai.workspace = true
|
||||
hex.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc_http_client.workspace = true
|
||||
jsonwebtoken.workspace = true
|
||||
live_kit_server.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
@@ -149,7 +149,8 @@ pub async fn post_crash(
|
||||
installation_id = %installation_id,
|
||||
description = %description,
|
||||
backtrace = %summary,
|
||||
"crash report");
|
||||
"crash report"
|
||||
);
|
||||
|
||||
if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
|
||||
let payload = slack::WebhookBody::new(|w| {
|
||||
@@ -627,7 +628,9 @@ where
|
||||
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct EditorEventRow {
|
||||
system_id: String,
|
||||
installation_id: String,
|
||||
session_id: Option<String>,
|
||||
metrics_id: String,
|
||||
operation: String,
|
||||
app_version: String,
|
||||
@@ -647,7 +650,6 @@ pub struct EditorEventRow {
|
||||
historical_event: bool,
|
||||
architecture: String,
|
||||
is_staff: Option<bool>,
|
||||
session_id: Option<String>,
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
@@ -677,9 +679,10 @@ impl EditorEventRow {
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
architecture: body.architecture.clone(),
|
||||
system_id: body.system_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(),
|
||||
metrics_id: body.metrics_id.clone().unwrap_or_default(),
|
||||
is_staff: body.is_staff,
|
||||
time: time.timestamp_millis(),
|
||||
operation: event.operation,
|
||||
@@ -699,6 +702,7 @@ impl EditorEventRow {
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct InlineCompletionEventRow {
|
||||
installation_id: String,
|
||||
session_id: Option<String>,
|
||||
provider: String,
|
||||
suggestion_accepted: bool,
|
||||
app_version: String,
|
||||
@@ -713,7 +717,6 @@ pub struct InlineCompletionEventRow {
|
||||
city: String,
|
||||
time: i64,
|
||||
is_staff: Option<bool>,
|
||||
session_id: Option<String>,
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
@@ -834,6 +837,7 @@ pub struct AssistantEventRow {
|
||||
// AssistantEventRow
|
||||
conversation_id: String,
|
||||
kind: String,
|
||||
phase: String,
|
||||
model: String,
|
||||
response_latency_in_ms: Option<i64>,
|
||||
error_message: Option<String>,
|
||||
@@ -866,6 +870,7 @@ impl AssistantEventRow {
|
||||
time: time.timestamp_millis(),
|
||||
conversation_id: event.conversation_id.unwrap_or_default(),
|
||||
kind: event.kind.to_string(),
|
||||
phase: event.phase.to_string(),
|
||||
model: event.model,
|
||||
response_latency_in_ms: event
|
||||
.response_latency
|
||||
@@ -877,7 +882,9 @@ impl AssistantEventRow {
|
||||
|
||||
#[derive(Debug, clickhouse::Row, Serialize)]
|
||||
pub struct CpuEventRow {
|
||||
system_id: Option<String>,
|
||||
installation_id: Option<String>,
|
||||
session_id: Option<String>,
|
||||
is_staff: Option<bool>,
|
||||
usage_as_percentage: f32,
|
||||
core_count: u32,
|
||||
@@ -886,7 +893,6 @@ pub struct CpuEventRow {
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
time: i64,
|
||||
session_id: Option<String>,
|
||||
// pub normalized_cpu_usage: f64, MATERIALIZED
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
@@ -915,6 +921,7 @@ impl CpuEventRow {
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
system_id: body.system_id.clone(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
@@ -938,6 +945,7 @@ pub struct MemoryEventRow {
|
||||
os_version: String,
|
||||
|
||||
// ClientEventBase
|
||||
system_id: Option<String>,
|
||||
installation_id: Option<String>,
|
||||
session_id: Option<String>,
|
||||
is_staff: Option<bool>,
|
||||
@@ -969,6 +977,7 @@ impl MemoryEventRow {
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
system_id: body.system_id.clone(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
@@ -992,6 +1001,7 @@ pub struct AppEventRow {
|
||||
os_version: String,
|
||||
|
||||
// ClientEventBase
|
||||
system_id: Option<String>,
|
||||
installation_id: Option<String>,
|
||||
session_id: Option<String>,
|
||||
is_staff: Option<bool>,
|
||||
@@ -1022,6 +1032,7 @@ impl AppEventRow {
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
system_id: body.system_id.clone(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
@@ -1044,6 +1055,7 @@ pub struct SettingEventRow {
|
||||
os_version: String,
|
||||
|
||||
// ClientEventBase
|
||||
system_id: Option<String>,
|
||||
installation_id: Option<String>,
|
||||
session_id: Option<String>,
|
||||
is_staff: Option<bool>,
|
||||
@@ -1074,6 +1086,7 @@ impl SettingEventRow {
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
system_id: body.system_id.clone(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
@@ -1097,6 +1110,7 @@ pub struct ExtensionEventRow {
|
||||
os_version: String,
|
||||
|
||||
// ClientEventBase
|
||||
system_id: Option<String>,
|
||||
installation_id: Option<String>,
|
||||
session_id: Option<String>,
|
||||
is_staff: Option<bool>,
|
||||
@@ -1132,6 +1146,7 @@ impl ExtensionEventRow {
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
system_id: body.system_id.clone(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
@@ -1222,6 +1237,7 @@ pub struct EditEventRow {
|
||||
os_version: String,
|
||||
|
||||
// ClientEventBase
|
||||
system_id: Option<String>,
|
||||
installation_id: Option<String>,
|
||||
// Note: This column name has a typo in the ClickHouse table.
|
||||
#[serde(rename = "sesssion_id")]
|
||||
@@ -1259,6 +1275,7 @@ impl EditEventRow {
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
system_id: body.system_id.clone(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
|
||||
@@ -104,7 +104,7 @@ pub enum ChannelRole {
|
||||
/// Admin can read/write and change permissions.
|
||||
#[sea_orm(string_value = "admin")]
|
||||
Admin,
|
||||
/// Member can read/write, but not change pemissions.
|
||||
/// Member can read/write, but not change permissions.
|
||||
#[sea_orm(string_value = "member")]
|
||||
#[default]
|
||||
Member,
|
||||
|
||||
@@ -22,7 +22,7 @@ use chrono::{DateTime, Duration, Utc};
|
||||
use collections::HashMap;
|
||||
use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase};
|
||||
use futures::{Stream, StreamExt as _};
|
||||
use http_client::IsahcHttpClient;
|
||||
use isahc_http_client::IsahcHttpClient;
|
||||
use rpc::ListModelsResponse;
|
||||
use rpc::{
|
||||
proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
|
||||
@@ -72,6 +72,7 @@ impl LlmState {
|
||||
let http_client = IsahcHttpClient::builder()
|
||||
.default_header("User-Agent", user_agent)
|
||||
.build()
|
||||
.map(IsahcHttpClient::from)
|
||||
.context("failed to construct http client")?;
|
||||
|
||||
let this = Self {
|
||||
|
||||
@@ -35,6 +35,8 @@ use chrono::Utc;
|
||||
use collections::{HashMap, HashSet};
|
||||
pub use connection_pool::{ConnectionPool, ZedVersion};
|
||||
use core::fmt::{self, Debug, Formatter};
|
||||
use http_client::HttpClient;
|
||||
use isahc_http_client::IsahcHttpClient;
|
||||
use open_ai::{OpenAiEmbeddingModel, OPEN_AI_API_URL};
|
||||
use sha2::Digest;
|
||||
use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi};
|
||||
@@ -45,7 +47,6 @@ use futures::{
|
||||
stream::FuturesUnordered,
|
||||
FutureExt, SinkExt, StreamExt, TryStreamExt,
|
||||
};
|
||||
use http_client::IsahcHttpClient;
|
||||
use prometheus::{register_int_gauge, IntGauge};
|
||||
use rpc::{
|
||||
proto::{
|
||||
@@ -139,7 +140,7 @@ struct Session {
|
||||
connection_pool: Arc<parking_lot::Mutex<ConnectionPool>>,
|
||||
app_state: Arc<AppState>,
|
||||
supermaven_client: Option<Arc<SupermavenAdminApi>>,
|
||||
http_client: Arc<IsahcHttpClient>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
/// The GeoIP country code for the user.
|
||||
#[allow(unused)]
|
||||
geoip_country_code: Option<String>,
|
||||
@@ -957,7 +958,7 @@ impl Server {
|
||||
|
||||
let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION"));
|
||||
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) => {
|
||||
tracing::error!(?error, "failed to create HTTP client");
|
||||
return;
|
||||
|
||||
@@ -1524,6 +1524,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: false,
|
||||
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_parameter_hints: false,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -1786,6 +1788,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
show_type_hints: false,
|
||||
show_parameter_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_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,16 +11,14 @@ pub use smol;
|
||||
pub use sqlez;
|
||||
pub use sqlez_macros;
|
||||
|
||||
use release_channel::ReleaseChannel;
|
||||
pub use release_channel::RELEASE_CHANNEL;
|
||||
use sqlez::domain::Migrator;
|
||||
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||
use sqlez_macros::sql;
|
||||
use std::env;
|
||||
use std::future::Future;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::LazyLock;
|
||||
use std::sync::{atomic::Ordering, LazyLock};
|
||||
use std::{env, sync::atomic::AtomicBool};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
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
|
||||
/// 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.
|
||||
pub async fn open_db<M: Migrator + 'static>(
|
||||
db_dir: &Path,
|
||||
release_channel: &ReleaseChannel,
|
||||
) -> ThreadSafeConnection<M> {
|
||||
pub async fn open_db<M: Migrator + 'static>(db_dir: &Path, scope: &str) -> ThreadSafeConnection<M> {
|
||||
if *ZED_STATELESS {
|
||||
return open_fallback_db().await;
|
||||
}
|
||||
|
||||
let release_channel_name = release_channel.dev_name();
|
||||
let main_db_dir = db_dir.join(Path::new(&format!("0-{}", release_channel_name)));
|
||||
let main_db_dir = db_dir.join(format!("0-{}", scope));
|
||||
|
||||
let connection = maybe!(async {
|
||||
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
|
||||
#[macro_export]
|
||||
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>);
|
||||
|
||||
impl ::std::ops::Deref for $t {
|
||||
@@ -139,18 +133,23 @@ macro_rules! define_connection {
|
||||
}
|
||||
}
|
||||
|
||||
use std::sync::LazyLock;
|
||||
#[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))))
|
||||
});
|
||||
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
pub static $id: LazyLock<$t> = LazyLock::new(|| {
|
||||
$t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)))
|
||||
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
||||
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 )>);
|
||||
|
||||
impl ::std::ops::Deref for $t {
|
||||
@@ -178,7 +177,13 @@ macro_rules! define_connection {
|
||||
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
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")
|
||||
.tempdir()
|
||||
.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)
|
||||
@@ -262,13 +271,19 @@ mod tests {
|
||||
.tempdir()
|
||||
.unwrap();
|
||||
{
|
||||
let corrupt_db =
|
||||
open_db::<CorruptedDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
||||
let corrupt_db = open_db::<CorruptedDB>(
|
||||
tempdir.path(),
|
||||
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||
)
|
||||
.await;
|
||||
assert!(corrupt_db.persistent());
|
||||
}
|
||||
|
||||
let good_db =
|
||||
open_db::<GoodDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
||||
let good_db = open_db::<GoodDB>(
|
||||
tempdir.path(),
|
||||
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
||||
.unwrap()
|
||||
@@ -311,8 +326,11 @@ mod tests {
|
||||
.unwrap();
|
||||
{
|
||||
// Setup the bad database
|
||||
let corrupt_db =
|
||||
open_db::<CorruptedDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
||||
let corrupt_db = open_db::<CorruptedDB>(
|
||||
tempdir.path(),
|
||||
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||
)
|
||||
.await;
|
||||
assert!(corrupt_db.persistent());
|
||||
}
|
||||
|
||||
@@ -323,7 +341,7 @@ mod tests {
|
||||
let guard = thread::spawn(move || {
|
||||
let good_db = smol::block_on(open_db::<GoodDB>(
|
||||
tmp_path.as_path(),
|
||||
&release_channel::ReleaseChannel::Dev,
|
||||
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||
));
|
||||
assert!(
|
||||
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
||||
|
||||
@@ -60,3 +60,33 @@ mod tests {
|
||||
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 = (?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,14 +156,14 @@ pub struct DeleteToPreviousWordStart {
|
||||
impl_actions!(
|
||||
editor,
|
||||
[
|
||||
ComposeCompletion,
|
||||
ConfirmCodeAction,
|
||||
ConfirmCompletion,
|
||||
ComposeCompletion,
|
||||
DeleteToNextWordEnd,
|
||||
DeleteToPreviousWordStart,
|
||||
ExpandExcerpts,
|
||||
ExpandExcerptsUp,
|
||||
ExpandExcerptsDown,
|
||||
ExpandExcerptsUp,
|
||||
FoldAt,
|
||||
HandleInput,
|
||||
MoveDownByLines,
|
||||
@@ -188,8 +188,8 @@ impl_actions!(
|
||||
gpui::actions!(
|
||||
editor,
|
||||
[
|
||||
AcceptPartialCopilotSuggestion,
|
||||
AcceptInlineCompletion,
|
||||
AcceptPartialCopilotSuggestion,
|
||||
AcceptPartialInlineCompletion,
|
||||
AddSelectionAbove,
|
||||
AddSelectionBelow,
|
||||
@@ -210,10 +210,10 @@ gpui::actions!(
|
||||
ConvertToUpperCamelCase,
|
||||
ConvertToUpperCase,
|
||||
Copy,
|
||||
CopyFileLocation,
|
||||
CopyHighlightJson,
|
||||
CopyPath,
|
||||
CopyPermalinkToLine,
|
||||
CopyFileLocation,
|
||||
CopyRelativePath,
|
||||
Cut,
|
||||
CutToEndOfLine,
|
||||
@@ -232,10 +232,10 @@ gpui::actions!(
|
||||
Fold,
|
||||
FoldSelectedRanges,
|
||||
Format,
|
||||
GoToDefinition,
|
||||
GoToDefinitionSplit,
|
||||
GoToDeclaration,
|
||||
GoToDeclarationSplit,
|
||||
GoToDefinition,
|
||||
GoToDefinitionSplit,
|
||||
GoToDiagnostic,
|
||||
GoToHunk,
|
||||
GoToImplementation,
|
||||
@@ -273,9 +273,9 @@ gpui::actions!(
|
||||
NextScreen,
|
||||
OpenExcerpts,
|
||||
OpenExcerptsSplit,
|
||||
OpenFile,
|
||||
OpenPermalinkToLine,
|
||||
OpenUrl,
|
||||
OpenFile,
|
||||
Outdent,
|
||||
PageDown,
|
||||
PageUp,
|
||||
@@ -284,23 +284,25 @@ gpui::actions!(
|
||||
Redo,
|
||||
RedoSelection,
|
||||
Rename,
|
||||
Rewrap,
|
||||
RestartLanguageServer,
|
||||
RevealInFileManager,
|
||||
ReverseLines,
|
||||
RevertFile,
|
||||
RevertSelectedHunks,
|
||||
Rewrap,
|
||||
ScrollCursorBottom,
|
||||
ScrollCursorCenter,
|
||||
ScrollCursorTop,
|
||||
ScrollCursorCenterTopBottom,
|
||||
ScrollCursorTop,
|
||||
SelectAll,
|
||||
SelectAllMatches,
|
||||
SelectDown,
|
||||
SelectLargerSyntaxNode,
|
||||
SelectEnclosingSymbol,
|
||||
SelectLargerSyntaxNode,
|
||||
SelectLeft,
|
||||
SelectLine,
|
||||
SelectPageDown,
|
||||
SelectPageUp,
|
||||
SelectRight,
|
||||
SelectSmallerSyntaxNode,
|
||||
SelectToBeginning,
|
||||
@@ -312,8 +314,6 @@ gpui::actions!(
|
||||
SelectToPreviousWordStart,
|
||||
SelectToStartOfParagraph,
|
||||
SelectUp,
|
||||
SelectPageDown,
|
||||
SelectPageUp,
|
||||
ShowCharacterPalette,
|
||||
ShowInlineCompletion,
|
||||
ShowSignatureHelp,
|
||||
@@ -327,13 +327,13 @@ gpui::actions!(
|
||||
ToggleAutoSignatureHelp,
|
||||
ToggleGitBlame,
|
||||
ToggleGitBlameInline,
|
||||
ToggleSelectionMenu,
|
||||
ToggleHunkDiff,
|
||||
ToggleIndentGuides,
|
||||
ToggleInlayHints,
|
||||
ToggleInlineCompletions,
|
||||
ToggleLineNumbers,
|
||||
ToggleRelativeLineNumbers,
|
||||
ToggleIndentGuides,
|
||||
ToggleSelectionMenu,
|
||||
ToggleSoftWrap,
|
||||
ToggleTabBar,
|
||||
Transpose,
|
||||
|
||||
@@ -127,7 +127,9 @@ impl DisplayMap {
|
||||
let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
|
||||
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 (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
|
||||
let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
|
||||
@@ -138,7 +140,6 @@ impl DisplayMap {
|
||||
excerpt_header_height,
|
||||
excerpt_footer_height,
|
||||
);
|
||||
let crease_map = CreaseMap::default();
|
||||
|
||||
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
|
||||
|
||||
|
||||
@@ -389,10 +389,10 @@ impl BlockMap {
|
||||
}
|
||||
|
||||
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 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 blocks_in_edit = Vec::new();
|
||||
let mut edits = edits.into_iter().peekable();
|
||||
@@ -757,7 +757,7 @@ impl<'a> BlockMapReader<'a> {
|
||||
.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, &());
|
||||
while let Some(transform) = cursor.item() {
|
||||
if cursor.start().0 > end_wrap_row {
|
||||
@@ -950,7 +950,7 @@ impl BlockSnapshot {
|
||||
highlights: Highlights<'a>,
|
||||
) -> BlockChunks<'a> {
|
||||
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 = {
|
||||
cursor.seek(&BlockRow(rows.end), Bias::Right, &());
|
||||
let overshoot = if cursor
|
||||
@@ -990,7 +990,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
|
||||
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, &());
|
||||
let (output_start, input_start) = cursor.start();
|
||||
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)> {
|
||||
let mut cursor = self.transforms.cursor::<BlockRow>();
|
||||
let mut cursor = self.transforms.cursor::<BlockRow>(&());
|
||||
cursor.seek(&BlockRow(rows.start), Bias::Left, &());
|
||||
while cursor.start().0 < rows.start && cursor.end(&()).0 <= rows.start {
|
||||
cursor.next(&());
|
||||
@@ -1050,7 +1050,7 @@ impl BlockSnapshot {
|
||||
let wrap_point = self
|
||||
.wrap_snapshot
|
||||
.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, &());
|
||||
while let Some(transform) = cursor.item() {
|
||||
if let Some(block) = transform.block.as_ref() {
|
||||
@@ -1072,7 +1072,7 @@ impl BlockSnapshot {
|
||||
.wrap_snapshot
|
||||
.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, &());
|
||||
while let Some(transform) = cursor.item() {
|
||||
if let Some(block) = transform.block.as_ref() {
|
||||
@@ -1102,7 +1102,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
|
||||
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, &());
|
||||
if let Some(transform) = cursor.item() {
|
||||
let (output_start, input_start) = cursor.start();
|
||||
@@ -1118,13 +1118,13 @@ impl BlockSnapshot {
|
||||
}
|
||||
|
||||
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.item().map_or(false, |t| t.block.is_some())
|
||||
}
|
||||
|
||||
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, &());
|
||||
|
||||
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 {
|
||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
|
||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
|
||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
|
||||
if let Some(transform) = cursor.item() {
|
||||
debug_assert!(transform.is_isomorphic());
|
||||
@@ -1188,7 +1188,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
|
||||
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, &());
|
||||
if let Some(transform) = cursor.item() {
|
||||
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 {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||
self.input_rows += summary.input_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 {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += summary.input_rows;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += summary.output_rows;
|
||||
}
|
||||
|
||||
@@ -12,19 +12,34 @@ use crate::FoldPlaceholder;
|
||||
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||
pub struct CreaseId(usize);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CreaseMap {
|
||||
snapshot: CreaseSnapshot,
|
||||
next_id: CreaseId,
|
||||
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 {
|
||||
creases: SumTree<CreaseItem>,
|
||||
}
|
||||
|
||||
impl CreaseSnapshot {
|
||||
pub fn new(snapshot: &MultiBufferSnapshot) -> Self {
|
||||
CreaseSnapshot {
|
||||
creases: SumTree::new(snapshot),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the first Crease starting on the specified buffer row.
|
||||
pub fn query_row<'a>(
|
||||
&'a self,
|
||||
@@ -32,7 +47,7 @@ impl CreaseSnapshot {
|
||||
snapshot: &'a MultiBufferSnapshot,
|
||||
) -> Option<&'a Crease> {
|
||||
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);
|
||||
while let Some(item) = cursor.item() {
|
||||
match Ord::cmp(&item.crease.range.start.to_point(snapshot).row, &row.0) {
|
||||
@@ -56,7 +71,7 @@ impl CreaseSnapshot {
|
||||
snapshot: &'a MultiBufferSnapshot,
|
||||
) -> impl '_ + Iterator<Item = &'a Crease> {
|
||||
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);
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
@@ -79,7 +94,7 @@ impl CreaseSnapshot {
|
||||
&self,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
) -> Vec<(CreaseId, Range<Point>)> {
|
||||
let mut cursor = self.creases.cursor::<ItemSummary>();
|
||||
let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
|
||||
let mut results = Vec::new();
|
||||
|
||||
cursor.next(snapshot);
|
||||
@@ -194,8 +209,8 @@ impl CreaseMap {
|
||||
) -> Vec<CreaseId> {
|
||||
let mut new_ids = Vec::new();
|
||||
self.snapshot.creases = {
|
||||
let mut new_creases = SumTree::new();
|
||||
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
|
||||
let mut new_creases = SumTree::new(snapshot);
|
||||
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
|
||||
for crease in creases {
|
||||
new_creases.append(cursor.slice(&crease.range, Bias::Left, snapshot), snapshot);
|
||||
|
||||
@@ -227,8 +242,8 @@ impl CreaseMap {
|
||||
});
|
||||
|
||||
self.snapshot.creases = {
|
||||
let mut new_creases = SumTree::new();
|
||||
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
|
||||
let mut new_creases = SumTree::new(snapshot);
|
||||
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
|
||||
|
||||
for (id, range) in removals {
|
||||
new_creases.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
|
||||
@@ -264,6 +279,10 @@ impl Default for ItemSummary {
|
||||
impl sum_tree::Summary for ItemSummary {
|
||||
type Context = MultiBufferSnapshot;
|
||||
|
||||
fn zero(_cx: &Self::Context) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, _snapshot: &MultiBufferSnapshot) {
|
||||
self.range = other.range.clone();
|
||||
}
|
||||
@@ -303,7 +322,7 @@ mod test {
|
||||
let text = "line1\nline2\nline3\nline4\nline5";
|
||||
let buffer = MultiBuffer::build_simple(text, 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
|
||||
let creases = [
|
||||
@@ -350,7 +369,7 @@ mod test {
|
||||
let text = "line1\nline2\nline3\nline4\nline5\nline6\nline7";
|
||||
let buffer = MultiBuffer::build_simple(text, 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 = [
|
||||
Crease::new(
|
||||
|
||||
@@ -79,7 +79,7 @@ impl FoldPoint {
|
||||
}
|
||||
|
||||
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, &());
|
||||
let overshoot = self.0 - cursor.start().0 .0;
|
||||
InlayPoint(cursor.start().1 .0 + overshoot)
|
||||
@@ -88,7 +88,7 @@ impl FoldPoint {
|
||||
pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset {
|
||||
let mut cursor = snapshot
|
||||
.transforms
|
||||
.cursor::<(FoldPoint, TransformSummary)>();
|
||||
.cursor::<(FoldPoint, TransformSummary)>(&());
|
||||
cursor.seek(&self, Bias::Right, &());
|
||||
let overshoot = self.0 - cursor.start().1.output.lines;
|
||||
let mut offset = cursor.start().1.output.len;
|
||||
@@ -105,6 +105,10 @@ impl FoldPoint {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
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));
|
||||
|
||||
self.0.snapshot.folds = {
|
||||
let mut new_tree = SumTree::new();
|
||||
let mut cursor = self.0.snapshot.folds.cursor::<FoldRange>();
|
||||
let mut new_tree = SumTree::new(buffer);
|
||||
let mut cursor = self.0.snapshot.folds.cursor::<FoldRange>(buffer);
|
||||
for fold in folds {
|
||||
new_tree.append(cursor.slice(&fold.range, Bias::Right, buffer), buffer);
|
||||
new_tree.push(fold, buffer);
|
||||
@@ -202,8 +206,8 @@ impl<'a> FoldMapWriter<'a> {
|
||||
fold_ixs_to_delete.dedup();
|
||||
|
||||
self.0.snapshot.folds = {
|
||||
let mut cursor = self.0.snapshot.folds.cursor::<usize>();
|
||||
let mut folds = SumTree::new();
|
||||
let mut cursor = self.0.snapshot.folds.cursor::<usize>(buffer);
|
||||
let mut folds = SumTree::new(buffer);
|
||||
for fold_ix in fold_ixs_to_delete {
|
||||
folds.append(cursor.slice(&fold_ix, Bias::Right, buffer), buffer);
|
||||
cursor.next(buffer);
|
||||
@@ -230,7 +234,7 @@ impl FoldMap {
|
||||
pub(crate) fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) {
|
||||
let this = Self {
|
||||
snapshot: FoldSnapshot {
|
||||
folds: Default::default(),
|
||||
folds: SumTree::new(&inlay_snapshot.buffer),
|
||||
transforms: SumTree::from_item(
|
||||
Transform {
|
||||
summary: TransformSummary {
|
||||
@@ -314,8 +318,8 @@ impl FoldMap {
|
||||
} else {
|
||||
let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable();
|
||||
|
||||
let mut new_transforms = SumTree::<Transform>::new();
|
||||
let mut cursor = self.snapshot.transforms.cursor::<InlayOffset>();
|
||||
let mut new_transforms = SumTree::<Transform>::default();
|
||||
let mut cursor = self.snapshot.transforms.cursor::<InlayOffset>(&());
|
||||
cursor.seek(&InlayOffset(0), Bias::Right, &());
|
||||
|
||||
while let Some(mut edit) = inlay_edits_iter.next() {
|
||||
@@ -367,7 +371,10 @@ impl FoldMap {
|
||||
let anchor = inlay_snapshot
|
||||
.buffer
|
||||
.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(
|
||||
&FoldRange(anchor..Anchor::max()),
|
||||
Bias::Left,
|
||||
@@ -470,8 +477,8 @@ impl FoldMap {
|
||||
let mut old_transforms = self
|
||||
.snapshot
|
||||
.transforms
|
||||
.cursor::<(InlayOffset, FoldOffset)>();
|
||||
let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>();
|
||||
.cursor::<(InlayOffset, FoldOffset)>(&());
|
||||
let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>(&());
|
||||
|
||||
for mut edit in inlay_edits {
|
||||
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 {
|
||||
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, &());
|
||||
if let Some(transform) = cursor.item() {
|
||||
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 {
|
||||
let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>();
|
||||
let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
if cursor.item().map_or(false, |t| t.is_fold()) {
|
||||
if bias == Bias::Left || point == cursor.start().0 {
|
||||
@@ -631,7 +638,7 @@ impl FoldSnapshot {
|
||||
}
|
||||
|
||||
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, &());
|
||||
|
||||
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 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.item().map_or(false, |t| t.placeholder.is_some())
|
||||
}
|
||||
@@ -681,7 +688,7 @@ impl FoldSnapshot {
|
||||
let mut inlay_point = self
|
||||
.inlay_snapshot
|
||||
.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, &());
|
||||
loop {
|
||||
match cursor.item() {
|
||||
@@ -711,7 +718,7 @@ impl FoldSnapshot {
|
||||
language_aware: bool,
|
||||
highlights: Highlights<'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, &());
|
||||
|
||||
let inlay_start = {
|
||||
@@ -766,7 +773,7 @@ impl FoldSnapshot {
|
||||
}
|
||||
|
||||
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, &());
|
||||
if let Some(transform) = cursor.item() {
|
||||
let transform_start = cursor.start().0 .0;
|
||||
@@ -826,7 +833,7 @@ where
|
||||
let buffer = &inlay_snapshot.buffer;
|
||||
let start = buffer.anchor_before(range.start.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 end_cmp = end.cmp(&summary.min_start, buffer);
|
||||
|
||||
@@ -945,6 +952,10 @@ impl sum_tree::Item for Transform {
|
||||
impl sum_tree::Summary for TransformSummary {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, _: &()) {
|
||||
self.input += &other.input;
|
||||
self.output += &other.output;
|
||||
@@ -1028,6 +1039,10 @@ impl Default for FoldSummary {
|
||||
impl sum_tree::Summary for FoldSummary {
|
||||
type Context = MultiBufferSnapshot;
|
||||
|
||||
fn zero(_cx: &MultiBufferSnapshot) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
|
||||
if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less {
|
||||
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 {
|
||||
fn zero(_cx: &MultiBufferSnapshot) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
|
||||
self.0.start = summary.start;
|
||||
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 {
|
||||
fn zero(_cx: &MultiBufferSnapshot) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
|
||||
*self += summary.count;
|
||||
}
|
||||
@@ -1196,7 +1219,7 @@ impl FoldOffset {
|
||||
pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint {
|
||||
let mut cursor = snapshot
|
||||
.transforms
|
||||
.cursor::<(FoldOffset, TransformSummary)>();
|
||||
.cursor::<(FoldOffset, TransformSummary)>(&());
|
||||
cursor.seek(&self, Bias::Right, &());
|
||||
let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) {
|
||||
Point::new(0, (self.0 - cursor.start().0 .0) as u32)
|
||||
@@ -1210,7 +1233,7 @@ impl FoldOffset {
|
||||
|
||||
#[cfg(test)]
|
||||
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, &());
|
||||
let overshoot = self.0 - cursor.start().0 .0;
|
||||
InlayOffset(cursor.start().1 .0 + overshoot)
|
||||
@@ -1240,18 +1263,30 @@ impl Sub 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, _: &()) {
|
||||
self.0 += &summary.output.len;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += &summary.input.lines;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += &summary.input.len;
|
||||
}
|
||||
|
||||
@@ -97,6 +97,10 @@ struct TransformSummary {
|
||||
impl sum_tree::Summary for TransformSummary {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, _: &()) {
|
||||
self.input += &other.input;
|
||||
self.output += &other.output;
|
||||
@@ -137,6 +141,10 @@ impl SubAssign 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, _: &()) {
|
||||
self.0 += &summary.output.len;
|
||||
}
|
||||
@@ -162,18 +170,30 @@ impl Sub 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, _: &()) {
|
||||
self.0 += &summary.output.lines;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
*self += &summary.input.len;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
*self += &summary.input.lines;
|
||||
}
|
||||
@@ -475,8 +495,8 @@ impl InlayMap {
|
||||
(snapshot.clone(), Vec::new())
|
||||
} else {
|
||||
let mut inlay_edits = Patch::default();
|
||||
let mut new_transforms = SumTree::new();
|
||||
let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>();
|
||||
let mut new_transforms = SumTree::default();
|
||||
let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>(&());
|
||||
let mut buffer_edits_iter = buffer_edits.iter().peekable();
|
||||
while let Some(buffer_edit) = buffer_edits_iter.next() {
|
||||
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 {
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<(InlayOffset, (InlayPoint, usize))>();
|
||||
.cursor::<(InlayOffset, (InlayPoint, usize))>(&());
|
||||
cursor.seek(&offset, Bias::Right, &());
|
||||
let overshoot = offset.0 - cursor.start().0 .0;
|
||||
match cursor.item() {
|
||||
@@ -723,7 +743,7 @@ impl InlaySnapshot {
|
||||
pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<(InlayPoint, (InlayOffset, Point))>();
|
||||
.cursor::<(InlayPoint, (InlayOffset, Point))>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
let overshoot = point.0 - cursor.start().0 .0;
|
||||
match cursor.item() {
|
||||
@@ -741,9 +761,8 @@ impl InlaySnapshot {
|
||||
None => self.len(),
|
||||
}
|
||||
}
|
||||
|
||||
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, &());
|
||||
match cursor.item() {
|
||||
Some(Transform::Isomorphic(_)) => {
|
||||
@@ -754,9 +773,8 @@ impl InlaySnapshot {
|
||||
None => self.buffer.max_point(),
|
||||
}
|
||||
}
|
||||
|
||||
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, &());
|
||||
match cursor.item() {
|
||||
Some(Transform::Isomorphic(_)) => {
|
||||
@@ -769,7 +787,7 @@ impl InlaySnapshot {
|
||||
}
|
||||
|
||||
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, &());
|
||||
loop {
|
||||
match cursor.item() {
|
||||
@@ -801,9 +819,8 @@ impl InlaySnapshot {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, &());
|
||||
loop {
|
||||
match cursor.item() {
|
||||
@@ -837,7 +854,7 @@ impl InlaySnapshot {
|
||||
}
|
||||
|
||||
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, &());
|
||||
loop {
|
||||
match cursor.item() {
|
||||
@@ -934,7 +951,7 @@ impl InlaySnapshot {
|
||||
pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
|
||||
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, &());
|
||||
|
||||
let overshoot = range.start.0 - cursor.start().0 .0;
|
||||
@@ -982,7 +999,7 @@ impl InlaySnapshot {
|
||||
}
|
||||
|
||||
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);
|
||||
cursor.seek(&inlay_point, Bias::Left, &());
|
||||
|
||||
@@ -1024,7 +1041,7 @@ impl InlaySnapshot {
|
||||
language_aware: bool,
|
||||
highlights: Highlights<'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, &());
|
||||
|
||||
let mut highlight_endpoints = Vec::new();
|
||||
|
||||
@@ -204,7 +204,7 @@ impl WrapMap {
|
||||
}
|
||||
} else {
|
||||
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();
|
||||
if !summary.lines.is_zero() {
|
||||
self.snapshot
|
||||
@@ -303,7 +303,7 @@ impl WrapMap {
|
||||
|
||||
impl WrapSnapshot {
|
||||
fn new(tab_snapshot: TabSnapshot) -> Self {
|
||||
let mut transforms = SumTree::new();
|
||||
let mut transforms = SumTree::default();
|
||||
let extent = tab_snapshot.text_summary();
|
||||
if !extent.lines.is_zero() {
|
||||
transforms.push(Transform::isomorphic(extent), &());
|
||||
@@ -324,7 +324,7 @@ impl WrapSnapshot {
|
||||
if tab_edits.is_empty() {
|
||||
new_transforms = self.transforms.clone();
|
||||
} 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();
|
||||
new_transforms =
|
||||
@@ -424,7 +424,7 @@ impl WrapSnapshot {
|
||||
new_transforms = self.transforms.clone();
|
||||
} else {
|
||||
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(
|
||||
&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> {
|
||||
let mut wrap_edits = Vec::new();
|
||||
let mut old_cursor = self.transforms.cursor::<TransformSummary>();
|
||||
let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>();
|
||||
let mut old_cursor = self.transforms.cursor::<TransformSummary>(&());
|
||||
let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>(&());
|
||||
for mut tab_edit in tab_edits.iter().cloned() {
|
||||
tab_edit.old.start.0.column = 0;
|
||||
tab_edit.old.end.0 += Point::new(1, 0);
|
||||
@@ -579,7 +579,7 @@ impl WrapSnapshot {
|
||||
) -> WrapChunks<'a> {
|
||||
let output_start = WrapPoint::new(rows.start, 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, &());
|
||||
let mut input_start = TabPoint(transforms.start().1 .0);
|
||||
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
|
||||
@@ -606,7 +606,7 @@ impl WrapSnapshot {
|
||||
}
|
||||
|
||||
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, &());
|
||||
if cursor
|
||||
.item()
|
||||
@@ -626,7 +626,7 @@ impl WrapSnapshot {
|
||||
}
|
||||
|
||||
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.item().and_then(|transform| {
|
||||
if transform.is_isomorphic() {
|
||||
@@ -642,7 +642,7 @@ impl WrapSnapshot {
|
||||
}
|
||||
|
||||
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, &());
|
||||
let mut input_row = transforms.start().1.row();
|
||||
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 {
|
||||
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
|
||||
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
let mut tab_point = cursor.start().1 .0;
|
||||
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 {
|
||||
let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>();
|
||||
let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
WrapPoint(cursor.start().1 .0 + (point.0 - cursor.start().0 .0))
|
||||
}
|
||||
|
||||
pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint {
|
||||
if bias == Bias::Left {
|
||||
let mut cursor = self.transforms.cursor::<WrapPoint>();
|
||||
let mut cursor = self.transforms.cursor::<WrapPoint>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
if cursor.item().map_or(false, |t| !t.is_isomorphic()) {
|
||||
point = *cursor.start();
|
||||
@@ -705,7 +705,7 @@ impl WrapSnapshot {
|
||||
|
||||
*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, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.prev(&());
|
||||
@@ -725,7 +725,7 @@ impl WrapSnapshot {
|
||||
pub fn next_row_boundary(&self, mut point: WrapPoint) -> Option<u32> {
|
||||
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, &());
|
||||
while let Some(transform) = cursor.item() {
|
||||
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() {
|
||||
if let Some(next_transform) = transforms.peek() {
|
||||
assert!(transform.is_isomorphic() != next_transform.is_isomorphic());
|
||||
@@ -982,6 +982,10 @@ impl WrapPoint {
|
||||
impl sum_tree::Summary for TransformSummary {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, _: &()) {
|
||||
self.input += &other.input;
|
||||
self.output += &other.output;
|
||||
@@ -989,6 +993,10 @@ impl sum_tree::Summary for TransformSummary {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
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 {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += summary.output.lines;
|
||||
}
|
||||
|
||||
@@ -412,6 +412,19 @@ 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;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -6736,22 +6749,22 @@ impl Editor {
|
||||
|
||||
let mut line_prefix = indent_size.chars().collect::<String>();
|
||||
|
||||
if selection.is_empty() {
|
||||
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 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)
|
||||
@@ -10034,9 +10047,8 @@ impl Editor {
|
||||
syntax: cx.editor_style.syntax.clone(),
|
||||
status: cx.editor_style.status.clone(),
|
||||
inlay_hints_style: HighlightStyle {
|
||||
color: Some(cx.theme().status().hint),
|
||||
font_weight: Some(FontWeight::BOLD),
|
||||
..HighlightStyle::default()
|
||||
..make_inlay_hints_style(cx)
|
||||
},
|
||||
suggestions_style: HighlightStyle {
|
||||
color: Some(cx.theme().status().predictive),
|
||||
@@ -12992,10 +13004,7 @@ impl Render for Editor {
|
||||
scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
status: cx.theme().status().clone(),
|
||||
inlay_hints_style: HighlightStyle {
|
||||
color: Some(cx.theme().status().hint),
|
||||
..HighlightStyle::default()
|
||||
},
|
||||
inlay_hints_style: make_inlay_hints_style(cx),
|
||||
suggestions_style: HighlightStyle {
|
||||
color: Some(cx.theme().status().predictive),
|
||||
..HighlightStyle::default()
|
||||
|
||||
@@ -4017,6 +4017,39 @@ async fn test_rewrap(cx: &mut TestAppContext) {
|
||||
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(
|
||||
|
||||
@@ -2079,13 +2079,13 @@ impl EditorElement {
|
||||
.id(("path excerpt header", EntityId::from(block_id)))
|
||||
.w_full()
|
||||
.px(header_padding)
|
||||
.pt(header_padding)
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
|
||||
.id("path header block")
|
||||
.h(2. * cx.line_height())
|
||||
.pl(gpui::px(12.))
|
||||
.pr(gpui::px(8.))
|
||||
.px(gpui::px(12.))
|
||||
.rounded_md()
|
||||
.shadow_md()
|
||||
.border_1()
|
||||
|
||||
@@ -37,12 +37,20 @@ impl sum_tree::Item for GitBlameEntry {
|
||||
impl sum_tree::Summary for GitBlameEntrySummary {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &Self, _cx: &()) {
|
||||
self.rows += summary.rows;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, GitBlameEntrySummary> for u32 {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a GitBlameEntrySummary, _cx: &()) {
|
||||
*self += summary.rows;
|
||||
}
|
||||
@@ -191,7 +199,7 @@ impl GitBlame {
|
||||
) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
|
||||
self.sync(cx);
|
||||
|
||||
let mut cursor = self.entries.cursor::<u32>();
|
||||
let mut cursor = self.entries.cursor::<u32>(&());
|
||||
rows.into_iter().map(move |row| {
|
||||
let row = row?;
|
||||
cursor.seek_forward(&row.0, Bias::Right, &());
|
||||
@@ -249,8 +257,8 @@ impl GitBlame {
|
||||
})
|
||||
.peekable();
|
||||
|
||||
let mut new_entries = SumTree::new();
|
||||
let mut cursor = self.entries.cursor::<u32>();
|
||||
let mut new_entries = SumTree::default();
|
||||
let mut cursor = self.entries.cursor::<u32>(&());
|
||||
|
||||
while let Some(mut edit) = row_edits.next() {
|
||||
while let Some(next_edit) = row_edits.peek() {
|
||||
|
||||
@@ -1205,6 +1205,7 @@ mod tests {
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -1337,6 +1337,7 @@ mod tests {
|
||||
show_type_hints: true,
|
||||
show_parameter_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.
|
||||
/// 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.
|
||||
/// 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(
|
||||
&mut self,
|
||||
reason_description: &'static str,
|
||||
@@ -1296,6 +1296,7 @@ pub mod tests {
|
||||
show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
|
||||
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1428,6 +1429,7 @@ pub mod tests {
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1547,6 +1549,7 @@ pub mod tests {
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1777,6 +1780,7 @@ pub mod tests {
|
||||
show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
|
||||
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
|
||||
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
|
||||
.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: new_allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -1987,6 +1992,7 @@ pub mod tests {
|
||||
show_parameter_hints: another_allowed_hint_kinds
|
||||
.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: another_allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -2047,6 +2053,7 @@ pub mod tests {
|
||||
show_parameter_hints: final_allowed_hint_kinds
|
||||
.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: final_allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -2122,6 +2129,7 @@ pub mod tests {
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2256,6 +2264,7 @@ pub mod tests {
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2551,6 +2560,7 @@ pub mod tests {
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2902,6 +2912,7 @@ pub mod tests {
|
||||
show_type_hints: false,
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: false,
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -3096,6 +3107,7 @@ pub mod tests {
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -3131,6 +3143,7 @@ pub mod tests {
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -3225,6 +3238,7 @@ pub mod tests {
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -3305,6 +3319,7 @@ pub mod tests {
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
38
crates/evals/Cargo.toml
Normal file
38
crates/evals/Cargo.toml
Normal file
@@ -0,0 +1,38 @@
|
||||
[package]
|
||||
name = "evals"
|
||||
description = "Evaluations for Zed's AI features"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "eval"
|
||||
path = "src/eval.rs"
|
||||
|
||||
[dependencies]
|
||||
clap.workspace = true
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
env_logger.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
isahc_http_client.workspace = true
|
||||
language.workspace = true
|
||||
languages.workspace = true
|
||||
http_client.workspace = true
|
||||
open_ai.workspace = true
|
||||
project.workspace = true
|
||||
settings.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smol.workspace = true
|
||||
semantic_index.workspace = true
|
||||
node_runtime.workspace = true
|
||||
14
crates/evals/build.rs
Normal file
14
crates/evals/build.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
fn main() {
|
||||
if cfg!(target_os = "macos") {
|
||||
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.15.7");
|
||||
|
||||
println!("cargo:rerun-if-env-changed=ZED_BUNDLE");
|
||||
if std::env::var("ZED_BUNDLE").ok().as_deref() == Some("true") {
|
||||
// Find WebRTC.framework in the Frameworks folder when running as part of an application bundle.
|
||||
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../Frameworks");
|
||||
} else {
|
||||
// Find WebRTC.framework as a sibling of the executable when running outside of an application bundle.
|
||||
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
|
||||
}
|
||||
}
|
||||
}
|
||||
644
crates/evals/src/eval.rs
Normal file
644
crates/evals/src/eval.rs
Normal file
@@ -0,0 +1,644 @@
|
||||
use ::fs::{Fs, RealFs};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use client::{Client, UserStore};
|
||||
use clock::RealSystemClock;
|
||||
use collections::BTreeMap;
|
||||
use feature_flags::FeatureFlagAppExt as _;
|
||||
use git::GitHostingProviderRegistry;
|
||||
use gpui::{AsyncAppContext, BackgroundExecutor, Context, Model};
|
||||
use http_client::{HttpClient, Method};
|
||||
use language::LanguageRegistry;
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
use open_ai::OpenAiEmbeddingModel;
|
||||
use project::Project;
|
||||
use semantic_index::{OpenAiEmbeddingProvider, ProjectIndex, SemanticDb, Status};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::SettingsStore;
|
||||
use smol::channel::bounded;
|
||||
use smol::io::AsyncReadExt;
|
||||
use smol::Timer;
|
||||
use std::ops::RangeInclusive;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
fs,
|
||||
path::Path,
|
||||
process::{exit, Command, Stdio},
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
const CODESEARCH_NET_DIR: &'static str = "target/datasets/code-search-net";
|
||||
const EVAL_REPOS_DIR: &'static str = "target/datasets/eval-repos";
|
||||
const EVAL_DB_PATH: &'static str = "target/eval_db";
|
||||
const SEARCH_RESULT_LIMIT: usize = 8;
|
||||
const SKIP_EVAL_PATH: &'static str = ".skip_eval";
|
||||
|
||||
#[derive(clap::Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand)]
|
||||
enum Commands {
|
||||
Fetch {},
|
||||
Run {
|
||||
#[arg(long)]
|
||||
repo: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
struct EvaluationProject {
|
||||
repo: String,
|
||||
sha: String,
|
||||
queries: Vec<EvaluationQuery>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
struct EvaluationQuery {
|
||||
query: String,
|
||||
expected_results: Vec<EvaluationSearchResult>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
struct EvaluationSearchResult {
|
||||
file: String,
|
||||
lines: RangeInclusive<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
struct EvaluationProjectOutcome {
|
||||
repo: String,
|
||||
sha: String,
|
||||
queries: Vec<EvaluationQueryOutcome>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
struct EvaluationQueryOutcome {
|
||||
repo: String,
|
||||
query: String,
|
||||
expected_results: Vec<EvaluationSearchResult>,
|
||||
actual_results: Vec<EvaluationSearchResult>,
|
||||
covered_file_count: usize,
|
||||
overlapped_result_count: usize,
|
||||
covered_result_count: usize,
|
||||
total_result_count: usize,
|
||||
covered_result_indices: Vec<usize>,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
env_logger::init();
|
||||
|
||||
gpui::App::headless().run(move |cx| {
|
||||
let executor = cx.background_executor().clone();
|
||||
let client = isahc_http_client::IsahcHttpClient::new(None, None);
|
||||
cx.set_http_client(client.clone());
|
||||
match cli.command {
|
||||
Commands::Fetch {} => {
|
||||
executor
|
||||
.clone()
|
||||
.spawn(async move {
|
||||
if let Err(err) = fetch_evaluation_resources(client, &executor).await {
|
||||
eprintln!("Error: {}", err);
|
||||
exit(1);
|
||||
}
|
||||
exit(0);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
Commands::Run { repo } => {
|
||||
cx.spawn(|mut cx| async move {
|
||||
if let Err(err) = run_evaluation(repo, &executor, &mut cx).await {
|
||||
eprintln!("Error: {}", err);
|
||||
exit(1);
|
||||
}
|
||||
exit(0);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_evaluation_resources(
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
executor: &BackgroundExecutor,
|
||||
) -> Result<()> {
|
||||
fetch_code_search_net_resources(&*http_client).await?;
|
||||
fetch_eval_repos(executor, &*http_client).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_code_search_net_resources(http_client: &dyn HttpClient) -> Result<()> {
|
||||
eprintln!("Fetching CodeSearchNet evaluations...");
|
||||
|
||||
let annotations_url = "https://raw.githubusercontent.com/github/CodeSearchNet/master/resources/annotationStore.csv";
|
||||
|
||||
let dataset_dir = Path::new(CODESEARCH_NET_DIR);
|
||||
fs::create_dir_all(&dataset_dir).expect("failed to create CodeSearchNet directory");
|
||||
|
||||
// Fetch the annotations CSV, which contains the human-annotated search relevances
|
||||
let annotations_path = dataset_dir.join("annotations.csv");
|
||||
let annotations_csv_content = if annotations_path.exists() {
|
||||
fs::read_to_string(&annotations_path).expect("failed to read annotations")
|
||||
} else {
|
||||
let response = http_client
|
||||
.get(annotations_url, Default::default(), true)
|
||||
.await
|
||||
.expect("failed to fetch annotations csv");
|
||||
let mut body = String::new();
|
||||
response
|
||||
.into_body()
|
||||
.read_to_string(&mut body)
|
||||
.await
|
||||
.expect("failed to read annotations.csv response");
|
||||
fs::write(annotations_path, &body).expect("failed to write annotations.csv");
|
||||
body
|
||||
};
|
||||
|
||||
// Parse the annotations CSV. Skip over queries with zero relevance.
|
||||
let rows = annotations_csv_content.lines().filter_map(|line| {
|
||||
let mut values = line.split(',');
|
||||
let _language = values.next()?;
|
||||
let query = values.next()?;
|
||||
let github_url = values.next()?;
|
||||
let score = values.next()?;
|
||||
|
||||
if score == "0" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let url_path = github_url.strip_prefix("https://github.com/")?;
|
||||
let (url_path, hash) = url_path.split_once('#')?;
|
||||
let (repo_name, url_path) = url_path.split_once("/blob/")?;
|
||||
let (sha, file_path) = url_path.split_once('/')?;
|
||||
let line_range = if let Some((start, end)) = hash.split_once('-') {
|
||||
start.strip_prefix("L")?.parse::<u32>().ok()?..=end.strip_prefix("L")?.parse().ok()?
|
||||
} else {
|
||||
let row = hash.strip_prefix("L")?.parse().ok()?;
|
||||
row..=row
|
||||
};
|
||||
Some((repo_name, sha, query, file_path, line_range))
|
||||
});
|
||||
|
||||
// Group the annotations by repo and sha.
|
||||
let mut evaluations_by_repo = BTreeMap::new();
|
||||
for (repo_name, sha, query, file_path, lines) in rows {
|
||||
let evaluation_project = evaluations_by_repo
|
||||
.entry((repo_name, sha))
|
||||
.or_insert_with(|| EvaluationProject {
|
||||
repo: repo_name.to_string(),
|
||||
sha: sha.to_string(),
|
||||
queries: Vec::new(),
|
||||
});
|
||||
|
||||
let ix = evaluation_project
|
||||
.queries
|
||||
.iter()
|
||||
.position(|entry| entry.query == query)
|
||||
.unwrap_or_else(|| {
|
||||
evaluation_project.queries.push(EvaluationQuery {
|
||||
query: query.to_string(),
|
||||
expected_results: Vec::new(),
|
||||
});
|
||||
evaluation_project.queries.len() - 1
|
||||
});
|
||||
let results = &mut evaluation_project.queries[ix].expected_results;
|
||||
let result = EvaluationSearchResult {
|
||||
file: file_path.to_string(),
|
||||
lines,
|
||||
};
|
||||
if !results.contains(&result) {
|
||||
results.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
let evaluations = evaluations_by_repo.into_values().collect::<Vec<_>>();
|
||||
let evaluations_path = dataset_dir.join("evaluations.json");
|
||||
fs::write(
|
||||
&evaluations_path,
|
||||
serde_json::to_vec_pretty(&evaluations).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
eprintln!(
|
||||
"Fetched CodeSearchNet evaluations into {}",
|
||||
evaluations_path.display()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_evaluation(
|
||||
only_repo: Option<String>,
|
||||
executor: &BackgroundExecutor,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let mut http_client = None;
|
||||
cx.update(|cx| {
|
||||
let mut store = SettingsStore::new(cx);
|
||||
store
|
||||
.set_default_settings(settings::default_settings().as_ref(), cx)
|
||||
.unwrap();
|
||||
cx.set_global(store);
|
||||
client::init_settings(cx);
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
http_client = Some(cx.http_client());
|
||||
cx.update_flags(false, vec![]);
|
||||
})
|
||||
.unwrap();
|
||||
let http_client = http_client.unwrap();
|
||||
let dataset_dir = Path::new(CODESEARCH_NET_DIR);
|
||||
let evaluations_path = dataset_dir.join("evaluations.json");
|
||||
let repos_dir = Path::new(EVAL_REPOS_DIR);
|
||||
let db_path = Path::new(EVAL_DB_PATH);
|
||||
let api_key = std::env::var("OPENAI_API_KEY").unwrap();
|
||||
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
|
||||
let fs = Arc::new(RealFs::new(git_hosting_provider_registry, None)) as Arc<dyn Fs>;
|
||||
let clock = Arc::new(RealSystemClock);
|
||||
let client = cx
|
||||
.update(|cx| {
|
||||
Client::new(
|
||||
clock,
|
||||
Arc::new(http_client::HttpClientWithUrl::new(
|
||||
http_client.clone(),
|
||||
"https://zed.dev",
|
||||
None,
|
||||
)),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let user_store = cx
|
||||
.new_model(|cx| UserStore::new(client.clone(), cx))
|
||||
.unwrap();
|
||||
let node_runtime = Arc::new(FakeNodeRuntime {});
|
||||
|
||||
let evaluations = fs::read(&evaluations_path).expect("failed to read evaluations.json");
|
||||
let evaluations: Vec<EvaluationProject> = serde_json::from_slice(&evaluations).unwrap();
|
||||
|
||||
let embedding_provider = Arc::new(OpenAiEmbeddingProvider::new(
|
||||
http_client.clone(),
|
||||
OpenAiEmbeddingModel::TextEmbedding3Small,
|
||||
open_ai::OPEN_AI_API_URL.to_string(),
|
||||
api_key,
|
||||
));
|
||||
|
||||
let language_registry = Arc::new(LanguageRegistry::new(executor.clone()));
|
||||
cx.update(|cx| languages::init(language_registry.clone(), node_runtime.clone(), cx))
|
||||
.unwrap();
|
||||
|
||||
let mut covered_result_count = 0;
|
||||
let mut overlapped_result_count = 0;
|
||||
let mut covered_file_count = 0;
|
||||
let mut total_result_count = 0;
|
||||
eprint!("Running evals.");
|
||||
|
||||
for evaluation_project in evaluations {
|
||||
if only_repo
|
||||
.as_ref()
|
||||
.map_or(false, |only_repo| only_repo != &evaluation_project.repo)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
eprint!("\r\x1B[2K");
|
||||
eprint!(
|
||||
"Running evals. {}/{} covered. {}/{} overlapped. {}/{} files captured. Project: {}...",
|
||||
covered_result_count,
|
||||
total_result_count,
|
||||
overlapped_result_count,
|
||||
total_result_count,
|
||||
covered_file_count,
|
||||
total_result_count,
|
||||
evaluation_project.repo
|
||||
);
|
||||
|
||||
let repo_db_path =
|
||||
db_path.join(format!("{}.db", evaluation_project.repo.replace('/', "_")));
|
||||
let mut semantic_index = SemanticDb::new(repo_db_path, embedding_provider.clone(), cx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let repo_dir = repos_dir.join(&evaluation_project.repo);
|
||||
if !repo_dir.exists() || repo_dir.join(SKIP_EVAL_PATH).exists() {
|
||||
eprintln!("Skipping {}: directory not found", evaluation_project.repo);
|
||||
continue;
|
||||
}
|
||||
|
||||
let project = cx
|
||||
.update(|cx| {
|
||||
Project::local(
|
||||
client.clone(),
|
||||
node_runtime.clone(),
|
||||
user_store.clone(),
|
||||
language_registry.clone(),
|
||||
fs.clone(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let (worktree, _) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree(repo_dir, true, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
worktree
|
||||
.update(cx, |worktree, _| {
|
||||
worktree.as_local().unwrap().scan_complete()
|
||||
})
|
||||
.unwrap()
|
||||
.await;
|
||||
|
||||
let project_index = cx
|
||||
.update(|cx| semantic_index.create_project_index(project.clone(), cx))
|
||||
.unwrap();
|
||||
wait_for_indexing_complete(&project_index, cx, Some(Duration::from_secs(120))).await;
|
||||
|
||||
for query in evaluation_project.queries {
|
||||
let results = cx
|
||||
.update(|cx| {
|
||||
let project_index = project_index.read(cx);
|
||||
project_index.search(query.query.clone(), SEARCH_RESULT_LIMIT, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let results = SemanticDb::load_results(results, &fs.clone(), &cx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut project_covered_result_count = 0;
|
||||
let mut project_overlapped_result_count = 0;
|
||||
let mut project_covered_file_count = 0;
|
||||
let mut covered_result_indices = Vec::new();
|
||||
for expected_result in &query.expected_results {
|
||||
let mut file_matched = false;
|
||||
let mut range_overlapped = false;
|
||||
let mut range_covered = false;
|
||||
|
||||
for (ix, result) in results.iter().enumerate() {
|
||||
if result.path.as_ref() == Path::new(&expected_result.file) {
|
||||
file_matched = true;
|
||||
let start_matched =
|
||||
result.row_range.contains(&expected_result.lines.start());
|
||||
let end_matched = result.row_range.contains(&expected_result.lines.end());
|
||||
|
||||
if start_matched || end_matched {
|
||||
range_overlapped = true;
|
||||
}
|
||||
|
||||
if start_matched && end_matched {
|
||||
range_covered = true;
|
||||
covered_result_indices.push(ix);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if range_covered {
|
||||
project_covered_result_count += 1
|
||||
};
|
||||
if range_overlapped {
|
||||
project_overlapped_result_count += 1
|
||||
};
|
||||
if file_matched {
|
||||
project_covered_file_count += 1
|
||||
};
|
||||
}
|
||||
let outcome_repo = evaluation_project.repo.clone();
|
||||
|
||||
let query_results = EvaluationQueryOutcome {
|
||||
repo: outcome_repo,
|
||||
query: query.query,
|
||||
total_result_count: query.expected_results.len(),
|
||||
covered_result_count: project_covered_result_count,
|
||||
overlapped_result_count: project_overlapped_result_count,
|
||||
covered_file_count: project_covered_file_count,
|
||||
expected_results: query.expected_results,
|
||||
actual_results: results
|
||||
.iter()
|
||||
.map(|result| EvaluationSearchResult {
|
||||
file: result.path.to_string_lossy().to_string(),
|
||||
lines: result.row_range.clone(),
|
||||
})
|
||||
.collect(),
|
||||
covered_result_indices,
|
||||
};
|
||||
|
||||
overlapped_result_count += query_results.overlapped_result_count;
|
||||
covered_result_count += query_results.covered_result_count;
|
||||
covered_file_count += query_results.covered_file_count;
|
||||
total_result_count += query_results.total_result_count;
|
||||
|
||||
println!("{}", serde_json::to_string(&query_results).unwrap());
|
||||
}
|
||||
|
||||
user_store
|
||||
.update(cx, |_, _| {
|
||||
drop(semantic_index);
|
||||
drop(project);
|
||||
drop(worktree);
|
||||
drop(project_index);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
eprint!(
|
||||
"Running evals. {}/{} covered. {}/{} overlapped. {}/{} files captured.",
|
||||
covered_result_count,
|
||||
total_result_count,
|
||||
overlapped_result_count,
|
||||
total_result_count,
|
||||
covered_file_count,
|
||||
total_result_count,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn wait_for_indexing_complete(
|
||||
project_index: &Model<ProjectIndex>,
|
||||
cx: &mut AsyncAppContext,
|
||||
timeout: Option<Duration>,
|
||||
) {
|
||||
let (tx, rx) = bounded(1);
|
||||
let subscription = cx.update(|cx| {
|
||||
cx.subscribe(project_index, move |_, event, _| {
|
||||
if let Status::Idle = event {
|
||||
let _ = tx.try_send(*event);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let result = match timeout {
|
||||
Some(timeout_duration) => {
|
||||
smol::future::or(
|
||||
async {
|
||||
rx.recv().await.map_err(|_| ())?;
|
||||
Ok(())
|
||||
},
|
||||
async {
|
||||
Timer::after(timeout_duration).await;
|
||||
Err(())
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
None => rx.recv().await.map(|_| ()).map_err(|_| ()),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(_) => {
|
||||
if let Some(timeout) = timeout {
|
||||
eprintln!("Timeout: Indexing did not complete within {:?}", timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(subscription);
|
||||
}
|
||||
|
||||
async fn fetch_eval_repos(
|
||||
executor: &BackgroundExecutor,
|
||||
http_client: &dyn HttpClient,
|
||||
) -> Result<()> {
|
||||
let dataset_dir = Path::new(CODESEARCH_NET_DIR);
|
||||
let evaluations_path = dataset_dir.join("evaluations.json");
|
||||
let repos_dir = Path::new(EVAL_REPOS_DIR);
|
||||
|
||||
let evaluations = fs::read(&evaluations_path).expect("failed to read evaluations.json");
|
||||
let evaluations: Vec<EvaluationProject> = serde_json::from_slice(&evaluations).unwrap();
|
||||
|
||||
eprint!("Fetching evaluation repositories...");
|
||||
|
||||
executor
|
||||
.scoped(move |scope| {
|
||||
let done_count = Arc::new(AtomicUsize::new(0));
|
||||
let len = evaluations.len();
|
||||
for chunk in evaluations.chunks(evaluations.len() / 8) {
|
||||
let chunk = chunk.to_vec();
|
||||
let done_count = done_count.clone();
|
||||
scope.spawn(async move {
|
||||
for EvaluationProject { repo, sha, .. } in chunk {
|
||||
eprint!(
|
||||
"\rFetching evaluation repositories ({}/{})...",
|
||||
done_count.load(SeqCst),
|
||||
len,
|
||||
);
|
||||
|
||||
fetch_eval_repo(repo, sha, repos_dir, http_client).await;
|
||||
done_count.fetch_add(1, SeqCst);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_eval_repo(
|
||||
repo: String,
|
||||
sha: String,
|
||||
repos_dir: &Path,
|
||||
http_client: &dyn HttpClient,
|
||||
) {
|
||||
let Some((owner, repo_name)) = repo.split_once('/') else {
|
||||
return;
|
||||
};
|
||||
let repo_dir = repos_dir.join(owner).join(repo_name);
|
||||
fs::create_dir_all(&repo_dir).unwrap();
|
||||
let skip_eval_path = repo_dir.join(SKIP_EVAL_PATH);
|
||||
if skip_eval_path.exists() {
|
||||
return;
|
||||
}
|
||||
if let Ok(head_content) = fs::read_to_string(&repo_dir.join(".git").join("HEAD")) {
|
||||
if head_content.trim() == sha {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let repo_response = http_client
|
||||
.send(
|
||||
http_client::Request::builder()
|
||||
.method(Method::HEAD)
|
||||
.uri(format!("https://github.com/{}", repo))
|
||||
.body(Default::default())
|
||||
.expect(""),
|
||||
)
|
||||
.await
|
||||
.expect("failed to check github repo");
|
||||
if !repo_response.status().is_success() && !repo_response.status().is_redirection() {
|
||||
fs::write(&skip_eval_path, "").unwrap();
|
||||
eprintln!(
|
||||
"Repo {repo} is no longer public ({:?}). Skipping",
|
||||
repo_response.status()
|
||||
);
|
||||
return;
|
||||
}
|
||||
if !repo_dir.join(".git").exists() {
|
||||
let init_output = Command::new("git")
|
||||
.current_dir(&repo_dir)
|
||||
.args(&["init"])
|
||||
.output()
|
||||
.unwrap();
|
||||
if !init_output.status.success() {
|
||||
eprintln!(
|
||||
"Failed to initialize git repository for {}: {}",
|
||||
repo,
|
||||
String::from_utf8_lossy(&init_output.stderr)
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let url = format!("https://github.com/{}.git", repo);
|
||||
Command::new("git")
|
||||
.current_dir(&repo_dir)
|
||||
.args(&["remote", "add", "-f", "origin", &url])
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.unwrap();
|
||||
let fetch_output = Command::new("git")
|
||||
.current_dir(&repo_dir)
|
||||
.args(&["fetch", "--depth", "1", "origin", &sha])
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.unwrap();
|
||||
if !fetch_output.status.success() {
|
||||
eprintln!(
|
||||
"Failed to fetch {} for {}: {}",
|
||||
sha,
|
||||
repo,
|
||||
String::from_utf8_lossy(&fetch_output.stderr)
|
||||
);
|
||||
return;
|
||||
}
|
||||
let checkout_output = Command::new("git")
|
||||
.current_dir(&repo_dir)
|
||||
.args(&["checkout", &sha])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
if !checkout_output.status.success() {
|
||||
eprintln!(
|
||||
"Failed to checkout {} for {}: {}",
|
||||
sha,
|
||||
repo,
|
||||
String::from_utf8_lossy(&checkout_output.stderr)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ task.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
isahc_http_client.workspace = true
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
||||
@@ -246,6 +246,7 @@ impl ExtensionBuilder {
|
||||
.args(scanner_path.exists().then_some(scanner_path))
|
||||
.output()
|
||||
.context("failed to run clang")?;
|
||||
|
||||
if !clang_output.status.success() {
|
||||
bail!(
|
||||
"failed to compile {} parser with clang: {}",
|
||||
@@ -431,6 +432,7 @@ impl ExtensionBuilder {
|
||||
let body = BufReader::new(response.body_mut());
|
||||
let body = GzipDecoder::new(body);
|
||||
let tar = Archive::new(body);
|
||||
|
||||
tar.unpack(&tar_out_dir)
|
||||
.await
|
||||
.context("failed to unpack wasi-sdk archive")?;
|
||||
|
||||
@@ -38,7 +38,7 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
|
||||
fn get_language_server_command<'a>(
|
||||
self: Arc<Self>,
|
||||
_: Arc<Path>,
|
||||
_: Option<Arc<Path>>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
|
||||
_: &'a mut AsyncAppContext,
|
||||
|
||||
@@ -190,6 +190,7 @@ pub fn init(
|
||||
None,
|
||||
fs,
|
||||
client.http_client().clone(),
|
||||
client.http_client().clone(),
|
||||
Some(client.telemetry().clone()),
|
||||
node_runtime,
|
||||
language_registry,
|
||||
@@ -225,6 +226,7 @@ impl ExtensionStore {
|
||||
build_dir: Option<PathBuf>,
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
builder_client: Arc<dyn HttpClient>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
node_runtime: Arc<dyn NodeRuntime>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
@@ -244,12 +246,7 @@ impl ExtensionStore {
|
||||
extension_index: Default::default(),
|
||||
installed_dir,
|
||||
index_path,
|
||||
builder: Arc::new(ExtensionBuilder::new(
|
||||
// Construct a real HTTP client for the extension builder, as we
|
||||
// don't want to use a fake one in the tests.
|
||||
::http_client::client(None, http_client.proxy().cloned()),
|
||||
build_dir,
|
||||
)),
|
||||
builder: Arc::new(ExtensionBuilder::new(builder_client, build_dir)),
|
||||
outstanding_operations: Default::default(),
|
||||
modified_extensions: Default::default(),
|
||||
reload_complete_senders: Vec::new(),
|
||||
|
||||
@@ -13,10 +13,12 @@ use futures::{io::BufReader, AsyncReadExt, StreamExt};
|
||||
use gpui::{Context, SemanticVersion, TestAppContext};
|
||||
use http_client::{FakeHttpClient, Response};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
use isahc_http_client::IsahcHttpClient;
|
||||
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
use parking_lot::Mutex;
|
||||
use project::{Project, DEFAULT_COMPLETION_CONTEXT};
|
||||
use release_channel::AppVersion;
|
||||
use serde_json::json;
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use snippet_provider::SnippetRegistry;
|
||||
@@ -270,6 +272,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
None,
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
http_client.clone(),
|
||||
None,
|
||||
node_runtime.clone(),
|
||||
language_registry.clone(),
|
||||
@@ -397,6 +400,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
None,
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
http_client.clone(),
|
||||
None,
|
||||
node_runtime.clone(),
|
||||
language_registry.clone(),
|
||||
@@ -502,7 +506,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
http_request_count: 0,
|
||||
}));
|
||||
|
||||
let http_client = FakeHttpClient::create({
|
||||
let extension_client = FakeHttpClient::create({
|
||||
let language_server_version = language_server_version.clone();
|
||||
move |request| {
|
||||
let language_server_version = language_server_version.clone();
|
||||
@@ -564,13 +568,23 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
}
|
||||
}
|
||||
});
|
||||
let user_agent = cx.update(|cx| {
|
||||
format!(
|
||||
"Zed/{} ({}; {})",
|
||||
AppVersion::global(cx),
|
||||
std::env::consts::OS,
|
||||
std::env::consts::ARCH
|
||||
)
|
||||
});
|
||||
let builder_client = IsahcHttpClient::new(None, Some(user_agent));
|
||||
|
||||
let extension_store = cx.new_model(|cx| {
|
||||
ExtensionStore::new(
|
||||
extensions_dir.clone(),
|
||||
Some(cache_dir),
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
extension_client.clone(),
|
||||
builder_client,
|
||||
None,
|
||||
node_runtime,
|
||||
language_registry.clone(),
|
||||
|
||||
@@ -18,7 +18,7 @@ clap = { workspace = true, features = ["derive"] }
|
||||
env_logger.workspace = true
|
||||
extension = { workspace = true, features = ["no-webrtc"] }
|
||||
fs.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc_http_client.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
rpc.workspace = true
|
||||
|
||||
@@ -7,13 +7,13 @@ use std::{
|
||||
};
|
||||
|
||||
use ::fs::{copy_recursive, CopyOptions, Fs, RealFs};
|
||||
use ::http_client::HttpClientWithProxy;
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use clap::Parser;
|
||||
use extension::{
|
||||
extension_builder::{CompileExtensionOptions, ExtensionBuilder},
|
||||
ExtensionManifest,
|
||||
};
|
||||
use isahc_http_client::IsahcHttpClient;
|
||||
use language::LanguageConfig;
|
||||
use theme::ThemeRegistry;
|
||||
use tree_sitter::{Language, Query, WasmStore};
|
||||
@@ -66,7 +66,13 @@ async fn main() -> Result<()> {
|
||||
std::env::consts::OS,
|
||||
std::env::consts::ARCH
|
||||
);
|
||||
let http_client = Arc::new(HttpClientWithProxy::new(Some(user_agent), None));
|
||||
let http_client = Arc::new(
|
||||
IsahcHttpClient::builder()
|
||||
.default_header("User-Agent", user_agent)
|
||||
.build()
|
||||
.map(IsahcHttpClient::from)?,
|
||||
);
|
||||
|
||||
let builder = ExtensionBuilder::new(http_client, scratch_dir);
|
||||
builder
|
||||
.compile_extension(
|
||||
|
||||
@@ -44,8 +44,8 @@ const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
|
||||
struct FeedbackRequestBody<'a> {
|
||||
feedback_text: &'a str,
|
||||
email: Option<String>,
|
||||
metrics_id: Option<Arc<str>>,
|
||||
installation_id: Option<Arc<str>>,
|
||||
metrics_id: Option<Arc<str>>,
|
||||
system_specs: SystemSpecs,
|
||||
is_staff: bool,
|
||||
}
|
||||
@@ -296,16 +296,16 @@ impl FeedbackModal {
|
||||
}
|
||||
|
||||
let telemetry = zed_client.telemetry();
|
||||
let metrics_id = telemetry.metrics_id();
|
||||
let installation_id = telemetry.installation_id();
|
||||
let metrics_id = telemetry.metrics_id();
|
||||
let is_staff = telemetry.is_staff();
|
||||
let http_client = zed_client.http_client();
|
||||
let feedback_endpoint = http_client.build_url("/api/feedback");
|
||||
let request = FeedbackRequestBody {
|
||||
feedback_text,
|
||||
email,
|
||||
metrics_id,
|
||||
installation_id,
|
||||
metrics_id,
|
||||
system_specs,
|
||||
is_staff: is_staff.unwrap_or(false),
|
||||
};
|
||||
|
||||
@@ -48,6 +48,10 @@ pub struct DiffHunkSummary {
|
||||
impl sum_tree::Summary for DiffHunkSummary {
|
||||
type Context = text::BufferSnapshot;
|
||||
|
||||
fn zero(_cx: &Self::Context) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
|
||||
self.buffer_range.start = self
|
||||
.buffer_range
|
||||
@@ -63,17 +67,11 @@ pub struct BufferDiff {
|
||||
tree: SumTree<DiffHunk<Anchor>>,
|
||||
}
|
||||
|
||||
impl Default for BufferDiff {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferDiff {
|
||||
pub fn new() -> BufferDiff {
|
||||
pub fn new(buffer: &BufferSnapshot) -> BufferDiff {
|
||||
BufferDiff {
|
||||
last_buffer_version: None,
|
||||
tree: SumTree::new(),
|
||||
tree: SumTree::new(buffer),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,11 +95,13 @@ impl BufferDiff {
|
||||
range: Range<Anchor>,
|
||||
buffer: &'a BufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
|
||||
let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| {
|
||||
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
|
||||
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
|
||||
!before_start && !after_end
|
||||
});
|
||||
let mut cursor = self
|
||||
.tree
|
||||
.filter::<_, DiffHunkSummary>(buffer, move |summary| {
|
||||
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
|
||||
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
|
||||
!before_start && !after_end
|
||||
});
|
||||
|
||||
let anchor_iter = std::iter::from_fn(move || {
|
||||
cursor.next(buffer);
|
||||
@@ -142,11 +142,13 @@ impl BufferDiff {
|
||||
range: Range<Anchor>,
|
||||
buffer: &'a BufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
|
||||
let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| {
|
||||
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
|
||||
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
|
||||
!before_start && !after_end
|
||||
});
|
||||
let mut cursor = self
|
||||
.tree
|
||||
.filter::<_, DiffHunkSummary>(buffer, move |summary| {
|
||||
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
|
||||
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
|
||||
!before_start && !after_end
|
||||
});
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
cursor.prev(buffer);
|
||||
@@ -171,11 +173,11 @@ impl BufferDiff {
|
||||
#[cfg(test)]
|
||||
fn clear(&mut self, buffer: &text::BufferSnapshot) {
|
||||
self.last_buffer_version = Some(buffer.version().clone());
|
||||
self.tree = SumTree::new();
|
||||
self.tree = SumTree::new(buffer);
|
||||
}
|
||||
|
||||
pub async fn update(&mut self, diff_base: &Rope, buffer: &text::BufferSnapshot) {
|
||||
let mut tree = SumTree::new();
|
||||
let mut tree = SumTree::new(buffer);
|
||||
|
||||
let diff_base_text = diff_base.to_string();
|
||||
let buffer_text = buffer.as_rope().to_string();
|
||||
@@ -351,7 +353,7 @@ mod tests {
|
||||
.unindent();
|
||||
|
||||
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
|
||||
let mut diff = BufferDiff::new();
|
||||
let mut diff = BufferDiff::new(&buffer);
|
||||
smol::block_on(diff.update(&diff_base_rope, &buffer));
|
||||
assert_hunks(
|
||||
diff.hunks(&buffer),
|
||||
@@ -412,7 +414,7 @@ mod tests {
|
||||
.unindent();
|
||||
|
||||
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
|
||||
let mut diff = BufferDiff::new();
|
||||
let mut diff = BufferDiff::new(&buffer);
|
||||
smol::block_on(diff.update(&diff_base_rope, &buffer));
|
||||
assert_eq!(diff.hunks(&buffer).count(), 8);
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ futures.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc.workspace = true
|
||||
regex.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -3,9 +3,7 @@ use std::sync::Arc;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::AsyncReadExt;
|
||||
use http_client::HttpClient;
|
||||
use isahc::config::Configurable;
|
||||
use isahc::{AsyncBody, Request};
|
||||
use http_client::{AsyncBody, HttpClient, Request};
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
@@ -51,16 +49,14 @@ impl Codeberg {
|
||||
let url =
|
||||
format!("https://codeberg.org/api/v1/repos/{repo_owner}/{repo}/git/commits/{commit}");
|
||||
|
||||
let mut request = Request::get(&url)
|
||||
.redirect_policy(isahc::config::RedirectPolicy::Follow)
|
||||
.header("Content-Type", "application/json");
|
||||
let mut request = Request::get(&url).header("Content-Type", "application/json");
|
||||
|
||||
if let Ok(codeberg_token) = std::env::var("CODEBERG_TOKEN") {
|
||||
request = request.header("Authorization", format!("Bearer {}", codeberg_token));
|
||||
}
|
||||
|
||||
let mut response = client
|
||||
.send(request.body(AsyncBody::default())?)
|
||||
.send_with_redirect_policy(request.body(AsyncBody::default())?, true)
|
||||
.await
|
||||
.with_context(|| format!("error fetching Codeberg commit details at {:?}", url))?;
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@ use std::sync::{Arc, OnceLock};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::AsyncReadExt;
|
||||
use http_client::HttpClient;
|
||||
use isahc::config::Configurable;
|
||||
use isahc::{AsyncBody, Request};
|
||||
use http_client::{AsyncBody, HttpClient, Request};
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
@@ -55,16 +53,14 @@ impl Github {
|
||||
) -> Result<Option<User>> {
|
||||
let url = format!("https://api.github.com/repos/{repo_owner}/{repo}/commits/{commit}");
|
||||
|
||||
let mut request = Request::get(&url)
|
||||
.redirect_policy(isahc::config::RedirectPolicy::Follow)
|
||||
.header("Content-Type", "application/json");
|
||||
let mut request = Request::get(&url).header("Content-Type", "application/json");
|
||||
|
||||
if let Ok(github_token) = std::env::var("GITHUB_TOKEN") {
|
||||
request = request.header("Authorization", format!("Bearer {}", github_token));
|
||||
}
|
||||
|
||||
let mut response = client
|
||||
.send(request.body(AsyncBody::default())?)
|
||||
.send_with_redirect_policy(request.body(AsyncBody::default())?, true)
|
||||
.await
|
||||
.with_context(|| format!("error fetching GitHub commit details at {:?}", url))?;
|
||||
|
||||
|
||||
@@ -11,13 +11,13 @@ license = "Apache-2.0"
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["http_client"]
|
||||
test-support = [
|
||||
"backtrace",
|
||||
"collections/test-support",
|
||||
"rand",
|
||||
"util/test-support",
|
||||
"http_client/test-support",
|
||||
"http_client?/test-support",
|
||||
]
|
||||
runtime_shaders = []
|
||||
macos-blade = ["blade-graphics", "blade-macros", "blade-util", "bytemuck"]
|
||||
@@ -40,7 +40,7 @@ derive_more.workspace = true
|
||||
etagere = "0.2"
|
||||
futures.workspace = true
|
||||
gpui_macros.workspace = true
|
||||
http_client.workspace = true
|
||||
http_client = { optional = true, workspace = true }
|
||||
image = "0.25.1"
|
||||
itertools.workspace = true
|
||||
linkme = "0.3"
|
||||
@@ -50,7 +50,7 @@ parking = "2.0.0"
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
profiling.workspace = true
|
||||
rand = { optional = true, workspace = true}
|
||||
rand = { optional = true, workspace = true }
|
||||
raw-window-handle = "0.6"
|
||||
refineable.workspace = true
|
||||
resvg = { version = "0.41.0", default-features = false }
|
||||
@@ -110,6 +110,7 @@ blade-graphics.workspace = true
|
||||
blade-macros.workspace = true
|
||||
blade-util.workspace = true
|
||||
bytemuck = "1"
|
||||
flume = "0.11"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
as-raw-xcb-connection = "1"
|
||||
@@ -117,7 +118,6 @@ ashpd.workspace = true
|
||||
calloop = "0.13.0"
|
||||
calloop-wayland-source = "0.3.0"
|
||||
cosmic-text = { git = "https://github.com/pop-os/cosmic-text", rev = "542b20c" }
|
||||
flume = "0.11"
|
||||
wayland-backend = { version = "0.3.3", features = ["client_system", "dlopen"] }
|
||||
wayland-client = { version = "0.31.2" }
|
||||
wayland-cursor = "0.31.1"
|
||||
|
||||
@@ -131,6 +131,7 @@ fn main() {
|
||||
PathBuf::from_str("crates/gpui/examples/image/app-icon.png").unwrap(),
|
||||
),
|
||||
remote_resource: "https://picsum.photos/512/512".into(),
|
||||
|
||||
asset_resource: "image/color.svg".into(),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -117,7 +117,7 @@ impl App {
|
||||
Self(AppContext::new(
|
||||
current_platform(false),
|
||||
Arc::new(()),
|
||||
http_client::client(None, None),
|
||||
Arc::new(NullHttpClient),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ impl App {
|
||||
Self(AppContext::new(
|
||||
current_platform(true),
|
||||
Arc::new(()),
|
||||
http_client::client(None, None),
|
||||
Arc::new(NullHttpClient),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -142,6 +142,14 @@ impl App {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the http client for the application
|
||||
pub fn with_http_client(self, http_client: Arc<dyn HttpClient>) -> Self {
|
||||
let mut context_lock = self.0.borrow_mut();
|
||||
context_lock.http_client = http_client;
|
||||
drop(context_lock);
|
||||
self
|
||||
}
|
||||
|
||||
/// Start the application. The provided callback will be called once the
|
||||
/// app is fully launched.
|
||||
pub fn run<F>(self, on_finish_launching: F)
|
||||
@@ -1512,3 +1520,22 @@ pub struct KeystrokeEvent {
|
||||
/// The action that was resolved for the keystroke, if any
|
||||
pub action: Option<Box<dyn Action>>,
|
||||
}
|
||||
|
||||
struct NullHttpClient;
|
||||
|
||||
impl HttpClient for NullHttpClient {
|
||||
fn send_with_redirect_policy(
|
||||
&self,
|
||||
_req: http_client::Request<http_client::AsyncBody>,
|
||||
_follow_redirects: bool,
|
||||
) -> futures::future::BoxFuture<
|
||||
'static,
|
||||
Result<http_client::Response<http_client::AsyncBody>, anyhow::Error>,
|
||||
> {
|
||||
async move { Err(anyhow!("No HttpClient available")) }.boxed()
|
||||
}
|
||||
|
||||
fn proxy(&self) -> Option<&http_client::Uri> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +345,10 @@ impl Asset for ImageAsset {
|
||||
let bytes = match source.clone() {
|
||||
UriOrPath::Path(uri) => fs::read(uri.as_ref())?,
|
||||
UriOrPath::Uri(uri) => {
|
||||
let mut response = client.get(uri.as_ref(), ().into(), true).await?;
|
||||
let mut response = client
|
||||
.get(uri.as_ref(), ().into(), true)
|
||||
.await
|
||||
.map_err(|e| ImageCacheError::Client(Arc::new(e)))?;
|
||||
let mut body = Vec::new();
|
||||
response.body_mut().read_to_end(&mut body).await?;
|
||||
if !response.status().is_success() {
|
||||
@@ -429,7 +432,7 @@ impl Asset for ImageAsset {
|
||||
pub enum ImageCacheError {
|
||||
/// An error that occurred while fetching an image from a remote source.
|
||||
#[error("http error: {0}")]
|
||||
Client(#[from] http_client::Error),
|
||||
Client(#[from] Arc<anyhow::Error>),
|
||||
/// An error that occurred while reading the image from disk.
|
||||
#[error("IO error: {0}")]
|
||||
Io(Arc<std::io::Error>),
|
||||
|
||||
@@ -181,7 +181,7 @@ impl ListState {
|
||||
last_layout_bounds: None,
|
||||
last_padding: None,
|
||||
render_item: Box::new(render_item),
|
||||
items: SumTree::new(),
|
||||
items: SumTree::default(),
|
||||
logical_scroll_top: None,
|
||||
alignment,
|
||||
overdraw,
|
||||
@@ -228,7 +228,7 @@ impl ListState {
|
||||
) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
|
||||
let mut old_items = state.items.cursor::<Count>();
|
||||
let mut old_items = state.items.cursor::<Count>(&());
|
||||
let mut new_items = old_items.slice(&Count(old_range.start), Bias::Right, &());
|
||||
old_items.seek_forward(&Count(old_range.end), Bias::Right, &());
|
||||
|
||||
@@ -297,7 +297,7 @@ impl ListState {
|
||||
scroll_top.item_ix = ix;
|
||||
scroll_top.offset_in_item = px(0.);
|
||||
} else {
|
||||
let mut cursor = state.items.cursor::<ListItemSummary>();
|
||||
let mut cursor = state.items.cursor::<ListItemSummary>(&());
|
||||
cursor.seek(&Count(ix + 1), Bias::Right, &());
|
||||
let bottom = cursor.start().height + padding.top;
|
||||
let goal_top = px(0.).max(bottom - height + padding.bottom);
|
||||
@@ -326,7 +326,7 @@ impl ListState {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut cursor = state.items.cursor::<(Count, Height)>();
|
||||
let mut cursor = state.items.cursor::<(Count, Height)>(&());
|
||||
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||
|
||||
let scroll_top = cursor.start().1 .0 + scroll_top.offset_in_item;
|
||||
@@ -348,7 +348,7 @@ impl ListState {
|
||||
|
||||
impl StateInner {
|
||||
fn visible_range(&self, height: Pixels, scroll_top: &ListOffset) -> Range<usize> {
|
||||
let mut cursor = self.items.cursor::<ListItemSummary>();
|
||||
let mut cursor = self.items.cursor::<ListItemSummary>(&());
|
||||
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||
let start_y = cursor.start().height + scroll_top.offset_in_item;
|
||||
cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
|
||||
@@ -378,7 +378,7 @@ impl StateInner {
|
||||
if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max {
|
||||
self.logical_scroll_top = None;
|
||||
} else {
|
||||
let mut cursor = self.items.cursor::<ListItemSummary>();
|
||||
let mut cursor = self.items.cursor::<ListItemSummary>(&());
|
||||
cursor.seek(&Height(new_scroll_top), Bias::Right, &());
|
||||
let item_ix = cursor.start().count;
|
||||
let offset_in_item = new_scroll_top - cursor.start().height;
|
||||
@@ -418,7 +418,7 @@ impl StateInner {
|
||||
}
|
||||
|
||||
fn scroll_top(&self, logical_scroll_top: &ListOffset) -> Pixels {
|
||||
let mut cursor = self.items.cursor::<ListItemSummary>();
|
||||
let mut cursor = self.items.cursor::<ListItemSummary>(&());
|
||||
cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &());
|
||||
cursor.start().height + logical_scroll_top.offset_in_item
|
||||
}
|
||||
@@ -445,7 +445,7 @@ impl StateInner {
|
||||
AvailableSpace::MinContent,
|
||||
);
|
||||
|
||||
let mut cursor = old_items.cursor::<Count>();
|
||||
let mut cursor = old_items.cursor::<Count>(&());
|
||||
|
||||
// Render items after the scroll top, including those in the trailing overdraw
|
||||
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||
@@ -560,7 +560,7 @@ impl StateInner {
|
||||
}
|
||||
|
||||
let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len());
|
||||
let mut cursor = old_items.cursor::<Count>();
|
||||
let mut cursor = old_items.cursor::<Count>(&());
|
||||
let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &());
|
||||
new_items.extend(measured_items, &());
|
||||
cursor.seek(&Count(measured_range.end), Bias::Right, &());
|
||||
@@ -573,7 +573,7 @@ impl StateInner {
|
||||
if !rendered_focused_item {
|
||||
let mut cursor = self
|
||||
.items
|
||||
.filter::<_, Count>(|summary| summary.has_focus_handles);
|
||||
.filter::<_, Count>(&(), |summary| summary.has_focus_handles);
|
||||
cursor.next(&());
|
||||
while let Some(item) = cursor.item() {
|
||||
if item.contains_focused(cx) {
|
||||
@@ -629,7 +629,7 @@ impl StateInner {
|
||||
offset_in_item: autoscroll_bounds.top() - item_origin.y,
|
||||
});
|
||||
} else if autoscroll_bounds.bottom() > bounds.bottom() {
|
||||
let mut cursor = self.items.cursor::<Count>();
|
||||
let mut cursor = self.items.cursor::<Count>(&());
|
||||
cursor.seek(&Count(item.index), Bias::Right, &());
|
||||
let mut height = bounds.size.height - padding.top - padding.bottom;
|
||||
|
||||
@@ -883,6 +883,10 @@ impl sum_tree::Item for ListItem {
|
||||
impl sum_tree::Summary for ListItemSummary {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||
self.count += summary.count;
|
||||
self.rendered_count += summary.rendered_count;
|
||||
@@ -893,12 +897,20 @@ impl sum_tree::Summary for ListItemSummary {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Count {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||
self.0 += summary.count;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||
self.0 += summary.height;
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@ pub use executor::*;
|
||||
pub use geometry::*;
|
||||
pub use global::*;
|
||||
pub use gpui_macros::{register_action, test, IntoElement, Render};
|
||||
pub use http_client;
|
||||
pub use input::*;
|
||||
pub use interactive::*;
|
||||
use key_dispatch::*;
|
||||
|
||||
@@ -488,8 +488,8 @@ fn fs_underline(input: UnderlineVarying) -> @location(0) vec4<f32> {
|
||||
|
||||
let half_thickness = underline.thickness * 0.5;
|
||||
let st = (input.position.xy - underline.bounds.origin) / underline.bounds.size.y - vec2<f32>(0.0, 0.5);
|
||||
let frequency = M_PI_F * 3.0 * underline.thickness / 8.0;
|
||||
let amplitude = 1.0 / (2.0 * underline.thickness);
|
||||
let frequency = M_PI_F * 3.0 * underline.thickness / 3.0;
|
||||
let amplitude = 1.0 / (4.0 * underline.thickness);
|
||||
let sine = sin(st.x * frequency) * amplitude;
|
||||
let dSine = cos(st.x * frequency) * amplitude * frequency;
|
||||
let distance = (st.y - sine) / sqrt(1.0 + dSine * dSine);
|
||||
|
||||
@@ -3,51 +3,39 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use async_task::Runnable;
|
||||
use flume::Sender;
|
||||
use parking::Parker;
|
||||
use parking_lot::Mutex;
|
||||
use util::ResultExt;
|
||||
use windows::{
|
||||
Foundation::TimeSpan,
|
||||
System::{
|
||||
DispatcherQueue, DispatcherQueueController, DispatcherQueueHandler,
|
||||
Threading::{
|
||||
ThreadPool, ThreadPoolTimer, TimerElapsedHandler, WorkItemHandler, WorkItemOptions,
|
||||
WorkItemPriority,
|
||||
},
|
||||
},
|
||||
Win32::System::WinRT::{
|
||||
CreateDispatcherQueueController, DispatcherQueueOptions, DQTAT_COM_NONE,
|
||||
DQTYPE_THREAD_CURRENT,
|
||||
System::Threading::{
|
||||
ThreadPool, ThreadPoolTimer, TimerElapsedHandler, WorkItemHandler, WorkItemOptions,
|
||||
WorkItemPriority,
|
||||
},
|
||||
Win32::{Foundation::HANDLE, System::Threading::SetEvent},
|
||||
};
|
||||
|
||||
use crate::{PlatformDispatcher, TaskLabel};
|
||||
use crate::{PlatformDispatcher, SafeHandle, TaskLabel};
|
||||
|
||||
pub(crate) struct WindowsDispatcher {
|
||||
controller: DispatcherQueueController,
|
||||
main_queue: DispatcherQueue,
|
||||
main_sender: Sender<Runnable>,
|
||||
dispatch_event: SafeHandle,
|
||||
parker: Mutex<Parker>,
|
||||
main_thread_id: ThreadId,
|
||||
}
|
||||
|
||||
impl WindowsDispatcher {
|
||||
pub(crate) fn new() -> Self {
|
||||
let controller = unsafe {
|
||||
let options = DispatcherQueueOptions {
|
||||
dwSize: std::mem::size_of::<DispatcherQueueOptions>() as u32,
|
||||
threadType: DQTYPE_THREAD_CURRENT,
|
||||
apartmentType: DQTAT_COM_NONE,
|
||||
};
|
||||
CreateDispatcherQueueController(options).unwrap()
|
||||
};
|
||||
let main_queue = controller.DispatcherQueue().unwrap();
|
||||
pub(crate) fn new(main_sender: Sender<Runnable>, dispatch_event: HANDLE) -> Self {
|
||||
let dispatch_event = dispatch_event.into();
|
||||
let parker = Mutex::new(Parker::new());
|
||||
let main_thread_id = current().id();
|
||||
|
||||
WindowsDispatcher {
|
||||
controller,
|
||||
main_queue,
|
||||
main_sender,
|
||||
dispatch_event,
|
||||
parker,
|
||||
main_thread_id,
|
||||
}
|
||||
@@ -86,12 +74,6 @@ impl WindowsDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WindowsDispatcher {
|
||||
fn drop(&mut self) {
|
||||
self.controller.ShutdownQueueAsync().log_err();
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformDispatcher for WindowsDispatcher {
|
||||
fn is_main_thread(&self) -> bool {
|
||||
current().id() == self.main_thread_id
|
||||
@@ -105,14 +87,11 @@ impl PlatformDispatcher for WindowsDispatcher {
|
||||
}
|
||||
|
||||
fn dispatch_on_main_thread(&self, runnable: Runnable) {
|
||||
let handler = {
|
||||
let mut task_wrapper = Some(runnable);
|
||||
DispatcherQueueHandler::new(move || {
|
||||
task_wrapper.take().unwrap().run();
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
self.main_queue.TryEnqueue(&handler).log_err();
|
||||
self.main_sender
|
||||
.send(runnable)
|
||||
.context("Dispatch on main thread failed")
|
||||
.log_err();
|
||||
unsafe { SetEvent(*self.dispatch_event).log_err() };
|
||||
}
|
||||
|
||||
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
|
||||
|
||||
@@ -177,6 +177,9 @@ fn handle_timer_msg(
|
||||
state_ptr: Rc<WindowsWindowStatePtr>,
|
||||
) -> Option<isize> {
|
||||
if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
|
||||
for runnable in state_ptr.main_receiver.drain() {
|
||||
runnable.run();
|
||||
}
|
||||
handle_paint_msg(handle, state_ptr)
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::{
|
||||
|
||||
use ::util::ResultExt;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_task::Runnable;
|
||||
use futures::channel::oneshot::{self, Receiver};
|
||||
use itertools::Itertools;
|
||||
use parking_lot::RwLock;
|
||||
@@ -46,6 +47,8 @@ pub(crate) struct WindowsPlatform {
|
||||
raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
|
||||
// The below members will never change throughout the entire lifecycle of the app.
|
||||
icon: HICON,
|
||||
main_receiver: flume::Receiver<Runnable>,
|
||||
dispatch_event: HANDLE,
|
||||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
text_system: Arc<DirectWriteTextSystem>,
|
||||
@@ -89,7 +92,9 @@ impl WindowsPlatform {
|
||||
unsafe {
|
||||
OleInitialize(None).expect("unable to initialize Windows OLE");
|
||||
}
|
||||
let dispatcher = Arc::new(WindowsDispatcher::new());
|
||||
let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
|
||||
let dispatch_event = unsafe { CreateEventW(None, false, false, None) }.unwrap();
|
||||
let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, dispatch_event));
|
||||
let background_executor = BackgroundExecutor::new(dispatcher.clone());
|
||||
let foreground_executor = ForegroundExecutor::new(dispatcher);
|
||||
let bitmap_factory = ManuallyDrop::new(unsafe {
|
||||
@@ -113,6 +118,8 @@ impl WindowsPlatform {
|
||||
state,
|
||||
raw_window_handles,
|
||||
icon,
|
||||
main_receiver,
|
||||
dispatch_event,
|
||||
background_executor,
|
||||
foreground_executor,
|
||||
text_system,
|
||||
@@ -176,6 +183,24 @@ impl WindowsPlatform {
|
||||
|
||||
lock.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn run_foreground_tasks(&self) {
|
||||
for runnable in self.main_receiver.drain() {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_creation_info(&self) -> WindowCreationInfo {
|
||||
WindowCreationInfo {
|
||||
icon: self.icon,
|
||||
executor: self.foreground_executor.clone(),
|
||||
current_cursor: self.state.borrow().current_cursor,
|
||||
windows_version: self.windows_version,
|
||||
validation_number: self.validation_number,
|
||||
main_receiver: self.main_receiver.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Platform for WindowsPlatform {
|
||||
@@ -197,16 +222,21 @@ impl Platform for WindowsPlatform {
|
||||
begin_vsync(*vsync_event);
|
||||
'a: loop {
|
||||
let wait_result = unsafe {
|
||||
MsgWaitForMultipleObjects(Some(&[*vsync_event]), false, INFINITE, QS_ALLINPUT)
|
||||
MsgWaitForMultipleObjects(
|
||||
Some(&[*vsync_event, self.dispatch_event]),
|
||||
false,
|
||||
INFINITE,
|
||||
QS_ALLINPUT,
|
||||
)
|
||||
};
|
||||
|
||||
match wait_result {
|
||||
// compositor clock ticked so we should draw a frame
|
||||
WAIT_EVENT(0) => {
|
||||
self.redraw_all();
|
||||
}
|
||||
WAIT_EVENT(0) => self.redraw_all(),
|
||||
// foreground tasks are dispatched
|
||||
WAIT_EVENT(1) => self.run_foreground_tasks(),
|
||||
// Windows thread messages are posted
|
||||
WAIT_EVENT(1) => {
|
||||
WAIT_EVENT(2) => {
|
||||
let mut msg = MSG::default();
|
||||
unsafe {
|
||||
while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
|
||||
@@ -230,6 +260,8 @@ impl Platform for WindowsPlatform {
|
||||
}
|
||||
}
|
||||
}
|
||||
// foreground tasks may have been queued in the message handlers
|
||||
self.run_foreground_tasks();
|
||||
}
|
||||
_ => {
|
||||
log::error!("Something went wrong while waiting {:?}", wait_result);
|
||||
@@ -319,17 +351,7 @@ impl Platform for WindowsPlatform {
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowParams,
|
||||
) -> Result<Box<dyn PlatformWindow>> {
|
||||
let lock = self.state.borrow();
|
||||
let window = WindowsWindow::new(
|
||||
handle,
|
||||
options,
|
||||
self.icon,
|
||||
self.foreground_executor.clone(),
|
||||
lock.current_cursor,
|
||||
self.windows_version,
|
||||
self.validation_number,
|
||||
)?;
|
||||
drop(lock);
|
||||
let window = WindowsWindow::new(handle, options, self.generate_creation_info())?;
|
||||
let handle = window.get_raw_handle();
|
||||
self.raw_window_handles.write().push(handle);
|
||||
|
||||
@@ -558,6 +580,15 @@ impl Drop for WindowsPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WindowCreationInfo {
|
||||
pub(crate) icon: HICON,
|
||||
pub(crate) executor: ForegroundExecutor,
|
||||
pub(crate) current_cursor: HCURSOR,
|
||||
pub(crate) windows_version: WindowsVersion,
|
||||
pub(crate) validation_number: usize,
|
||||
pub(crate) main_receiver: flume::Receiver<Runnable>,
|
||||
}
|
||||
|
||||
fn open_target(target: &str) {
|
||||
unsafe {
|
||||
let ret = ShellExecuteW(
|
||||
@@ -631,22 +662,33 @@ fn file_open_dialog(options: PathPromptOptions) -> Result<Option<Vec<PathBuf>>>
|
||||
|
||||
fn file_save_dialog(directory: PathBuf) -> Result<Option<PathBuf>> {
|
||||
let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? };
|
||||
if let Some(full_path) = directory.canonicalize().log_err() {
|
||||
let full_path = full_path.to_string_lossy().to_string();
|
||||
if !full_path.is_empty() {
|
||||
let path_item: IShellItem =
|
||||
unsafe { SHCreateItemFromParsingName(&HSTRING::from(&full_path), None)? };
|
||||
unsafe { dialog.SetFolder(&path_item).log_err() };
|
||||
if !directory.to_string_lossy().is_empty() {
|
||||
if let Some(full_path) = directory.canonicalize().log_err() {
|
||||
let full_path = full_path.to_string_lossy().to_string();
|
||||
if !full_path.is_empty() {
|
||||
let path_item: IShellItem =
|
||||
unsafe { SHCreateItemFromParsingName(&HSTRING::from(&full_path), None)? };
|
||||
unsafe { dialog.SetFolder(&path_item).log_err() };
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
dialog.SetFileTypes(&[Common::COMDLG_FILTERSPEC {
|
||||
pszName: windows::core::w!("All files"),
|
||||
pszSpec: windows::core::w!("*.*"),
|
||||
}])?;
|
||||
if dialog.Show(None).is_err() {
|
||||
// User cancelled
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
let shell_item = unsafe { dialog.GetResult()? };
|
||||
let file_path_string = unsafe { shell_item.GetDisplayName(SIGDN_FILESYSPATH)?.to_string()? };
|
||||
let file_path_string = unsafe {
|
||||
let pwstr = shell_item.GetDisplayName(SIGDN_FILESYSPATH)?;
|
||||
let string = pwstr.to_string()?;
|
||||
CoTaskMemFree(Some(pwstr.0 as _));
|
||||
string
|
||||
};
|
||||
Ok(Some(PathBuf::from(file_path_string)))
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ use std::{
|
||||
|
||||
use ::util::ResultExt;
|
||||
use anyhow::{Context, Result};
|
||||
use async_task::Runnable;
|
||||
use futures::channel::oneshot::{self, Receiver};
|
||||
use itertools::Itertools;
|
||||
use raw_window_handle as rwh;
|
||||
@@ -63,6 +64,7 @@ pub(crate) struct WindowsWindowStatePtr {
|
||||
pub(crate) executor: ForegroundExecutor,
|
||||
pub(crate) windows_version: WindowsVersion,
|
||||
pub(crate) validation_number: usize,
|
||||
pub(crate) main_receiver: flume::Receiver<Runnable>,
|
||||
}
|
||||
|
||||
impl WindowsWindowState {
|
||||
@@ -226,6 +228,7 @@ impl WindowsWindowStatePtr {
|
||||
executor: context.executor.clone(),
|
||||
windows_version: context.windows_version,
|
||||
validation_number: context.validation_number,
|
||||
main_receiver: context.main_receiver.clone(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -253,18 +256,23 @@ struct WindowCreateContext {
|
||||
current_cursor: HCURSOR,
|
||||
windows_version: WindowsVersion,
|
||||
validation_number: usize,
|
||||
main_receiver: flume::Receiver<Runnable>,
|
||||
}
|
||||
|
||||
impl WindowsWindow {
|
||||
pub(crate) fn new(
|
||||
handle: AnyWindowHandle,
|
||||
params: WindowParams,
|
||||
icon: HICON,
|
||||
executor: ForegroundExecutor,
|
||||
current_cursor: HCURSOR,
|
||||
windows_version: WindowsVersion,
|
||||
validation_number: usize,
|
||||
creation_info: WindowCreationInfo,
|
||||
) -> Result<Self> {
|
||||
let WindowCreationInfo {
|
||||
icon,
|
||||
executor,
|
||||
current_cursor,
|
||||
windows_version,
|
||||
validation_number,
|
||||
main_receiver,
|
||||
} = creation_info;
|
||||
let classname = register_wnd_class(icon);
|
||||
let hide_title_bar = params
|
||||
.titlebar
|
||||
@@ -305,6 +313,7 @@ impl WindowsWindow {
|
||||
current_cursor,
|
||||
windows_version,
|
||||
validation_number,
|
||||
main_receiver,
|
||||
};
|
||||
let lpparam = Some(&context as *const _ as *const _);
|
||||
let creation_result = unsafe {
|
||||
|
||||
@@ -16,13 +16,12 @@ path = "src/http_client.rs"
|
||||
doctest = true
|
||||
|
||||
[dependencies]
|
||||
http = "1.0.0"
|
||||
http = "0.2"
|
||||
anyhow.workspace = true
|
||||
derive_more.workspace = true
|
||||
futures.workspace = true
|
||||
isahc.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
futures-lite.workspace = true
|
||||
smol.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
109
crates/http_client/src/async_body.rs
Normal file
109
crates/http_client/src/async_body.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
use std::{borrow::Cow, io::Read, pin::Pin, task::Poll};
|
||||
|
||||
use futures::{AsyncRead, AsyncReadExt};
|
||||
|
||||
/// Based on the implementation of AsyncBody in
|
||||
/// https://github.com/sagebind/isahc/blob/5c533f1ef4d6bdf1fd291b5103c22110f41d0bf0/src/body/mod.rs
|
||||
pub struct AsyncBody(pub Inner);
|
||||
|
||||
pub enum Inner {
|
||||
/// An empty body.
|
||||
Empty,
|
||||
|
||||
/// A body stored in memory.
|
||||
SyncReader(std::io::Cursor<Cow<'static, [u8]>>),
|
||||
|
||||
/// An asynchronous reader.
|
||||
AsyncReader(Pin<Box<dyn futures::AsyncRead + Send + Sync>>),
|
||||
}
|
||||
|
||||
impl AsyncBody {
|
||||
/// Create a new empty body.
|
||||
///
|
||||
/// An empty body represents the *absence* of a body, which is semantically
|
||||
/// different than the presence of a body of zero length.
|
||||
pub fn empty() -> Self {
|
||||
Self(Inner::Empty)
|
||||
}
|
||||
/// Create a streaming body that reads from the given reader.
|
||||
pub fn from_reader<R>(read: R) -> Self
|
||||
where
|
||||
R: AsyncRead + Send + Sync + 'static,
|
||||
{
|
||||
Self(Inner::AsyncReader(Box::pin(read)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AsyncBody {
|
||||
fn default() -> Self {
|
||||
Self(Inner::Empty)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for AsyncBody {
|
||||
fn from(_: ()) -> Self {
|
||||
Self(Inner::Empty)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for AsyncBody {
|
||||
fn from(body: Vec<u8>) -> Self {
|
||||
Self(Inner::SyncReader(std::io::Cursor::new(Cow::Owned(body))))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'_ [u8]> for AsyncBody {
|
||||
fn from(body: &[u8]) -> Self {
|
||||
body.to_vec().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for AsyncBody {
|
||||
fn from(body: String) -> Self {
|
||||
body.into_bytes().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'_ str> for AsyncBody {
|
||||
fn from(body: &str) -> Self {
|
||||
body.as_bytes().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<Self>> From<Option<T>> for AsyncBody {
|
||||
fn from(body: Option<T>) -> Self {
|
||||
match body {
|
||||
Some(body) => body.into(),
|
||||
None => Self(Inner::Empty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::io::Read for AsyncBody {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
match &mut self.0 {
|
||||
Inner::Empty => Ok(0),
|
||||
Inner::SyncReader(cursor) => cursor.read(buf),
|
||||
Inner::AsyncReader(async_reader) => smol::block_on(async_reader.read(buf)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl futures::AsyncRead for AsyncBody {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> std::task::Poll<std::io::Result<usize>> {
|
||||
// SAFETY: Standard Enum pin projection
|
||||
let inner = unsafe { &mut self.get_unchecked_mut().0 };
|
||||
match inner {
|
||||
Inner::Empty => Poll::Ready(Ok(0)),
|
||||
// Blocking call is over an in-memory buffer
|
||||
Inner::SyncReader(cursor) => Poll::Ready(cursor.read(buf)),
|
||||
Inner::AsyncReader(async_reader) => {
|
||||
AsyncRead::poll_read(async_reader.as_mut(), cx, buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ pub async fn latest_github_release(
|
||||
) -> Result<GithubRelease, anyhow::Error> {
|
||||
let mut response = http
|
||||
.get(
|
||||
&format!("https://api.github.com/repos/{repo_name_with_owner}/releases"),
|
||||
format!("https://api.github.com/repos/{repo_name_with_owner}/releases").as_str(),
|
||||
Default::default(),
|
||||
true,
|
||||
)
|
||||
@@ -91,13 +91,14 @@ pub async fn get_release_by_tag_name(
|
||||
.context("error fetching latest release")?;
|
||||
|
||||
let mut body = Vec::new();
|
||||
let status = response.status();
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut body)
|
||||
.await
|
||||
.context("error reading latest release")?;
|
||||
|
||||
if response.status().is_client_error() {
|
||||
if status.is_client_error() {
|
||||
let text = String::from_utf8_lossy(body.as_slice());
|
||||
bail!(
|
||||
"status error {}, response: {text:?}",
|
||||
|
||||
@@ -1,46 +1,48 @@
|
||||
mod async_body;
|
||||
pub mod github;
|
||||
|
||||
pub use anyhow::{anyhow, Result};
|
||||
pub use async_body::{AsyncBody, Inner};
|
||||
use derive_more::Deref;
|
||||
pub use http::{self, Method, Request, Response, StatusCode, Uri};
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use futures_lite::FutureExt;
|
||||
use isahc::config::{Configurable, RedirectPolicy};
|
||||
pub use isahc::{
|
||||
http::{Method, StatusCode, Uri},
|
||||
AsyncBody, Error, HttpClient as IsahcHttpClient, Request, Response,
|
||||
};
|
||||
use http::request::Builder;
|
||||
#[cfg(feature = "test-support")]
|
||||
use std::fmt;
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
pub use url::Url;
|
||||
|
||||
pub trait HttpClient: Send + Sync {
|
||||
pub trait HttpClient: 'static + Send + Sync {
|
||||
fn send(
|
||||
&self,
|
||||
req: http::Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
self.send_with_redirect_policy(req, false)
|
||||
}
|
||||
|
||||
// TODO: Make a better API for this
|
||||
fn send_with_redirect_policy(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>>;
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>>;
|
||||
|
||||
fn get<'a>(
|
||||
&'a self,
|
||||
uri: &str,
|
||||
body: AsyncBody,
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
|
||||
let request = isahc::Request::builder()
|
||||
.redirect_policy(if follow_redirects {
|
||||
RedirectPolicy::Follow
|
||||
} else {
|
||||
RedirectPolicy::None
|
||||
})
|
||||
.method(Method::GET)
|
||||
.uri(uri)
|
||||
.body(body);
|
||||
) -> BoxFuture<'a, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
let request = Builder::new().uri(uri).body(body);
|
||||
|
||||
match request {
|
||||
Ok(request) => self.send(request),
|
||||
Err(error) => async move { Err(error.into()) }.boxed(),
|
||||
Ok(request) => Box::pin(async move {
|
||||
self.send_with_redirect_policy(request, follow_redirects)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}),
|
||||
Err(e) => Box::pin(async move { Err(e.into()) }),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,15 +50,16 @@ pub trait HttpClient: Send + Sync {
|
||||
&'a self,
|
||||
uri: &str,
|
||||
body: AsyncBody,
|
||||
) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
|
||||
let request = isahc::Request::builder()
|
||||
.method(Method::POST)
|
||||
) -> BoxFuture<'a, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
let request = Builder::new()
|
||||
.uri(uri)
|
||||
.method(Method::POST)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(body);
|
||||
|
||||
match request {
|
||||
Ok(request) => self.send(request),
|
||||
Err(error) => async move { Err(error.into()) }.boxed(),
|
||||
Ok(request) => Box::pin(async move { self.send(request).await.map_err(Into::into) }),
|
||||
Err(e) => Box::pin(async move { Err(e.into()) }),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,29 +76,28 @@ pub struct HttpClientWithProxy {
|
||||
|
||||
impl HttpClientWithProxy {
|
||||
/// Returns a new [`HttpClientWithProxy`] with the given proxy URL.
|
||||
pub fn new(user_agent: Option<String>, proxy_url: Option<String>) -> Self {
|
||||
let proxy_url = proxy_url
|
||||
.and_then(|input| {
|
||||
input
|
||||
.parse::<Uri>()
|
||||
.inspect_err(|e| log::error!("Error parsing proxy settings: {}", e))
|
||||
.ok()
|
||||
})
|
||||
pub fn new(client: Arc<dyn HttpClient>, proxy_url: Option<String>) -> Self {
|
||||
let proxy_uri = proxy_url
|
||||
.and_then(|proxy| proxy.parse().ok())
|
||||
.or_else(read_proxy_from_env);
|
||||
|
||||
Self::new_uri(client, proxy_uri)
|
||||
}
|
||||
pub fn new_uri(client: Arc<dyn HttpClient>, proxy_uri: Option<Uri>) -> Self {
|
||||
Self {
|
||||
client: client(user_agent, proxy_url.clone()),
|
||||
proxy: proxy_url,
|
||||
client,
|
||||
proxy: proxy_uri,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClient for HttpClientWithProxy {
|
||||
fn send(
|
||||
fn send_with_redirect_policy(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
|
||||
self.client.send(req)
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
self.client.send_with_redirect_policy(req, follow_redirects)
|
||||
}
|
||||
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
@@ -104,11 +106,12 @@ impl HttpClient for HttpClientWithProxy {
|
||||
}
|
||||
|
||||
impl HttpClient for Arc<HttpClientWithProxy> {
|
||||
fn send(
|
||||
fn send_with_redirect_policy(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
|
||||
self.client.send(req)
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
self.client.send_with_redirect_policy(req, follow_redirects)
|
||||
}
|
||||
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
@@ -122,14 +125,35 @@ pub struct HttpClientWithUrl {
|
||||
client: HttpClientWithProxy,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for HttpClientWithUrl {
|
||||
type Target = HttpClientWithProxy;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClientWithUrl {
|
||||
/// Returns a new [`HttpClientWithUrl`] with the given base URL.
|
||||
pub fn new(
|
||||
client: Arc<dyn HttpClient>,
|
||||
base_url: impl Into<String>,
|
||||
user_agent: Option<String>,
|
||||
proxy_url: Option<String>,
|
||||
) -> Self {
|
||||
let client = HttpClientWithProxy::new(user_agent, proxy_url);
|
||||
let client = HttpClientWithProxy::new(client, proxy_url);
|
||||
|
||||
Self {
|
||||
base_url: Mutex::new(base_url.into()),
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_uri(
|
||||
client: Arc<dyn HttpClient>,
|
||||
base_url: impl Into<String>,
|
||||
proxy_uri: Option<Uri>,
|
||||
) -> Self {
|
||||
let client = HttpClientWithProxy::new_uri(client, proxy_uri);
|
||||
|
||||
Self {
|
||||
base_url: Mutex::new(base_url.into()),
|
||||
@@ -194,11 +218,12 @@ impl HttpClientWithUrl {
|
||||
}
|
||||
|
||||
impl HttpClient for Arc<HttpClientWithUrl> {
|
||||
fn send(
|
||||
fn send_with_redirect_policy(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
|
||||
self.client.send(req)
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
self.client.send_with_redirect_policy(req, follow_redirects)
|
||||
}
|
||||
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
@@ -207,11 +232,12 @@ impl HttpClient for Arc<HttpClientWithUrl> {
|
||||
}
|
||||
|
||||
impl HttpClient for HttpClientWithUrl {
|
||||
fn send(
|
||||
fn send_with_redirect_policy(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
|
||||
self.client.send(req)
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
self.client.send_with_redirect_policy(req, follow_redirects)
|
||||
}
|
||||
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
@@ -219,26 +245,7 @@ impl HttpClient for HttpClientWithUrl {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client(user_agent: Option<String>, proxy: Option<Uri>) -> Arc<dyn HttpClient> {
|
||||
let mut builder = isahc::HttpClient::builder()
|
||||
// Some requests to Qwen2 models on Runpod can take 32+ seconds,
|
||||
// especially if there's a cold boot involved. We may need to have
|
||||
// those requests use a different http client, because global timeouts
|
||||
// of 50 and 60 seconds, respectively, would be very high!
|
||||
.connect_timeout(Duration::from_secs(5))
|
||||
.low_speed_timeout(100, Duration::from_secs(5))
|
||||
.proxy(proxy.clone());
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.default_header("User-Agent", user_agent);
|
||||
}
|
||||
|
||||
Arc::new(HttpClientWithProxy {
|
||||
client: Arc::new(builder.build().unwrap()),
|
||||
proxy,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_proxy_from_env() -> Option<Uri> {
|
||||
pub fn read_proxy_from_env() -> Option<Uri> {
|
||||
const ENV_VARS: &[&str] = &[
|
||||
"ALL_PROXY",
|
||||
"all_proxy",
|
||||
@@ -257,23 +264,9 @@ fn read_proxy_from_env() -> Option<Uri> {
|
||||
None
|
||||
}
|
||||
|
||||
impl HttpClient for isahc::HttpClient {
|
||||
fn send(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
|
||||
let client = self.clone();
|
||||
Box::pin(async move { client.send_async(req).await })
|
||||
}
|
||||
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
type FakeHttpHandler = Box<
|
||||
dyn Fn(Request<AsyncBody>) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>>
|
||||
dyn Fn(Request<AsyncBody>) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
@@ -288,7 +281,7 @@ pub struct FakeHttpClient {
|
||||
impl FakeHttpClient {
|
||||
pub fn create<Fut, F>(handler: F) -> Arc<HttpClientWithUrl>
|
||||
where
|
||||
Fut: futures::Future<Output = Result<Response<AsyncBody>, Error>> + Send + 'static,
|
||||
Fut: futures::Future<Output = Result<Response<AsyncBody>, anyhow::Error>> + Send + 'static,
|
||||
F: Fn(Request<AsyncBody>) -> Fut + Send + Sync + 'static,
|
||||
{
|
||||
Arc::new(HttpClientWithUrl {
|
||||
@@ -330,12 +323,13 @@ impl fmt::Debug for FakeHttpClient {
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
impl HttpClient for FakeHttpClient {
|
||||
fn send(
|
||||
fn send_with_redirect_policy(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
|
||||
_follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
let future = (self.handler)(req);
|
||||
Box::pin(async move { future.await.map(Into::into) })
|
||||
future
|
||||
}
|
||||
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
|
||||
22
crates/isahc_http_client/Cargo.toml
Normal file
22
crates/isahc_http_client/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "isahc_http_client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
[lib]
|
||||
path = "src/isahc_http_client.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc.workspace = true
|
||||
util.workspace = true
|
||||
1
crates/isahc_http_client/LICENSE-APACHE
Symbolic link
1
crates/isahc_http_client/LICENSE-APACHE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-APACHE
|
||||
93
crates/isahc_http_client/src/isahc_http_client.rs
Normal file
93
crates/isahc_http_client/src/isahc_http_client.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use std::{mem, sync::Arc, time::Duration};
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use isahc::config::RedirectPolicy;
|
||||
use util::maybe;
|
||||
|
||||
pub use isahc::config::Configurable;
|
||||
pub struct IsahcHttpClient(isahc::HttpClient);
|
||||
|
||||
pub use http_client::*;
|
||||
|
||||
impl IsahcHttpClient {
|
||||
pub fn new(proxy: Option<Uri>, user_agent: Option<String>) -> Arc<IsahcHttpClient> {
|
||||
let mut builder = isahc::HttpClient::builder()
|
||||
.connect_timeout(Duration::from_secs(5))
|
||||
.low_speed_timeout(100, Duration::from_secs(5))
|
||||
.proxy(proxy.clone());
|
||||
if let Some(agent) = user_agent {
|
||||
builder = builder.default_header("User-Agent", agent);
|
||||
}
|
||||
Arc::new(IsahcHttpClient(builder.build().unwrap()))
|
||||
}
|
||||
pub fn builder() -> isahc::HttpClientBuilder {
|
||||
isahc::HttpClientBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<isahc::HttpClient> for IsahcHttpClient {
|
||||
fn from(client: isahc::HttpClient) -> Self {
|
||||
Self(client)
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClient for IsahcHttpClient {
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
None
|
||||
}
|
||||
|
||||
fn send_with_redirect_policy(
|
||||
&self,
|
||||
req: http_client::http::Request<http_client::AsyncBody>,
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<http_client::Response<http_client::AsyncBody>, anyhow::Error>>
|
||||
{
|
||||
let req = maybe!({
|
||||
let (mut parts, body) = req.into_parts();
|
||||
let mut builder = isahc::Request::builder()
|
||||
.method(parts.method)
|
||||
.uri(parts.uri)
|
||||
.version(parts.version);
|
||||
|
||||
let headers = builder.headers_mut()?;
|
||||
mem::swap(headers, &mut parts.headers);
|
||||
|
||||
let extensions = builder.extensions_mut()?;
|
||||
mem::swap(extensions, &mut parts.extensions);
|
||||
|
||||
let isahc_body = match body.0 {
|
||||
http_client::Inner::Empty => isahc::AsyncBody::empty(),
|
||||
http_client::Inner::AsyncReader(reader) => isahc::AsyncBody::from_reader(reader),
|
||||
http_client::Inner::SyncReader(reader) => {
|
||||
isahc::AsyncBody::from_bytes_static(reader.into_inner())
|
||||
}
|
||||
};
|
||||
|
||||
builder
|
||||
.redirect_policy(if follow_redirects {
|
||||
RedirectPolicy::Follow
|
||||
} else {
|
||||
RedirectPolicy::None
|
||||
})
|
||||
.body(isahc_body)
|
||||
.ok()
|
||||
});
|
||||
|
||||
let client = self.0.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
match req {
|
||||
Some(req) => client
|
||||
.send_async(req)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.map(|response| {
|
||||
let (parts, body) = response.into_parts();
|
||||
let body = http_client::AsyncBody::from_reader(body);
|
||||
http_client::Response::from_parts(parts, body)
|
||||
}),
|
||||
None => Err(anyhow::anyhow!("Request was malformed")),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -722,7 +722,9 @@ impl Buffer {
|
||||
capability: Capability,
|
||||
) -> Self {
|
||||
let saved_mtime = file.as_ref().and_then(|file| file.mtime());
|
||||
|
||||
let snapshot = buffer.snapshot();
|
||||
let git_diff = git::diff::BufferDiff::new(&snapshot);
|
||||
let syntax_map = Mutex::new(SyntaxMap::new(&snapshot));
|
||||
Self {
|
||||
saved_mtime,
|
||||
saved_version: buffer.version(),
|
||||
@@ -739,10 +741,10 @@ impl Buffer {
|
||||
})
|
||||
.map(Rope::from),
|
||||
diff_base_version: 0,
|
||||
git_diff: git::diff::BufferDiff::new(),
|
||||
git_diff,
|
||||
file,
|
||||
capability,
|
||||
syntax_map: Mutex::new(SyntaxMap::new()),
|
||||
syntax_map,
|
||||
parsing_in_background: false,
|
||||
non_text_state_update_count: 0,
|
||||
sync_parse_timeout: Duration::from_millis(1),
|
||||
@@ -809,7 +811,7 @@ impl Buffer {
|
||||
/// Assign a language to the buffer.
|
||||
pub fn set_language(&mut self, language: Option<Arc<Language>>, cx: &mut ModelContext<Self>) {
|
||||
self.non_text_state_update_count += 1;
|
||||
self.syntax_map.lock().clear();
|
||||
self.syntax_map.lock().clear(&self.text);
|
||||
self.language = language;
|
||||
self.reparse(cx);
|
||||
cx.emit(BufferEvent::LanguageChanged);
|
||||
@@ -3025,7 +3027,7 @@ impl BufferSnapshot {
|
||||
let mut start = text.len();
|
||||
let end = start + buffer_range.len();
|
||||
|
||||
// When multiple names are captured, then the matcheable text
|
||||
// When multiple names are captured, then the matchable text
|
||||
// includes the whitespace in between the names.
|
||||
if !name_ranges.is_empty() {
|
||||
start -= 1;
|
||||
|
||||
@@ -382,7 +382,7 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
|
||||
let buffer = cx.new_model(|cx| Buffer::local(text, cx));
|
||||
|
||||
// Spawn a task to format the buffer's whitespace.
|
||||
// Pause so that the foratting task starts running.
|
||||
// Pause so that the formatting task starts running.
|
||||
let format = buffer.update(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));
|
||||
smol::future::yield_now().await;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use text::{Anchor, FromAnchor, PointUtf16, ToOffset};
|
||||
/// The diagnostics are stored in a [`SumTree`], which allows this struct
|
||||
/// to be cheaply copied, and allows for efficient retrieval of the
|
||||
/// diagnostics that intersect a given range of the buffer.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DiagnosticSet {
|
||||
diagnostics: SumTree<DiagnosticEntry<Anchor>>,
|
||||
}
|
||||
@@ -135,7 +135,7 @@ impl DiagnosticSet {
|
||||
{
|
||||
let end_bias = if inclusive { Bias::Right } else { Bias::Left };
|
||||
let range = buffer.anchor_before(range.start)..buffer.anchor_at(range.end, end_bias);
|
||||
let mut cursor = self.diagnostics.filter::<_, ()>({
|
||||
let mut cursor = self.diagnostics.filter::<_, ()>(buffer, {
|
||||
move |summary: &Summary| {
|
||||
let start_cmp = range.start.cmp(&summary.max_end, buffer);
|
||||
let end_cmp = range.end.cmp(&summary.min_start, buffer);
|
||||
@@ -261,6 +261,10 @@ impl Default for Summary {
|
||||
impl sum_tree::Summary for Summary {
|
||||
type Context = text::BufferSnapshot;
|
||||
|
||||
fn zero(_cx: &Self::Context) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
|
||||
if other.min_start.cmp(&self.min_start, buffer).is_lt() {
|
||||
self.min_start = other.min_start;
|
||||
|
||||
@@ -208,7 +208,7 @@ impl CachedLspAdapter {
|
||||
|
||||
pub async fn get_language_server_command(
|
||||
self: Arc<Self>,
|
||||
container_dir: Arc<Path>,
|
||||
container_dir: Option<Arc<Path>>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
@@ -294,7 +294,7 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
|
||||
fn get_language_server_command<'a>(
|
||||
self: Arc<Self>,
|
||||
container_dir: Arc<Path>,
|
||||
container_dir: Option<Arc<Path>>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
|
||||
cx: &'a mut AsyncAppContext,
|
||||
@@ -325,6 +325,10 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
return Ok(cached_binary.clone());
|
||||
}
|
||||
|
||||
let Some(container_dir) = container_dir else {
|
||||
anyhow::bail!("cannot download language servers for remotes (yet)")
|
||||
};
|
||||
|
||||
if !container_dir.exists() {
|
||||
smol::fs::create_dir_all(&container_dir)
|
||||
.await
|
||||
@@ -1664,7 +1668,7 @@ impl LspAdapter for FakeLspAdapter {
|
||||
|
||||
fn get_language_server_command<'a>(
|
||||
self: Arc<Self>,
|
||||
_: Arc<Path>,
|
||||
_: Option<Arc<Path>>,
|
||||
_: Arc<dyn LspAdapterDelegate>,
|
||||
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
|
||||
_: &'a mut AsyncAppContext,
|
||||
|
||||
@@ -869,12 +869,10 @@ impl LanguageRegistry {
|
||||
adapter.name.0
|
||||
);
|
||||
|
||||
let download_dir = &self
|
||||
.language_server_download_dir
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
|
||||
.log_err()?;
|
||||
let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
|
||||
let container_dir: Option<Arc<Path>> = self
|
||||
.language_server_download_dir
|
||||
.as_ref()
|
||||
.map(|dir| Arc::from(dir.join(adapter.name.0.as_ref())));
|
||||
let root_path = root_path.clone();
|
||||
let this = Arc::downgrade(self);
|
||||
|
||||
@@ -969,7 +967,7 @@ impl LanguageRegistry {
|
||||
Some(PendingLanguageServer {
|
||||
server_id,
|
||||
task,
|
||||
container_dir: Some(container_dir),
|
||||
container_dir,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -741,6 +741,14 @@ pub struct InlayHintSettings {
|
||||
/// Default: true
|
||||
#[serde(default = "default_true")]
|
||||
pub show_other_hints: bool,
|
||||
/// Whether to show a background for inlay hints.
|
||||
///
|
||||
/// If set to `true`, the background will use the `hint.background` color
|
||||
/// from the current theme.
|
||||
///
|
||||
/// Default: false
|
||||
#[serde(default)]
|
||||
pub show_background: bool,
|
||||
/// Whether or not to debounce inlay hints updates after buffer edits.
|
||||
///
|
||||
/// Set to 0 to disable debouncing.
|
||||
|
||||
@@ -18,13 +18,12 @@ use sum_tree::{Bias, SeekTarget, SumTree};
|
||||
use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint};
|
||||
use tree_sitter::{Node, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatches, Tree};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SyntaxMap {
|
||||
snapshot: SyntaxSnapshot,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Clone)]
|
||||
pub struct SyntaxSnapshot {
|
||||
layers: SumTree<SyntaxLayerEntry>,
|
||||
parsed_version: clock::Global,
|
||||
@@ -212,8 +211,11 @@ struct ByteChunks<'a>(text::Chunks<'a>);
|
||||
pub(crate) struct QueryCursorHandle(Option<QueryCursor>);
|
||||
|
||||
impl SyntaxMap {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
pub fn new(text: &BufferSnapshot) -> Self {
|
||||
Self {
|
||||
snapshot: SyntaxSnapshot::new(text),
|
||||
language_registry: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_language_registry(&mut self, registry: Arc<LanguageRegistry>) {
|
||||
@@ -242,12 +244,21 @@ impl SyntaxMap {
|
||||
self.snapshot = snapshot;
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.snapshot = SyntaxSnapshot::default();
|
||||
pub fn clear(&mut self, text: &BufferSnapshot) {
|
||||
self.snapshot = SyntaxSnapshot::new(text);
|
||||
}
|
||||
}
|
||||
|
||||
impl SyntaxSnapshot {
|
||||
fn new(text: &BufferSnapshot) -> Self {
|
||||
Self {
|
||||
layers: SumTree::new(text),
|
||||
parsed_version: clock::Global::default(),
|
||||
interpolated_version: clock::Global::default(),
|
||||
language_registry_version: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.layers.is_empty()
|
||||
}
|
||||
@@ -262,10 +273,10 @@ impl SyntaxSnapshot {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut layers = SumTree::new();
|
||||
let mut layers = SumTree::new(text);
|
||||
let mut first_edit_ix_for_depth = 0;
|
||||
let mut prev_depth = 0;
|
||||
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>();
|
||||
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>(text);
|
||||
cursor.next(text);
|
||||
|
||||
'outer: loop {
|
||||
@@ -388,7 +399,7 @@ impl SyntaxSnapshot {
|
||||
let mut resolved_injection_ranges = Vec::new();
|
||||
let mut cursor = self
|
||||
.layers
|
||||
.filter::<_, ()>(|summary| summary.contains_unknown_injections);
|
||||
.filter::<_, ()>(text, |summary| summary.contains_unknown_injections);
|
||||
cursor.next(text);
|
||||
while let Some(layer) = cursor.item() {
|
||||
let SyntaxLayerContent::Pending { language_name } = &layer.content else {
|
||||
@@ -430,9 +441,9 @@ impl SyntaxSnapshot {
|
||||
log::trace!("reparse. invalidated ranges:{:?}", invalidated_ranges);
|
||||
|
||||
let max_depth = self.layers.summary().max_depth;
|
||||
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>();
|
||||
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>(text);
|
||||
cursor.next(text);
|
||||
let mut layers = SumTree::new();
|
||||
let mut layers = SumTree::new(text);
|
||||
|
||||
let mut changed_regions = ChangeRegionSet::default();
|
||||
let mut queue = BinaryHeap::new();
|
||||
@@ -823,7 +834,7 @@ impl SyntaxSnapshot {
|
||||
let start = buffer.anchor_before(start_offset);
|
||||
let end = buffer.anchor_after(end_offset);
|
||||
|
||||
let mut cursor = self.layers.filter::<_, ()>(move |summary| {
|
||||
let mut cursor = self.layers.filter::<_, ()>(buffer, move |summary| {
|
||||
if summary.max_depth > summary.min_depth {
|
||||
true
|
||||
} else {
|
||||
@@ -1666,6 +1677,10 @@ impl Default for SyntaxLayerSummary {
|
||||
impl sum_tree::Summary for SyntaxLayerSummary {
|
||||
type Context = BufferSnapshot;
|
||||
|
||||
fn zero(_cx: &BufferSnapshot) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
|
||||
if other.max_depth > self.max_depth {
|
||||
self.max_depth = other.max_depth;
|
||||
|
||||
@@ -103,7 +103,7 @@ fn test_syntax_map_layers_for_range(cx: &mut AppContext) {
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
let mut syntax_map = SyntaxMap::new();
|
||||
let mut syntax_map = SyntaxMap::new(&buffer);
|
||||
syntax_map.set_language_registry(registry.clone());
|
||||
syntax_map.reparse(language.clone(), &buffer);
|
||||
|
||||
@@ -202,7 +202,7 @@ fn test_dynamic_language_injection(cx: &mut AppContext) {
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
let mut syntax_map = SyntaxMap::new();
|
||||
let mut syntax_map = SyntaxMap::new(&buffer);
|
||||
syntax_map.set_language_registry(registry.clone());
|
||||
syntax_map.reparse(markdown.clone(), &buffer);
|
||||
syntax_map.reparse(markdown_inline.clone(), &buffer);
|
||||
@@ -897,11 +897,11 @@ fn test_random_edits(
|
||||
|
||||
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), text);
|
||||
|
||||
let mut syntax_map = SyntaxMap::new();
|
||||
let mut syntax_map = SyntaxMap::new(&buffer);
|
||||
syntax_map.set_language_registry(registry.clone());
|
||||
syntax_map.reparse(language.clone(), &buffer);
|
||||
|
||||
let mut reference_syntax_map = SyntaxMap::new();
|
||||
let mut reference_syntax_map = SyntaxMap::new(&buffer);
|
||||
reference_syntax_map.set_language_registry(registry.clone());
|
||||
|
||||
log::info!("initial text:\n{}", buffer.text());
|
||||
@@ -918,7 +918,7 @@ fn test_random_edits(
|
||||
|
||||
syntax_map.reparse(language.clone(), &buffer);
|
||||
|
||||
reference_syntax_map.clear();
|
||||
reference_syntax_map.clear(&buffer);
|
||||
reference_syntax_map.reparse(language.clone(), &buffer);
|
||||
}
|
||||
|
||||
@@ -931,7 +931,7 @@ fn test_random_edits(
|
||||
syntax_map.interpolate(&buffer);
|
||||
syntax_map.reparse(language.clone(), &buffer);
|
||||
|
||||
reference_syntax_map.clear();
|
||||
reference_syntax_map.clear(&buffer);
|
||||
reference_syntax_map.reparse(language.clone(), &buffer);
|
||||
assert_eq!(
|
||||
syntax_map.layers(&buffer).len(),
|
||||
@@ -1082,7 +1082,7 @@ fn test_edit_sequence(
|
||||
.unwrap();
|
||||
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), Default::default());
|
||||
|
||||
let mut mutated_syntax_map = SyntaxMap::new();
|
||||
let mut mutated_syntax_map = SyntaxMap::new(&buffer);
|
||||
mutated_syntax_map.set_language_registry(registry.clone());
|
||||
mutated_syntax_map.reparse(language.clone(), &buffer);
|
||||
|
||||
@@ -1097,7 +1097,7 @@ fn test_edit_sequence(
|
||||
|
||||
// Create a second syntax map from scratch
|
||||
log::info!("fresh parse {i}: {marked_string:?}");
|
||||
let mut reference_syntax_map = SyntaxMap::new();
|
||||
let mut reference_syntax_map = SyntaxMap::new(&buffer);
|
||||
reference_syntax_map.set_language_registry(registry.clone());
|
||||
reference_syntax_map.reparse(language.clone(), &buffer);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ pub trait ContextProvider: Send + Sync {
|
||||
&self,
|
||||
_variables: &TaskVariables,
|
||||
_location: &Location,
|
||||
_project_env: Option<&HashMap<String, String>>,
|
||||
_cx: &mut AppContext,
|
||||
) -> Result<TaskVariables> {
|
||||
Ok(TaskVariables::default())
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext, AsyncAppContext, Task};
|
||||
use http_client::github::latest_github_release;
|
||||
@@ -454,6 +455,7 @@ impl ContextProvider for GoContextProvider {
|
||||
&self,
|
||||
variables: &TaskVariables,
|
||||
location: &Location,
|
||||
_: Option<&HashMap<String, String>>,
|
||||
cx: &mut gpui::AppContext,
|
||||
) -> Result<TaskVariables> {
|
||||
let local_abs_path = location
|
||||
@@ -517,7 +519,6 @@ impl ContextProvider for GoContextProvider {
|
||||
command: "go".into(),
|
||||
args: vec![
|
||||
"test".into(),
|
||||
GO_PACKAGE_TASK_VARIABLE.template_value(),
|
||||
"-run".into(),
|
||||
format!("^{}\\$", VariableName::Symbol.template_value(),),
|
||||
],
|
||||
@@ -528,7 +529,7 @@ impl ContextProvider for GoContextProvider {
|
||||
TaskTemplate {
|
||||
label: format!("go test {}", GO_PACKAGE_TASK_VARIABLE.template_value()),
|
||||
command: "go".into(),
|
||||
args: vec!["test".into(), GO_PACKAGE_TASK_VARIABLE.template_value()],
|
||||
args: vec!["test".into()],
|
||||
cwd: package_cwd.clone(),
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
@@ -570,7 +571,6 @@ impl ContextProvider for GoContextProvider {
|
||||
command: "go".into(),
|
||||
args: vec![
|
||||
"test".into(),
|
||||
GO_PACKAGE_TASK_VARIABLE.template_value(),
|
||||
"-benchmem".into(),
|
||||
"-run=^$".into(),
|
||||
"-bench".into(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use gpui::AppContext;
|
||||
use gpui::AsyncAppContext;
|
||||
use language::{ContextProvider, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
@@ -215,6 +216,7 @@ impl ContextProvider for PythonContextProvider {
|
||||
&self,
|
||||
variables: &task::TaskVariables,
|
||||
_location: &project::Location,
|
||||
_: Option<&HashMap<String, String>>,
|
||||
_cx: &mut gpui::AppContext,
|
||||
) -> Result<task::TaskVariables> {
|
||||
let python_module_name = python_module_name_from_relative_path(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use futures::{io::BufReader, StreamExt};
|
||||
use gpui::{AppContext, AsyncAppContext};
|
||||
use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
|
||||
@@ -76,7 +77,7 @@ impl LspAdapter for RustLspAdapter {
|
||||
{
|
||||
Ok(()) => (Some(path), Some(env), None),
|
||||
Err(err) => {
|
||||
log::error!("failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {:?}", path, err);
|
||||
log::error!("failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}", path, err);
|
||||
(None, None, None)
|
||||
}
|
||||
}
|
||||
@@ -434,6 +435,7 @@ impl ContextProvider for RustContextProvider {
|
||||
&self,
|
||||
task_variables: &TaskVariables,
|
||||
location: &Location,
|
||||
project_env: Option<&HashMap<String, String>>,
|
||||
cx: &mut gpui::AppContext,
|
||||
) -> Result<TaskVariables> {
|
||||
let local_abs_path = location
|
||||
@@ -449,8 +451,8 @@ impl ContextProvider for RustContextProvider {
|
||||
.is_some();
|
||||
|
||||
if is_main_function {
|
||||
if let Some((package_name, bin_name)) =
|
||||
local_abs_path.and_then(package_name_and_bin_name_from_abs_path)
|
||||
if let Some((package_name, bin_name)) = local_abs_path
|
||||
.and_then(|path| package_name_and_bin_name_from_abs_path(path, project_env))
|
||||
{
|
||||
return Ok(TaskVariables::from_iter([
|
||||
(RUST_PACKAGE_TASK_VARIABLE.clone(), package_name),
|
||||
@@ -461,7 +463,7 @@ impl ContextProvider for RustContextProvider {
|
||||
|
||||
if let Some(package_name) = local_abs_path
|
||||
.and_then(|local_abs_path| local_abs_path.parent())
|
||||
.and_then(human_readable_package_name)
|
||||
.and_then(|path| human_readable_package_name(path, project_env))
|
||||
{
|
||||
return Ok(TaskVariables::from_iter([(
|
||||
RUST_PACKAGE_TASK_VARIABLE.clone(),
|
||||
@@ -615,8 +617,15 @@ struct CargoTarget {
|
||||
src_path: String,
|
||||
}
|
||||
|
||||
fn package_name_and_bin_name_from_abs_path(abs_path: &Path) -> Option<(String, String)> {
|
||||
let output = std::process::Command::new("cargo")
|
||||
fn package_name_and_bin_name_from_abs_path(
|
||||
abs_path: &Path,
|
||||
project_env: Option<&HashMap<String, String>>,
|
||||
) -> Option<(String, String)> {
|
||||
let mut command = std::process::Command::new("cargo");
|
||||
if let Some(envs) = project_env {
|
||||
command.envs(envs);
|
||||
}
|
||||
let output = command
|
||||
.current_dir(abs_path.parent()?)
|
||||
.arg("metadata")
|
||||
.arg("--no-deps")
|
||||
@@ -654,9 +663,17 @@ fn retrieve_package_id_and_bin_name_from_metadata(
|
||||
None
|
||||
}
|
||||
|
||||
fn human_readable_package_name(package_directory: &Path) -> Option<String> {
|
||||
fn human_readable_package_name(
|
||||
package_directory: &Path,
|
||||
project_env: Option<&HashMap<String, String>>,
|
||||
) -> Option<String> {
|
||||
let mut command = std::process::Command::new("cargo");
|
||||
if let Some(envs) = project_env {
|
||||
command.envs(envs);
|
||||
}
|
||||
|
||||
let pkgid = String::from_utf8(
|
||||
std::process::Command::new("cargo")
|
||||
command
|
||||
.current_dir(package_directory)
|
||||
.arg("pkgid")
|
||||
.output()
|
||||
|
||||
@@ -139,7 +139,6 @@
|
||||
] @comment.doc
|
||||
|
||||
[
|
||||
"!"
|
||||
"!="
|
||||
"%"
|
||||
"%="
|
||||
@@ -159,7 +158,6 @@
|
||||
".."
|
||||
"..="
|
||||
"..."
|
||||
"/"
|
||||
"/="
|
||||
":"
|
||||
";"
|
||||
@@ -183,6 +181,10 @@
|
||||
"?"
|
||||
] @operator
|
||||
|
||||
; Avoid highlighting these as operators when used in doc comments.
|
||||
(unary_expression "!" @operator)
|
||||
operator: "/" @operator
|
||||
|
||||
(lifetime) @lifetime
|
||||
|
||||
(parameter (identifier) @variable.parameter)
|
||||
|
||||
@@ -18,20 +18,15 @@ use std::{
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
const SERVER_PATH: &str = "node_modules/.bin/tailwindcss-language-server.ps1";
|
||||
const SERVER_PATH: &str =
|
||||
"node_modules/@tailwindcss/language-server/bin/tailwindcss-language-server";
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
const SERVER_PATH: &str = "node_modules/.bin/tailwindcss-language-server";
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec!["-File".into(), server_path.into(), "--stdio".into()]
|
||||
}
|
||||
|
||||
pub struct TailwindLspAdapter {
|
||||
node: Arc<dyn NodeRuntime>,
|
||||
}
|
||||
@@ -114,26 +109,11 @@ impl LspAdapter for TailwindLspAdapter {
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let env_path = self.node.node_environment_path().await?;
|
||||
let mut env = HashMap::default();
|
||||
env.insert("PATH".to_string(), env_path.to_string_lossy().to_string());
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: "powershell.exe".into(),
|
||||
env: Some(env),
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
}
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
|
||||
@@ -561,7 +561,7 @@ impl MultiBuffer {
|
||||
}
|
||||
let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default();
|
||||
let mut edited_excerpt_ids = Vec::new();
|
||||
let mut cursor = snapshot.excerpts.cursor::<usize>();
|
||||
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
|
||||
for (ix, (range, new_text)) in edits.enumerate() {
|
||||
let new_text: Arc<str> = new_text.into();
|
||||
let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0);
|
||||
@@ -841,7 +841,7 @@ impl MultiBuffer {
|
||||
let mut ranges = Vec::new();
|
||||
let snapshot = self.read(cx);
|
||||
let buffers = self.buffers.borrow();
|
||||
let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>();
|
||||
let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(&());
|
||||
|
||||
for (buffer_id, buffer_transaction) in &transaction.buffer_transactions {
|
||||
let Some(buffer_state) = buffers.get(buffer_id) else {
|
||||
@@ -957,7 +957,7 @@ impl MultiBuffer {
|
||||
let mut selections_by_buffer: HashMap<BufferId, Vec<Selection<text::Anchor>>> =
|
||||
Default::default();
|
||||
let snapshot = self.read(cx);
|
||||
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
|
||||
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(&());
|
||||
for selection in selections {
|
||||
let start_locator = snapshot.excerpt_locator_for_id(selection.start.excerpt_id);
|
||||
let end_locator = snapshot.excerpt_locator_for_id(selection.end.excerpt_id);
|
||||
@@ -1281,7 +1281,7 @@ impl MultiBuffer {
|
||||
|
||||
let mut prev_locator = snapshot.excerpt_locator_for_id(prev_excerpt_id).clone();
|
||||
let mut new_excerpt_ids = mem::take(&mut snapshot.excerpt_ids);
|
||||
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
|
||||
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(&());
|
||||
let mut new_excerpts = cursor.slice(&prev_locator, Bias::Right, &());
|
||||
prev_locator = cursor.start().unwrap_or(Locator::min_ref()).clone();
|
||||
|
||||
@@ -1388,7 +1388,7 @@ impl MultiBuffer {
|
||||
let mut excerpts = Vec::new();
|
||||
let snapshot = self.read(cx);
|
||||
let buffers = self.buffers.borrow();
|
||||
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
|
||||
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(&());
|
||||
for locator in buffers
|
||||
.get(&buffer.read(cx).remote_id())
|
||||
.map(|state| &state.excerpts)
|
||||
@@ -1432,7 +1432,7 @@ impl MultiBuffer {
|
||||
let snapshot = self.read(cx);
|
||||
let position = position.to_offset(&snapshot);
|
||||
|
||||
let mut cursor = snapshot.excerpts.cursor::<usize>();
|
||||
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
|
||||
cursor.seek(&position, Bias::Right, &());
|
||||
cursor
|
||||
.item()
|
||||
@@ -1459,7 +1459,7 @@ impl MultiBuffer {
|
||||
) -> Option<(Model<Buffer>, usize, ExcerptId)> {
|
||||
let snapshot = self.read(cx);
|
||||
let offset = point.to_offset(&snapshot);
|
||||
let mut cursor = snapshot.excerpts.cursor::<usize>();
|
||||
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
|
||||
cursor.seek(&offset, Bias::Right, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.prev(&());
|
||||
@@ -1482,7 +1482,7 @@ impl MultiBuffer {
|
||||
) -> Option<(Model<Buffer>, Point, ExcerptId)> {
|
||||
let snapshot = self.read(cx);
|
||||
let point = point.to_point(&snapshot);
|
||||
let mut cursor = snapshot.excerpts.cursor::<Point>();
|
||||
let mut cursor = snapshot.excerpts.cursor::<Point>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.prev(&());
|
||||
@@ -1507,7 +1507,7 @@ impl MultiBuffer {
|
||||
let end = range.end.to_offset(&snapshot);
|
||||
|
||||
let mut result = Vec::new();
|
||||
let mut cursor = snapshot.excerpts.cursor::<usize>();
|
||||
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
|
||||
cursor.seek(&start, Bias::Right, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.prev(&());
|
||||
@@ -1546,8 +1546,8 @@ impl MultiBuffer {
|
||||
|
||||
let mut buffers = self.buffers.borrow_mut();
|
||||
let mut snapshot = self.snapshot.borrow_mut();
|
||||
let mut new_excerpts = SumTree::new();
|
||||
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
|
||||
let mut new_excerpts = SumTree::default();
|
||||
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(&());
|
||||
let mut edits = Vec::new();
|
||||
let mut excerpt_ids = ids.iter().copied().peekable();
|
||||
|
||||
@@ -1801,8 +1801,8 @@ impl MultiBuffer {
|
||||
let ids = ids.into_iter().collect::<Vec<_>>();
|
||||
let snapshot = self.snapshot(cx);
|
||||
let locators = snapshot.excerpt_locators_for_ids(ids.iter().copied());
|
||||
let mut new_excerpts = SumTree::new();
|
||||
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
|
||||
let mut new_excerpts = SumTree::default();
|
||||
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(&());
|
||||
let mut edits = Vec::<Edit<usize>>::new();
|
||||
|
||||
for locator in &locators {
|
||||
@@ -1927,8 +1927,8 @@ impl MultiBuffer {
|
||||
excerpts_to_edit.sort_unstable_by_key(|(locator, _, _)| *locator);
|
||||
|
||||
let mut edits = Vec::new();
|
||||
let mut new_excerpts = SumTree::new();
|
||||
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
|
||||
let mut new_excerpts = SumTree::default();
|
||||
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(&());
|
||||
|
||||
for (locator, buffer, buffer_edited) in excerpts_to_edit {
|
||||
new_excerpts.append(cursor.slice(&Some(locator), Bias::Left, &()), &());
|
||||
@@ -2230,7 +2230,7 @@ impl MultiBufferSnapshot {
|
||||
|
||||
pub fn reversed_chars_at<T: ToOffset>(&self, position: T) -> impl Iterator<Item = char> + '_ {
|
||||
let mut offset = position.to_offset(self);
|
||||
let mut cursor = self.excerpts.cursor::<usize>();
|
||||
let mut cursor = self.excerpts.cursor::<usize>(&());
|
||||
cursor.seek(&offset, Bias::Left, &());
|
||||
let mut excerpt_chunks = cursor.item().map(|excerpt| {
|
||||
let end_before_footer = cursor.start() + excerpt.text_summary.len;
|
||||
@@ -2357,7 +2357,7 @@ impl MultiBufferSnapshot {
|
||||
return buffer.clip_offset(offset, bias);
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<usize>();
|
||||
let mut cursor = self.excerpts.cursor::<usize>(&());
|
||||
cursor.seek(&offset, Bias::Right, &());
|
||||
let overshoot = if let Some(excerpt) = cursor.item() {
|
||||
let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
|
||||
@@ -2376,7 +2376,7 @@ impl MultiBufferSnapshot {
|
||||
return buffer.clip_point(point, bias);
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<Point>();
|
||||
let mut cursor = self.excerpts.cursor::<Point>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
let overshoot = if let Some(excerpt) = cursor.item() {
|
||||
let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer);
|
||||
@@ -2395,7 +2395,7 @@ impl MultiBufferSnapshot {
|
||||
return buffer.clip_offset_utf16(offset, bias);
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<OffsetUtf16>();
|
||||
let mut cursor = self.excerpts.cursor::<OffsetUtf16>(&());
|
||||
cursor.seek(&offset, Bias::Right, &());
|
||||
let overshoot = if let Some(excerpt) = cursor.item() {
|
||||
let excerpt_start = excerpt.range.context.start.to_offset_utf16(&excerpt.buffer);
|
||||
@@ -2414,7 +2414,7 @@ impl MultiBufferSnapshot {
|
||||
return buffer.clip_point_utf16(point, bias);
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<PointUtf16>();
|
||||
let mut cursor = self.excerpts.cursor::<PointUtf16>(&());
|
||||
cursor.seek(&point.0, Bias::Right, &());
|
||||
let overshoot = if let Some(excerpt) = cursor.item() {
|
||||
let excerpt_start = excerpt
|
||||
@@ -2432,7 +2432,7 @@ impl MultiBufferSnapshot {
|
||||
|
||||
pub fn bytes_in_range<T: ToOffset>(&self, range: Range<T>) -> MultiBufferBytes {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
let mut excerpts = self.excerpts.cursor::<usize>();
|
||||
let mut excerpts = self.excerpts.cursor::<usize>(&());
|
||||
excerpts.seek(&range.start, Bias::Right, &());
|
||||
|
||||
let mut chunk = &[][..];
|
||||
@@ -2457,7 +2457,7 @@ impl MultiBufferSnapshot {
|
||||
range: Range<T>,
|
||||
) -> ReversedMultiBufferBytes {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
let mut excerpts = self.excerpts.cursor::<usize>();
|
||||
let mut excerpts = self.excerpts.cursor::<usize>(&());
|
||||
excerpts.seek(&range.end, Bias::Left, &());
|
||||
|
||||
let mut chunk = &[][..];
|
||||
@@ -2482,7 +2482,7 @@ impl MultiBufferSnapshot {
|
||||
pub fn buffer_rows(&self, start_row: MultiBufferRow) -> MultiBufferRows {
|
||||
let mut result = MultiBufferRows {
|
||||
buffer_row_range: 0..0,
|
||||
excerpts: self.excerpts.cursor(),
|
||||
excerpts: self.excerpts.cursor(&()),
|
||||
};
|
||||
result.seek(start_row);
|
||||
result
|
||||
@@ -2492,7 +2492,7 @@ impl MultiBufferSnapshot {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
let mut chunks = MultiBufferChunks {
|
||||
range: range.clone(),
|
||||
excerpts: self.excerpts.cursor(),
|
||||
excerpts: self.excerpts.cursor(&()),
|
||||
excerpt_chunks: None,
|
||||
language_aware,
|
||||
};
|
||||
@@ -2505,7 +2505,7 @@ impl MultiBufferSnapshot {
|
||||
return buffer.offset_to_point(offset);
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<(usize, Point)>();
|
||||
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
|
||||
cursor.seek(&offset, Bias::Right, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
let (start_offset, start_point) = cursor.start();
|
||||
@@ -2526,7 +2526,7 @@ impl MultiBufferSnapshot {
|
||||
return buffer.offset_to_point_utf16(offset);
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<(usize, PointUtf16)>();
|
||||
let mut cursor = self.excerpts.cursor::<(usize, PointUtf16)>(&());
|
||||
cursor.seek(&offset, Bias::Right, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
let (start_offset, start_point) = cursor.start();
|
||||
@@ -2547,7 +2547,7 @@ impl MultiBufferSnapshot {
|
||||
return buffer.point_to_point_utf16(point);
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<(Point, PointUtf16)>();
|
||||
let mut cursor = self.excerpts.cursor::<(Point, PointUtf16)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
let (start_offset, start_point) = cursor.start();
|
||||
@@ -2569,7 +2569,7 @@ impl MultiBufferSnapshot {
|
||||
return buffer.point_to_offset(point);
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<(Point, usize)>();
|
||||
let mut cursor = self.excerpts.cursor::<(Point, usize)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
let (start_point, start_offset) = cursor.start();
|
||||
@@ -2590,7 +2590,7 @@ impl MultiBufferSnapshot {
|
||||
return buffer.offset_utf16_to_offset(offset_utf16);
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<(OffsetUtf16, usize)>();
|
||||
let mut cursor = self.excerpts.cursor::<(OffsetUtf16, usize)>(&());
|
||||
cursor.seek(&offset_utf16, Bias::Right, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
let (start_offset_utf16, start_offset) = cursor.start();
|
||||
@@ -2612,7 +2612,7 @@ impl MultiBufferSnapshot {
|
||||
return buffer.offset_to_offset_utf16(offset);
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<(usize, OffsetUtf16)>();
|
||||
let mut cursor = self.excerpts.cursor::<(usize, OffsetUtf16)>(&());
|
||||
cursor.seek(&offset, Bias::Right, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
let (start_offset, start_offset_utf16) = cursor.start();
|
||||
@@ -2636,7 +2636,7 @@ impl MultiBufferSnapshot {
|
||||
return buffer.point_utf16_to_offset(point);
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>();
|
||||
let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
let (start_point, start_offset) = cursor.start();
|
||||
@@ -2659,7 +2659,7 @@ impl MultiBufferSnapshot {
|
||||
point: T,
|
||||
) -> Option<(&BufferSnapshot, usize)> {
|
||||
let offset = point.to_offset(self);
|
||||
let mut cursor = self.excerpts.cursor::<usize>();
|
||||
let mut cursor = self.excerpts.cursor::<usize>(&());
|
||||
cursor.seek(&offset, Bias::Right, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.prev(&());
|
||||
@@ -2680,7 +2680,7 @@ impl MultiBufferSnapshot {
|
||||
let mut result = BTreeMap::new();
|
||||
|
||||
let mut rows_for_excerpt = Vec::new();
|
||||
let mut cursor = self.excerpts.cursor::<Point>();
|
||||
let mut cursor = self.excerpts.cursor::<Point>(&());
|
||||
let mut rows = rows.into_iter().peekable();
|
||||
let mut prev_row = u32::MAX;
|
||||
let mut prev_language_indent_size = IndentSize::default();
|
||||
@@ -2769,7 +2769,7 @@ impl MultiBufferSnapshot {
|
||||
&self,
|
||||
row: MultiBufferRow,
|
||||
) -> Option<(&BufferSnapshot, Range<Point>)> {
|
||||
let mut cursor = self.excerpts.cursor::<Point>();
|
||||
let mut cursor = self.excerpts.cursor::<Point>(&());
|
||||
let point = Point::new(row.0, 0);
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
if cursor.item().is_none() && *cursor.start() == point {
|
||||
@@ -2803,9 +2803,9 @@ impl MultiBufferSnapshot {
|
||||
D: TextDimension,
|
||||
O: ToOffset,
|
||||
{
|
||||
let mut summary = D::default();
|
||||
let mut summary = D::zero(&());
|
||||
let mut range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
let mut cursor = self.excerpts.cursor::<usize>();
|
||||
let mut cursor = self.excerpts.cursor::<usize>(&());
|
||||
cursor.seek(&range.start, Bias::Right, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
let mut end_before_newline = cursor.end(&());
|
||||
@@ -2856,7 +2856,7 @@ impl MultiBufferSnapshot {
|
||||
where
|
||||
D: TextDimension + Ord + Sub<D, Output = D>,
|
||||
{
|
||||
let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
|
||||
let mut cursor = self.excerpts.cursor::<ExcerptSummary>(&());
|
||||
let locator = self.excerpt_locator_for_id(anchor.excerpt_id);
|
||||
|
||||
cursor.seek(locator, Bias::Left, &());
|
||||
@@ -2894,7 +2894,7 @@ impl MultiBufferSnapshot {
|
||||
}
|
||||
|
||||
let mut anchors = anchors.into_iter().peekable();
|
||||
let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
|
||||
let mut cursor = self.excerpts.cursor::<ExcerptSummary>(&());
|
||||
let mut summaries = Vec::new();
|
||||
while let Some(anchor) = anchors.peek() {
|
||||
let excerpt_id = anchor.excerpt_id;
|
||||
@@ -2949,7 +2949,7 @@ impl MultiBufferSnapshot {
|
||||
I: 'a + IntoIterator<Item = &'a Anchor>,
|
||||
{
|
||||
let mut anchors = anchors.into_iter().enumerate().peekable();
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
|
||||
cursor.next(&());
|
||||
|
||||
let mut result = Vec::new();
|
||||
@@ -3064,7 +3064,7 @@ impl MultiBufferSnapshot {
|
||||
};
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<(usize, Option<ExcerptId>)>();
|
||||
let mut cursor = self.excerpts.cursor::<(usize, Option<ExcerptId>)>(&());
|
||||
cursor.seek(&offset, Bias::Right, &());
|
||||
if cursor.item().is_none() && offset == cursor.start().0 && bias == Bias::Left {
|
||||
cursor.prev(&());
|
||||
@@ -3099,7 +3099,7 @@ impl MultiBufferSnapshot {
|
||||
text_anchor: text::Anchor,
|
||||
) -> Option<Anchor> {
|
||||
let locator = self.excerpt_locator_for_id(excerpt_id);
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
|
||||
cursor.seek(locator, Bias::Left, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
if excerpt.id == excerpt_id {
|
||||
@@ -3139,7 +3139,7 @@ impl MultiBufferSnapshot {
|
||||
) -> impl Iterator<Item = (&Excerpt, usize)> + '_ {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<usize>();
|
||||
let mut cursor = self.excerpts.cursor::<usize>(&());
|
||||
cursor.seek(&range.start, Bias::Right, &());
|
||||
cursor.prev(&());
|
||||
|
||||
@@ -3183,7 +3183,7 @@ impl MultiBufferSnapshot {
|
||||
};
|
||||
let bounds = (start, end);
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<(usize, Point)>();
|
||||
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
|
||||
cursor.seek(&start_offset, Bias::Right, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.prev(&());
|
||||
@@ -3550,7 +3550,7 @@ impl MultiBufferSnapshot {
|
||||
&self,
|
||||
row_range: Range<MultiBufferRow>,
|
||||
) -> impl Iterator<Item = DiffHunk<MultiBufferRow>> + '_ {
|
||||
let mut cursor = self.excerpts.cursor::<Point>();
|
||||
let mut cursor = self.excerpts.cursor::<Point>(&());
|
||||
|
||||
cursor.seek(&Point::new(row_range.end.0, 0), Bias::Left, &());
|
||||
if cursor.item().is_none() {
|
||||
@@ -3617,7 +3617,7 @@ impl MultiBufferSnapshot {
|
||||
&self,
|
||||
row_range: Range<MultiBufferRow>,
|
||||
) -> impl Iterator<Item = DiffHunk<MultiBufferRow>> + '_ {
|
||||
let mut cursor = self.excerpts.cursor::<Point>();
|
||||
let mut cursor = self.excerpts.cursor::<Point>(&());
|
||||
|
||||
cursor.seek(&Point::new(row_range.start.0, 0), Bias::Left, &());
|
||||
|
||||
@@ -3779,7 +3779,7 @@ impl MultiBufferSnapshot {
|
||||
} else if id == ExcerptId::max() {
|
||||
Locator::max_ref()
|
||||
} else {
|
||||
let mut cursor = self.excerpt_ids.cursor::<ExcerptId>();
|
||||
let mut cursor = self.excerpt_ids.cursor::<ExcerptId>(&());
|
||||
cursor.seek(&id, Bias::Left, &());
|
||||
if let Some(entry) = cursor.item() {
|
||||
if entry.id == id {
|
||||
@@ -3814,7 +3814,7 @@ impl MultiBufferSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpt_ids.cursor::<ExcerptId>();
|
||||
let mut cursor = self.excerpt_ids.cursor::<ExcerptId>(&());
|
||||
for id in sorted_ids {
|
||||
if cursor.seek_forward(&id, Bias::Left, &()) {
|
||||
locators.push(cursor.item().unwrap().locator.clone());
|
||||
@@ -3839,7 +3839,7 @@ impl MultiBufferSnapshot {
|
||||
&'a self,
|
||||
excerpt_id: ExcerptId,
|
||||
) -> Option<Range<T>> {
|
||||
let mut cursor = self.excerpts.cursor::<(Option<&Locator>, T)>();
|
||||
let mut cursor = self.excerpts.cursor::<(Option<&Locator>, T)>(&());
|
||||
let locator = self.excerpt_locator_for_id(excerpt_id);
|
||||
if cursor.seek(&Some(locator), Bias::Left, &()) {
|
||||
let start = cursor.start().1.clone();
|
||||
@@ -3851,7 +3851,7 @@ impl MultiBufferSnapshot {
|
||||
}
|
||||
|
||||
fn excerpt(&self, excerpt_id: ExcerptId) -> Option<&Excerpt> {
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
|
||||
let locator = self.excerpt_locator_for_id(excerpt_id);
|
||||
cursor.seek(&Some(locator), Bias::Left, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
@@ -3866,7 +3866,7 @@ impl MultiBufferSnapshot {
|
||||
pub fn excerpt_containing<T: ToOffset>(&self, range: Range<T>) -> Option<MultiBufferExcerpt> {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<usize>();
|
||||
let mut cursor = self.excerpts.cursor::<usize>(&());
|
||||
cursor.seek(&range.start, Bias::Right, &());
|
||||
let start_excerpt = cursor.item()?;
|
||||
|
||||
@@ -3891,7 +3891,7 @@ impl MultiBufferSnapshot {
|
||||
I: IntoIterator<Item = Range<Anchor>> + 'a,
|
||||
{
|
||||
let mut ranges = ranges.into_iter().map(|range| range.to_offset(self));
|
||||
let mut cursor = self.excerpts.cursor::<usize>();
|
||||
let mut cursor = self.excerpts.cursor::<usize>(&());
|
||||
cursor.next(&());
|
||||
let mut current_range = ranges.next();
|
||||
iter::from_fn(move || {
|
||||
@@ -3943,7 +3943,7 @@ impl MultiBufferSnapshot {
|
||||
ranges: impl IntoIterator<Item = Range<Anchor>>,
|
||||
) -> impl Iterator<Item = (ExcerptId, &BufferSnapshot, Range<usize>)> {
|
||||
let mut ranges = ranges.into_iter().map(|range| range.to_offset(self));
|
||||
let mut cursor = self.excerpts.cursor::<usize>();
|
||||
let mut cursor = self.excerpts.cursor::<usize>(&());
|
||||
cursor.next(&());
|
||||
let mut current_range = ranges.next();
|
||||
iter::from_fn(move || {
|
||||
@@ -3980,7 +3980,7 @@ impl MultiBufferSnapshot {
|
||||
range: &'a Range<Anchor>,
|
||||
include_local: bool,
|
||||
) -> impl 'a + Iterator<Item = (ReplicaId, bool, CursorShape, Selection<Anchor>)> {
|
||||
let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
|
||||
let mut cursor = self.excerpts.cursor::<ExcerptSummary>(&());
|
||||
let start_locator = self.excerpt_locator_for_id(range.start.excerpt_id);
|
||||
let end_locator = self.excerpt_locator_for_id(range.end.excerpt_id);
|
||||
cursor.seek(start_locator, Bias::Left, &());
|
||||
@@ -4519,6 +4519,10 @@ impl sum_tree::KeyedItem for ExcerptIdMapping {
|
||||
impl sum_tree::Summary for ExcerptId {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, _: &()) {
|
||||
*self = *other;
|
||||
}
|
||||
@@ -4527,6 +4531,10 @@ impl sum_tree::Summary for ExcerptId {
|
||||
impl sum_tree::Summary for ExcerptSummary {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||
debug_assert!(summary.excerpt_locator > self.excerpt_locator);
|
||||
self.excerpt_locator = summary.excerpt_locator.clone();
|
||||
@@ -4536,12 +4544,20 @@ impl sum_tree::Summary for ExcerptSummary {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for TextSummary {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
||||
*self += &summary.text;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for usize {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
||||
*self += summary.text.len;
|
||||
}
|
||||
@@ -4566,30 +4582,50 @@ impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Locator {
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for OffsetUtf16 {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
||||
*self += summary.text.len_utf16;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Point {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
||||
*self += summary.text.lines;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
||||
*self += summary.text.lines_utf16()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a Locator> {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
||||
*self = Some(&summary.excerpt_locator);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<ExcerptId> {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
||||
*self = Some(summary.excerpt_id);
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
[package]
|
||||
name = "multi_buffer2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/multi_buffer2.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"text/test-support",
|
||||
"language/test-support",
|
||||
"gpui/test-support",
|
||||
"util/test-support",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
futures.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
rand.workspace = true
|
||||
settings.workspace = true
|
||||
serde.workspace = true
|
||||
smallvec.workspace = true
|
||||
sum_tree.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
text = { workspace = true, features = ["test-support"] }
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
@@ -1,138 +0,0 @@
|
||||
use super::{MultiBufferSnapshot, ToOffset, ToOffsetUtf16, ToPoint};
|
||||
use language::{OffsetUtf16, Point, TextDimension};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
ops::{Range, Sub},
|
||||
};
|
||||
use sum_tree::Bias;
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
|
||||
pub struct Anchor(pub text::Anchor);
|
||||
|
||||
impl Anchor {
|
||||
pub fn min() -> Self {
|
||||
Self(text::Anchor::MIN)
|
||||
}
|
||||
|
||||
pub fn max() -> Self {
|
||||
Self(text::Anchor::MAX)
|
||||
}
|
||||
|
||||
pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
|
||||
todo!()
|
||||
// let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id, snapshot);
|
||||
// if excerpt_id_cmp.is_eq() {
|
||||
// if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() {
|
||||
// Ordering::Equal
|
||||
// } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
|
||||
// self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer)
|
||||
// } else {
|
||||
// Ordering::Equal
|
||||
// }
|
||||
// } else {
|
||||
// excerpt_id_cmp
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn bias(&self) -> Bias {
|
||||
self.0.bias
|
||||
}
|
||||
|
||||
pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
|
||||
if self.0.bias != Bias::Left {
|
||||
todo!()
|
||||
// if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
|
||||
// return Self {
|
||||
// buffer_id: self.buffer_id,
|
||||
// excerpt_id: self.excerpt_id,
|
||||
// text_anchor: self.text_anchor.bias_left(&excerpt.buffer),
|
||||
// };
|
||||
// }
|
||||
}
|
||||
*self
|
||||
}
|
||||
|
||||
pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
|
||||
if self.0.bias != Bias::Right {
|
||||
todo!()
|
||||
// if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
|
||||
// return Self {
|
||||
// buffer_id: self.buffer_id,
|
||||
// excerpt_id: self.excerpt_id,
|
||||
// text_anchor: self.text_anchor.bias_right(&excerpt.buffer),
|
||||
// };
|
||||
// }
|
||||
}
|
||||
*self
|
||||
}
|
||||
|
||||
pub fn summary<D>(&self, snapshot: &MultiBufferSnapshot) -> D
|
||||
where
|
||||
D: TextDimension + Ord + Sub<D, Output = D>,
|
||||
{
|
||||
snapshot.summary_for_anchor(self)
|
||||
}
|
||||
|
||||
pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
|
||||
todo!()
|
||||
// if *self == Anchor::min() || *self == Anchor::max() {
|
||||
// true
|
||||
// } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
|
||||
// excerpt.contains(self)
|
||||
// && (self.text_anchor == excerpt.range.context.start
|
||||
// || self.text_anchor == excerpt.range.context.end
|
||||
// || self.text_anchor.is_valid(&excerpt.buffer))
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for Anchor {
|
||||
fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize {
|
||||
self.summary(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffsetUtf16 for Anchor {
|
||||
fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16 {
|
||||
self.summary(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPoint for Anchor {
|
||||
fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
|
||||
self.summary(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnchorRangeExt {
|
||||
fn cmp(&self, b: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering;
|
||||
fn overlaps(&self, b: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
|
||||
fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize>;
|
||||
fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>;
|
||||
}
|
||||
|
||||
impl AnchorRangeExt for Range<Anchor> {
|
||||
fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering {
|
||||
match self.start.cmp(&other.start, buffer) {
|
||||
Ordering::Equal => other.end.cmp(&self.end, buffer),
|
||||
ord => ord,
|
||||
}
|
||||
}
|
||||
|
||||
fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool {
|
||||
self.end.cmp(&other.start, buffer).is_ge() && self.start.cmp(&other.end, buffer).is_le()
|
||||
}
|
||||
|
||||
fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize> {
|
||||
self.start.to_offset(content)..self.end.to_offset(content)
|
||||
}
|
||||
|
||||
fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point> {
|
||||
self.start.to_point(content)..self.end.to_point(content)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Ord, PartialOrd)]
|
||||
pub struct Offset(pub usize);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user