diff --git a/.github/actions/run_tests/action.yml b/.github/actions/run_tests/action.yml index 815953398b..07284e2f58 100644 --- a/.github/actions/run_tests/action.yml +++ b/.github/actions/run_tests/action.yml @@ -10,7 +10,7 @@ runs: cargo install cargo-nextest - name: Install Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4 with: node-version: "18" diff --git a/.github/workflows/bump_patch_version.yml b/.github/workflows/bump_patch_version.yml index d05da31e6a..b875ff7b22 100644 --- a/.github/workflows/bump_patch_version.yml +++ b/.github/workflows/bump_patch_version.yml @@ -41,7 +41,7 @@ jobs: exit 1 ;; esac - which cargo-set-version > /dev/null || cargo install cargo-edit --features vendored-openssl + which cargo-set-version > /dev/null || cargo install cargo-edit output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //') git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot " git tag v${output}${tag_suffix} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f059b47004..6718b91ade 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,9 +7,13 @@ on: - "v[0-9]+.[0-9]+.x" tags: - "v*" + paths-ignore: + - "docs/**" pull_request: branches: - "**" + paths-ignore: + - "docs/**" concurrency: # Allow only one workflow per any non-`main` branch. @@ -172,7 +176,7 @@ jobs: DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }} steps: - name: Install Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4 with: node-version: "18" @@ -192,29 +196,12 @@ jobs: - name: Determine version and release channel if: ${{ startsWith(github.ref, 'refs/tags/v') }} run: | - set -eu + # This exports RELEASE_CHANNEL into env (GITHUB_ENV) + script/determine-release-channel - version=$(script/get-crate-version zed) - channel=$(cat crates/zed/RELEASE_CHANNEL) - echo "Publishing version: ${version} on release channel ${channel}" - echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV - - expected_tag_name="" - case ${channel} in - stable) - expected_tag_name="v${version}";; - preview) - expected_tag_name="v${version}-pre";; - nightly) - expected_tag_name="v${version}-nightly";; - *) - echo "can't publish a release on channel ${channel}" - exit 1;; - esac - if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then - echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}" - exit 1 - fi + - name: Draft release notes + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + run: | mkdir -p target/ # Ignore any errors that occur while drafting release notes to not fail the build. script/draft-release-notes "$version" "$channel" > target/release-notes.md || true @@ -271,7 +258,7 @@ jobs: timeout-minutes: 60 name: Create a Linux bundle runs-on: - - buildjet-16vcpu-ubuntu-2204 + - buildjet-16vcpu-ubuntu-2004 if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }} needs: [linux_tests] env: @@ -284,34 +271,13 @@ jobs: clean: false - name: Install Linux dependencies - run: ./script/linux + run: ./script/linux && ./script/install-mold 2.34.0 - name: Determine version and release channel if: ${{ startsWith(github.ref, 'refs/tags/v') }} run: | - set -eu - - version=$(script/get-crate-version zed) - channel=$(cat crates/zed/RELEASE_CHANNEL) - echo "Publishing version: ${version} on release channel ${channel}" - echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV - - expected_tag_name="" - case ${channel} in - stable) - expected_tag_name="v${version}";; - preview) - expected_tag_name="v${version}-pre";; - nightly) - expected_tag_name="v${version}-nightly";; - *) - echo "can't publish a release on channel ${channel}" - exit 1;; - esac - if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then - echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}" - exit 1 - fi + # This exports RELEASE_CHANNEL into env (GITHUB_ENV) + script/determine-release-channel - name: Create Linux .tar.gz bundle run: script/bundle-linux @@ -357,29 +323,8 @@ jobs: - name: Determine version and release channel if: ${{ startsWith(github.ref, 'refs/tags/v') }} run: | - set -eu - - version=$(script/get-crate-version zed) - channel=$(cat crates/zed/RELEASE_CHANNEL) - echo "Publishing version: ${version} on release channel ${channel}" - echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV - - expected_tag_name="" - case ${channel} in - stable) - expected_tag_name="v${version}";; - preview) - expected_tag_name="v${version}-pre";; - nightly) - expected_tag_name="v${version}-nightly";; - *) - echo "can't publish a release on channel ${channel}" - exit 1;; - esac - if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then - echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}" - exit 1 - fi + # This exports RELEASE_CHANNEL into env (GITHUB_ENV) + script/determine-release-channel - name: Create and upload Linux .tar.gz bundle run: script/bundle-linux diff --git a/.github/workflows/close_stale_issues.yml b/.github/workflows/close_stale_issues.yml new file mode 100644 index 0000000000..c38db5bfd7 --- /dev/null +++ b/.github/workflows/close_stale_issues.yml @@ -0,0 +1,31 @@ +name: "Close Stale Issues" +on: + schedule: + - cron: "0 11 * * 2" + workflow_dispatch: + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: > + Hi there! 👋 + + We're working to clean up our issue tracker by closing older issues that might not be relevant anymore. Are you able to reproduce this issue in the latest version of Zed? If so, please let us know by commenting on this issue and we will keep it open; otherwise, we'll close it in 7 days. Feel free to open a new issue if you're seeing this message after the issue has been closed. + + Thanks for your help! + close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, feel free to ping a Zed team member to reopen this issue or open a new one." + # We will increase `days-before-stale` to 365 on or after Jan 24th, + # 2024. This date marks one year since migrating issues from + # 'community' to 'zed' repository. The migration added activity to all + # issues, preventing 365 days from working until then. + days-before-stale: 180 + days-before-close: 7 + any-of-issue-labels: "defect,panic / crash" + operations-per-run: 1000 + ascending: true + enable-statistics: true + stale-issue-label: "stale" diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 8ff35b9e26..0278bbce02 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -21,7 +21,7 @@ jobs: version: 9 - name: Setup Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4 with: node-version: "20" cache: "pnpm" diff --git a/.github/workflows/deploy_cloudflare.yml b/.github/workflows/deploy_cloudflare.yml index 5cf4d6fd13..5c09c29b0f 100644 --- a/.github/workflows/deploy_cloudflare.yml +++ b/.github/workflows/deploy_cloudflare.yml @@ -36,28 +36,28 @@ jobs: mdbook build ./docs --dest-dir=../target/deploy/docs/ - name: Deploy Docs - uses: cloudflare/wrangler-action@f84a562284fc78278ff9052435d9526f9c718361 # v3 + uses: cloudflare/wrangler-action@168bc28b7078db16f6f1ecc26477fc2248592143 # v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: pages deploy target/deploy --project-name=docs - name: Deploy Install - uses: cloudflare/wrangler-action@f84a562284fc78278ff9052435d9526f9c718361 # v3 + uses: cloudflare/wrangler-action@168bc28b7078db16f6f1ecc26477fc2248592143 # v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh - name: Deploy Docs Workers - uses: cloudflare/wrangler-action@f84a562284fc78278ff9052435d9526f9c718361 # v3 + uses: cloudflare/wrangler-action@168bc28b7078db16f6f1ecc26477fc2248592143 # v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: deploy .cloudflare/docs-proxy/src/worker.js - name: Deploy Install Workers - uses: cloudflare/wrangler-action@f84a562284fc78278ff9052435d9526f9c718361 # v3 + uses: cloudflare/wrangler-action@168bc28b7078db16f6f1ecc26477fc2248592143 # v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} diff --git a/.github/workflows/deploy_collab.yml b/.github/workflows/deploy_collab.yml index c4193adcd2..1e6e6cf280 100644 --- a/.github/workflows/deploy_collab.yml +++ b/.github/workflows/deploy_collab.yml @@ -76,7 +76,11 @@ jobs: clean: false - name: Build docker image - run: docker build . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA + run: | + docker build -f Dockerfile-collab \ + --build-arg GITHUB_SHA=$GITHUB_SHA \ + --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA \ + . - name: Publish docker image run: docker push registry.digitalocean.com/zed/collab:${GITHUB_SHA} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 13fe0411a0..7d7b564a54 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,11 +20,14 @@ jobs: with: version: 9 - - run: | + - name: Prettier Check on /docs + working-directory: ./docs + run: | pnpm dlx prettier . --check || { echo "To fix, run from the root of the zed repo:" echo " cd docs && pnpm dlx prettier . --write && cd .." false } - working-directory: ./docs + - name: Check spelling + run: script/check-spelling docs/ diff --git a/.github/workflows/randomized_tests.yml b/.github/workflows/randomized_tests.yml index 57f43d4961..947b5059bd 100644 --- a/.github/workflows/randomized_tests.yml +++ b/.github/workflows/randomized_tests.yml @@ -22,7 +22,7 @@ jobs: - buildjet-16vcpu-ubuntu-2204 steps: - name: Install Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4 with: node-version: "18" diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index 2b973dcddc..349d14f990 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -70,7 +70,7 @@ jobs: ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }} steps: - name: Install Node - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4 with: node-version: "18" @@ -100,7 +100,7 @@ jobs: name: Create a Linux *.tar.gz bundle for x86 if: github.repository_owner == 'zed-industries' runs-on: - - buildjet-16vcpu-ubuntu-2204 + - buildjet-16vcpu-ubuntu-2004 needs: tests env: DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }} @@ -117,7 +117,7 @@ jobs: run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: Install Linux dependencies - run: ./script/linux + run: ./script/linux && ./script/install-mold 2.34.0 - name: Limit target directory size run: script/clear-target-dir-if-larger-than 100 diff --git a/.gitignore b/.gitignore index 634b73ac94..d19c5a102a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ /crates/collab/seed.json /crates/zed/resources/flatpak/flatpak-cargo-sources.json /dev.zed.Zed*.json -/assets/*licenses.md +/assets/*licenses.* **/venv .build *.wasm diff --git a/.zed/settings.json b/.zed/settings.json index 176fd33a9b..41adfdbf59 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -38,6 +38,10 @@ } } }, + "file_types": { + "Dockerfile": ["Dockerfile*[!dockerignore]"], + "Git Ignore": ["dockerignore"] + }, "hard_tabs": false, "formatter": "auto", "remove_trailing_whitespace_on_save": true, diff --git a/Cargo.lock b/Cargo.lock index 4bbd7a6da0..ea8b48d781 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,7 +245,6 @@ dependencies = [ "chrono", "futures 0.3.30", "http_client", - "isahc", "schemars", "serde", "serde_json", @@ -404,6 +403,7 @@ dependencies = [ "language_model", "languages", "log", + "lsp", "markdown", "menu", "multi_buffer", @@ -846,8 +846,8 @@ dependencies = [ "chrono", "futures-util", "http-types", - "hyper", - "hyper-rustls", + "hyper 0.14.30", + "hyper-rustls 0.24.2", "serde", "serde_json", "serde_path_to_error", @@ -880,23 +880,22 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-tls" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795" +checksum = "b2ae3c9eba89d472a0e4fe1dea433df78fbbe63d2b764addaf2ba3a6bde89a5e" dependencies = [ "futures-core", "futures-io", - "rustls 0.20.9", + "rustls 0.21.12", "rustls-pemfile 1.0.4", - "webpki", "webpki-roots 0.22.6", ] [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", @@ -905,9 +904,9 @@ dependencies = [ [[package]] name = "async-tungstenite" -version = "0.23.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e9efbe14612da0a19fb983059a0b621e9cf6225d7018ecab4f9988215540dc" +checksum = "90e661b6cb0a6eb34d02c520b052daa3aa9ac0cc02495c9d066bbce13ead132b" dependencies = [ "async-std", "async-tls", @@ -915,7 +914,7 @@ dependencies = [ "futures-util", "log", "pin-project-lite", - "tungstenite 0.20.1", + "tungstenite 0.24.0", ] [[package]] @@ -1064,7 +1063,7 @@ dependencies = [ "fastrand 2.1.1", "hex", "http 0.2.12", - "ring 0.17.8", + "ring", "time", "tokio", "tracing", @@ -1233,7 +1232,7 @@ dependencies = [ "once_cell", "p256", "percent-encoding", - "ring 0.17.8", + "ring", "sha2", "subtle", "time", @@ -1336,13 +1335,13 @@ dependencies = [ "aws-smithy-types", "bytes 1.7.1", "fastrand 2.1.1", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "http-body 1.0.1", "httparse", - "hyper", - "hyper-rustls", + "hyper 0.14.30", + "hyper-rustls 0.24.2", "once_cell", "pin-project-lite", "pin-utils", @@ -1432,7 +1431,7 @@ dependencies = [ "headers", "http 0.2.12", "http-body 0.4.6", - "hyper", + "hyper 0.14.30", "itoa", "matchit", "memchr", @@ -1445,7 +1444,7 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper", + "sync_wrapper 0.1.2", "tokio", "tokio-tungstenite 0.20.1", "tower", @@ -1584,7 +1583,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.76", ] @@ -1604,7 +1603,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.76", ] @@ -2086,9 +2085,9 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad639525b1c67b6a298f378417b060fbc04618bea559482a8484381cce27d965" +checksum = "88da5a13c620b4ca0078845707ea9c3faf11edbc3ffd8497d11d686211cd1ac0" dependencies = [ "serde", "toml 0.8.19", @@ -2100,12 +2099,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - [[package]] name = "cbc" version = "0.1.2" @@ -2282,9 +2275,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.17" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -2292,9 +2285,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.17" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -2314,9 +2307,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -2363,8 +2356,8 @@ dependencies = [ "clickhouse-derive", "clickhouse-rs-cityhash-sys", "futures 0.3.30", - "hyper", - "hyper-tls", + "hyper 0.14.30", + "hyper-tls 0.5.0", "lz4", "sealed", "serde", @@ -2402,13 +2395,13 @@ dependencies = [ "anyhow", "async-native-tls", "async-recursion 0.3.2", + "async-tls", "async-tungstenite", "chrono", "clock", "cocoa 0.26.0", "collections", "feature_flags", - "fs", "futures 0.3.30", "gpui", "http_client", @@ -2420,8 +2413,6 @@ dependencies = [ "rand 0.8.5", "release_channel", "rpc", - "rustls 0.20.9", - "rustls-native-certs 0.8.0", "schemars", "serde", "serde_json", @@ -2568,9 +2559,8 @@ dependencies = [ "headless", "hex", "http_client", - "hyper", + "hyper 0.14.30", "indoc", - "isahc_http_client", "jsonwebtoken", "language", "language_model", @@ -2594,7 +2584,8 @@ dependencies = [ "release_channel", "remote", "remote_server", - "reqwest", + "reqwest 0.11.27", + "reqwest_client", "rpc", "rustc-demangle", "scrypt", @@ -2678,7 +2669,7 @@ dependencies = [ name = "collections" version = "0.1.0" dependencies = [ - "rustc-hash", + "rustc-hash 1.1.0", ] [[package]] @@ -2850,7 +2841,6 @@ dependencies = [ "gpui", "http_client", "indoc", - "isahc", "language", "lsp", "menu", @@ -2997,7 +2987,7 @@ dependencies = [ "log", "rangemap", "rayon", - "rustc-hash", + "rustc-hash 1.1.0", "rustybuzz", "self_cell", "swash", @@ -3087,7 +3077,7 @@ dependencies = [ "hashbrown 0.14.5", "log", "regalloc2", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "target-lexicon", ] @@ -3343,36 +3333,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "curl" -version = "0.4.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2 0.5.7", - "windows-sys 0.52.0", -] - -[[package]] -name = "curl-sys" -version = "0.4.74+curl-8.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8af10b986114528fcdc4b63b6f5f021b7057618411046a4de2ba0f0149a097bf" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "windows-sys 0.52.0", -] - [[package]] name = "cursor-icon" version = "1.1.0" @@ -3731,6 +3691,7 @@ dependencies = [ "multi_buffer", "ordered-float 2.10.1", "parking_lot", + "pretty_assertions", "project", "rand 0.8.5", "release_channel", @@ -4033,7 +3994,6 @@ dependencies = [ "git", "gpui", "http_client", - "isahc_http_client", "language", "languages", "node_runtime", @@ -4044,6 +4004,7 @@ dependencies = [ "serde_json", "settings", "smol", + "ureq_client", ] [[package]] @@ -4128,8 +4089,6 @@ dependencies = [ "gpui", "http_client", "indexed_docs", - "isahc", - "isahc_http_client", "language", "log", "lsp", @@ -4138,6 +4097,7 @@ dependencies = [ "paths", "project", "release_channel", + "reqwest_client", "schemars", "semantic_version", "serde", @@ -4147,8 +4107,10 @@ dependencies = [ "snippet_provider", "task", "theme", + "tokio", "toml 0.8.19", "ui", + "ureq_client", "url", "util", "wasm-encoder 0.215.0", @@ -4168,9 +4130,9 @@ dependencies = [ "env_logger", "extension", "fs", - "isahc_http_client", "language", "log", + "reqwest_client", "rpc", "serde", "serde_json", @@ -4289,7 +4251,6 @@ dependencies = [ "gpui", "http_client", "human_bytes", - "isahc", "language", "log", "menu", @@ -4418,7 +4379,7 @@ dependencies = [ "futures-core", "futures-sink", "nanorand", - "spin 0.9.8", + "spin", ] [[package]] @@ -5016,7 +4977,6 @@ dependencies = [ "anyhow", "futures 0.3.30", "http_client", - "isahc", "schemars", "serde", "serde_json", @@ -5151,9 +5111,9 @@ dependencies = [ [[package]] name = "grid" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d196ffc1627db18a531359249b2bf8416178d84b729f3cebeb278f285fb9b58c" +checksum = "be136d9dacc2a13cc70bb6c8f902b414fb2641f8db1314637c6b7933411a8f82" [[package]] name = "group" @@ -5185,6 +5145,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes 1.7.1", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.4.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.4.1" @@ -5565,8 +5544,10 @@ dependencies = [ "anyhow", "derive_more", "futures 0.3.30", - "http 0.2.12", + "http 1.1.0", "log", + "rustls 0.21.12", + "rustls-native-certs 0.8.0", "serde", "serde_json", "smol", @@ -5607,7 +5588,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -5621,6 +5602,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes 1.7.1", + "futures-channel", + "futures-util", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -5629,12 +5630,29 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper", + "hyper 0.14.30", "log", "rustls 0.21.12", "rustls-native-certs 0.6.3", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls 0.23.13", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", ] [[package]] @@ -5644,12 +5662,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes 1.7.1", - "hyper", + "hyper 0.14.30", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes 1.7.1", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +dependencies = [ + "bytes 1.7.1", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.4.1", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -6017,44 +6070,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "isahc" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" -dependencies = [ - "async-channel 1.9.0", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "encoding_rs", - "event-listener 2.5.3", - "futures-lite 1.13.0", - "http 0.2.12", - "log", - "mime", - "once_cell", - "polling 2.8.0", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "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" @@ -6159,7 +6174,7 @@ dependencies = [ "base64 0.21.7", "js-sys", "pem", - "ring 0.17.8", + "ring", "serde", "serde_json", "simple_asn1", @@ -6288,7 +6303,6 @@ dependencies = [ "http_client", "image", "inline_completion_button", - "isahc", "language", "log", "menu", @@ -6372,7 +6386,6 @@ dependencies = [ "node_runtime", "paths", "project", - "protols-tree-sitter-proto", "regex", "rope", "rust-embed", @@ -6411,7 +6424,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.9.8", + "spin", ] [[package]] @@ -6434,9 +6447,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libdbus-sys" @@ -6606,7 +6619,7 @@ dependencies = [ "prost", "prost-build", "prost-types", - "reqwest", + "reqwest 0.12.8", "serde", ] @@ -7090,7 +7103,7 @@ dependencies = [ "hexf-parse", "indexmap 2.4.0", "log", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "termcolor", "thiserror", @@ -7189,6 +7202,7 @@ dependencies = [ "async-std", "async-tar", "async-trait", + "async-watch", "async_zip", "futures 0.3.30", "http_client", @@ -7201,6 +7215,7 @@ dependencies = [ "tempfile", "util", "walkdir", + "which 6.0.3", "windows 0.58.0", ] @@ -7589,7 +7604,6 @@ dependencies = [ "anyhow", "futures 0.3.30", "http_client", - "isahc", "schemars", "serde", "serde_json", @@ -7845,9 +7859,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -8355,9 +8369,9 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", @@ -8629,15 +8643,6 @@ version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" -[[package]] -name = "protols-tree-sitter-proto" -version = "0.2.0" -source = "git+https://github.com/zed-industries/tree-sitter-proto?rev=0848bd30a64be48772e15fbb9d5ba8c0cc5772ad#0848bd30a64be48772e15fbb9d5ba8c0cc5772ad" -dependencies = [ - "cc", - "tree-sitter-language", -] - [[package]] name = "psm" version = "0.1.21" @@ -8746,6 +8751,54 @@ dependencies = [ "zed_actions", ] +[[package]] +name = "quinn" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +dependencies = [ + "bytes 1.7.1", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.0.0", + "rustls 0.23.13", + "socket2 0.5.7", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes 1.7.1", + "rand 0.8.5", + "ring", + "rustc-hash 2.0.0", + "rustls 0.23.13", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +dependencies = [ + "libc", + "once_cell", + "socket2 0.5.7", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.37" @@ -8949,7 +9002,6 @@ dependencies = [ "gpui", "language", "log", - "markdown", "menu", "ordered-float 2.10.1", "picker", @@ -9023,7 +9075,7 @@ checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" dependencies = [ "hashbrown 0.13.2", "log", - "rustc-hash", + "rustc-hash 1.1.0", "slice-group-by", "smallvec", ] @@ -9111,7 +9163,9 @@ name = "remote_server" version = "0.1.0" dependencies = [ "anyhow", + "backtrace", "cargo_toml", + "clap", "client", "clock", "env_logger", @@ -9120,6 +9174,7 @@ dependencies = [ "gpui", "http_client", "language", + "languages", "log", "lsp", "node_runtime", @@ -9199,11 +9254,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper", - "hyper-tls", + "hyper 0.14.30", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -9216,8 +9271,8 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", - "system-configuration", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", "tokio", "tokio-native-tls", "tower-service", @@ -9228,6 +9283,68 @@ dependencies = [ "winreg 0.50.0", ] +[[package]] +name = "reqwest" +version = "0.12.8" +source = "git+https://github.com/zed-industries/reqwest.git?rev=fd110f6998da16bbca97b6dddda9be7827c50e29#fd110f6998da16bbca97b6dddda9be7827c50e29" +dependencies = [ + "base64 0.22.1", + "bytes 1.7.1", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-rustls 0.27.3", + "hyper-tls 0.6.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.13", + "rustls-pemfile 2.1.3", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration 0.6.1", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.0", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry", +] + +[[package]] +name = "reqwest_client" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes 1.7.1", + "futures 0.3.30", + "http_client", + "reqwest 0.12.8", + "serde", + "smol", + "tokio", +] + [[package]] name = "resvg" version = "0.41.0" @@ -9276,21 +9393,6 @@ 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" @@ -9301,8 +9403,8 @@ dependencies = [ "cfg-if", "getrandom 0.2.15", "libc", - "spin 0.9.8", - "untrusted 0.9.0", + "spin", + "untrusted", "windows-sys 0.52.0", ] @@ -9458,7 +9560,7 @@ dependencies = [ "futures 0.3.30", "glob", "rand 0.8.5", - "ring 0.17.8", + "ring", "serde", "serde_json", "shellexpand 3.1.0", @@ -9540,6 +9642,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc_version" version = "0.4.1" @@ -9589,18 +9697,6 @@ 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" @@ -9608,11 +9704,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", - "rustls-webpki", + "ring", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -9669,8 +9779,19 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -9784,8 +9905,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -9981,7 +10102,6 @@ dependencies = [ "gpui", "heed", "http_client", - "isahc_http_client", "language", "language_model", "languages", @@ -10000,6 +10120,7 @@ dependencies = [ "tree-sitter", "ui", "unindent", + "ureq_client", "util", "workspace", "worktree", @@ -10075,9 +10196,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "indexmap 2.4.0", "itoa", @@ -10432,17 +10553,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel 1.9.0", - "futures-core", - "futures-io", -] - [[package]] name = "smallvec" version = "1.13.2" @@ -10507,12 +10617,27 @@ dependencies = [ "futures 0.3.30", "gpui", "parking_lot", + "paths", "serde", "serde_json", "snippet", "util", ] +[[package]] +name = "snippets_ui" +version = "0.1.0" +dependencies = [ + "fuzzy", + "gpui", + "language", + "paths", + "picker", + "ui", + "util", + "workspace", +] + [[package]] name = "socket2" version = "0.4.10" @@ -10542,12 +10667,6 @@ 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" @@ -10603,6 +10722,7 @@ dependencies = [ "libsqlite3-sys", "parking_lot", "smol", + "sqlformat", "thread_local", "util", "uuid", @@ -10619,9 +10739,9 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" dependencies = [ "nom", "unicode_categories", @@ -10890,6 +11010,7 @@ dependencies = [ "theme", "title_bar", "ui", + "ureq_client", ] [[package]] @@ -11014,6 +11135,7 @@ dependencies = [ "text", "theme", "ui", + "unicode-segmentation", "util", "windows 0.58.0", ] @@ -11176,6 +11298,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + [[package]] name = "synchronoise" version = "1.0.1" @@ -11216,7 +11347,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", ] [[package]] @@ -11229,6 +11371,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -11274,6 +11426,7 @@ dependencies = [ "project", "serde", "serde_json", + "settings", "theme", "ui", "util", @@ -11282,9 +11435,9 @@ dependencies = [ [[package]] name = "taffy" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ec17858c2d465b2f734b798b920818a974faf0babb15d7fef81818a4b2d16f1" +checksum = "9cb893bff0f80ae17d3a57e030622a967b8dbc90e38284d9b4b1442e23873c94" dependencies = [ "arrayvec", "grid", @@ -11358,6 +11511,7 @@ dependencies = [ name = "telemetry_events" version = "0.1.0" dependencies = [ + "language", "semantic_version", "serde", ] @@ -11423,12 +11577,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ "rustix 0.38.35", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -11507,7 +11661,6 @@ dependencies = [ "serde_json_lenient", "serde_repr", "settings", - "story", "util", "uuid", ] @@ -11554,18 +11707,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", @@ -11605,7 +11758,7 @@ dependencies = [ "fancy-regex", "lazy_static", "parking_lot", - "rustc-hash", + "rustc-hash 1.1.0", ] [[package]] @@ -11744,6 +11897,7 @@ dependencies = [ "pretty_assertions", "project", "recent_projects", + "remote", "rpc", "serde", "settings", @@ -11820,6 +11974,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.13", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-socks" version = "0.5.2" @@ -11869,9 +12034,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes 1.7.1", "futures-core", @@ -12053,16 +12218,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -12369,6 +12524,24 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes 1.7.1", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror", + "utf-8", +] + [[package]] name = "typeid" version = "1.0.2" @@ -12527,18 +12700,43 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97" +dependencies = [ + "base64 0.21.7", + "flate2", + "log", + "once_cell", + "rustls 0.21.12", + "rustls-webpki 0.101.7", + "url", + "webpki-roots 0.25.4", +] + +[[package]] +name = "ureq_client" +version = "0.1.0" +dependencies = [ + "anyhow", + "futures 0.3.30", + "gpui", + "http_client", + "parking_lot", + "serde", + "smol", + "ureq", + "util", +] + [[package]] name = "url" version = "2.5.2" @@ -12842,7 +13040,7 @@ dependencies = [ "futures-util", "headers", "http 0.2.12", - "hyper", + "hyper 0.14.30", "log", "mime", "mime_guess", @@ -12978,6 +13176,19 @@ dependencies = [ "wasmparser 0.201.0", ] +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.201.0" @@ -13393,8 +13604,8 @@ 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", + "ring", + "untrusted", ] [[package]] @@ -13651,6 +13862,17 @@ dependencies = [ "syn 2.0.76", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -14116,6 +14338,7 @@ dependencies = [ "parking_lot", "postage", "project", + "release_channel", "remote", "schemars", "serde", @@ -14311,9 +14534,9 @@ dependencies = [ [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yazi" @@ -14396,13 +14619,14 @@ dependencies = [ [[package]] name = "zed" -version = "0.155.0" +version = "0.157.0" dependencies = [ "activity_indicator", "anyhow", "ashpd", "assets", "assistant", + "async-watch", "audio", "auto_update", "backtrace", @@ -14440,8 +14664,6 @@ dependencies = [ "image_viewer", "inline_completion_button", "install_cli", - "isahc", - "isahc_http_client", "journal", "language", "language_model", @@ -14476,9 +14698,11 @@ dependencies = [ "session", "settings", "settings_ui", + "shellexpand 2.1.2", "simplelog", "smol", "snippet_provider", + "snippets_ui", "supermaven", "sysinfo", "tab_switcher", @@ -14492,6 +14716,7 @@ dependencies = [ "tree-sitter-md", "tree-sitter-rust", "ui", + "ureq_client", "url", "urlencoding", "util", @@ -14627,7 +14852,7 @@ dependencies = [ [[package]] name = "zed_lua" -version = "0.0.3" +version = "0.1.0" dependencies = [ "zed_extension_api 0.1.0", ] @@ -14641,7 +14866,7 @@ dependencies = [ [[package]] name = "zed_php" -version = "0.2.0" +version = "0.2.1" dependencies = [ "zed_extension_api 0.1.0", ] @@ -14653,6 +14878,13 @@ dependencies = [ "zed_extension_api 0.1.0", ] +[[package]] +name = "zed_proto" +version = "0.2.0" +dependencies = [ + "zed_extension_api 0.1.0", +] + [[package]] name = "zed_purescript" version = "0.0.1" @@ -14691,7 +14923,7 @@ dependencies = [ [[package]] name = "zed_terraform" -version = "0.1.0" +version = "0.1.1" dependencies = [ "zed_extension_api 0.1.0", ] diff --git a/Cargo.toml b/Cargo.toml index 3902fd31f6..30cf5f0684 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,6 @@ members = [ "crates/indexed_docs", "crates/inline_completion_button", "crates/install_cli", - "crates/isahc_http_client", "crates/journal", "crates/language", "crates/language_model", @@ -88,6 +87,7 @@ members = [ "crates/remote", "crates/remote_server", "crates/repl", + "crates/reqwest_client", "crates/rich_text", "crates/rope", "crates/rpc", @@ -99,6 +99,7 @@ members = [ "crates/settings_ui", "crates/snippet", "crates/snippet_provider", + "crates/snippets_ui", "crates/sqlez", "crates/sqlez_macros", "crates/story", @@ -121,6 +122,7 @@ members = [ "crates/ui", "crates/ui_input", "crates/ui_macros", + "crates/ureq_client", "crates/util", "crates/vcs_menu", "crates/vim", @@ -152,6 +154,7 @@ members = [ "extensions/php", "extensions/perplexity", "extensions/prisma", + "extensions/proto", "extensions/purescript", "extensions/ruff", "extensions/ruby", @@ -174,6 +177,7 @@ members = [ default-members = ["crates/zed"] [workspace.dependencies] + # # Workspace member crates # @@ -219,7 +223,6 @@ go_to_line = { path = "crates/go_to_line" } google_ai = { path = "crates/google_ai" } gpui = { path = "crates/gpui" } gpui_macros = { path = "crates/gpui_macros" } -handlebars = "4.3" headless = { path = "crates/headless" } html_to_markdown = { path = "crates/html_to_markdown" } http_client = { path = "crates/http_client" } @@ -227,7 +230,6 @@ 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" } @@ -264,6 +266,7 @@ release_channel = { path = "crates/release_channel" } remote = { path = "crates/remote" } remote_server = { path = "crates/remote_server" } repl = { path = "crates/repl" } +reqwest_client = { path = "crates/reqwest_client" } rich_text = { path = "crates/rich_text" } rope = { path = "crates/rope" } rpc = { path = "crates/rpc" } @@ -275,6 +278,7 @@ settings = { path = "crates/settings" } settings_ui = { path = "crates/settings_ui" } snippet = { path = "crates/snippet" } snippet_provider = { path = "crates/snippet_provider" } +snippets_ui = { path = "crates/snippets_ui" } sqlez = { path = "crates/sqlez" } sqlez_macros = { path = "crates/sqlez_macros" } story = { path = "crates/story" } @@ -297,6 +301,7 @@ title_bar = { path = "crates/title_bar" } ui = { path = "crates/ui" } ui_input = { path = "crates/ui_input" } ui_macros = { path = "crates/ui_macros" } +ureq_client = { path = "crates/ureq_client" } util = { path = "crates/util" } vcs_menu = { path = "crates/vcs_menu" } vim = { path = "crates/vim" } @@ -316,6 +321,7 @@ any_vec = "0.14" anyhow = "1.0.86" arrayvec = { version = "0.7.4", features = ["serde"] } ashpd = "0.9.1" +async-compat = "0.2.1" async-compression = { version = "0.4", features = ["gzip", "futures-io"] } async-dispatcher = "0.1" async-fs = "1.6" @@ -323,7 +329,7 @@ async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "8 async-recursion = "1.0.0" async-tar = "0.5.0" async-trait = "0.1" -async-tungstenite = "0.23" +async-tungstenite = "0.28" async-watch = "0.3.1" async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] } base64 = "0.22" @@ -354,18 +360,15 @@ futures-batch = "0.6.1" futures-lite = "1.13" git2 = { version = "0.19", default-features = false } globset = "0.4" +handlebars = "4.3" heed = { version = "0.20.1", features = ["read-txn-no-tls"] } hex = "0.4.3" -hyper = "0.14" html5ever = "0.27.0" +hyper = "0.14" ignore = "0.4.22" image = "0.25.1" indexmap = { version = "1.6.2", features = ["serde"] } indoc = "2" -# We explicitly disable http2 support in isahc. -isahc = { version = "1.7.2", default-features = false, features = [ - "text-decoding", -] } itertools = "0.13.0" jsonwebtoken = "9.3" libc = "0.2" @@ -380,9 +383,9 @@ ordered-float = "2.1.1" palette = { version = "0.7.5", default-features = false, features = ["std"] } parking_lot = "0.12.1" pathdiff = "0.2" -profiling = "1" postage = { version = "0.5", features = ["futures-traits"] } pretty_assertions = "1.3.0" +profiling = "1" prost = "0.9" prost-build = "0.9" prost-types = "0.9" @@ -390,13 +393,14 @@ pulldown-cmark = { version = "0.12.0", default-features = false } rand = "0.8.5" regex = "1.5" repair_json = "0.1.0" +reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29" } rsa = "0.9.6" runtimelib = { version = "0.15", default-features = false, features = [ "async-dispatcher-runtime", ] } rustc-demangle = "0.1.23" rust-embed = { version = "8.4", features = ["include-exclude"] } -rustls = "0.20.3" +rustls = "0.21.12" rustls-native-certs = "0.8.0" rust-stemmers = "1.2" schemars = { version = "0.8", features = ["impl_json_schema"] } @@ -417,6 +421,7 @@ similar = "1.3" simplelog = "0.12.2" smallvec = { version = "1.6", features = ["union"] } smol = "1.2" +sqlformat = "0.2" strsim = "0.11" strum = { version = "0.25.0", features = ["derive"] } subtle = "2.5.0" @@ -451,15 +456,14 @@ tree-sitter-html = "0.20" tree-sitter-jsdoc = "0.23" tree-sitter-json = "0.23" tree-sitter-md = { git = "https://github.com/zed-industries/tree-sitter-markdown", rev = "4cfa6aad6b75052a5077c80fd934757d9267d81b" } -protols-tree-sitter-proto = { git = "https://github.com/zed-industries/tree-sitter-proto", rev = "0848bd30a64be48772e15fbb9d5ba8c0cc5772ad" } tree-sitter-python = "0.23" tree-sitter-regex = "0.23" tree-sitter-ruby = "0.23" tree-sitter-rust = "0.23" tree-sitter-typescript = "0.23" -tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" } -unindent = "0.1.7" +tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" } unicase = "2.6" +unindent = "0.1.7" unicode-segmentation = "1.10" url = "2.2" uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] } diff --git a/Dockerfile b/Dockerfile-collab similarity index 96% rename from Dockerfile rename to Dockerfile-collab index 2fb5f04147..7945eb426d 100644 --- a/Dockerfile +++ b/Dockerfile-collab @@ -31,10 +31,12 @@ ENV GITHUB_SHA=$GITHUB_SHA # - Staging: `4f408ec65a3867278322a189b4eb20f1ab51f508` # - Production: `fc4c533d0a8c489e5636a4249d2b52a80039fbd7` # +# Also add `cmake`, since we need it to build `wasmtime`. +# # Installing these as a temporary workaround, but I think ideally we'd want to figure # out what caused them to be included in the first place. RUN apt-get update; \ - apt-get install -y --no-install-recommends libxkbcommon-dev libxkbcommon-x11-dev + apt-get install -y --no-install-recommends libxkbcommon-dev libxkbcommon-x11-dev cmake RUN --mount=type=cache,target=./script/node_modules \ --mount=type=cache,target=/usr/local/cargo/registry \ diff --git a/.dockerignore b/Dockerfile-collab.dockerignore similarity index 100% rename from .dockerignore rename to Dockerfile-collab.dockerignore diff --git a/Dockerfile-distros b/Dockerfile-distros new file mode 100644 index 0000000000..c8a98d2f7d --- /dev/null +++ b/Dockerfile-distros @@ -0,0 +1,26 @@ +# syntax=docker/dockerfile:1 + +ARG BASE_IMAGE +FROM ${BASE_IMAGE} +WORKDIR /app +ARG TZ=Etc/UTC \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + DEBIAN_FRONTEND=noninteractive +ENV CARGO_TERM_COLOR=always + +COPY script/linux script/ +RUN ./script/linux +COPY script/install-mold script/install-cmake script/ +RUN ./script/install-mold "2.34.0" +RUN ./script/install-cmake "3.30.4" + +COPY . . + +# When debugging, make these into individual RUN statements. +# Cleanup to avoid saving big layers we aren't going to use. +RUN . "$HOME/.cargo/env" \ + && cargo fetch \ + && cargo build \ + && cargo run -- --help \ + && cargo clean --quiet diff --git a/Dockerfile-distros.dockerignore b/Dockerfile-distros.dockerignore new file mode 100644 index 0000000000..de70e0d167 --- /dev/null +++ b/Dockerfile-distros.dockerignore @@ -0,0 +1,2 @@ +**/target +**/node_modules diff --git a/assets/icons/trash_alt.svg b/assets/icons/trash_alt.svg new file mode 100644 index 0000000000..6867b42147 --- /dev/null +++ b/assets/icons/trash_alt.svg @@ -0,0 +1 @@ + diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index f15c4dfe22..d33df02747 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -196,7 +196,7 @@ } }, { - "context": "BufferSearchBar && in_replace", + "context": "BufferSearchBar && in_replace > Editor", "bindings": { "enter": "search::ReplaceNext", "ctrl-enter": "search::ReplaceAll" @@ -310,6 +310,11 @@ "ctrl-shift-\\": "editor::MoveToEnclosingBracket", "ctrl-shift-[": "editor::Fold", "ctrl-shift-]": "editor::UnfoldLines", + "ctrl-k ctrl-l": "editor::ToggleFold", + "ctrl-k ctrl-[": "editor::FoldRecursive", + "ctrl-k ctrl-]": "editor::UnfoldRecursive", + "ctrl-k ctrl-0": "editor::FoldAll", + "ctrl-k ctrl-j": "editor::UnfoldAll", "ctrl-space": "editor::ShowCompletions", "ctrl-.": "editor::ToggleCodeActions", "alt-ctrl-r": "editor::RevealInFileManager", diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index a58112b3c0..de929b8dd1 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -232,7 +232,7 @@ } }, { - "context": "BufferSearchBar && in_replace", + "context": "BufferSearchBar && in_replace > Editor", "bindings": { "enter": "search::ReplaceNext", "cmd-enter": "search::ReplaceAll" @@ -347,6 +347,11 @@ "cmd-shift-\\": "editor::MoveToEnclosingBracket", "alt-cmd-[": "editor::Fold", "alt-cmd-]": "editor::UnfoldLines", + "cmd-k cmd-l": "editor::ToggleFold", + "cmd-k cmd-[": "editor::FoldRecursive", + "cmd-k cmd-]": "editor::UnfoldRecursive", + "cmd-k cmd-0": "editor::FoldAll", + "cmd-k cmd-j": "editor::UnfoldAll", "ctrl-space": "editor::ShowCompletions", "cmd-.": "editor::ToggleCodeActions", "alt-cmd-r": "editor::RevealInFileManager", @@ -435,7 +440,12 @@ "cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"], "cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"], "cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"], - "cmd-shift-x": "zed::Extensions", + "cmd-shift-x": "zed::Extensions" + } + }, + { + "context": "Workspace && !Terminal", + "bindings": { "alt-t": "task::Rerun", "alt-shift-t": "task::Spawn" } diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 8d933f19af..f3a088f11e 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -132,9 +132,15 @@ "z z": "editor::ScrollCursorCenter", "z .": ["workspace::SendKeystrokes", "z z ^"], "z b": "editor::ScrollCursorBottom", + "z a": "editor::ToggleFold", + "z A": "editor::ToggleFoldRecursive", "z c": "editor::Fold", + "z C": "editor::FoldRecursive", "z o": "editor::UnfoldLines", + "z O": "editor::UnfoldRecursive", "z f": "editor::FoldSelectedRanges", + "z M": "editor::FoldAll", + "z R": "editor::UnfoldAll", "shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }], "shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }], // Count support @@ -292,6 +298,8 @@ "g ctrl-x": ["vim::Decrement", { "step": true }], "shift-i": "vim::InsertBefore", "shift-a": "vim::InsertAfter", + "g I": "vim::VisualInsertFirstNonWhiteSpace", + "g A": "vim::VisualInsertEndOfLine", "shift-j": "vim::JoinLines", "r": ["vim::PushOperator", "Replace"], "ctrl-c": ["vim::SwitchMode", "Normal"], diff --git a/assets/prompts/content_prompt.hbs b/assets/prompts/content_prompt.hbs index e944e230f5..c029f84b24 100644 --- a/assets/prompts/content_prompt.hbs +++ b/assets/prompts/content_prompt.hbs @@ -50,6 +50,9 @@ And here's the section to rewrite based on that prompt again for reference: {{#if diagnostic_errors}} {{#each diagnostic_errors}} + +Below are the diagnostic errors visible to the user. If the user requests problems to be fixed, use this information, but do not try to fix these errors if the user hasn't asked you to. + {{line_number}} {{error_message}} diff --git a/assets/settings/default.json b/assets/settings/default.json index e04ab90f21..e5cbcd2f9c 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -356,9 +356,19 @@ /// Scrollbar-related settings "scrollbar": { /// When to show the scrollbar in the project panel. + /// This setting can take four values: /// - /// Default: always - "show": "always" + /// 1. null (default): Inherit editor settings + /// 2. Show the scrollbar if there's important information or + /// follow the system's configured behavior (default): + /// "auto" + /// 3. Match the system's configured behavior: + /// "system" + /// 4. Always show the scrollbar: + /// "always" + /// 5. Never show the scrollbar: + /// "never" + "show": null } }, "outline_panel": { @@ -535,17 +545,16 @@ // How to soft-wrap long lines of text. // Possible values: // - // 1. Do not soft wrap. + // 1. Prefer a single line generally, unless an overly long line is encountered. // "soft_wrap": "none", - // 2. Prefer a single line generally, unless an overly long line is encountered. - // "soft_wrap": "prefer_line", - // 3. Soft wrap lines that overflow the editor. + // "soft_wrap": "prefer_line", // (deprecated, same as "none") + // 2. Soft wrap lines that overflow the editor. // "soft_wrap": "editor_width", - // 4. Soft wrap lines at the preferred line length. + // 3. Soft wrap lines at the preferred line length. // "soft_wrap": "preferred_line_length", - // 5. Soft wrap lines at the preferred line length or the editor width (whichever is smaller). + // 4. Soft wrap lines at the preferred line length or the editor width (whichever is smaller). // "soft_wrap": "bounded", - "soft_wrap": "prefer_line", + "soft_wrap": "none", // The column at which to soft-wrap lines, for buffers where soft-wrap // is enabled. "preferred_line_length": 80, @@ -600,13 +609,11 @@ } }, // Configuration for how direnv configuration should be loaded. May take 2 values: - // 1. Load direnv configuration through the shell hook, works for POSIX shells and fish. - // "load_direnv": "shell_hook" - // 2. Load direnv configuration using `direnv export json` directly. - // This can help with some shells that otherwise would not detect - // the direnv environment, such as nushell or elvish. + // 1. Load direnv configuration using `direnv export json` directly. // "load_direnv": "direct" - "load_direnv": "shell_hook", + // 2. Load direnv configuration through the shell hook, works for POSIX shells and fish. + // "load_direnv": "shell_hook" + "load_direnv": "direct", "inline_completions": { // A list of globs representing files that inline completions should be disabled for. "disabled_globs": [".env"] @@ -672,6 +679,18 @@ // 3. Always blink the cursor, ignoring the terminal mode // "blinking": "on", "blinking": "terminal_controlled", + // Default cursor shape for the terminal. + // 1. A block that surrounds the following character + // "block" + // 2. A vertical bar + // "bar" + // 3. An underline that runs along the following character + // "underscore" + // 4. A box drawn around the following character + // "hollow" + // + // Default: not set, defaults to "block" + "cursor_shape": null, // Set whether Alternate Scroll mode (code: ?1007) is active by default. // Alternate Scroll mode converts mouse scroll events into up / down key // presses when in the alternate screen (e.g. when running applications @@ -762,6 +781,7 @@ // } // "file_types": { + "Plain Text": ["txt"], "JSON": ["flake.lock"], "JSONC": [ "**/.zed/**/*.json", @@ -769,8 +789,24 @@ "**/Zed/**/*.json", "tsconfig.json", "pyrightconfig.json" - ] + ], + "TOML": ["uv.lock"] }, + /// By default use a recent system version of node, or install our own. + /// You can override this to use a version of node that is not in $PATH with: + /// { + /// "node": { + /// "node_path": "/path/to/node" + /// "npm_path": "/path/to/npm" (defaults to node_path/../npm) + /// } + /// } + /// or to ensure Zed always downloads and installs an isolated version of node: + /// { + /// "node": { + /// "ignore_system_version": true, + /// } + /// NOTE: changing this setting currently requires restarting Zed. + "node": {}, // The extensions that Zed should automatically install on startup. // // If you don't want any of these extensions, add this field to your settings @@ -804,6 +840,9 @@ "allowed": true } }, + "Dart": { + "tab_size": 2 + }, "Elixir": { "language_servers": ["elixir-ls", "!next-ls", "!lexical", "..."] }, diff --git a/clippy.toml b/clippy.toml index 787620d865..8c8da03a26 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1,2 @@ allow-private-module-inception = true +avoid-breaking-exported-api = false diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index a9ae7d075d..687519068d 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -10,7 +10,7 @@ use gpui::{ use language::{ LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId, LanguageServerName, }; -use project::{LanguageServerProgress, Project}; +use project::{EnvironmentErrorMessage, LanguageServerProgress, Project, WorktreeId}; use smallvec::SmallVec; use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration}; use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle}; @@ -175,7 +175,31 @@ impl ActivityIndicator { .flatten() } + fn pending_environment_errors<'a>( + &'a self, + cx: &'a AppContext, + ) -> impl Iterator { + self.project.read(cx).shell_environment_errors(cx) + } + fn content_to_render(&mut self, cx: &mut ViewContext) -> Option { + // Show if any direnv calls failed + if let Some((&worktree_id, error)) = self.pending_environment_errors(cx).next() { + return Some(Content { + icon: Some( + Icon::new(IconName::Warning) + .size(IconSize::Small) + .into_any_element(), + ), + message: error.0.clone(), + on_click: Some(Arc::new(move |this, cx| { + this.project.update(cx, |project, cx| { + project.remove_environment_error(cx, worktree_id); + }); + cx.dispatch_action(Box::new(workspace::OpenLog)); + })), + }); + } // Show any language server has pending activity. let mut pending_work = self.pending_language_server_work(cx); if let Some(PendingWork { @@ -227,10 +251,10 @@ impl ActivityIndicator { for status in &self.statuses { match status.status { LanguageServerBinaryStatus::CheckingForUpdate => { - checking_for_update.push(status.name.0.as_ref()) + checking_for_update.push(status.name.clone()) } - LanguageServerBinaryStatus::Downloading => downloading.push(status.name.0.as_ref()), - LanguageServerBinaryStatus::Failed { .. } => failed.push(status.name.0.as_ref()), + LanguageServerBinaryStatus::Downloading => downloading.push(status.name.clone()), + LanguageServerBinaryStatus::Failed { .. } => failed.push(status.name.clone()), LanguageServerBinaryStatus::None => {} } } @@ -242,8 +266,24 @@ impl ActivityIndicator { .size(IconSize::Small) .into_any_element(), ), - message: format!("Downloading {}...", downloading.join(", "),), - on_click: None, + message: format!( + "Downloading {}...", + downloading.iter().map(|name| name.0.as_ref()).fold( + String::new(), + |mut acc, s| { + if !acc.is_empty() { + acc.push_str(", "); + } + acc.push_str(s); + acc + } + ) + ), + on_click: Some(Arc::new(move |this, cx| { + this.statuses + .retain(|status| !downloading.contains(&status.name)); + this.dismiss_error_message(&DismissErrorMessage, cx) + })), }); } @@ -256,9 +296,22 @@ impl ActivityIndicator { ), message: format!( "Checking for updates to {}...", - checking_for_update.join(", "), + checking_for_update.iter().map(|name| name.0.as_ref()).fold( + String::new(), + |mut acc, s| { + if !acc.is_empty() { + acc.push_str(", "); + } + acc.push_str(s); + acc + } + ), ), - on_click: None, + on_click: Some(Arc::new(move |this, cx| { + this.statuses + .retain(|status| !checking_for_update.contains(&status.name)); + this.dismiss_error_message(&DismissErrorMessage, cx) + })), }); } @@ -270,8 +323,17 @@ impl ActivityIndicator { .into_any_element(), ), message: format!( - "Failed to download {}. Click to show error.", - failed.join(", "), + "Failed to run {}. Click to show error.", + failed + .iter() + .map(|name| name.0.as_ref()) + .fold(String::new(), |mut acc, s| { + if !acc.is_empty() { + acc.push_str(", "); + } + acc.push_str(s); + acc + }), ), on_click: Some(Arc::new(|this, cx| { this.show_error_message(&Default::default(), cx) @@ -280,7 +342,7 @@ impl ActivityIndicator { } // Show any formatting failure - if let Some(failure) = self.project.read(cx).last_formatting_failure() { + if let Some(failure) = self.project.read(cx).last_formatting_failure(cx) { return Some(Content { icon: Some( Icon::new(IconName::Warning) @@ -304,7 +366,9 @@ impl ActivityIndicator { .into_any_element(), ), message: "Checking for Zed updates…".to_string(), - on_click: None, + on_click: Some(Arc::new(|this, cx| { + this.dismiss_error_message(&DismissErrorMessage, cx) + })), }), AutoUpdateStatus::Downloading => Some(Content { icon: Some( @@ -313,7 +377,9 @@ impl ActivityIndicator { .into_any_element(), ), message: "Downloading Zed update…".to_string(), - on_click: None, + on_click: Some(Arc::new(|this, cx| { + this.dismiss_error_message(&DismissErrorMessage, cx) + })), }), AutoUpdateStatus::Installing => Some(Content { icon: Some( @@ -322,7 +388,9 @@ impl ActivityIndicator { .into_any_element(), ), message: "Installing Zed update…".to_string(), - on_click: None, + on_click: Some(Arc::new(|this, cx| { + this.dismiss_error_message(&DismissErrorMessage, cx) + })), }), AutoUpdateStatus::Updated { binary_path } => Some(Content { icon: None, @@ -342,7 +410,7 @@ impl ActivityIndicator { ), message: "Auto update failed".to_string(), on_click: Some(Arc::new(|this, cx| { - this.dismiss_error_message(&Default::default(), cx) + this.dismiss_error_message(&DismissErrorMessage, cx) })), }), AutoUpdateStatus::Idle => None, @@ -360,7 +428,9 @@ impl ActivityIndicator { .into_any_element(), ), message: format!("Updating {extension_id} extension…"), - on_click: None, + on_click: Some(Arc::new(|this, cx| { + this.dismiss_error_message(&DismissErrorMessage, cx) + })), }); } } diff --git a/crates/anthropic/Cargo.toml b/crates/anthropic/Cargo.toml index 9e48ad0e57..ec12932fb7 100644 --- a/crates/anthropic/Cargo.toml +++ b/crates/anthropic/Cargo.toml @@ -20,7 +20,6 @@ anyhow.workspace = true chrono.workspace = true futures.workspace = true http_client.workspace = true -isahc.workspace = true schemars = { workspace = true, optional = true } serde.workspace = true serde_json.workspace = true diff --git a/crates/anthropic/src/anthropic.rs b/crates/anthropic/src/anthropic.rs index 91b6723e90..08c8f27bd9 100644 --- a/crates/anthropic/src/anthropic.rs +++ b/crates/anthropic/src/anthropic.rs @@ -6,9 +6,8 @@ use std::{pin::Pin, str::FromStr}; use anyhow::{anyhow, Context, Result}; use chrono::{DateTime, Utc}; use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt}; -use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest}; -use isahc::config::Configurable; -use isahc::http::{HeaderMap, HeaderValue}; +use http_client::http::{HeaderMap, HeaderValue}; +use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest}; use serde::{Deserialize, Serialize}; use strum::{EnumIter, EnumString}; use thiserror::Error; @@ -289,7 +288,7 @@ pub async fn stream_completion_with_rate_limit_info( .header("X-Api-Key", api_key) .header("Content-Type", "application/json"); if let Some(low_speed_timeout) = low_speed_timeout { - request_builder = request_builder.low_speed_timeout(100, low_speed_timeout); + request_builder = request_builder.read_timeout(low_speed_timeout); } let serialized_request = serde_json::to_string(&request).context("failed to serialize request")?; @@ -522,6 +521,10 @@ pub struct Usage { pub input_tokens: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub output_tokens: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_creation_input_tokens: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cache_read_input_tokens: Option, } #[derive(Debug, Serialize, Deserialize)] diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 9f715d8224..9e61eee18a 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -51,6 +51,7 @@ indoc.workspace = true language.workspace = true language_model.workspace = true log.workspace = true +lsp.workspace = true markdown.workspace = true menu.workspace = true multi_buffer.workspace = true diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 22237eeb07..20e6347d01 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -72,11 +72,12 @@ use std::{ time::Duration, }; use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; +use text::SelectionGoal; use ui::TintColor; use ui::{ prelude::*, utils::{format_distance_from_now, DateTimeType}, - Avatar, AvatarShape, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem, + Avatar, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tooltip, }; use util::{maybe, ResultExt}; @@ -261,9 +262,7 @@ impl PickerDelegate for SavedContextPickerDelegate { .gap_2() .children(if let Some(host_user) = host_user { vec![ - Avatar::new(host_user.avatar_uri.clone()) - .shape(AvatarShape::Circle) - .into_any_element(), + Avatar::new(host_user.avatar_uri.clone()).into_any_element(), Label::new(format!("Shared by @{}", host_user.github_login)) .color(Color::Muted) .size(LabelSize::Small) @@ -960,7 +959,8 @@ impl AssistantPanel { } fn new_context(&mut self, cx: &mut ViewContext) -> Option> { - if self.project.read(cx).is_via_collab() { + let project = self.project.read(cx); + if project.is_via_collab() && project.dev_server_project_id().is_none() { let task = self .context_store .update(cx, |store, cx| store.create_remote_context(cx)); @@ -3437,7 +3437,7 @@ impl ContextEditor { fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext) { if self.editor.read(cx).selections.count() == 1 { - let (copied_text, metadata) = self.get_clipboard_contents(cx); + let (copied_text, metadata, _) = self.get_clipboard_contents(cx); cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata( copied_text, metadata, @@ -3451,11 +3451,9 @@ impl ContextEditor { fn cut(&mut self, _: &editor::actions::Cut, cx: &mut ViewContext) { if self.editor.read(cx).selections.count() == 1 { - let (copied_text, metadata) = self.get_clipboard_contents(cx); + let (copied_text, metadata, selections) = self.get_clipboard_contents(cx); self.editor.update(cx, |editor, cx| { - let selections = editor.selections.all::(cx); - editor.transact(cx, |this, cx| { this.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select(selections); @@ -3475,52 +3473,71 @@ impl ContextEditor { cx.propagate(); } - fn get_clipboard_contents(&mut self, cx: &mut ViewContext) -> (String, CopyMetadata) { - let creases = self.editor.update(cx, |editor, cx| { - let selection = editor.selections.newest::(cx); - let selection_start = editor.selections.newest::(cx).start; + fn get_clipboard_contents( + &mut self, + cx: &mut ViewContext, + ) -> (String, CopyMetadata, Vec>) { + let (snapshot, selection, creases) = self.editor.update(cx, |editor, cx| { + let mut selection = editor.selections.newest::(cx); let snapshot = editor.buffer().read(cx).snapshot(cx); - editor.display_map.update(cx, |display_map, cx| { - display_map - .snapshot(cx) - .crease_snapshot - .creases_in_range( - MultiBufferRow(selection.start.row)..MultiBufferRow(selection.end.row + 1), - &snapshot, - ) - .filter_map(|crease| { - if let Some(metadata) = &crease.metadata { - let start = crease - .range - .start - .to_offset(&snapshot) - .saturating_sub(selection_start); - let end = crease - .range - .end - .to_offset(&snapshot) - .saturating_sub(selection_start); - let range_relative_to_selection = start..end; + let is_entire_line = selection.is_empty() || editor.selections.line_mode; + if is_entire_line { + selection.start = Point::new(selection.start.row, 0); + selection.end = + cmp::min(snapshot.max_point(), Point::new(selection.start.row + 1, 0)); + selection.goal = SelectionGoal::None; + } - if range_relative_to_selection.is_empty() { - None + let selection_start = snapshot.point_to_offset(selection.start); + + ( + snapshot.clone(), + selection.clone(), + editor.display_map.update(cx, |display_map, cx| { + display_map + .snapshot(cx) + .crease_snapshot + .creases_in_range( + MultiBufferRow(selection.start.row) + ..MultiBufferRow(selection.end.row + 1), + &snapshot, + ) + .filter_map(|crease| { + if let Some(metadata) = &crease.metadata { + let start = crease + .range + .start + .to_offset(&snapshot) + .saturating_sub(selection_start); + let end = crease + .range + .end + .to_offset(&snapshot) + .saturating_sub(selection_start); + + let range_relative_to_selection = start..end; + + if range_relative_to_selection.is_empty() { + None + } else { + Some(SelectedCreaseMetadata { + range_relative_to_selection, + crease: metadata.clone(), + }) + } } else { - Some(SelectedCreaseMetadata { - range_relative_to_selection, - crease: metadata.clone(), - }) + None } - } else { - None - } - }) - .collect::>() - }) + }) + .collect::>() + }), + ) }); + let selection = selection.map(|point| snapshot.point_to_offset(point)); let context = self.context.read(cx); - let selection = self.editor.read(cx).selections.newest::(cx); + let mut text = String::new(); for message in context.messages(cx) { if message.offset_range.start >= selection.range().end { @@ -3539,7 +3556,7 @@ impl ContextEditor { } } - (text, CopyMetadata { creases }) + (text, CopyMetadata { creases }, vec![selection]) } fn paste(&mut self, action: &editor::actions::Paste, cx: &mut ViewContext) { diff --git a/crates/assistant/src/context.rs b/crates/assistant/src/context.rs index 4f1f885b33..c27d17f8c5 100644 --- a/crates/assistant/src/context.rs +++ b/crates/assistant/src/context.rs @@ -46,7 +46,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use telemetry_events::{AssistantKind, AssistantPhase}; +use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use text::BufferSnapshot; use util::{post_inc, ResultExt, TryFutureExt}; use uuid::Uuid; @@ -549,7 +549,7 @@ impl Context { cx: &mut ModelContext, ) -> Self { let buffer = cx.new_model(|_cx| { - let mut buffer = Buffer::remote( + let buffer = Buffer::remote( language::BufferId::new(1).unwrap(), replica_id, capability, @@ -2133,14 +2133,21 @@ impl Context { }); if let Some(telemetry) = this.telemetry.as_ref() { - telemetry.report_assistant_event( - Some(this.id.0.clone()), - AssistantKind::Panel, - AssistantPhase::Response, - model.telemetry_id(), + let language_name = this + .buffer + .read(cx) + .language() + .map(|language| language.name()); + telemetry.report_assistant_event(AssistantEvent { + conversation_id: Some(this.id.0.clone()), + kind: AssistantKind::Panel, + phase: AssistantPhase::Response, + model: model.telemetry_id(), + model_provider: model.provider_id().to_string(), response_latency, error_message, - ); + language_name, + }); } if let Ok(stop_reason) = result { diff --git a/crates/assistant/src/context_store.rs b/crates/assistant/src/context_store.rs index f57a2fbca6..f4f03dda37 100644 --- a/crates/assistant/src/context_store.rs +++ b/crates/assistant/src/context_store.rs @@ -357,9 +357,6 @@ impl ContextStore { let Some(project_id) = project.remote_id() else { return Task::ready(Err(anyhow!("project was not remote"))); }; - if project.is_local_or_ssh() { - return Task::ready(Err(anyhow!("cannot create remote contexts as the host"))); - } let replica_id = project.replica_id(); let capability = project.capability(); @@ -488,9 +485,6 @@ impl ContextStore { let Some(project_id) = project.remote_id() else { return Task::ready(Err(anyhow!("project was not remote"))); }; - if project.is_local_or_ssh() { - return Task::ready(Err(anyhow!("cannot open remote contexts as the host"))); - } if let Some(context) = self.loaded_context_for_id(&context_id, cx) { return Task::ready(Ok(context)); diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index f2428c3a2e..b3427271b9 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -12,8 +12,9 @@ use editor::{ BlockContext, BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, RenderBlock, ToDisplayPoint, }, - Anchor, AnchorRangeExt, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, - ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, + Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode, + EditorStyle, ExcerptId, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, + ToOffset as _, ToPoint, }; use feature_flags::{FeatureFlagAppExt as _, ZedPro}; use fs::Fs; @@ -35,6 +36,7 @@ use language_model::{ }; use multi_buffer::MultiBufferRow; use parking_lot::Mutex; +use project::{CodeAction, ProjectTransaction}; use rope::Rope; use settings::{Settings, SettingsStore}; use smol::future::FutureExt; @@ -48,11 +50,13 @@ use std::{ task::{self, Poll}, time::{Duration, Instant}, }; +use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use terminal_view::terminal_panel::TerminalPanel; +use text::{OffsetRangeExt, ToPoint as _}; use theme::ThemeSettings; use ui::{prelude::*, CheckboxWithLabel, IconButtonShape, Popover, Tooltip}; use util::{RangeExt, ResultExt}; -use workspace::{notifications::NotificationId, Toast, Workspace}; +use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace}; pub fn init( fs: Arc, @@ -129,8 +133,10 @@ impl InlineAssistant { } pub fn register_workspace(&mut self, workspace: &View, cx: &mut WindowContext) { - cx.subscribe(workspace, |_, event, cx| { - Self::update_global(cx, |this, cx| this.handle_workspace_event(event, cx)); + cx.subscribe(workspace, |workspace, event, cx| { + Self::update_global(cx, |this, cx| { + this.handle_workspace_event(workspace, event, cx) + }); }) .detach(); @@ -150,19 +156,49 @@ impl InlineAssistant { .detach(); } - fn handle_workspace_event(&mut self, event: &workspace::Event, cx: &mut WindowContext) { - // When the user manually saves an editor, automatically accepts all finished transformations. - if let workspace::Event::UserSavedItem { item, .. } = event { - if let Some(editor) = item.upgrade().and_then(|item| item.act_as::(cx)) { - if let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) { - for assist_id in editor_assists.assist_ids.clone() { - let assist = &self.assists[&assist_id]; - if let CodegenStatus::Done = assist.codegen.read(cx).status(cx) { - self.finish_assist(assist_id, false, cx) + fn handle_workspace_event( + &mut self, + workspace: View, + event: &workspace::Event, + cx: &mut WindowContext, + ) { + match event { + workspace::Event::UserSavedItem { item, .. } => { + // When the user manually saves an editor, automatically accepts all finished transformations. + if let Some(editor) = item.upgrade().and_then(|item| item.act_as::(cx)) { + if let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) { + for assist_id in editor_assists.assist_ids.clone() { + let assist = &self.assists[&assist_id]; + if let CodegenStatus::Done = assist.codegen.read(cx).status(cx) { + self.finish_assist(assist_id, false, cx) + } } } } } + workspace::Event::ItemAdded { item } => { + self.register_workspace_item(&workspace, item.as_ref(), cx); + } + _ => (), + } + } + + fn register_workspace_item( + &mut self, + workspace: &View, + item: &dyn ItemHandle, + cx: &mut WindowContext, + ) { + if let Some(editor) = item.act_as::(cx) { + editor.update(cx, |editor, cx| { + editor.push_code_action_provider( + Arc::new(AssistantCodeActionProvider { + editor: cx.view().downgrade(), + workspace: workspace.downgrade(), + }), + cx, + ); + }); } } @@ -174,18 +210,6 @@ impl InlineAssistant { initial_prompt: Option, 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::>::new(); @@ -232,6 +256,21 @@ impl InlineAssistant { text_anchor: buffer.anchor_after(buffer_range.end), }; codegen_ranges.push(start..end); + + if let Some(telemetry) = self.telemetry.as_ref() { + if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() { + telemetry.report_assistant_event(AssistantEvent { + conversation_id: None, + kind: AssistantKind::Inline, + phase: AssistantPhase::Invoked, + model: model.telemetry_id(), + model_provider: model.provider_id().to_string(), + response_latency: None, + error_message: None, + language_name: buffer.language().map(|language| language.name()), + }); + } + } } let assist_group_id = self.next_assist_group_id.post_inc(); @@ -332,6 +371,7 @@ impl InlineAssistant { mut range: Range, initial_prompt: String, initial_transaction_id: Option, + focus: bool, workspace: Option>, assistant_panel: Option<&View>, cx: &mut WindowContext, @@ -404,6 +444,11 @@ impl InlineAssistant { assist_group.assist_ids.push(assist_id); editor_assists.assist_ids.push(assist_id); self.assist_groups.insert(assist_group_id, assist_group); + + if focus { + self.focus_assist(assist_id, cx); + } + assist_id } @@ -720,23 +765,34 @@ 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) { + if let Some(telemetry) = self.telemetry.as_ref() { + if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() { + let language_name = assist.editor.upgrade().and_then(|editor| { + let multibuffer = editor.read(cx).buffer().read(cx); + let ranges = multibuffer.range_to_buffer_ranges(assist.range.clone(), cx); + ranges + .first() + .and_then(|(buffer, _, _)| buffer.read(cx).language()) + .map(|language| language.name()) + }); + telemetry.report_assistant_event(AssistantEvent { + conversation_id: None, + kind: AssistantKind::Inline, + phase: if undo { + AssistantPhase::Rejected + } else { + AssistantPhase::Accepted + }, + model: model.telemetry_id(), + model_provider: model.provider_id().to_string(), + response_latency: None, + error_message: None, + language_name, + }); + } + } + let assist_group_id = assist.group_id; if self.assist_groups[&assist_group_id].linked { for assist_id in self.unlink_assist_group(assist_group_id, cx) { @@ -1101,7 +1157,7 @@ impl InlineAssistant { for row_range in inserted_row_ranges { editor.highlight_rows::( row_range, - Some(cx.theme().status().info_background), + cx.theme().status().info_background, false, cx, ); @@ -1167,8 +1223,8 @@ impl InlineAssistant { editor.set_read_only(true); editor.set_show_inline_completions(Some(false), cx); editor.highlight_rows::( - Anchor::min()..=Anchor::max(), - Some(cx.theme().status().deleted_background), + Anchor::min()..Anchor::max(), + cx.theme().status().deleted_background, false, cx, ); @@ -2516,7 +2572,7 @@ enum CodegenStatus { #[derive(Default)] struct Diff { deleted_row_ranges: Vec<(Anchor, RangeInclusive)>, - inserted_row_ranges: Vec>, + inserted_row_ranges: Vec>, } impl Diff { @@ -2665,6 +2721,7 @@ impl CodegenAlternative { self.edit_position = Some(self.range.start.bias_right(&self.snapshot)); let telemetry_id = model.telemetry_id(); + let provider_id = model.provider_id(); let chunks: LocalBoxFuture>>> = if user_prompt.trim().to_lowercase() == "delete" { async { Ok(stream::empty().boxed()) }.boxed_local() @@ -2675,7 +2732,7 @@ impl CodegenAlternative { .spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await }); async move { Ok(chunks.await?.boxed()) }.boxed_local() }; - self.handle_stream(telemetry_id, chunks, cx); + self.handle_stream(telemetry_id, provider_id.to_string(), chunks, cx); Ok(()) } @@ -2739,6 +2796,7 @@ impl CodegenAlternative { pub fn handle_stream( &mut self, model_telemetry_id: String, + model_provider_id: String, stream: impl 'static + Future>>>, cx: &mut ModelContext, ) { @@ -2769,6 +2827,15 @@ impl CodegenAlternative { } let telemetry = self.telemetry.clone(); + let language_name = { + let multibuffer = self.buffer.read(cx); + let ranges = multibuffer.range_to_buffer_ranges(self.range.clone(), cx); + ranges + .first() + .and_then(|(buffer, _, _)| buffer.read(cx).language()) + .map(|language| language.name()) + }; + self.diff = Diff::default(); self.status = CodegenStatus::Pending; let mut edit_start = self.range.start.to_offset(&snapshot); @@ -2879,14 +2946,16 @@ impl CodegenAlternative { let error_message = result.as_ref().err().map(|error| error.to_string()); if let Some(telemetry) = telemetry { - telemetry.report_assistant_event( - None, - telemetry_events::AssistantKind::Inline, - telemetry_events::AssistantPhase::Response, - model_telemetry_id, + telemetry.report_assistant_event(AssistantEvent { + conversation_id: None, + kind: AssistantKind::Inline, + phase: AssistantPhase::Response, + model: model_telemetry_id, + model_provider: model_provider_id.to_string(), response_latency, error_message, - ); + language_name, + }); } result?; @@ -3062,7 +3131,7 @@ impl CodegenAlternative { new_end_row, new_snapshot.line_len(MultiBufferRow(new_end_row)), )); - self.diff.inserted_row_ranges.push(start..=end); + self.diff.inserted_row_ranges.push(start..end); new_row += lines; } } @@ -3140,7 +3209,7 @@ impl CodegenAlternative { new_end_row, new_snapshot.line_len(MultiBufferRow(new_end_row)), )); - inserted_row_ranges.push(start..=end); + inserted_row_ranges.push(start..end); new_row += line_count; } } @@ -3289,6 +3358,132 @@ where } } +struct AssistantCodeActionProvider { + editor: WeakView, + workspace: WeakView, +} + +impl CodeActionProvider for AssistantCodeActionProvider { + fn code_actions( + &self, + buffer: &Model, + range: Range, + cx: &mut WindowContext, + ) -> Task>> { + let snapshot = buffer.read(cx).snapshot(); + let mut range = range.to_point(&snapshot); + + // Expand the range to line boundaries. + range.start.column = 0; + range.end.column = snapshot.line_len(range.end.row); + + let mut has_diagnostics = false; + for diagnostic in snapshot.diagnostics_in_range::<_, Point>(range.clone(), false) { + range.start = cmp::min(range.start, diagnostic.range.start); + range.end = cmp::max(range.end, diagnostic.range.end); + has_diagnostics = true; + } + if has_diagnostics { + if let Some(symbols_containing_start) = snapshot.symbols_containing(range.start, None) { + if let Some(symbol) = symbols_containing_start.last() { + range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot)); + range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot)); + } + } + + if let Some(symbols_containing_end) = snapshot.symbols_containing(range.end, None) { + if let Some(symbol) = symbols_containing_end.last() { + range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot)); + range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot)); + } + } + + Task::ready(Ok(vec![CodeAction { + server_id: language::LanguageServerId(0), + range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end), + lsp_action: lsp::CodeAction { + title: "Fix with Assistant".into(), + ..Default::default() + }, + }])) + } else { + Task::ready(Ok(Vec::new())) + } + } + + fn apply_code_action( + &self, + buffer: Model, + action: CodeAction, + excerpt_id: ExcerptId, + _push_to_history: bool, + cx: &mut WindowContext, + ) -> Task> { + let editor = self.editor.clone(); + let workspace = self.workspace.clone(); + cx.spawn(|mut cx| async move { + let editor = editor.upgrade().context("editor was released")?; + let range = editor + .update(&mut cx, |editor, cx| { + editor.buffer().update(cx, |multibuffer, cx| { + let buffer = buffer.read(cx); + let multibuffer_snapshot = multibuffer.read(cx); + + let old_context_range = + multibuffer_snapshot.context_range_for_excerpt(excerpt_id)?; + let mut new_context_range = old_context_range.clone(); + if action + .range + .start + .cmp(&old_context_range.start, buffer) + .is_lt() + { + new_context_range.start = action.range.start; + } + if action.range.end.cmp(&old_context_range.end, buffer).is_gt() { + new_context_range.end = action.range.end; + } + drop(multibuffer_snapshot); + + if new_context_range != old_context_range { + multibuffer.resize_excerpt(excerpt_id, new_context_range, cx); + } + + let multibuffer_snapshot = multibuffer.read(cx); + Some( + multibuffer_snapshot + .anchor_in_excerpt(excerpt_id, action.range.start)? + ..multibuffer_snapshot + .anchor_in_excerpt(excerpt_id, action.range.end)?, + ) + }) + })? + .context("invalid range")?; + let assistant_panel = workspace.update(&mut cx, |workspace, cx| { + workspace + .panel::(cx) + .context("assistant panel was released") + })??; + + cx.update_global(|assistant: &mut InlineAssistant, cx| { + let assist_id = assistant.suggest_assist( + &editor, + range, + "Fix Diagnostics".into(), + None, + true, + Some(workspace), + Some(&assistant_panel), + cx, + ); + assistant.start_assist(assist_id, cx); + })?; + + Ok(ProjectTransaction::default()) + }) + } +} + fn prefixes(text: &str) -> impl Iterator { (0..text.len() - 1).map(|ix| &text[..ix + 1]) } @@ -3372,6 +3567,7 @@ mod tests { let (chunks_tx, chunks_rx) = mpsc::unbounded(); codegen.update(cx, |codegen, cx| { codegen.handle_stream( + String::new(), String::new(), future::ready(Ok(chunks_rx.map(Ok).boxed())), cx, @@ -3443,6 +3639,7 @@ mod tests { let (chunks_tx, chunks_rx) = mpsc::unbounded(); codegen.update(cx, |codegen, cx| { codegen.handle_stream( + String::new(), String::new(), future::ready(Ok(chunks_rx.map(Ok).boxed())), cx, @@ -3517,6 +3714,7 @@ mod tests { let (chunks_tx, chunks_rx) = mpsc::unbounded(); codegen.update(cx, |codegen, cx| { codegen.handle_stream( + String::new(), String::new(), future::ready(Ok(chunks_rx.map(Ok).boxed())), cx, @@ -3590,6 +3788,7 @@ mod tests { let (chunks_tx, chunks_rx) = mpsc::unbounded(); codegen.update(cx, |codegen, cx| { codegen.handle_stream( + String::new(), String::new(), future::ready(Ok(chunks_rx.map(Ok).boxed())), cx, @@ -3653,6 +3852,7 @@ mod tests { let (chunks_tx, chunks_rx) = mpsc::unbounded(); codegen.update(cx, |codegen, cx| { codegen.handle_stream( + String::new(), String::new(), future::ready(Ok(chunks_rx.map(Ok).boxed())), cx, diff --git a/crates/assistant/src/prompt_library.rs b/crates/assistant/src/prompt_library.rs index 24e20a18a7..6c43579d6e 100644 --- a/crates/assistant/src/prompt_library.rs +++ b/crates/assistant/src/prompt_library.rs @@ -910,7 +910,7 @@ impl PromptLibrary { .features .clone(), font_size: HeadlineSize::Large - .size() + .rems() .into(), font_weight: settings.ui_font.weight, line_height: relative( diff --git a/crates/assistant/src/slash_command/auto_command.rs b/crates/assistant/src/slash_command/auto_command.rs index 14cee29682..14bbb7c841 100644 --- a/crates/assistant/src/slash_command/auto_command.rs +++ b/crates/assistant/src/slash_command/auto_command.rs @@ -31,11 +31,11 @@ impl SlashCommand for AutoCommand { } fn description(&self) -> String { - "Automatically infer what context to add, based on your prompt".into() + "Automatically infer what context to add".into() } fn menu_text(&self) -> String { - "Automatically Infer Context".into() + self.description() } fn label(&self, cx: &AppContext) -> CodeLabel { diff --git a/crates/assistant/src/slash_command/delta_command.rs b/crates/assistant/src/slash_command/delta_command.rs index 6a66ad3f09..6f697ecbb9 100644 --- a/crates/assistant/src/slash_command/delta_command.rs +++ b/crates/assistant/src/slash_command/delta_command.rs @@ -19,11 +19,11 @@ impl SlashCommand for DeltaSlashCommand { } fn description(&self) -> String { - "re-insert changed files".into() + "Re-insert changed files".into() } fn menu_text(&self) -> String { - "Re-insert Changed Files".into() + self.description() } fn requires_argument(&self) -> bool { diff --git a/crates/assistant/src/slash_command/diagnostics_command.rs b/crates/assistant/src/slash_command/diagnostics_command.rs index 3f79c01675..146a4e5d36 100644 --- a/crates/assistant/src/slash_command/diagnostics_command.rs +++ b/crates/assistant/src/slash_command/diagnostics_command.rs @@ -95,7 +95,7 @@ impl SlashCommand for DiagnosticsSlashCommand { } fn menu_text(&self) -> String { - "Insert Diagnostics".into() + self.description() } fn requires_argument(&self) -> bool { diff --git a/crates/assistant/src/slash_command/fetch_command.rs b/crates/assistant/src/slash_command/fetch_command.rs index 23d3c884a8..3a01bb645a 100644 --- a/crates/assistant/src/slash_command/fetch_command.rs +++ b/crates/assistant/src/slash_command/fetch_command.rs @@ -104,11 +104,11 @@ impl SlashCommand for FetchSlashCommand { } fn description(&self) -> String { - "insert URL contents".into() + "Insert fetched URL contents".into() } fn menu_text(&self) -> String { - "Insert fetched URL contents".into() + self.description() } fn requires_argument(&self) -> bool { diff --git a/crates/assistant/src/slash_command/file_command.rs b/crates/assistant/src/slash_command/file_command.rs index 260c6b0e2a..6da56d0641 100644 --- a/crates/assistant/src/slash_command/file_command.rs +++ b/crates/assistant/src/slash_command/file_command.rs @@ -110,11 +110,11 @@ impl SlashCommand for FileSlashCommand { } fn description(&self) -> String { - "insert file".into() + "Insert file".into() } fn menu_text(&self) -> String { - "Insert File".into() + self.description() } fn requires_argument(&self) -> bool { diff --git a/crates/assistant/src/slash_command/now_command.rs b/crates/assistant/src/slash_command/now_command.rs index eb0ca926f0..221ba05caf 100644 --- a/crates/assistant/src/slash_command/now_command.rs +++ b/crates/assistant/src/slash_command/now_command.rs @@ -19,11 +19,11 @@ impl SlashCommand for NowSlashCommand { } fn description(&self) -> String { - "insert the current date and time".into() + "Insert current date and time".into() } fn menu_text(&self) -> String { - "Insert Current Date and Time".into() + self.description() } fn requires_argument(&self) -> bool { diff --git a/crates/assistant/src/slash_command/project_command.rs b/crates/assistant/src/slash_command/project_command.rs index a466668bf3..ef6bdffe83 100644 --- a/crates/assistant/src/slash_command/project_command.rs +++ b/crates/assistant/src/slash_command/project_command.rs @@ -48,11 +48,11 @@ impl SlashCommand for ProjectSlashCommand { } fn description(&self) -> String { - "Generate semantic searches based on the current context".into() + "Generate a semantic search based on context".into() } fn menu_text(&self) -> String { - "Project Context".into() + self.description() } fn requires_argument(&self) -> bool { diff --git a/crates/assistant/src/slash_command/prompt_command.rs b/crates/assistant/src/slash_command/prompt_command.rs index effbcc0f90..978c6d7504 100644 --- a/crates/assistant/src/slash_command/prompt_command.rs +++ b/crates/assistant/src/slash_command/prompt_command.rs @@ -16,11 +16,11 @@ impl SlashCommand for PromptSlashCommand { } fn description(&self) -> String { - "insert prompt from library".into() + "Insert prompt from library".into() } fn menu_text(&self) -> String { - "Insert Prompt from Library".into() + self.description() } fn requires_argument(&self) -> bool { diff --git a/crates/assistant/src/slash_command/search_command.rs b/crates/assistant/src/slash_command/search_command.rs index dd777bb880..114fb27b23 100644 --- a/crates/assistant/src/slash_command/search_command.rs +++ b/crates/assistant/src/slash_command/search_command.rs @@ -40,11 +40,11 @@ impl SlashCommand for SearchSlashCommand { } fn description(&self) -> String { - "semantic search".into() + "Search your project semantically".into() } fn menu_text(&self) -> String { - "Semantic Search".into() + self.description() } fn requires_argument(&self) -> bool { diff --git a/crates/assistant/src/slash_command/symbols_command.rs b/crates/assistant/src/slash_command/symbols_command.rs index 1cf8536c0d..887b57ba99 100644 --- a/crates/assistant/src/slash_command/symbols_command.rs +++ b/crates/assistant/src/slash_command/symbols_command.rs @@ -17,11 +17,11 @@ impl SlashCommand for OutlineSlashCommand { } fn description(&self) -> String { - "insert symbols for active tab".into() + "Insert symbols for active tab".into() } fn menu_text(&self) -> String { - "Insert Symbols for Active Tab".into() + self.description() } fn complete_argument( diff --git a/crates/assistant/src/slash_command/tab_command.rs b/crates/assistant/src/slash_command/tab_command.rs index bdf8450d43..0bff4730d8 100644 --- a/crates/assistant/src/slash_command/tab_command.rs +++ b/crates/assistant/src/slash_command/tab_command.rs @@ -24,11 +24,11 @@ impl SlashCommand for TabSlashCommand { } fn description(&self) -> String { - "insert open tabs (active tab by default)".to_owned() + "Insert open tabs (active tab by default)".to_owned() } fn menu_text(&self) -> String { - "Insert Open Tabs".to_owned() + self.description() } fn requires_argument(&self) -> bool { diff --git a/crates/assistant/src/slash_command/terminal_command.rs b/crates/assistant/src/slash_command/terminal_command.rs index 1d0293c235..1d4959fb19 100644 --- a/crates/assistant/src/slash_command/terminal_command.rs +++ b/crates/assistant/src/slash_command/terminal_command.rs @@ -29,11 +29,11 @@ impl SlashCommand for TerminalSlashCommand { } fn description(&self) -> String { - "insert terminal output".into() + "Insert terminal output".into() } fn menu_text(&self) -> String { - "Insert Terminal Output".into() + self.description() } fn requires_argument(&self) -> bool { diff --git a/crates/assistant/src/slash_command/workflow_command.rs b/crates/assistant/src/slash_command/workflow_command.rs index c66dd9bebf..071b4feaf4 100644 --- a/crates/assistant/src/slash_command/workflow_command.rs +++ b/crates/assistant/src/slash_command/workflow_command.rs @@ -29,11 +29,11 @@ impl SlashCommand for WorkflowSlashCommand { } fn description(&self) -> String { - "insert a prompt that opts into the edit workflow".into() + "Insert prompt to opt into the edit workflow".into() } fn menu_text(&self) -> String { - "Insert Workflow Prompt".into() + self.description() } fn requires_argument(&self) -> bool { diff --git a/crates/assistant/src/slash_command_picker.rs b/crates/assistant/src/slash_command_picker.rs index 4b57dcfb33..58023848b0 100644 --- a/crates/assistant/src/slash_command_picker.rs +++ b/crates/assistant/src/slash_command_picker.rs @@ -184,7 +184,7 @@ impl PickerDelegate for SlashCommandDelegate { h_flex() .group(format!("command-entry-label-{ix}")) .w_full() - .min_w(px(220.)) + .min_w(px(250.)) .child( v_flex() .child( @@ -203,7 +203,9 @@ impl PickerDelegate for SlashCommandDelegate { div() .font_buffer(cx) .child( - Label::new(args).size(LabelSize::Small), + Label::new(args) + .size(LabelSize::Small) + .color(Color::Muted), ) .visible_on_hover(format!( "command-entry-label-{ix}" diff --git a/crates/assistant/src/terminal_inline_assistant.rs b/crates/assistant/src/terminal_inline_assistant.rs index e1a26d8510..d30ec2df11 100644 --- a/crates/assistant/src/terminal_inline_assistant.rs +++ b/crates/assistant/src/terminal_inline_assistant.rs @@ -25,6 +25,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; +use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use terminal::Terminal; use terminal_view::TerminalView; use theme::ThemeSettings; @@ -1039,6 +1040,7 @@ impl Codegen { self.transaction = Some(TerminalTransaction::start(self.terminal.clone())); self.generation = cx.spawn(|this, mut cx| async move { let model_telemetry_id = model.telemetry_id(); + let model_provider_id = model.provider_id(); let response = model.stream_completion_text(prompt, &cx).await; let generate = async { let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1); @@ -1063,14 +1065,16 @@ impl Codegen { let error_message = result.as_ref().err().map(|error| error.to_string()); if let Some(telemetry) = telemetry { - telemetry.report_assistant_event( - None, - telemetry_events::AssistantKind::Inline, - telemetry_events::AssistantPhase::Response, - model_telemetry_id, + telemetry.report_assistant_event(AssistantEvent { + conversation_id: None, + kind: AssistantKind::Inline, + phase: AssistantPhase::Response, + model: model_telemetry_id, + model_provider: model_provider_id.to_string(), response_latency, error_message, - ); + language_name: None, + }); } result?; diff --git a/crates/assistant/src/workflow.rs b/crates/assistant/src/workflow.rs index 75c65ed0a7..8a770e21aa 100644 --- a/crates/assistant/src/workflow.rs +++ b/crates/assistant/src/workflow.rs @@ -187,6 +187,7 @@ impl WorkflowSuggestion { suggestion_range, initial_prompt, initial_transaction_id, + false, Some(workspace.clone()), Some(assistant_panel), cx, diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 1fe89cce0f..2c93ee4171 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -264,6 +264,18 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<( fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext) { let release_channel = ReleaseChannel::global(cx); + + let url = match release_channel { + ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"), + ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"), + _ => None, + }; + + if let Some(url) = url { + cx.open_url(url); + return; + } + let version = AppVersion::global(cx).to_string(); let client = client::Client::global(cx).http_client(); @@ -345,15 +357,17 @@ pub fn notify_of_any_new_update(cx: &mut ViewContext) -> Option<()> { let should_show_notification = should_show_notification.await?; if should_show_notification { workspace.update(&mut cx, |workspace, cx| { + let workspace_handle = workspace.weak_handle(); workspace.show_notification( NotificationId::unique::(), cx, - |cx| cx.new_view(|_| UpdateNotification::new(version)), + |cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)), ); - updater - .read(cx) - .set_should_show_update_notification(false, cx) - .detach_and_log_err(cx); + updater.update(cx, |updater, cx| { + updater + .set_should_show_update_notification(false, cx) + .detach_and_log_err(cx); + }); })?; } anyhow::Ok(()) diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index 66028c2401..7568a0eb1a 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -1,13 +1,18 @@ use gpui::{ div, DismissEvent, EventEmitter, InteractiveElement, IntoElement, ParentElement, Render, - SemanticVersion, StatefulInteractiveElement, Styled, ViewContext, + SemanticVersion, StatefulInteractiveElement, Styled, ViewContext, WeakView, }; use menu::Cancel; use release_channel::ReleaseChannel; -use workspace::ui::{h_flex, v_flex, Icon, IconName, Label, StyledExt}; +use util::ResultExt; +use workspace::{ + ui::{h_flex, v_flex, Icon, IconName, Label, StyledExt}, + Workspace, +}; pub struct UpdateNotification { version: SemanticVersion, + workspace: WeakView, } impl EventEmitter for UpdateNotification {} @@ -41,7 +46,11 @@ impl Render for UpdateNotification { .child(Label::new("View the release notes")) .cursor_pointer() .on_click(cx.listener(|this, _, cx| { - crate::view_release_notes(&Default::default(), cx); + this.workspace + .update(cx, |workspace, cx| { + crate::view_release_notes_locally(workspace, cx); + }) + .log_err(); this.dismiss(&menu::Cancel, cx) })), ) @@ -49,8 +58,8 @@ impl Render for UpdateNotification { } impl UpdateNotification { - pub fn new(version: SemanticVersion) -> Self { - Self { version } + pub fn new(version: SemanticVersion, workspace: WeakView) -> Self { + Self { version, workspace } } pub fn dismiss(&mut self, _: &Cancel, cx: &mut ViewContext) { diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index 93ebfa0643..09b29c0436 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -1,7 +1,7 @@ use editor::Editor; use gpui::{ - Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription, - ViewContext, + Element, EventEmitter, FocusableView, IntoElement, ParentElement, Render, StyledText, + Subscription, ViewContext, }; use itertools::Itertools; use std::cmp; @@ -90,17 +90,30 @@ impl Render for Breadcrumbs { ButtonLike::new("toggle outline view") .child(breadcrumbs_stack) .style(ButtonStyle::Transparent) - .on_click(move |_, cx| { - if let Some(editor) = editor.upgrade() { - outline::toggle(editor, &editor::actions::ToggleOutline, cx) + .on_click({ + let editor = editor.clone(); + move |_, cx| { + if let Some(editor) = editor.upgrade() { + outline::toggle(editor, &editor::actions::ToggleOutline, cx) + } } }) - .tooltip(|cx| { - Tooltip::for_action( - "Show symbol outline", - &editor::actions::ToggleOutline, - cx, - ) + .tooltip(move |cx| { + if let Some(editor) = editor.upgrade() { + let focus_handle = editor.read(cx).focus_handle(cx); + Tooltip::for_action_in( + "Show symbol outline", + &editor::actions::ToggleOutline, + &focus_handle, + cx, + ) + } else { + Tooltip::for_action( + "Show symbol outline", + &editor::actions::ToggleOutline, + cx, + ) + } }), ), None => element diff --git a/crates/channel/src/channel_chat.rs b/crates/channel/src/channel_chat.rs index 1a9e46db04..e5b5b74c16 100644 --- a/crates/channel/src/channel_chat.rs +++ b/crates/channel/src/channel_chat.rs @@ -808,7 +808,7 @@ pub fn mentions_to_proto(mentions: &[(Range, UserId)]) -> Vec Self::Summary { + fn summary(&self, _cx: &()) -> Self::Summary { ChannelMessageSummary { max_id: self.id, count: 1, diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 8ae4f15c97..c3fbea1f98 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -18,12 +18,12 @@ test-support = ["clock/test-support", "collections/test-support", "gpui/test-sup [dependencies] anyhow.workspace = true async-recursion = "0.3" +async-tls = "0.13" async-tungstenite = { workspace = true, features = ["async-std", "async-tls"] } chrono = { workspace = true, features = ["serde"] } clock.workspace = true collections.workspace = true feature_flags.workspace = true -fs.workspace = true futures.workspace = true gpui.workspace = true http_client.workspace = true @@ -35,8 +35,6 @@ 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 diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 48bd646d8a..03d81b117f 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -394,7 +394,7 @@ pub struct PendingEntitySubscription { } impl PendingEntitySubscription { - pub fn set_model(mut self, model: &Model, cx: &mut AsyncAppContext) -> Subscription { + pub fn set_model(mut self, model: &Model, cx: &AsyncAppContext) -> Subscription { self.consumed = true; let mut handlers = self.client.handler_set.lock(); let id = (TypeId::of::(), self.remote_id); @@ -1023,7 +1023,7 @@ impl Client { &self, http: Arc, release_channel: Option, - ) -> impl Future> { + ) -> impl Future> { #[cfg(any(test, feature = "test-support"))] let url_override = self.rpc_url.read().clone(); @@ -1117,7 +1117,7 @@ impl Client { // for us from the RPC URL. // // Among other things, it will generate and set a `Sec-WebSocket-Key` header for us. - let mut request = rpc_url.into_client_request()?; + let mut request = IntoClientRequest::into_client_request(rpc_url.as_str())?; // We then modify the request to add our desired headers. let request_headers = request.headers_mut(); @@ -1137,30 +1137,13 @@ 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::>(), - ); - rustls::ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(root_store) - .with_no_client_auth() - }; let (stream, _) = async_tungstenite::async_tls::client_async_tls_with_connector( request, stream, - Some(client_config.into()), + Some(async_tls::TlsConnector::from( + http_client::TLS_CONFIG.clone(), + )), ) .await?; Ok(Connection::new( @@ -1752,7 +1735,7 @@ impl CredentialsProvider for KeychainCredentialsProvider { } /// prefix for the zed:// url scheme -pub static ZED_URL_SCHEME: &str = "zed"; +pub const ZED_URL_SCHEME: &str = "zed"; /// Parses the given link into a Zed link. /// diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 6c1803df3d..24d448aa02 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -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, AssistantPhase, CallEvent, CpuEvent, - EditEvent, EditorEvent, Event, EventRequestBody, EventWrapper, ExtensionEvent, - InlineCompletionEvent, MemoryEvent, ReplEvent, SettingEvent, + ActionEvent, AppEvent, AssistantEvent, CallEvent, CpuEvent, EditEvent, EditorEvent, Event, + EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, ReplEvent, + SettingEvent, }; use tempfile::NamedTempFile; #[cfg(not(debug_assertions))] @@ -288,7 +288,7 @@ impl Telemetry { system_id: Option, installation_id: Option, session_id: String, - cx: &mut AppContext, + cx: &AppContext, ) { let mut state = self.state.lock(); state.system_id = system_id.map(|id| id.into()); @@ -364,6 +364,7 @@ impl Telemetry { operation: &'static str, copilot_enabled: bool, copilot_enabled_for_language: bool, + is_via_ssh: bool, ) { let event = Event::Editor(EditorEvent { file_extension, @@ -371,6 +372,7 @@ impl Telemetry { operation: operation.into(), copilot_enabled, copilot_enabled_for_language, + is_via_ssh, }); self.report_event(event) @@ -391,25 +393,8 @@ impl Telemetry { self.report_event(event) } - pub fn report_assistant_event( - self: &Arc, - conversation_id: Option, - kind: AssistantKind, - phase: AssistantPhase, - model: String, - response_latency: Option, - error_message: Option, - ) { - let event = Event::Assistant(AssistantEvent { - conversation_id, - kind, - phase, - model: model.to_string(), - response_latency, - error_message, - }); - - self.report_event(event) + pub fn report_assistant_event(self: &Arc, event: AssistantEvent) { + self.report_event(Event::Assistant(event)); } pub fn report_call_event( @@ -502,7 +487,7 @@ impl Telemetry { worktree_id: WorktreeId, updated_entries_set: &UpdatedEntriesSet, ) { - let project_names: Vec = { + let project_type_names: Vec = { let mut state = self.state.lock(); state .worktree_id_map @@ -538,8 +523,8 @@ impl Telemetry { }; // Done on purpose to avoid calling `self.state.lock()` multiple times - for project_name in project_names { - self.report_app_event(format!("open {} project", project_name)); + for project_type_name in project_type_names { + self.report_app_event(format!("open {} project", project_type_name)); } } diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 0f53b35fc1..a312dd3495 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -138,7 +138,7 @@ enum UpdateContacts { } impl UserStore { - pub fn new(client: Arc, cx: &mut ModelContext) -> Self { + pub fn new(client: Arc, cx: &ModelContext) -> Self { let (mut current_user_tx, current_user_rx) = watch::channel(); let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded(); let rpc_subscriptions = vec![ @@ -310,7 +310,7 @@ impl UserStore { fn update_contacts( &mut self, message: UpdateContacts, - cx: &mut ModelContext, + cx: &ModelContext, ) -> Task> { match message { UpdateContacts::Wait(barrier) => { @@ -525,9 +525,9 @@ impl UserStore { } pub fn dismiss_contact_request( - &mut self, + &self, requester_id: u64, - cx: &mut ModelContext, + cx: &ModelContext, ) -> Task> { let client = self.client.upgrade(); cx.spawn(move |_, _| async move { @@ -573,7 +573,7 @@ impl UserStore { }) } - pub fn clear_contacts(&mut self) -> impl Future { + pub fn clear_contacts(&self) -> impl Future { let (tx, mut rx) = postage::barrier::channel(); self.update_contacts_tx .unbounded_send(UpdateContacts::Clear(tx)) @@ -583,7 +583,7 @@ impl UserStore { } } - pub fn contact_updates_done(&mut self) -> impl Future { + pub fn contact_updates_done(&self) -> impl Future { let (tx, mut rx) = postage::barrier::channel(); self.update_contacts_tx .unbounded_send(UpdateContacts::Wait(tx)) @@ -594,9 +594,9 @@ impl UserStore { } pub fn get_users( - &mut self, + &self, user_ids: Vec, - cx: &mut ModelContext, + cx: &ModelContext, ) -> Task>>> { let mut user_ids_to_fetch = user_ids.clone(); user_ids_to_fetch.retain(|id| !self.users.contains_key(id)); @@ -629,9 +629,9 @@ impl UserStore { } pub fn fuzzy_search_users( - &mut self, + &self, query: String, - cx: &mut ModelContext, + cx: &ModelContext, ) -> Task>>> { self.load_users(proto::FuzzySearchUsers { query }, cx) } @@ -640,11 +640,7 @@ impl UserStore { self.users.get(&user_id).cloned() } - pub fn get_user_optimistic( - &mut self, - user_id: u64, - cx: &mut ModelContext, - ) -> Option> { + pub fn get_user_optimistic(&self, user_id: u64, cx: &ModelContext) -> Option> { if let Some(user) = self.users.get(&user_id).cloned() { return Some(user); } @@ -653,11 +649,7 @@ impl UserStore { None } - pub fn get_user( - &mut self, - user_id: u64, - cx: &mut ModelContext, - ) -> Task>> { + pub fn get_user(&self, user_id: u64, cx: &ModelContext) -> Task>> { if let Some(user) = self.users.get(&user_id).cloned() { return Task::ready(Ok(user)); } @@ -697,7 +689,7 @@ impl UserStore { .map(|accepted_tos_at| accepted_tos_at.is_some()) } - pub fn accept_terms_of_service(&mut self, cx: &mut ModelContext) -> Task> { + pub fn accept_terms_of_service(&self, cx: &ModelContext) -> Task> { if self.current_user().is_none() { return Task::ready(Err(anyhow!("no current user"))); }; @@ -726,9 +718,9 @@ impl UserStore { } fn load_users( - &mut self, + &self, request: impl RequestMessage, - cx: &mut ModelContext, + cx: &ModelContext, ) -> Task>>> { let client = self.client.clone(); cx.spawn(|this, mut cx| async move { diff --git a/crates/clock/src/clock.rs b/crates/clock/src/clock.rs index 2b45e4a8fa..acbde90dc1 100644 --- a/crates/clock/src/clock.rs +++ b/crates/clock/src/clock.rs @@ -216,10 +216,11 @@ impl fmt::Debug for Global { if timestamp.replica_id > 0 { write!(f, ", ")?; } - write!(f, "{}: {}", timestamp.replica_id, timestamp.value)?; - } - if self.local_branch_value > 0 { - write!(f, ": {}", self.local_branch_value)?; + if timestamp.replica_id == LOCAL_BRANCH_REPLICA_ID { + write!(f, ": {}", timestamp.value)?; + } else { + write!(f, "{}: {}", timestamp.replica_id, timestamp.value)?; + } } write!(f, "}}") } diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index ad43d2d1f0..de7a3c6214 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -28,8 +28,8 @@ axum = { version = "0.6", features = ["json", "headers", "ws"] } axum-extra = { version = "0.4", features = ["erased-json"] } base64.workspace = true chrono.workspace = true -clock.workspace = true clickhouse.workspace = true +clock.workspace = true collections.workspace = true dashmap.workspace = true envy = "0.4.2" @@ -37,19 +37,19 @@ 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 nanoid.workspace = true open_ai.workspace = true -supermaven_api.workspace = true parking_lot.workspace = true prometheus = "0.13" prost.workspace = true rand.workspace = true reqwest = { version = "0.11", features = ["json"] } +reqwest_client.workspace = true rpc.workspace = true +rustc-demangle.workspace = true scrypt = "0.11" sea-orm = { version = "1.1.0-rc.1", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] } semantic_version.workspace = true @@ -61,7 +61,7 @@ sha2.workspace = true sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] } strum.workspace = true subtle.workspace = true -rustc-demangle.workspace = true +supermaven_api.workspace = true telemetry_events.workspace = true text.workspace = true thiserror.workspace = true @@ -85,6 +85,7 @@ client = { workspace = true, features = ["test-support"] } collab_ui = { workspace = true, features = ["test-support"] } collections = { workspace = true, features = ["test-support"] } ctor.workspace = true +dev_server_projects.workspace = true editor = { workspace = true, features = ["test-support"] } env_logger.workspace = true file_finder.workspace = true @@ -92,6 +93,7 @@ fs = { workspace = true, features = ["test-support"] } git = { workspace = true, features = ["test-support"] } git_hosting_providers.workspace = true gpui = { workspace = true, features = ["test-support"] } +headless.workspace = true hyper.workspace = true indoc.workspace = true language = { workspace = true, features = ["test-support"] } @@ -108,7 +110,6 @@ recent_projects = { workspace = true } release_channel.workspace = true remote = { workspace = true, features = ["test-support"] } remote_server.workspace = true -dev_server_projects.workspace = true rpc = { workspace = true, features = ["test-support"] } sea-orm = { version = "1.1.0-rc.1", features = ["sqlx-sqlite"] } serde_json.workspace = true @@ -120,7 +121,6 @@ unindent.workspace = true util.workspace = true workspace = { workspace = true, features = ["test-support"] } worktree = { workspace = true, features = ["test-support"] } -headless.workspace = true [package.metadata.cargo-machete] ignored = ["async-stripe"] diff --git a/crates/collab/README.md b/crates/collab/README.md index 345e82aefe..5aa964ee79 100644 --- a/crates/collab/README.md +++ b/crates/collab/README.md @@ -23,8 +23,7 @@ To use a different set of admin users, create `crates/collab/seed.json`. ```json { "admins": ["yourgithubhere"], - "channels": ["zed"], - "number_of_users": 20 + "channels": ["zed"] } ``` diff --git a/crates/collab/k8s/collab.template.yml b/crates/collab/k8s/collab.template.yml index 7ddb871503..7d4ea6eb9a 100644 --- a/crates/collab/k8s/collab.template.yml +++ b/crates/collab/k8s/collab.template.yml @@ -149,18 +149,6 @@ spec: secretKeyRef: name: google-ai key: api_key - - name: RUNPOD_API_KEY - valueFrom: - secretKeyRef: - name: runpod - key: api_key - optional: true - - name: RUNPOD_API_SUMMARY_URL - valueFrom: - secretKeyRef: - name: runpod - key: summary - optional: true - name: BLOB_STORE_ACCESS_KEY valueFrom: secretKeyRef: diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 5c2c396160..5764aceea5 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -112,6 +112,7 @@ CREATE TABLE "worktree_settings_files" ( "worktree_id" INTEGER NOT NULL, "path" VARCHAR NOT NULL, "content" TEXT, + "kind" VARCHAR, PRIMARY KEY(project_id, worktree_id, path), FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE ); diff --git a/crates/collab/migrations/20241002120231_add_local_settings_kind.sql b/crates/collab/migrations/20241002120231_add_local_settings_kind.sql new file mode 100644 index 0000000000..aec4ffb8f8 --- /dev/null +++ b/crates/collab/migrations/20241002120231_add_local_settings_kind.sql @@ -0,0 +1 @@ +ALTER TABLE "worktree_settings_files" ADD COLUMN "kind" VARCHAR; diff --git a/crates/collab/migrations_llm/20241007173634_add_cache_token_counts.sql b/crates/collab/migrations_llm/20241007173634_add_cache_token_counts.sql new file mode 100644 index 0000000000..855e46ab02 --- /dev/null +++ b/crates/collab/migrations_llm/20241007173634_add_cache_token_counts.sql @@ -0,0 +1,11 @@ +alter table models + add column price_per_million_cache_creation_input_tokens integer not null default 0, + add column price_per_million_cache_read_input_tokens integer not null default 0; + +alter table usages + add column cache_creation_input_tokens_this_month bigint not null default 0, + add column cache_read_input_tokens_this_month bigint not null default 0; + +alter table lifetime_usages + add column cache_creation_input_tokens bigint not null default 0, + add column cache_read_input_tokens bigint not null default 0; diff --git a/crates/collab/migrations_llm/20241007220716_drop_incorrect_usages_columns.sql b/crates/collab/migrations_llm/20241007220716_drop_incorrect_usages_columns.sql new file mode 100644 index 0000000000..c204451b75 --- /dev/null +++ b/crates/collab/migrations_llm/20241007220716_drop_incorrect_usages_columns.sql @@ -0,0 +1,3 @@ +alter table usages + drop column cache_creation_input_tokens_this_month, + drop column cache_read_input_tokens_this_month; diff --git a/crates/collab/seed.default.json b/crates/collab/seed.default.json index 1abec644be..dee924e103 100644 --- a/crates/collab/seed.default.json +++ b/crates/collab/seed.default.json @@ -8,6 +8,5 @@ "JosephTLyons", "rgbkrk" ], - "channels": ["zed"], - "number_of_users": 100 + "channels": ["zed"] } diff --git a/crates/collab/seed/github_users.json b/crates/collab/seed/github_users.json new file mode 100644 index 0000000000..88acd6aa54 --- /dev/null +++ b/crates/collab/seed/github_users.json @@ -0,0 +1,602 @@ +[ + { + "id": 1, + "login": "mojombo", + "email": "tom@mojombo.com", + "created_at": "2007-10-20T05:24:19Z" + }, + { + "id": 2, + "login": "defunkt", + "email": null, + "created_at": "2007-10-20T05:24:19Z" + }, + { + "id": 3, + "login": "pjhyett", + "email": "pj@hyett.com", + "created_at": "2008-01-07T17:54:22Z" + }, + { + "id": 4, + "login": "wycats", + "email": "wycats@gmail.com", + "created_at": "2008-01-12T05:38:33Z" + }, + { + "id": 5, + "login": "ezmobius", + "email": null, + "created_at": "2008-01-12T07:51:46Z" + }, + { + "id": 6, + "login": "ivey", + "email": "ivey@gweezlebur.com", + "created_at": "2008-01-12T15:15:00Z" + }, + { + "id": 7, + "login": "evanphx", + "email": "evan@phx.io", + "created_at": "2008-01-12T16:46:24Z" + }, + { + "id": 17, + "login": "vanpelt", + "email": "vanpelt@wandb.com", + "created_at": "2008-01-13T05:57:18Z" + }, + { + "id": 18, + "login": "wayneeseguin", + "email": "wayneeseguin@gmail.com", + "created_at": "2008-01-13T06:02:21Z" + }, + { + "id": 19, + "login": "brynary", + "email": null, + "created_at": "2008-01-13T10:19:47Z" + }, + { + "id": 20, + "login": "kevinclark", + "email": "kevin.clark@gmail.com", + "created_at": "2008-01-13T18:33:26Z" + }, + { + "id": 21, + "login": "technoweenie", + "email": "technoweenie@hey.com", + "created_at": "2008-01-14T04:33:35Z" + }, + { + "id": 22, + "login": "macournoyer", + "email": "macournoyer@gmail.com", + "created_at": "2008-01-14T10:49:35Z" + }, + { + "id": 23, + "login": "takeo", + "email": "toby@takeo.email", + "created_at": "2008-01-14T11:25:49Z" + }, + { + "id": 25, + "login": "caged", + "email": "encytemedia@gmail.com", + "created_at": "2008-01-15T04:47:24Z" + }, + { + "id": 26, + "login": "topfunky", + "email": null, + "created_at": "2008-01-15T05:40:05Z" + }, + { + "id": 27, + "login": "anotherjesse", + "email": "anotherjesse@gmail.com", + "created_at": "2008-01-15T07:49:30Z" + }, + { + "id": 28, + "login": "roland", + "email": null, + "created_at": "2008-01-15T08:12:51Z" + }, + { + "id": 29, + "login": "lukas", + "email": "lukas@wandb.com", + "created_at": "2008-01-15T12:50:02Z" + }, + { + "id": 30, + "login": "fanvsfan", + "email": null, + "created_at": "2008-01-15T14:15:23Z" + }, + { + "id": 31, + "login": "tomtt", + "email": null, + "created_at": "2008-01-15T15:44:31Z" + }, + { + "id": 32, + "login": "railsjitsu", + "email": null, + "created_at": "2008-01-16T04:57:23Z" + }, + { + "id": 34, + "login": "nitay", + "email": null, + "created_at": "2008-01-18T14:09:11Z" + }, + { + "id": 35, + "login": "kevwil", + "email": null, + "created_at": "2008-01-19T05:50:12Z" + }, + { + "id": 36, + "login": "KirinDave", + "email": null, + "created_at": "2008-01-19T08:01:02Z" + }, + { + "id": 37, + "login": "jamesgolick", + "email": "jamesgolick@gmail.com", + "created_at": "2008-01-19T22:52:30Z" + }, + { + "id": 38, + "login": "atmos", + "email": "atmos@atmos.org", + "created_at": "2008-01-22T09:14:11Z" + }, + { + "id": 44, + "login": "errfree", + "email": null, + "created_at": "2008-01-24T02:08:37Z" + }, + { + "id": 45, + "login": "mojodna", + "email": null, + "created_at": "2008-01-24T04:40:22Z" + }, + { + "id": 46, + "login": "bmizerany", + "email": "blake.mizerany@gmail.com", + "created_at": "2008-01-24T04:44:30Z" + }, + { + "id": 47, + "login": "jnewland", + "email": "jesse@jnewland.com", + "created_at": "2008-01-25T02:28:12Z" + }, + { + "id": 48, + "login": "joshknowles", + "email": "joshknowles@gmail.com", + "created_at": "2008-01-25T21:30:42Z" + }, + { + "id": 49, + "login": "hornbeck", + "email": "hornbeck@gmail.com", + "created_at": "2008-01-25T21:49:23Z" + }, + { + "id": 50, + "login": "jwhitmire", + "email": "jeff@jwhitmire.com", + "created_at": "2008-01-25T22:07:48Z" + }, + { + "id": 51, + "login": "elbowdonkey", + "email": null, + "created_at": "2008-01-25T22:08:20Z" + }, + { + "id": 52, + "login": "reinh", + "email": null, + "created_at": "2008-01-25T22:16:29Z" + }, + { + "id": 53, + "login": "knzai", + "email": "git@knz.ai", + "created_at": "2008-01-25T22:33:10Z" + }, + { + "id": 68, + "login": "bs", + "email": "yap@bri.tt", + "created_at": "2008-01-27T01:46:29Z" + }, + { + "id": 69, + "login": "rsanheim", + "email": null, + "created_at": "2008-01-27T07:09:47Z" + }, + { + "id": 70, + "login": "schacon", + "email": "schacon@gmail.com", + "created_at": "2008-01-27T17:19:28Z" + }, + { + "id": 71, + "login": "uggedal", + "email": null, + "created_at": "2008-01-27T22:18:57Z" + }, + { + "id": 72, + "login": "bruce", + "email": "brwcodes@gmail.com", + "created_at": "2008-01-28T07:16:45Z" + }, + { + "id": 73, + "login": "sam", + "email": "ssmoot@gmail.com", + "created_at": "2008-01-28T19:01:26Z" + }, + { + "id": 74, + "login": "mmower", + "email": "self@mattmower.com", + "created_at": "2008-01-28T19:47:50Z" + }, + { + "id": 75, + "login": "abhay", + "email": null, + "created_at": "2008-01-28T21:08:23Z" + }, + { + "id": 76, + "login": "rabble", + "email": "evan@protest.net", + "created_at": "2008-01-28T23:27:02Z" + }, + { + "id": 77, + "login": "benburkert", + "email": "ben@benburkert.com", + "created_at": "2008-01-28T23:44:14Z" + }, + { + "id": 78, + "login": "indirect", + "email": "andre@arko.net", + "created_at": "2008-01-29T07:59:27Z" + }, + { + "id": 79, + "login": "fearoffish", + "email": "me@fearof.fish", + "created_at": "2008-01-29T08:43:10Z" + }, + { + "id": 80, + "login": "ry", + "email": "ry@tinyclouds.org", + "created_at": "2008-01-29T08:50:34Z" + }, + { + "id": 81, + "login": "engineyard", + "email": null, + "created_at": "2008-01-29T09:51:30Z" + }, + { + "id": 82, + "login": "jsierles", + "email": null, + "created_at": "2008-01-29T11:10:25Z" + }, + { + "id": 83, + "login": "tweibley", + "email": null, + "created_at": "2008-01-29T13:52:07Z" + }, + { + "id": 84, + "login": "peimei", + "email": "james@railsjitsu.com", + "created_at": "2008-01-29T15:44:11Z" + }, + { + "id": 85, + "login": "brixen", + "email": "brixen@gmail.com", + "created_at": "2008-01-29T16:47:55Z" + }, + { + "id": 87, + "login": "tmornini", + "email": null, + "created_at": "2008-01-29T18:43:39Z" + }, + { + "id": 88, + "login": "outerim", + "email": "lee@outerim.com", + "created_at": "2008-01-29T18:48:32Z" + }, + { + "id": 89, + "login": "daksis", + "email": null, + "created_at": "2008-01-29T19:18:16Z" + }, + { + "id": 90, + "login": "sr", + "email": "me@simonrozet.com", + "created_at": "2008-01-29T20:37:53Z" + }, + { + "id": 91, + "login": "lifo", + "email": null, + "created_at": "2008-01-29T23:09:30Z" + }, + { + "id": 92, + "login": "rsl", + "email": "sconds@gmail.com", + "created_at": "2008-01-29T23:13:36Z" + }, + { + "id": 93, + "login": "imownbey", + "email": null, + "created_at": "2008-01-29T23:13:44Z" + }, + { + "id": 94, + "login": "dylanegan", + "email": null, + "created_at": "2008-01-29T23:15:18Z" + }, + { + "id": 95, + "login": "jm", + "email": "jeremymcanally@gmail.com", + "created_at": "2008-01-29T23:15:32Z" + }, + { + "id": 100, + "login": "kmarsh", + "email": "kevin.marsh@gmail.com", + "created_at": "2008-01-29T23:48:24Z" + }, + { + "id": 101, + "login": "jvantuyl", + "email": "jayson@aggressive.ly", + "created_at": "2008-01-30T01:11:50Z" + }, + { + "id": 102, + "login": "BrianTheCoder", + "email": "wbsmith83@gmail.com", + "created_at": "2008-01-30T02:22:32Z" + }, + { + "id": 103, + "login": "freeformz", + "email": "freeformz@gmail.com", + "created_at": "2008-01-30T06:19:57Z" + }, + { + "id": 104, + "login": "hassox", + "email": "dneighman@gmail.com", + "created_at": "2008-01-30T06:31:06Z" + }, + { + "id": 105, + "login": "automatthew", + "email": "automatthew@gmail.com", + "created_at": "2008-01-30T19:00:58Z" + }, + { + "id": 106, + "login": "queso", + "email": "Joshua.owens@gmail.com", + "created_at": "2008-01-30T19:48:45Z" + }, + { + "id": 107, + "login": "lancecarlson", + "email": null, + "created_at": "2008-01-30T19:53:29Z" + }, + { + "id": 108, + "login": "drnic", + "email": "drnicwilliams@gmail.com", + "created_at": "2008-01-30T23:19:18Z" + }, + { + "id": 109, + "login": "lukesutton", + "email": null, + "created_at": "2008-01-31T04:01:02Z" + }, + { + "id": 110, + "login": "danwrong", + "email": null, + "created_at": "2008-01-31T08:51:31Z" + }, + { + "id": 111, + "login": "HamptonMakes", + "email": "hampton@hamptoncatlin.com", + "created_at": "2008-01-31T17:03:51Z" + }, + { + "id": 112, + "login": "jfrost", + "email": null, + "created_at": "2008-01-31T22:14:27Z" + }, + { + "id": 113, + "login": "mattetti", + "email": null, + "created_at": "2008-01-31T22:56:31Z" + }, + { + "id": 114, + "login": "ctennis", + "email": "c@leb.tennis", + "created_at": "2008-01-31T23:43:14Z" + }, + { + "id": 115, + "login": "lawrencepit", + "email": "lawrence.pit@gmail.com", + "created_at": "2008-01-31T23:57:16Z" + }, + { + "id": 116, + "login": "marcjeanson", + "email": "github@marcjeanson.com", + "created_at": "2008-02-01T01:27:19Z" + }, + { + "id": 117, + "login": "grempe", + "email": null, + "created_at": "2008-02-01T04:12:42Z" + }, + { + "id": 118, + "login": "peterc", + "email": "git@peterc.org", + "created_at": "2008-02-02T01:00:36Z" + }, + { + "id": 119, + "login": "ministrycentered", + "email": null, + "created_at": "2008-02-02T03:50:26Z" + }, + { + "id": 120, + "login": "afarnham", + "email": null, + "created_at": "2008-02-02T05:11:03Z" + }, + { + "id": 121, + "login": "up_the_irons", + "email": null, + "created_at": "2008-02-02T10:59:51Z" + }, + { + "id": 122, + "login": "cristibalan", + "email": "cristibalan@gmail.com", + "created_at": "2008-02-02T11:29:45Z" + }, + { + "id": 123, + "login": "heavysixer", + "email": null, + "created_at": "2008-02-02T15:06:53Z" + }, + { + "id": 124, + "login": "brosner", + "email": "brosner@gmail.com", + "created_at": "2008-02-02T19:03:54Z" + }, + { + "id": 125, + "login": "danielmorrison", + "email": "daniel@collectiveidea.com", + "created_at": "2008-02-02T19:46:35Z" + }, + { + "id": 126, + "login": "danielharan", + "email": "chebuctonian@gmail.com", + "created_at": "2008-02-02T21:42:21Z" + }, + { + "id": 127, + "login": "kvnsmth", + "email": null, + "created_at": "2008-02-02T22:00:03Z" + }, + { + "id": 128, + "login": "collectiveidea", + "email": "info@collectiveidea.com", + "created_at": "2008-02-02T22:34:46Z" + }, + { + "id": 129, + "login": "canadaduane", + "email": "duane.johnson@gmail.com", + "created_at": "2008-02-02T23:25:39Z" + }, + { + "id": 130, + "login": "corasaurus-hex", + "email": "cora@sutton.me", + "created_at": "2008-02-03T04:20:22Z" + }, + { + "id": 131, + "login": "dstrelau", + "email": null, + "created_at": "2008-02-03T14:59:12Z" + }, + { + "id": 132, + "login": "sunny", + "email": "sunny@sunfox.org", + "created_at": "2008-02-03T15:43:43Z" + }, + { + "id": 133, + "login": "dkubb", + "email": "github@dan.kubb.ca", + "created_at": "2008-02-03T20:40:13Z" + }, + { + "id": 134, + "login": "jnicklas", + "email": "jonas@jnicklas.com", + "created_at": "2008-02-03T20:43:50Z" + }, + { + "id": 135, + "login": "richcollins", + "email": "richcollins@gmail.com", + "created_at": "2008-02-03T21:11:25Z" + } +] diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index f8ae532013..f5cd1c00ea 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -23,7 +23,7 @@ use telemetry_events::{ }; use uuid::Uuid; -static CRASH_REPORTS_BUCKET: &str = "zed-crash-reports"; +const CRASH_REPORTS_BUCKET: &str = "zed-crash-reports"; pub fn router() -> Router { Router::new() @@ -364,17 +364,19 @@ pub async fn post_panic( } fn report_to_slack(panic: &Panic) -> bool { - if panic.os_name == "Linux" { - if panic.payload.contains("ERROR_SURFACE_LOST_KHR") { - return false; - } + if panic.payload.contains("ERROR_SURFACE_LOST_KHR") { + return false; + } - if panic - .payload - .contains("GPU has crashed, and no debug information is available") - { - return false; - } + if panic.payload.contains("ERROR_INITIALIZATION_FAILED") { + return false; + } + + if panic + .payload + .contains("GPU has crashed, and no debug information is available") + { + return false; } true @@ -677,6 +679,7 @@ pub struct EditorEventRow { minor: Option, patch: Option, checksum_matched: bool, + is_via_ssh: bool, } impl EditorEventRow { @@ -718,6 +721,7 @@ impl EditorEventRow { region_code: "".to_string(), city: "".to_string(), historical_event: false, + is_via_ssh: event.is_via_ssh, } } } diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 5c30a85738..f717566824 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -35,6 +35,7 @@ use std::{ }; use time::PrimitiveDateTime; use tokio::sync::{Mutex, OwnedMutexGuard}; +use worktree_settings_file::LocalSettingsKind; #[cfg(test)] pub use tests::TestDb; @@ -766,6 +767,7 @@ pub struct Worktree { pub struct WorktreeSettingsFile { pub path: String, pub content: String, + pub kind: LocalSettingsKind, } pub struct NewExtensionVersion { @@ -783,3 +785,21 @@ pub struct ExtensionVersionConstraints { pub schema_versions: RangeInclusive, pub wasm_api_versions: RangeInclusive, } + +impl LocalSettingsKind { + pub fn from_proto(proto_kind: proto::LocalSettingsKind) -> Self { + match proto_kind { + proto::LocalSettingsKind::Settings => Self::Settings, + proto::LocalSettingsKind::Tasks => Self::Tasks, + proto::LocalSettingsKind::Editorconfig => Self::Editorconfig, + } + } + + pub fn to_proto(&self) -> proto::LocalSettingsKind { + match self { + Self::Settings => proto::LocalSettingsKind::Settings, + Self::Tasks => proto::LocalSettingsKind::Tasks, + Self::Editorconfig => proto::LocalSettingsKind::Editorconfig, + } + } +} diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 1434bc07cf..9bf767329d 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -32,6 +32,7 @@ macro_rules! id_type { #[allow(unused)] #[allow(missing_docs)] pub fn from_proto(value: u64) -> Self { + debug_assert!(value != 0); Self(value as i32) } diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index b514d4bb03..ceac78203d 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -1,3 +1,4 @@ +use anyhow::Context as _; use util::ResultExt; use super::*; @@ -285,7 +286,7 @@ impl Database { ) .one(&*tx) .await? - .ok_or_else(|| anyhow!("no such project"))?; + .ok_or_else(|| anyhow!("no such project: {project_id}"))?; // Update metadata. worktree::Entity::update(worktree::ActiveModel { @@ -527,6 +528,12 @@ impl Database { connection: ConnectionId, ) -> Result>> { let project_id = ProjectId::from_proto(update.project_id); + let kind = match update.kind { + Some(kind) => proto::LocalSettingsKind::from_i32(kind) + .with_context(|| format!("unknown worktree settings kind: {kind}"))?, + None => proto::LocalSettingsKind::Settings, + }; + let kind = LocalSettingsKind::from_proto(kind); self.project_transaction(project_id, |tx| async move { // Ensure the update comes from the host. let project = project::Entity::find_by_id(project_id) @@ -543,6 +550,7 @@ impl Database { worktree_id: ActiveValue::Set(update.worktree_id as i64), path: ActiveValue::Set(update.path.clone()), content: ActiveValue::Set(content.clone()), + kind: ActiveValue::Set(kind), }) .on_conflict( OnConflict::columns([ @@ -800,6 +808,7 @@ impl Database { worktree.settings_files.push(WorktreeSettingsFile { path: db_settings_file.path, content: db_settings_file.content, + kind: db_settings_file.kind, }); } } diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index 635e2d232f..baba0f2cf9 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -735,6 +735,7 @@ impl Database { worktree.settings_files.push(WorktreeSettingsFile { path: db_settings_file.path, content: db_settings_file.content, + kind: db_settings_file.kind, }); } } diff --git a/crates/collab/src/db/queries/users.rs b/crates/collab/src/db/queries/users.rs index b755476e33..4443d75154 100644 --- a/crates/collab/src/db/queries/users.rs +++ b/crates/collab/src/db/queries/users.rs @@ -298,6 +298,12 @@ impl Database { result } + /// Returns all feature flags. + pub async fn list_feature_flags(&self) -> Result> { + self.transaction(|tx| async move { Ok(feature_flag::Entity::find().all(&*tx).await?) }) + .await + } + /// Creates a new feature flag. pub async fn create_user_flag(&self, flag: &str, enabled_for_all: bool) -> Result { self.transaction(|tx| async move { diff --git a/crates/collab/src/db/tables/worktree_settings_file.rs b/crates/collab/src/db/tables/worktree_settings_file.rs index 92348c1ec9..71f7b73fc1 100644 --- a/crates/collab/src/db/tables/worktree_settings_file.rs +++ b/crates/collab/src/db/tables/worktree_settings_file.rs @@ -11,9 +11,25 @@ pub struct Model { #[sea_orm(primary_key)] pub path: String, pub content: String, + pub kind: LocalSettingsKind, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Default, Hash, serde::Serialize, +)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")] +#[serde(rename_all = "snake_case")] +pub enum LocalSettingsKind { + #[default] + #[sea_orm(string_value = "settings")] + Settings, + #[sea_orm(string_value = "tasks")] + Tasks, + #[sea_orm(string_value = "editorconfig")] + Editorconfig, +} diff --git a/crates/collab/src/lib.rs b/crates/collab/src/lib.rs index 81ff3ff21f..6c32023a97 100644 --- a/crates/collab/src/lib.rs +++ b/crates/collab/src/lib.rs @@ -170,8 +170,6 @@ pub struct Config { pub anthropic_api_key: Option>, pub anthropic_staff_api_key: Option>, pub llm_closed_beta_model_name: Option>, - pub runpod_api_key: Option>, - pub runpod_api_summary_url: Option>, pub zed_client_checksum_seed: Option, pub slack_panics_webhook: Option, pub auto_join_channel_id: Option, @@ -235,8 +233,6 @@ impl Config { stripe_api_key: None, stripe_price_id: None, supermaven_admin_api_key: None, - runpod_api_key: None, - runpod_api_summary_url: None, user_backfiller_github_access_token: None, } } diff --git a/crates/collab/src/llm.rs b/crates/collab/src/llm.rs index 53f0bfdfd0..9809985ac7 100644 --- a/crates/collab/src/llm.rs +++ b/crates/collab/src/llm.rs @@ -22,7 +22,8 @@ use chrono::{DateTime, Duration, Utc}; use collections::HashMap; use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase}; use futures::{Stream, StreamExt as _}; -use isahc_http_client::IsahcHttpClient; + +use reqwest_client::ReqwestClient; use rpc::ListModelsResponse; use rpc::{ proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME, @@ -43,7 +44,7 @@ pub struct LlmState { pub config: Config, pub executor: Executor, pub db: Arc, - pub http_client: IsahcHttpClient, + pub http_client: ReqwestClient, pub clickhouse_client: Option, active_user_count_by_model: RwLock, ActiveUserCount)>>, @@ -69,11 +70,8 @@ impl LlmState { let db = Arc::new(db); let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION")); - let http_client = IsahcHttpClient::builder() - .default_header("User-Agent", user_agent) - .build() - .map(IsahcHttpClient::from) - .context("failed to construct http client")?; + let http_client = + ReqwestClient::user_agent(&user_agent).context("failed to construct http client")?; let this = Self { executor, @@ -320,22 +318,31 @@ async fn perform_completion( chunks .map(move |event| { let chunk = event?; - let (input_tokens, output_tokens) = match &chunk { + let ( + input_tokens, + output_tokens, + cache_creation_input_tokens, + cache_read_input_tokens, + ) = match &chunk { anthropic::Event::MessageStart { message: anthropic::Response { usage, .. }, } | anthropic::Event::MessageDelta { usage, .. } => ( usage.input_tokens.unwrap_or(0) as usize, usage.output_tokens.unwrap_or(0) as usize, + usage.cache_creation_input_tokens.unwrap_or(0) as usize, + usage.cache_read_input_tokens.unwrap_or(0) as usize, ), - _ => (0, 0), + _ => (0, 0, 0, 0), }; - anyhow::Ok(( - serde_json::to_vec(&chunk).unwrap(), + anyhow::Ok(CompletionChunk { + bytes: serde_json::to_vec(&chunk).unwrap(), input_tokens, output_tokens, - )) + cache_creation_input_tokens, + cache_read_input_tokens, + }) }) .boxed() } @@ -361,11 +368,13 @@ async fn perform_completion( chunk.usage.as_ref().map_or(0, |u| u.prompt_tokens) as usize; let output_tokens = chunk.usage.as_ref().map_or(0, |u| u.completion_tokens) as usize; - ( - serde_json::to_vec(&chunk).unwrap(), + CompletionChunk { + bytes: serde_json::to_vec(&chunk).unwrap(), input_tokens, output_tokens, - ) + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, + } }) }) .boxed() @@ -389,49 +398,13 @@ async fn perform_completion( .map(|event| { event.map(|chunk| { // TODO - implement token counting for Google AI - let input_tokens = 0; - let output_tokens = 0; - ( - serde_json::to_vec(&chunk).unwrap(), - input_tokens, - output_tokens, - ) - }) - }) - .boxed() - } - LanguageModelProvider::Zed => { - let api_key = state - .config - .runpod_api_key - .as_ref() - .context("no Qwen2-7B API key configured on the server")?; - let api_url = state - .config - .runpod_api_summary_url - .as_ref() - .context("no Qwen2-7B URL configured on the server")?; - let chunks = open_ai::stream_completion( - &state.http_client, - api_url, - api_key, - serde_json::from_str(params.provider_request.get())?, - None, - ) - .await?; - - chunks - .map(|event| { - event.map(|chunk| { - let input_tokens = - chunk.usage.as_ref().map_or(0, |u| u.prompt_tokens) as usize; - let output_tokens = - chunk.usage.as_ref().map_or(0, |u| u.completion_tokens) as usize; - ( - serde_json::to_vec(&chunk).unwrap(), - input_tokens, - output_tokens, - ) + CompletionChunk { + bytes: serde_json::to_vec(&chunk).unwrap(), + input_tokens: 0, + output_tokens: 0, + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, + } }) }) .boxed() @@ -445,6 +418,8 @@ async fn perform_completion( model, input_tokens: 0, output_tokens: 0, + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, inner_stream: stream, }))) } @@ -589,6 +564,14 @@ async fn check_usage_limit( Ok(()) } +struct CompletionChunk { + bytes: Vec, + input_tokens: usize, + output_tokens: usize, + cache_creation_input_tokens: usize, + cache_read_input_tokens: usize, +} + struct TokenCountingStream { state: Arc, claims: LlmTokenClaims, @@ -596,22 +579,26 @@ struct TokenCountingStream { model: String, input_tokens: usize, output_tokens: usize, + cache_creation_input_tokens: usize, + cache_read_input_tokens: usize, inner_stream: S, } impl Stream for TokenCountingStream where - S: Stream, usize, usize), anyhow::Error>> + Unpin, + S: Stream> + Unpin, { type Item = Result, anyhow::Error>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match Pin::new(&mut self.inner_stream).poll_next(cx) { - Poll::Ready(Some(Ok((mut bytes, input_tokens, output_tokens)))) => { - bytes.push(b'\n'); - self.input_tokens += input_tokens; - self.output_tokens += output_tokens; - Poll::Ready(Some(Ok(bytes))) + Poll::Ready(Some(Ok(mut chunk))) => { + chunk.bytes.push(b'\n'); + self.input_tokens += chunk.input_tokens; + self.output_tokens += chunk.output_tokens; + self.cache_creation_input_tokens += chunk.cache_creation_input_tokens; + self.cache_read_input_tokens += chunk.cache_read_input_tokens; + Poll::Ready(Some(Ok(chunk.bytes))) } Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))), Poll::Ready(None) => Poll::Ready(None), @@ -628,6 +615,8 @@ impl Drop for TokenCountingStream { let model = std::mem::take(&mut self.model); let input_token_count = self.input_tokens; let output_token_count = self.output_tokens; + let cache_creation_input_token_count = self.cache_creation_input_tokens; + let cache_read_input_token_count = self.cache_read_input_tokens; self.state.executor.spawn_detached(async move { let usage = state .db @@ -637,6 +626,8 @@ impl Drop for TokenCountingStream { provider, &model, input_token_count, + cache_creation_input_token_count, + cache_read_input_token_count, output_token_count, Utc::now(), ) @@ -668,11 +659,20 @@ impl Drop for TokenCountingStream { model, provider: provider.to_string(), input_token_count: input_token_count as u64, + cache_creation_input_token_count: cache_creation_input_token_count + as u64, + cache_read_input_token_count: cache_read_input_token_count as u64, output_token_count: output_token_count as u64, requests_this_minute: usage.requests_this_minute as u64, tokens_this_minute: usage.tokens_this_minute as u64, tokens_this_day: usage.tokens_this_day as u64, input_tokens_this_month: usage.input_tokens_this_month as u64, + cache_creation_input_tokens_this_month: usage + .cache_creation_input_tokens_this_month + as u64, + cache_read_input_tokens_this_month: usage + .cache_read_input_tokens_this_month + as u64, output_tokens_this_month: usage.output_tokens_this_month as u64, spending_this_month: usage.spending_this_month as u64, lifetime_spending: usage.lifetime_spending as u64, diff --git a/crates/collab/src/llm/authorization.rs b/crates/collab/src/llm/authorization.rs index cc345579ec..9f82af51c3 100644 --- a/crates/collab/src/llm/authorization.rs +++ b/crates/collab/src/llm/authorization.rs @@ -77,7 +77,6 @@ fn authorize_access_for_country( LanguageModelProvider::Anthropic => anthropic::is_supported_country(country_code), LanguageModelProvider::OpenAi => open_ai::is_supported_country(country_code), LanguageModelProvider::Google => google_ai::is_supported_country(country_code), - LanguageModelProvider::Zed => true, }; if !is_country_supported_by_provider { Err(Error::http( @@ -213,7 +212,6 @@ mod tests { (LanguageModelProvider::Anthropic, "T1"), // Tor (LanguageModelProvider::OpenAi, "T1"), // Tor (LanguageModelProvider::Google, "T1"), // Tor - (LanguageModelProvider::Zed, "T1"), // Tor ]; for (provider, country_code) in cases { diff --git a/crates/collab/src/llm/db/queries/usages.rs b/crates/collab/src/llm/db/queries/usages.rs index 65a0bd6734..128a42bc58 100644 --- a/crates/collab/src/llm/db/queries/usages.rs +++ b/crates/collab/src/llm/db/queries/usages.rs @@ -14,6 +14,8 @@ pub struct Usage { pub tokens_this_minute: usize, pub tokens_this_day: usize, pub input_tokens_this_month: usize, + pub cache_creation_input_tokens_this_month: usize, + pub cache_read_input_tokens_this_month: usize, pub output_tokens_this_month: usize, pub spending_this_month: usize, pub lifetime_spending: usize, @@ -160,17 +162,14 @@ impl LlmDatabase { .all(&*tx) .await?; - let (lifetime_input_tokens, lifetime_output_tokens) = lifetime_usage::Entity::find() + let lifetime_usage = lifetime_usage::Entity::find() .filter( lifetime_usage::Column::UserId .eq(user_id) .and(lifetime_usage::Column::ModelId.eq(model.id)), ) .one(&*tx) - .await? - .map_or((0, 0), |usage| { - (usage.input_tokens as usize, usage.output_tokens as usize) - }); + .await?; let requests_this_minute = self.get_usage_for_measure(&usages, now, UsageMeasure::RequestsPerMinute)?; @@ -180,18 +179,44 @@ impl LlmDatabase { self.get_usage_for_measure(&usages, now, UsageMeasure::TokensPerDay)?; let input_tokens_this_month = self.get_usage_for_measure(&usages, now, UsageMeasure::InputTokensPerMonth)?; + let cache_creation_input_tokens_this_month = self.get_usage_for_measure( + &usages, + now, + UsageMeasure::CacheCreationInputTokensPerMonth, + )?; + let cache_read_input_tokens_this_month = self.get_usage_for_measure( + &usages, + now, + UsageMeasure::CacheReadInputTokensPerMonth, + )?; let output_tokens_this_month = self.get_usage_for_measure(&usages, now, UsageMeasure::OutputTokensPerMonth)?; - let spending_this_month = - calculate_spending(model, input_tokens_this_month, output_tokens_this_month); - let lifetime_spending = - calculate_spending(model, lifetime_input_tokens, lifetime_output_tokens); + let spending_this_month = calculate_spending( + model, + input_tokens_this_month, + cache_creation_input_tokens_this_month, + cache_read_input_tokens_this_month, + output_tokens_this_month, + ); + let lifetime_spending = if let Some(lifetime_usage) = lifetime_usage { + calculate_spending( + model, + lifetime_usage.input_tokens as usize, + lifetime_usage.cache_creation_input_tokens as usize, + lifetime_usage.cache_read_input_tokens as usize, + lifetime_usage.output_tokens as usize, + ) + } else { + 0 + }; Ok(Usage { requests_this_minute, tokens_this_minute, tokens_this_day, input_tokens_this_month, + cache_creation_input_tokens_this_month, + cache_read_input_tokens_this_month, output_tokens_this_month, spending_this_month, lifetime_spending, @@ -208,6 +233,8 @@ impl LlmDatabase { provider: LanguageModelProvider, model_name: &str, input_token_count: usize, + cache_creation_input_tokens: usize, + cache_read_input_tokens: usize, output_token_count: usize, now: DateTimeUtc, ) -> Result { @@ -235,6 +262,10 @@ impl LlmDatabase { &tx, ) .await?; + let total_token_count = input_token_count + + cache_read_input_tokens + + cache_creation_input_tokens + + output_token_count; let tokens_this_minute = self .update_usage_for_measure( user_id, @@ -243,7 +274,7 @@ impl LlmDatabase { &usages, UsageMeasure::TokensPerMinute, now, - input_token_count + output_token_count, + total_token_count, &tx, ) .await?; @@ -255,7 +286,7 @@ impl LlmDatabase { &usages, UsageMeasure::TokensPerDay, now, - input_token_count + output_token_count, + total_token_count, &tx, ) .await?; @@ -271,6 +302,30 @@ impl LlmDatabase { &tx, ) .await?; + let cache_creation_input_tokens_this_month = self + .update_usage_for_measure( + user_id, + is_staff, + model.id, + &usages, + UsageMeasure::CacheCreationInputTokensPerMonth, + now, + cache_creation_input_tokens, + &tx, + ) + .await?; + let cache_read_input_tokens_this_month = self + .update_usage_for_measure( + user_id, + is_staff, + model.id, + &usages, + UsageMeasure::CacheReadInputTokensPerMonth, + now, + cache_read_input_tokens, + &tx, + ) + .await?; let output_tokens_this_month = self .update_usage_for_measure( user_id, @@ -283,8 +338,13 @@ impl LlmDatabase { &tx, ) .await?; - let spending_this_month = - calculate_spending(model, input_tokens_this_month, output_tokens_this_month); + let spending_this_month = calculate_spending( + model, + input_tokens_this_month, + cache_creation_input_tokens_this_month, + cache_read_input_tokens_this_month, + output_tokens_this_month, + ); // Update lifetime usage let lifetime_usage = lifetime_usage::Entity::find() @@ -303,6 +363,12 @@ impl LlmDatabase { input_tokens: ActiveValue::set( usage.input_tokens + input_token_count as i64, ), + cache_creation_input_tokens: ActiveValue::set( + usage.cache_creation_input_tokens + cache_creation_input_tokens as i64, + ), + cache_read_input_tokens: ActiveValue::set( + usage.cache_read_input_tokens + cache_read_input_tokens as i64, + ), output_tokens: ActiveValue::set( usage.output_tokens + output_token_count as i64, ), @@ -327,6 +393,8 @@ impl LlmDatabase { let lifetime_spending = calculate_spending( model, lifetime_usage.input_tokens as usize, + lifetime_usage.cache_creation_input_tokens as usize, + lifetime_usage.cache_read_input_tokens as usize, lifetime_usage.output_tokens as usize, ); @@ -335,6 +403,8 @@ impl LlmDatabase { tokens_this_minute, tokens_this_day, input_tokens_this_month, + cache_creation_input_tokens_this_month, + cache_read_input_tokens_this_month, output_tokens_this_month, spending_this_month, lifetime_spending, @@ -501,13 +571,24 @@ impl LlmDatabase { fn calculate_spending( model: &model::Model, input_tokens_this_month: usize, + cache_creation_input_tokens_this_month: usize, + cache_read_input_tokens_this_month: usize, output_tokens_this_month: usize, ) -> usize { let input_token_cost = input_tokens_this_month * model.price_per_million_input_tokens as usize / 1_000_000; + let cache_creation_input_token_cost = cache_creation_input_tokens_this_month + * model.price_per_million_cache_creation_input_tokens as usize + / 1_000_000; + let cache_read_input_token_cost = cache_read_input_tokens_this_month + * model.price_per_million_cache_read_input_tokens as usize + / 1_000_000; let output_token_cost = output_tokens_this_month * model.price_per_million_output_tokens as usize / 1_000_000; - input_token_cost + output_token_cost + input_token_cost + + cache_creation_input_token_cost + + cache_read_input_token_cost + + output_token_cost } const MINUTE_BUCKET_COUNT: usize = 12; @@ -521,6 +602,8 @@ impl UsageMeasure { UsageMeasure::TokensPerMinute => MINUTE_BUCKET_COUNT, UsageMeasure::TokensPerDay => DAY_BUCKET_COUNT, UsageMeasure::InputTokensPerMonth => MONTH_BUCKET_COUNT, + UsageMeasure::CacheCreationInputTokensPerMonth => MONTH_BUCKET_COUNT, + UsageMeasure::CacheReadInputTokensPerMonth => MONTH_BUCKET_COUNT, UsageMeasure::OutputTokensPerMonth => MONTH_BUCKET_COUNT, } } @@ -531,6 +614,8 @@ impl UsageMeasure { UsageMeasure::TokensPerMinute => Duration::minutes(1), UsageMeasure::TokensPerDay => Duration::hours(24), UsageMeasure::InputTokensPerMonth => Duration::days(30), + UsageMeasure::CacheCreationInputTokensPerMonth => Duration::days(30), + UsageMeasure::CacheReadInputTokensPerMonth => Duration::days(30), UsageMeasure::OutputTokensPerMonth => Duration::days(30), } } diff --git a/crates/collab/src/llm/db/seed.rs b/crates/collab/src/llm/db/seed.rs index 24bc224227..55c6c30cd5 100644 --- a/crates/collab/src/llm/db/seed.rs +++ b/crates/collab/src/llm/db/seed.rs @@ -40,15 +40,6 @@ pub async fn seed_database(_config: &Config, db: &mut LlmDatabase, _force: bool) price_per_million_input_tokens: 25, // $0.25/MTok price_per_million_output_tokens: 125, // $1.25/MTok }, - ModelParams { - provider: LanguageModelProvider::Zed, - name: "Qwen/Qwen2-7B-Instruct".into(), - max_requests_per_minute: 5, - max_tokens_per_minute: 25_000, // These are arbitrary limits we've set to cap costs; we control this number - max_tokens_per_day: 300_000, - price_per_million_input_tokens: 25, - price_per_million_output_tokens: 125, - }, ]) .await } diff --git a/crates/collab/src/llm/db/tables/lifetime_usage.rs b/crates/collab/src/llm/db/tables/lifetime_usage.rs index 05ad2d5e94..fc8354699b 100644 --- a/crates/collab/src/llm/db/tables/lifetime_usage.rs +++ b/crates/collab/src/llm/db/tables/lifetime_usage.rs @@ -9,6 +9,8 @@ pub struct Model { pub user_id: UserId, pub model_id: ModelId, pub input_tokens: i64, + pub cache_creation_input_tokens: i64, + pub cache_read_input_tokens: i64, pub output_tokens: i64, } diff --git a/crates/collab/src/llm/db/tables/model.rs b/crates/collab/src/llm/db/tables/model.rs index c87789f27e..4d7d2d8da9 100644 --- a/crates/collab/src/llm/db/tables/model.rs +++ b/crates/collab/src/llm/db/tables/model.rs @@ -14,6 +14,8 @@ pub struct Model { pub max_tokens_per_minute: i64, pub max_tokens_per_day: i64, pub price_per_million_input_tokens: i32, + pub price_per_million_cache_creation_input_tokens: i32, + pub price_per_million_cache_read_input_tokens: i32, pub price_per_million_output_tokens: i32, } diff --git a/crates/collab/src/llm/db/tables/usage_measure.rs b/crates/collab/src/llm/db/tables/usage_measure.rs index 1105d997c2..50c9501e54 100644 --- a/crates/collab/src/llm/db/tables/usage_measure.rs +++ b/crates/collab/src/llm/db/tables/usage_measure.rs @@ -10,6 +10,8 @@ pub enum UsageMeasure { TokensPerMinute, TokensPerDay, InputTokensPerMonth, + CacheCreationInputTokensPerMonth, + CacheReadInputTokensPerMonth, OutputTokensPerMonth, } diff --git a/crates/collab/src/llm/db/tests/provider_tests.rs b/crates/collab/src/llm/db/tests/provider_tests.rs index ef0da1c373..0bb55ee4b6 100644 --- a/crates/collab/src/llm/db/tests/provider_tests.rs +++ b/crates/collab/src/llm/db/tests/provider_tests.rs @@ -26,7 +26,6 @@ async fn test_initialize_providers(db: &mut LlmDatabase) { LanguageModelProvider::Anthropic, LanguageModelProvider::Google, LanguageModelProvider::OpenAi, - LanguageModelProvider::Zed ] ) } diff --git a/crates/collab/src/llm/db/tests/usage_tests.rs b/crates/collab/src/llm/db/tests/usage_tests.rs index 905a3dda08..97bcc20e44 100644 --- a/crates/collab/src/llm/db/tests/usage_tests.rs +++ b/crates/collab/src/llm/db/tests/usage_tests.rs @@ -33,12 +33,12 @@ async fn test_tracking_usage(db: &mut LlmDatabase) { let user_id = UserId::from_proto(123); let now = t0; - db.record_usage(user_id, false, provider, model, 1000, 0, now) + db.record_usage(user_id, false, provider, model, 1000, 0, 0, 0, now) .await .unwrap(); let now = t0 + Duration::seconds(10); - db.record_usage(user_id, false, provider, model, 2000, 0, now) + db.record_usage(user_id, false, provider, model, 2000, 0, 0, 0, now) .await .unwrap(); @@ -50,6 +50,8 @@ async fn test_tracking_usage(db: &mut LlmDatabase) { tokens_this_minute: 3000, tokens_this_day: 3000, input_tokens_this_month: 3000, + cache_creation_input_tokens_this_month: 0, + cache_read_input_tokens_this_month: 0, output_tokens_this_month: 0, spending_this_month: 0, lifetime_spending: 0, @@ -65,6 +67,8 @@ async fn test_tracking_usage(db: &mut LlmDatabase) { tokens_this_minute: 2000, tokens_this_day: 3000, input_tokens_this_month: 3000, + cache_creation_input_tokens_this_month: 0, + cache_read_input_tokens_this_month: 0, output_tokens_this_month: 0, spending_this_month: 0, lifetime_spending: 0, @@ -72,7 +76,7 @@ async fn test_tracking_usage(db: &mut LlmDatabase) { ); let now = t0 + Duration::seconds(60); - db.record_usage(user_id, false, provider, model, 3000, 0, now) + db.record_usage(user_id, false, provider, model, 3000, 0, 0, 0, now) .await .unwrap(); @@ -84,6 +88,8 @@ async fn test_tracking_usage(db: &mut LlmDatabase) { tokens_this_minute: 5000, tokens_this_day: 6000, input_tokens_this_month: 6000, + cache_creation_input_tokens_this_month: 0, + cache_read_input_tokens_this_month: 0, output_tokens_this_month: 0, spending_this_month: 0, lifetime_spending: 0, @@ -100,13 +106,15 @@ async fn test_tracking_usage(db: &mut LlmDatabase) { tokens_this_minute: 0, tokens_this_day: 5000, input_tokens_this_month: 6000, + cache_creation_input_tokens_this_month: 0, + cache_read_input_tokens_this_month: 0, output_tokens_this_month: 0, spending_this_month: 0, lifetime_spending: 0, } ); - db.record_usage(user_id, false, provider, model, 4000, 0, now) + db.record_usage(user_id, false, provider, model, 4000, 0, 0, 0, now) .await .unwrap(); @@ -118,6 +126,8 @@ async fn test_tracking_usage(db: &mut LlmDatabase) { tokens_this_minute: 4000, tokens_this_day: 9000, input_tokens_this_month: 10000, + cache_creation_input_tokens_this_month: 0, + cache_read_input_tokens_this_month: 0, output_tokens_this_month: 0, spending_this_month: 0, lifetime_spending: 0, @@ -134,6 +144,50 @@ async fn test_tracking_usage(db: &mut LlmDatabase) { tokens_this_minute: 0, tokens_this_day: 0, input_tokens_this_month: 9000, + cache_creation_input_tokens_this_month: 0, + cache_read_input_tokens_this_month: 0, + output_tokens_this_month: 0, + spending_this_month: 0, + lifetime_spending: 0, + } + ); + + // Test cache creation input tokens + db.record_usage(user_id, false, provider, model, 1000, 500, 0, 0, now) + .await + .unwrap(); + + let usage = db.get_usage(user_id, provider, model, now).await.unwrap(); + assert_eq!( + usage, + Usage { + requests_this_minute: 1, + tokens_this_minute: 1500, + tokens_this_day: 1500, + input_tokens_this_month: 10000, + cache_creation_input_tokens_this_month: 500, + cache_read_input_tokens_this_month: 0, + output_tokens_this_month: 0, + spending_this_month: 0, + lifetime_spending: 0, + } + ); + + // Test cache read input tokens + db.record_usage(user_id, false, provider, model, 1000, 0, 300, 0, now) + .await + .unwrap(); + + let usage = db.get_usage(user_id, provider, model, now).await.unwrap(); + assert_eq!( + usage, + Usage { + requests_this_minute: 2, + tokens_this_minute: 2800, + tokens_this_day: 2800, + input_tokens_this_month: 11000, + cache_creation_input_tokens_this_month: 500, + cache_read_input_tokens_this_month: 300, output_tokens_this_month: 0, spending_this_month: 0, lifetime_spending: 0, diff --git a/crates/collab/src/llm/telemetry.rs b/crates/collab/src/llm/telemetry.rs index 17a2cb9cd3..9daaaf3032 100644 --- a/crates/collab/src/llm/telemetry.rs +++ b/crates/collab/src/llm/telemetry.rs @@ -12,11 +12,15 @@ pub struct LlmUsageEventRow { pub model: String, pub provider: String, pub input_token_count: u64, + pub cache_creation_input_token_count: u64, + pub cache_read_input_token_count: u64, pub output_token_count: u64, pub requests_this_minute: u64, pub tokens_this_minute: u64, pub tokens_this_day: u64, pub input_tokens_this_month: u64, + pub cache_creation_input_tokens_this_month: u64, + pub cache_read_input_tokens_this_month: u64, pub output_tokens_this_month: u64, pub spending_this_month: u64, pub lifetime_spending: u64, diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index bc0f827e78..27c95a5b44 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -36,8 +36,8 @@ 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 reqwest_client::ReqwestClient; use sha2::Digest; use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi}; @@ -474,9 +474,6 @@ impl Server { .add_request_handler(user_handler( forward_read_only_project_request::, )) - .add_request_handler(user_handler( - forward_read_only_project_request::, - )) .add_request_handler(user_handler(forward_find_search_candidates_request)) .add_request_handler(user_handler( forward_read_only_project_request::, @@ -957,8 +954,8 @@ impl Server { tracing::info!("connection opened"); 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(IsahcHttpClient::from(http_client)), + let http_client = match ReqwestClient::user_agent(&user_agent) { + Ok(http_client) => Arc::new(http_client), Err(error) => { tracing::error!(?error, "failed to create HTTP client"); return; @@ -1742,6 +1739,7 @@ fn notify_rejoined_projects( worktree_id: worktree.id, path: settings_file.path, content: Some(settings_file.content), + kind: Some(settings_file.kind.to_proto().into()), }, )?; } @@ -2223,6 +2221,7 @@ fn join_project_internal( worktree_id: worktree.id, path: settings_file.path, content: Some(settings_file.content), + kind: Some(proto::update_user_settings::Kind::Settings.into()), }, )?; } @@ -2298,7 +2297,7 @@ async fn list_remote_directory( let dev_server_connection_id = session .connection_pool() .await - .dev_server_connection_id_supporting(dev_server_id, ZedVersion::with_list_directory())?; + .online_dev_server_connection_id(dev_server_id)?; session .db() @@ -2337,10 +2336,7 @@ async fn update_dev_server_project( let dev_server_connection_id = session .connection_pool() .await - .dev_server_connection_id_supporting( - dev_server_project.dev_server_id, - ZedVersion::with_list_directory(), - )?; + .online_dev_server_connection_id(dev_server_project.dev_server_id)?; session.peer.send( dev_server_connection_id, @@ -2950,40 +2946,6 @@ async fn forward_find_search_candidates_request( .await .host_for_read_only_project_request(project_id, session.connection_id, session.user_id()) .await?; - - let host_version = session - .connection_pool() - .await - .connection(host_connection_id) - .map(|c| c.zed_version); - - if host_version.is_some_and(|host_version| host_version < ZedVersion::with_search_candidates()) - { - let query = request.query.ok_or_else(|| anyhow!("missing query"))?; - let search = proto::SearchProject { - project_id: project_id.to_proto(), - query: query.query, - regex: query.regex, - whole_word: query.whole_word, - case_sensitive: query.case_sensitive, - files_to_include: query.files_to_include, - files_to_exclude: query.files_to_exclude, - include_ignored: query.include_ignored, - }; - - let payload = session - .peer - .forward_request(session.connection_id, host_connection_id, search) - .await?; - return response.send(proto::FindSearchCandidatesResponse { - buffer_ids: payload - .locations - .into_iter() - .map(|loc| loc.buffer_id) - .collect(), - }); - } - let payload = session .peer .forward_request(session.connection_id, host_connection_id, request) diff --git a/crates/collab/src/rpc/connection_pool.rs b/crates/collab/src/rpc/connection_pool.rs index ad0131aaa1..96deefba79 100644 --- a/crates/collab/src/rpc/connection_pool.rs +++ b/crates/collab/src/rpc/connection_pool.rs @@ -32,15 +32,7 @@ impl fmt::Display for ZedVersion { impl ZedVersion { pub fn can_collaborate(&self) -> bool { - self.0 >= SemanticVersion::new(0, 134, 0) - } - - pub fn with_list_directory() -> ZedVersion { - ZedVersion(SemanticVersion::new(0, 145, 0)) - } - - pub fn with_search_candidates() -> ZedVersion { - ZedVersion(SemanticVersion::new(0, 151, 0)) + self.0 >= SemanticVersion::new(0, 151, 0) } } @@ -169,6 +161,16 @@ impl ConnectionPool { self.connected_dev_servers.get(&dev_server_id).copied() } + pub fn online_dev_server_connection_id( + &self, + dev_server_id: DevServerId, + ) -> Result { + match self.connected_dev_servers.get(&dev_server_id) { + Some(cid) => Ok(*cid), + None => Err(anyhow!(proto::ErrorCode::DevServerOffline)), + } + } + pub fn dev_server_connection_id_supporting( &self, dev_server_id: DevServerId, diff --git a/crates/collab/src/seed.rs b/crates/collab/src/seed.rs index 15aa9d1591..5de6515ae3 100644 --- a/crates/collab/src/seed.rs +++ b/crates/collab/src/seed.rs @@ -4,10 +4,13 @@ use anyhow::Context; use chrono::{DateTime, Utc}; use db::Database; use serde::{de::DeserializeOwned, Deserialize}; -use std::{fmt::Write, fs, path::Path}; +use std::{fs, path::Path}; use crate::Config; +/// A GitHub user. +/// +/// This representation corresponds to the entries in the `seed/github_users.json` file. #[derive(Debug, Deserialize)] struct GithubUser { id: i32, @@ -18,12 +21,10 @@ struct GithubUser { #[derive(Deserialize)] struct SeedConfig { - // Which users to create as admins. + /// Which users to create as admins. admins: Vec, - // Which channels to create (all admins are invited to all channels) + /// Which channels to create (all admins are invited to all channels). channels: Vec, - // Number of random users to create from the Github API - number_of_users: Option, } pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result<()> { @@ -47,11 +48,21 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result let flag_names = ["remoting", "language-models"]; let mut flags = Vec::new(); + let existing_feature_flags = db.list_feature_flags().await?; + for flag_name in flag_names { + if existing_feature_flags + .iter() + .any(|flag| flag.flag == flag_name) + { + log::info!("Flag {flag_name:?} already exists"); + continue; + } + let flag = db .create_user_flag(flag_name, false) .await - .unwrap_or_else(|_| panic!("failed to create flag: '{flag_name}'")); + .unwrap_or_else(|err| panic!("failed to create flag: '{flag_name}': {err}")); flags.push(flag); } @@ -106,44 +117,29 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result } } - // TODO: Fix this later - if let Some(number_of_users) = seed_config.number_of_users { - // Fetch 100 other random users from GitHub and insert them into the database - // (for testing autocompleters, etc.) - let mut user_count = db - .get_all_users(0, 200) + let github_users_filepath = seed_path.parent().unwrap().join("seed/github_users.json"); + let github_users: Vec = + serde_json::from_str(&fs::read_to_string(github_users_filepath)?)?; + + for github_user in github_users { + log::info!("Seeding {:?} from GitHub", github_user.login); + + let user = db + .get_or_create_user_by_github_account( + &github_user.login, + github_user.id, + github_user.email.as_deref(), + github_user.created_at, + None, + ) .await - .expect("failed to load users from db") - .len(); - let mut last_user_id = None; - while user_count < number_of_users { - let mut uri = "https://api.github.com/users?per_page=100".to_string(); - if let Some(last_user_id) = last_user_id { - write!(&mut uri, "&since={}", last_user_id).unwrap(); - } - let users = fetch_github::>(&client, &uri).await; + .expect("failed to insert user"); - for github_user in users { - last_user_id = Some(github_user.id); - user_count += 1; - let user = db - .get_or_create_user_by_github_account( - &github_user.login, - github_user.id, - github_user.email.as_deref(), - github_user.created_at, - None, - ) - .await - .expect("failed to insert user"); - - for flag in &flags { - db.add_user_flag(user.id, *flag).await.context(format!( - "Unable to enable flag '{}' for user '{}'", - flag, user.id - ))?; - } - } + for flag in &flags { + db.add_user_flag(user.id, *flag).await.context(format!( + "Unable to enable flag '{}' for user '{}'", + flag, user.id + ))?; } } diff --git a/crates/collab/src/tests/channel_buffer_tests.rs b/crates/collab/src/tests/channel_buffer_tests.rs index 1ba41c45bb..b5bfd0f03b 100644 --- a/crates/collab/src/tests/channel_buffer_tests.rs +++ b/crates/collab/src/tests/channel_buffer_tests.rs @@ -246,7 +246,7 @@ async fn test_channel_notes_participant_indices( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); // Clients A and B open the same file. diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 7fb1a49f87..f9bc21efb1 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -7,18 +7,12 @@ use collections::HashMap; use editor::{ actions::{ ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, Redo, Rename, - RevertSelectedHunks, ToggleCodeActions, Undo, - }, - display_map::DisplayRow, - test::{ - editor_hunks, - editor_test_context::{AssertionContextManager, EditorTestContext}, - expanded_hunks, expanded_hunks_background_highlights, + ToggleCodeActions, Undo, }, + test::editor_test_context::{AssertionContextManager, EditorTestContext}, Editor, }; use futures::StreamExt; -use git::diff::DiffHunkStatus; use gpui::{TestAppContext, UpdateGlobal, VisualContext, VisualTestContext}; use indoc::indoc; use language::{ @@ -82,7 +76,7 @@ async fn test_host_disconnect( .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; cx_a.background_executor.run_until_parked(); assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer())); @@ -198,7 +192,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor( .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Open a buffer as client A let buffer_a = project_a @@ -314,7 +308,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Open a file in an editor as the guest. let buffer_b = project_b @@ -571,7 +565,7 @@ async fn test_collaborating_with_code_actions( .unwrap(); // Join the project as client B. - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let editor_b = workspace_b .update(cx_b, |workspace, cx| { @@ -786,7 +780,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let editor_b = workspace_b @@ -1036,7 +1030,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes .await .unwrap(); executor.run_until_parked(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; project_b.read_with(cx_b, |project, cx| { let status = project.language_server_statuses(cx).next().unwrap().1; @@ -1132,9 +1126,7 @@ async fn test_share_project( .await .unwrap(); let client_b_peer_id = client_b.peer_id().unwrap(); - let project_b = client_b - .build_dev_server_project(initial_project.id, cx_b) - .await; + let project_b = client_b.join_remote_project(initial_project.id, cx_b).await; let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id()); @@ -1236,9 +1228,7 @@ async fn test_share_project( .update(cx_c, |call, cx| call.accept_incoming(cx)) .await .unwrap(); - let _project_c = client_c - .build_dev_server_project(initial_project.id, cx_c) - .await; + let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await; // Client B closes the editor, and client A sees client B's selections removed. cx_b.update(move |_| drop(editor_b)); @@ -1297,7 +1287,7 @@ async fn test_on_input_format_from_host_to_guest( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Open a file in an editor as the host. let buffer_a = project_a @@ -1417,7 +1407,7 @@ async fn test_on_input_format_from_guest_to_host( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Open a file in an editor as the guest. let buffer_b = project_b @@ -1580,7 +1570,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( .unwrap(); // Client B joins the project - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; active_call_b .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .await @@ -1842,7 +1832,7 @@ async fn test_inlay_hint_refresh_is_forwarded( .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; active_call_b .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .await @@ -1970,288 +1960,6 @@ async fn test_inlay_hint_refresh_is_forwarded( }); } -#[gpui::test] -async fn test_multiple_hunk_types_revert(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { - let mut server = TestServer::start(cx_a.executor()).await; - let client_a = server.create_client(cx_a, "user_a").await; - let client_b = server.create_client(cx_b, "user_b").await; - server - .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) - .await; - let active_call_a = cx_a.read(ActiveCall::global); - let active_call_b = cx_b.read(ActiveCall::global); - - cx_a.update(editor::init); - cx_b.update(editor::init); - - client_a.language_registry().add(rust_lang()); - client_b.language_registry().add(rust_lang()); - - let base_text = indoc! {r#"struct Row; -struct Row1; -struct Row2; - -struct Row4; -struct Row5; -struct Row6; - -struct Row8; -struct Row9; -struct Row10;"#}; - - client_a - .fs() - .insert_tree( - "/a", - json!({ - "main.rs": base_text, - }), - ) - .await; - let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; - active_call_a - .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) - .await - .unwrap(); - let project_id = active_call_a - .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) - .await - .unwrap(); - - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; - active_call_b - .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) - .await - .unwrap(); - - let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); - let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); - - let editor_a = workspace_a - .update(cx_a, |workspace, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, cx) - }) - .await - .unwrap() - .downcast::() - .unwrap(); - - let editor_b = workspace_b - .update(cx_b, |workspace, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, cx) - }) - .await - .unwrap() - .downcast::() - .unwrap(); - - let mut editor_cx_a = EditorTestContext { - cx: cx_a.clone(), - window: cx_a.handle(), - editor: editor_a, - assertion_cx: AssertionContextManager::new(), - }; - let mut editor_cx_b = EditorTestContext { - cx: cx_b.clone(), - window: cx_b.handle(), - editor: editor_b, - assertion_cx: AssertionContextManager::new(), - }; - - // host edits the file, that differs from the base text, producing diff hunks - editor_cx_a.set_state(indoc! {r#"struct Row; - struct Row0.1; - struct Row0.2; - struct Row1; - - struct Row4; - struct Row5444; - struct Row6; - - struct Row9; - struct Row1220;ˇ"#}); - editor_cx_a.update_editor(|editor, cx| { - editor - .buffer() - .read(cx) - .as_singleton() - .unwrap() - .update(cx, |buffer, cx| { - buffer.set_diff_base(Some(base_text.into()), cx); - }); - }); - editor_cx_b.update_editor(|editor, cx| { - editor - .buffer() - .read(cx) - .as_singleton() - .unwrap() - .update(cx, |buffer, cx| { - buffer.set_diff_base(Some(base_text.into()), cx); - }); - }); - cx_a.executor().run_until_parked(); - cx_b.executor().run_until_parked(); - - // the client selects a range in the updated buffer, expands it to see the diff for each hunk in the selection - // the host does not see the diffs toggled - editor_cx_b.set_selections_state(indoc! {r#"«ˇstruct Row; - struct Row0.1; - struct Row0.2; - struct Row1; - - struct Row4; - struct Row5444; - struct Row6; - - struct R»ow9; - struct Row1220;"#}); - editor_cx_b - .update_editor(|editor, cx| editor.toggle_hunk_diff(&editor::actions::ToggleHunkDiff, cx)); - cx_a.executor().run_until_parked(); - cx_b.executor().run_until_parked(); - editor_cx_a.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new()); - assert_eq!( - all_hunks, - vec![ - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(1)..DisplayRow(3) - ), - ( - "struct Row2;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(4)..DisplayRow(4) - ), - ( - "struct Row5;\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(6)..DisplayRow(7) - ), - ( - "struct Row8;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(9)..DisplayRow(9) - ), - ( - "struct Row10;".to_string(), - DiffHunkStatus::Modified, - DisplayRow(10)..DisplayRow(10), - ), - ] - ); - assert_eq!(all_expanded_hunks, Vec::new()); - }); - editor_cx_b.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(1)..=DisplayRow(2), DisplayRow(8)..=DisplayRow(8)], - ); - assert_eq!( - all_hunks, - vec![ - ( - "".to_string(), - DiffHunkStatus::Added, - DisplayRow(1)..DisplayRow(3) - ), - ( - "struct Row2;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(5)..DisplayRow(5) - ), - ( - "struct Row5;\n".to_string(), - DiffHunkStatus::Modified, - DisplayRow(8)..DisplayRow(9) - ), - ( - "struct Row8;\n".to_string(), - DiffHunkStatus::Removed, - DisplayRow(12)..DisplayRow(12) - ), - ( - "struct Row10;".to_string(), - DiffHunkStatus::Modified, - DisplayRow(13)..DisplayRow(13), - ), - ] - ); - assert_eq!(all_expanded_hunks, &all_hunks[..all_hunks.len() - 1]); - }); - - // the client reverts the hunks, removing the expanded diffs too - // both host and the client observe the reverted state (with one hunk left, not covered by client's selection) - editor_cx_b.update_editor(|editor, cx| { - editor.revert_selected_hunks(&RevertSelectedHunks, cx); - }); - cx_a.executor().run_until_parked(); - cx_b.executor().run_until_parked(); - editor_cx_a.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new()); - assert_eq!( - all_hunks, - vec![( - "struct Row10;".to_string(), - DiffHunkStatus::Modified, - DisplayRow(10)..DisplayRow(10), - )] - ); - assert_eq!(all_expanded_hunks, Vec::new()); - }); - editor_cx_b.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); - let all_hunks = editor_hunks(editor, &snapshot, cx); - let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx); - assert_eq!( - expanded_hunks_background_highlights(editor, cx), - vec![DisplayRow(5)..=DisplayRow(5)] - ); - assert_eq!( - all_hunks, - vec![( - "struct Row10;".to_string(), - DiffHunkStatus::Modified, - DisplayRow(10)..DisplayRow(10), - )] - ); - assert_eq!(all_expanded_hunks, Vec::new()); - }); - editor_cx_a.assert_editor_state(indoc! {r#"struct Row; - struct Row1; - struct Row2; - - struct Row4; - struct Row5; - struct Row6; - - struct Row8; - struct Row9; - struct Row1220;ˇ"#}); - editor_cx_b.assert_editor_state(indoc! {r#"«ˇstruct Row; - struct Row1; - struct Row2; - - struct Row4; - struct Row5; - struct Row6; - - struct Row8; - struct R»ow9; - struct Row1220;"#}); -} - #[gpui::test(iterations = 10)] async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { let mut server = TestServer::start(cx_a.executor()).await; @@ -2338,7 +2046,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA .unwrap(); // Join the project as client B. - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); let editor_b = workspace_b .update(cx_b, |workspace, cx| { diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 9a39d6f3eb..5e9c001491 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -74,7 +74,7 @@ async fn test_basic_following( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; active_call_b .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .await @@ -162,7 +162,7 @@ async fn test_basic_following( executor.run_until_parked(); let active_call_c = cx_c.read(ActiveCall::global); - let project_c = client_c.build_dev_server_project(project_id, cx_c).await; + let project_c = client_c.join_remote_project(project_id, cx_c).await; let (workspace_c, cx_c) = client_c.build_workspace(&project_c, cx_c); active_call_c .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx)) @@ -175,7 +175,7 @@ async fn test_basic_following( cx_d.executor().run_until_parked(); let active_call_d = cx_d.read(ActiveCall::global); - let project_d = client_d.build_dev_server_project(project_id, cx_d).await; + let project_d = client_d.join_remote_project(project_id, cx_d).await; let (workspace_d, cx_d) = client_d.build_workspace(&project_d, cx_d); active_call_d .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx)) @@ -569,7 +569,7 @@ async fn test_following_tab_order( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; active_call_b .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .await @@ -686,7 +686,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T .unwrap(); // Client B joins the project. - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; active_call_b .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .await @@ -1199,7 +1199,7 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; active_call_b .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .await @@ -1335,7 +1335,7 @@ async fn test_peers_simultaneously_following_each_other( .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); executor.run_until_parked(); @@ -1685,7 +1685,7 @@ async fn test_following_into_excluded_file( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; active_call_b .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .await diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 51593e081e..2859113634 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -28,12 +28,12 @@ use live_kit_client::MacOSDisplay; use lsp::LanguageServerId; use parking_lot::Mutex; use project::{ - search::SearchQuery, search::SearchResult, DiagnosticSummary, FormatTrigger, HoverBlockKind, - Project, ProjectPath, + lsp_store::FormatTrigger, search::SearchQuery, search::SearchResult, DiagnosticSummary, + HoverBlockKind, Project, ProjectPath, }; use rand::prelude::*; use serde_json::json; -use settings::SettingsStore; +use settings::{LocalSettingsKind, SettingsStore}; use std::{ cell::{Cell, RefCell}, env, future, mem, @@ -1372,7 +1372,7 @@ async fn test_unshare_project( .unwrap(); let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap()); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; executor.run_until_parked(); assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer())); @@ -1392,7 +1392,7 @@ async fn test_unshare_project( assert!(project_b.read_with(cx_b, |project, _| project.is_disconnected())); // Client C opens the project. - let project_c = client_c.build_dev_server_project(project_id, cx_c).await; + let project_c = client_c.join_remote_project(project_id, cx_c).await; // When client A unshares the project, client C's project becomes read-only. project_a @@ -1409,7 +1409,7 @@ async fn test_unshare_project( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_c2 = client_c.build_dev_server_project(project_id, cx_c).await; + let project_c2 = client_c.join_remote_project(project_id, cx_c).await; executor.run_until_parked(); assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer())); @@ -1514,9 +1514,9 @@ async fn test_project_reconnect( .await .unwrap(); - let project_b1 = client_b.build_dev_server_project(project1_id, cx_b).await; - let project_b2 = client_b.build_dev_server_project(project2_id, cx_b).await; - let project_b3 = client_b.build_dev_server_project(project3_id, cx_b).await; + let project_b1 = client_b.join_remote_project(project1_id, cx_b).await; + let project_b2 = client_b.join_remote_project(project2_id, cx_b).await; + let project_b3 = client_b.join_remote_project(project3_id, cx_b).await; executor.run_until_parked(); let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| { @@ -2310,8 +2310,8 @@ async fn test_propagate_saves_and_fs_changes( .unwrap(); // Join that worktree as clients B and C. - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; - let project_c = client_c.build_dev_server_project(project_id, cx_c).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; + let project_c = client_c.join_remote_project(project_id, cx_c).await; let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap()); @@ -2535,7 +2535,7 @@ async fn test_git_diff_base_change( .await .unwrap(); - let project_remote = client_b.build_dev_server_project(project_id, cx_b).await; + let project_remote = client_b.join_remote_project(project_id, cx_b).await; let diff_base = " one @@ -2791,7 +2791,7 @@ async fn test_git_branch_name( .await .unwrap(); - let project_remote = client_b.build_dev_server_project(project_id, cx_b).await; + let project_remote = client_b.join_remote_project(project_id, cx_b).await; client_a .fs() .set_branch_name(Path::new("/dir/.git"), Some("branch-1")); @@ -2836,7 +2836,7 @@ async fn test_git_branch_name( assert_branch(Some("branch-2"), project, cx) }); - let project_remote_c = client_c.build_dev_server_project(project_id, cx_c).await; + let project_remote_c = client_c.join_remote_project(project_id, cx_c).await; executor.run_until_parked(); project_remote_c.read_with(cx_c, |project, cx| { @@ -2891,7 +2891,7 @@ async fn test_git_status_sync( .await .unwrap(); - let project_remote = client_b.build_dev_server_project(project_id, cx_b).await; + let project_remote = client_b.join_remote_project(project_id, cx_b).await; // Wait for it to catch up to the new status executor.run_until_parked(); @@ -2967,7 +2967,7 @@ async fn test_git_status_sync( }); // And synchronization while joining - let project_remote_c = client_c.build_dev_server_project(project_id, cx_c).await; + let project_remote_c = client_c.join_remote_project(project_id, cx_c).await; executor.run_until_parked(); project_remote_c.read_with(cx_c, |project, cx| { @@ -3015,7 +3015,7 @@ async fn test_fs_operations( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap()); let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap()); @@ -3316,7 +3316,7 @@ async fn test_local_settings( executor.run_until_parked(); // As client B, join that project and observe the local settings. - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap()); executor.run_until_parked(); @@ -3327,8 +3327,16 @@ async fn test_local_settings( .local_settings(worktree_b.read(cx).id()) .collect::>(), &[ - (Path::new("").into(), r#"{"tab_size":2}"#.to_string()), - (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), + ( + Path::new("").into(), + LocalSettingsKind::Settings, + r#"{"tab_size":2}"#.to_string() + ), + ( + Path::new("a").into(), + LocalSettingsKind::Settings, + r#"{"tab_size":8}"#.to_string() + ), ] ) }); @@ -3346,8 +3354,16 @@ async fn test_local_settings( .local_settings(worktree_b.read(cx).id()) .collect::>(), &[ - (Path::new("").into(), r#"{}"#.to_string()), - (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), + ( + Path::new("").into(), + LocalSettingsKind::Settings, + r#"{}"#.to_string() + ), + ( + Path::new("a").into(), + LocalSettingsKind::Settings, + r#"{"tab_size":8}"#.to_string() + ), ] ) }); @@ -3375,8 +3391,16 @@ async fn test_local_settings( .local_settings(worktree_b.read(cx).id()) .collect::>(), &[ - (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), - (Path::new("b").into(), r#"{"tab_size":4}"#.to_string()), + ( + Path::new("a").into(), + LocalSettingsKind::Settings, + r#"{"tab_size":8}"#.to_string() + ), + ( + Path::new("b").into(), + LocalSettingsKind::Settings, + r#"{"tab_size":4}"#.to_string() + ), ] ) }); @@ -3406,7 +3430,11 @@ async fn test_local_settings( store .local_settings(worktree_b.read(cx).id()) .collect::>(), - &[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),] + &[( + Path::new("a").into(), + LocalSettingsKind::Settings, + r#"{"hard_tabs":true}"#.to_string() + ),] ) }); } @@ -3439,7 +3467,7 @@ async fn test_buffer_conflict_after_save( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Open a buffer as client B let buffer_b = project_b @@ -3503,7 +3531,7 @@ async fn test_buffer_reloading( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Open a buffer as client B let buffer_b = project_b @@ -3557,7 +3585,7 @@ async fn test_editing_while_guest_opens_buffer( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Open a buffer as client A let buffer_a = project_a @@ -3605,7 +3633,7 @@ async fn test_leaving_worktree_while_opening_buffer( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // See that a guest has joined as client A. executor.run_until_parked(); @@ -3652,7 +3680,7 @@ async fn test_canceling_buffer_opening( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let buffer_a = project_a .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) @@ -3709,8 +3737,8 @@ async fn test_leaving_project( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b1 = client_b.build_dev_server_project(project_id, cx_b).await; - let project_c = client_c.build_dev_server_project(project_id, cx_c).await; + let project_b1 = client_b.join_remote_project(project_id, cx_b).await; + let project_c = client_c.join_remote_project(project_id, cx_c).await; // Client A sees that a guest has joined. executor.run_until_parked(); @@ -3751,7 +3779,7 @@ async fn test_leaving_project( }); // Client B re-joins the project and can open buffers as before. - let project_b2 = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b2 = client_b.join_remote_project(project_id, cx_b).await; executor.run_until_parked(); project_a.read_with(cx_a, |project, _| { @@ -3927,7 +3955,7 @@ async fn test_collaborating_with_diagnostics( ); // Join the worktree as client B. - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Wait for server to see the diagnostics update. executor.run_until_parked(); @@ -3952,7 +3980,7 @@ async fn test_collaborating_with_diagnostics( }); // Join project as client C and observe the diagnostics. - let project_c = client_c.build_dev_server_project(project_id, cx_c).await; + let project_c = client_c.join_remote_project(project_id, cx_c).await; executor.run_until_parked(); let project_c_diagnostic_summaries = Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| { @@ -4160,7 +4188,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering( .unwrap(); // Join the project as client B and open all three files. - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| { project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, file_name), cx)) })) @@ -4266,7 +4294,7 @@ async fn test_reloading_buffer_manually( .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)); let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap(); @@ -4364,7 +4392,7 @@ async fn test_formatting_buffer( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)); let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap(); @@ -4409,7 +4437,7 @@ async fn test_formatting_buffer( file.defaults.formatter = Some(SelectedFormatter::List(FormatterList( vec![Formatter::External { command: "awk".into(), - arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(), + arguments: Some(vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into()), }] .into(), ))); @@ -4486,7 +4514,7 @@ async fn test_prettier_formatting_buffer( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx)); let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap(); @@ -4599,7 +4627,7 @@ async fn test_definition( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Open the file on client B. let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)); @@ -4744,7 +4772,7 @@ async fn test_references( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Open the file on client B. let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)); @@ -4901,7 +4929,7 @@ async fn test_project_search( .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Perform a search as the guest. let mut results = HashMap::default(); @@ -4991,7 +5019,7 @@ async fn test_document_highlights( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Open the file on client B. let open_b = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)); @@ -5109,7 +5137,7 @@ async fn test_lsp_hover( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Open the file as the guest let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)); @@ -5286,7 +5314,7 @@ async fn test_project_symbols( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Cause the language server to start. let open_buffer_task = @@ -5381,7 +5409,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let open_buffer_task = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)); let buffer_b1 = cx_b.executor().spawn(open_buffer_task).await.unwrap(); @@ -6470,7 +6498,7 @@ async fn test_context_collaboration_with_reconnect( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; // Client A sees that a guest has joined. executor.run_until_parked(); diff --git a/crates/collab/src/tests/random_project_collaboration_tests.rs b/crates/collab/src/tests/random_project_collaboration_tests.rs index 831114ba1a..19d37f8786 100644 --- a/crates/collab/src/tests/random_project_collaboration_tests.rs +++ b/crates/collab/src/tests/random_project_collaboration_tests.rs @@ -298,8 +298,7 @@ impl RandomizedTest for ProjectCollaborationTest { continue; }; let project_root_name = root_name_for_project(&project, cx); - let is_local = - project.read_with(cx, |project, _| project.is_local_or_ssh()); + let is_local = project.read_with(cx, |project, _| project.is_local()); let worktree = project.read_with(cx, |project, cx| { project .worktrees(cx) @@ -335,7 +334,7 @@ impl RandomizedTest for ProjectCollaborationTest { continue; }; let project_root_name = root_name_for_project(&project, cx); - let is_local = project.read_with(cx, |project, _| project.is_local_or_ssh()); + let is_local = project.read_with(cx, |project, _| project.is_local()); match rng.gen_range(0..100_u32) { // Manipulate an existing buffer @@ -1256,7 +1255,7 @@ impl RandomizedTest for ProjectCollaborationTest { let buffers = client.buffers().clone(); for (guest_project, guest_buffers) in &buffers { let project_id = if guest_project.read_with(client_cx, |project, _| { - project.is_local_or_ssh() || project.is_disconnected() + project.is_local() || project.is_disconnected() }) { continue; } else { @@ -1560,9 +1559,7 @@ async fn ensure_project_shared( let first_root_name = root_name_for_project(project, cx); let active_call = cx.read(ActiveCall::global); if active_call.read_with(cx, |call, _| call.room().is_some()) - && project.read_with(cx, |project, _| { - project.is_local_or_ssh() && !project.is_shared() - }) + && project.read_with(cx, |project, _| project.is_local() && !project.is_shared()) { match active_call .update(cx, |call, cx| call.share_project(project.clone(), cx)) diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index cdcf69cf7e..7de50511ea 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs @@ -3,12 +3,13 @@ use call::ActiveCall; use fs::{FakeFs, Fs as _}; use gpui::{Context as _, TestAppContext}; use language::language_settings::all_language_settings; -use remote::SshSession; +use project::ProjectPath; +use remote::SshRemoteClient; use remote_server::HeadlessProject; use serde_json::json; use std::{path::Path, sync::Arc}; -#[gpui::test] +#[gpui::test(iterations = 10)] async fn test_sharing_an_ssh_remote_project( cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, @@ -23,7 +24,7 @@ async fn test_sharing_an_ssh_remote_project( .await; // Set up project on remote FS - let (client_ssh, server_ssh) = SshSession::fake(cx_a, server_cx); + let (client_ssh, server_ssh) = SshRemoteClient::fake(cx_a, server_cx); let remote_fs = FakeFs::new(server_cx.executor()); remote_fs .insert_tree( @@ -54,7 +55,7 @@ async fn test_sharing_an_ssh_remote_project( .build_ssh_project("/code/project1", client_ssh, cx_a) .await; - // User A shares the remote project. + // While the SSH worktree is being scanned, user A shares the remote project. let active_call_a = cx_a.read(ActiveCall::global); let project_id = active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) @@ -62,12 +63,30 @@ async fn test_sharing_an_ssh_remote_project( .unwrap(); // User B joins the project. - let project_b = client_b.build_dev_server_project(project_id, cx_b).await; + let project_b = client_b.join_remote_project(project_id, cx_b).await; let worktree_b = project_b .update(cx_b, |project, cx| project.worktree_for_id(worktree_id, cx)) .unwrap(); + let worktree_a = project_a + .update(cx_a, |project, cx| project.worktree_for_id(worktree_id, cx)) + .unwrap(); + executor.run_until_parked(); + + worktree_a.update(cx_a, |worktree, _cx| { + assert_eq!( + worktree.paths().map(Arc::as_ref).collect::>(), + vec![ + Path::new(".zed"), + Path::new(".zed/settings.json"), + Path::new("README.md"), + Path::new("src"), + Path::new("src/lib.rs"), + ] + ); + }); + worktree_b.update(cx_b, |worktree, _cx| { assert_eq!( worktree.paths().map(Arc::as_ref).collect::>(), @@ -107,14 +126,36 @@ async fn test_sharing_an_ssh_remote_project( }); project_b - .update(cx_b, |project, cx| project.save_buffer(buffer_b, cx)) + .update(cx_b, |project, cx| { + project.save_buffer_as( + buffer_b.clone(), + ProjectPath { + worktree_id: worktree_id.to_owned(), + path: Arc::from(Path::new("src/renamed.rs")), + }, + cx, + ) + }) .await .unwrap(); assert_eq!( remote_fs - .load("/code/project1/src/lib.rs".as_ref()) + .load("/code/project1/src/renamed.rs".as_ref()) .await .unwrap(), "fn one() -> usize { 100 }" ); + cx_b.run_until_parked(); + cx_b.update(|cx| { + assert_eq!( + buffer_b + .read(cx) + .file() + .unwrap() + .path() + .to_string_lossy() + .to_string(), + "src/renamed.rs".to_string() + ); + }); } diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 1421e4c7f7..8d2396eef0 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -21,11 +21,11 @@ use git::GitHostingProviderRegistry; use gpui::{BackgroundExecutor, Context, Model, Task, TestAppContext, View, VisualTestContext}; use http_client::FakeHttpClient; use language::LanguageRegistry; -use node_runtime::FakeNodeRuntime; +use node_runtime::NodeRuntime; use notifications::NotificationStore; use parking_lot::Mutex; use project::{Project, WorktreeId}; -use remote::SshSession; +use remote::SshRemoteClient; use rpc::{ proto::{self, ChannelRole}, RECEIVE_TIMEOUT, @@ -278,7 +278,7 @@ impl TestServer { languages: language_registry, fs: fs.clone(), build_window_options: |_, _| Default::default(), - node_runtime: FakeNodeRuntime::new(), + node_runtime: NodeRuntime::unavailable(), session, }); @@ -408,7 +408,7 @@ impl TestServer { languages: language_registry, fs: fs.clone(), build_window_options: |_, _| Default::default(), - node_runtime: FakeNodeRuntime::new(), + node_runtime: NodeRuntime::unavailable(), session, }); @@ -679,8 +679,6 @@ impl TestServer { stripe_api_key: None, stripe_price_id: None, supermaven_admin_api_key: None, - runpod_api_key: None, - runpod_api_summary_url: None, user_backfiller_github_access_token: None, }, }) @@ -837,7 +835,7 @@ impl TestClient { pub async fn build_ssh_project( &self, root_path: impl AsRef, - ssh: Arc, + ssh: Model, cx: &mut TestAppContext, ) -> (Model, WorktreeId) { let project = cx.update(|cx| { @@ -921,7 +919,7 @@ impl TestClient { }) } - pub async fn build_dev_server_project( + pub async fn join_remote_project( &self, host_project_id: u64, guest_cx: &mut TestAppContext, diff --git a/crates/copilot/Cargo.toml b/crates/copilot/Cargo.toml index 54abbaa112..2a54497562 100644 --- a/crates/copilot/Cargo.toml +++ b/crates/copilot/Cargo.toml @@ -37,7 +37,6 @@ fs.workspace = true futures.workspace = true gpui.workspace = true http_client.workspace = true -isahc.workspace = true language.workspace = true lsp.workspace = true menu.workspace = true diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index cdbe65ba1d..a1fd7a9bb9 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -57,7 +57,7 @@ pub fn init( new_server_id: LanguageServerId, fs: Arc, http: Arc, - node_runtime: Arc, + node_runtime: NodeRuntime, cx: &mut AppContext, ) { copilot_chat::init(fs, http.clone(), cx); @@ -302,7 +302,7 @@ pub struct Completion { pub struct Copilot { http: Arc, - node_runtime: Arc, + node_runtime: NodeRuntime, server: CopilotServer, buffers: HashSet>, server_id: LanguageServerId, @@ -334,7 +334,7 @@ impl Copilot { fn start( new_server_id: LanguageServerId, http: Arc, - node_runtime: Arc, + node_runtime: NodeRuntime, cx: &mut ModelContext, ) -> Self { let mut this = Self { @@ -392,7 +392,7 @@ impl Copilot { #[cfg(any(test, feature = "test-support"))] pub fn fake(cx: &mut gpui::TestAppContext) -> (Model, lsp::FakeLanguageServer) { use lsp::FakeLanguageServer; - use node_runtime::FakeNodeRuntime; + use node_runtime::NodeRuntime; let (server, fake_server) = FakeLanguageServer::new( LanguageServerId(0), @@ -406,7 +406,7 @@ impl Copilot { cx.to_async(), ); let http = http_client::FakeHttpClient::create(|_| async { unreachable!() }); - let node_runtime = FakeNodeRuntime::new(); + let node_runtime = NodeRuntime::unavailable(); let this = cx.new_model(|cx| Self { server_id: LanguageServerId(0), http: http.clone(), @@ -425,7 +425,7 @@ impl Copilot { async fn start_language_server( new_server_id: LanguageServerId, http: Arc, - node_runtime: Arc, + node_runtime: NodeRuntime, this: WeakModel, mut cx: AsyncAppContext, ) { diff --git a/crates/copilot/src/copilot_chat.rs b/crates/copilot/src/copilot_chat.rs index 5d80c89a66..c5ba1bfc6a 100644 --- a/crates/copilot/src/copilot_chat.rs +++ b/crates/copilot/src/copilot_chat.rs @@ -7,8 +7,7 @@ use chrono::DateTime; use fs::Fs; use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, StreamExt}; use gpui::{AppContext, AsyncAppContext, Global}; -use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest}; -use isahc::config::Configurable; +use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest}; use paths::home_dir; use serde::{Deserialize, Serialize}; use settings::watch_config_file; @@ -275,7 +274,7 @@ async fn request_api_token( .header("Accept", "application/json"); if let Some(low_speed_timeout) = low_speed_timeout { - request_builder = request_builder.low_speed_timeout(100, low_speed_timeout); + request_builder = request_builder.read_timeout(low_speed_timeout); } let request = request_builder.body(AsyncBody::empty())?; @@ -332,7 +331,7 @@ async fn stream_completion( .header("Copilot-Integration-Id", "vscode-chat"); if let Some(low_speed_timeout) = low_speed_timeout { - request_builder = request_builder.low_speed_timeout(100, low_speed_timeout); + request_builder = request_builder.read_timeout(low_speed_timeout); } let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?; let mut response = client.send(request).await?; diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index 4d87222c77..98fca60d63 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -188,7 +188,7 @@ macro_rules! define_connection { }; } -pub fn write_and_log(cx: &mut AppContext, db_write: impl FnOnce() -> F + Send + 'static) +pub fn write_and_log(cx: &AppContext, db_write: impl FnOnce() -> F + Send + 'static) where F: Future> + Send, { diff --git a/crates/diagnostics/src/toolbar_controls.rs b/crates/diagnostics/src/toolbar_controls.rs index b546db50a0..0d30008142 100644 --- a/crates/diagnostics/src/toolbar_controls.rs +++ b/crates/diagnostics/src/toolbar_controls.rs @@ -1,7 +1,7 @@ use crate::ProjectDiagnosticsEditor; use gpui::{EventEmitter, ParentElement, Render, View, ViewContext, WeakView}; use ui::prelude::*; -use ui::{IconButton, IconName, Tooltip}; +use ui::{IconButton, IconButtonShape, IconName, Tooltip}; use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; pub struct ToolbarControls { @@ -33,11 +33,19 @@ impl Render for ToolbarControls { "Include Warnings" }; + let warning_color = if include_warnings { + Color::Warning + } else { + Color::Muted + }; + h_flex() + .gap_1() .when(has_stale_excerpts, |div| { div.child( IconButton::new("update-excerpts", IconName::Update) .icon_color(Color::Info) + .shape(IconButtonShape::Square) .disabled(is_updating) .tooltip(move |cx| Tooltip::text("Update excerpts", cx)) .on_click(cx.listener(|this, _, cx| { @@ -51,6 +59,8 @@ impl Render for ToolbarControls { }) .child( IconButton::new("toggle-warnings", IconName::Warning) + .icon_color(warning_color) + .shape(IconButtonShape::Square) .tooltip(move |cx| Tooltip::text(tooltip, cx)) .on_click(cx.listener(|this, _, cx| { if let Some(editor) = this.editor() { diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index b6b22ef64d..cfd9284f80 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -24,7 +24,8 @@ test-support = [ "workspace/test-support", "tree-sitter-rust", "tree-sitter-typescript", - "tree-sitter-html" + "tree-sitter-html", + "unindent", ] [dependencies] @@ -54,6 +55,7 @@ markdown.workspace = true multi_buffer.workspace = true ordered-float.workspace = true parking_lot.workspace = true +pretty_assertions.workspace = true project.workspace = true rand.workspace = true rpc.workspace = true @@ -74,6 +76,7 @@ theme.workspace = true tree-sitter-html = { workspace = true, optional = true } tree-sitter-rust = { workspace = true, optional = true } tree-sitter-typescript = { workspace = true, optional = true } +unindent = { workspace = true, optional = true } ui.workspace = true url.workspace = true util.workspace = true diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 2383c7f71a..502b70361b 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -193,6 +193,7 @@ gpui::actions!( AcceptPartialInlineCompletion, AddSelectionAbove, AddSelectionBelow, + ApplyDiffHunk, Backspace, Cancel, CancelLanguageServerWork, @@ -230,7 +231,11 @@ gpui::actions!( ExpandMacroRecursively, FindAllReferences, Fold, + FoldAll, + FoldRecursive, FoldSelectedRanges, + ToggleFold, + ToggleFoldRecursive, Format, GoToDeclaration, GoToDeclarationSplit, @@ -340,7 +345,9 @@ gpui::actions!( Transpose, Undo, UndoSelection, + UnfoldAll, UnfoldLines, + UnfoldRecursive, UniqueLinesCaseInsensitive, UniqueLinesCaseSensitive, ] diff --git a/crates/editor/src/clangd_ext.rs b/crates/editor/src/clangd_ext.rs index 2f0f7aaee4..501f81b107 100644 --- a/crates/editor/src/clangd_ext.rs +++ b/crates/editor/src/clangd_ext.rs @@ -9,7 +9,7 @@ use crate::lsp_ext::find_specific_language_server_in_selection; use crate::{element::register_action, Editor, SwitchSourceHeader}; -static CLANGD_SERVER_NAME: &str = "clangd"; +const CLANGD_SERVER_NAME: &str = "clangd"; fn is_c_language(language: &Language) -> bool { return language.name() == "C++".into() || language.name() == "C".into(); diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index efa026a56c..52e0ca2486 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1360,7 +1360,7 @@ impl<'a> Iterator for BlockBufferRows<'a> { impl sum_tree::Item for Transform { type Summary = TransformSummary; - fn summary(&self) -> Self::Summary { + fn summary(&self, _cx: &()) -> Self::Summary { self.summary.clone() } } diff --git a/crates/editor/src/display_map/crease_map.rs b/crates/editor/src/display_map/crease_map.rs index bfc9c7d1a4..531c650c43 100644 --- a/crates/editor/src/display_map/crease_map.rs +++ b/crates/editor/src/display_map/crease_map.rs @@ -69,7 +69,7 @@ impl CreaseSnapshot { &'a self, range: Range, snapshot: &'a MultiBufferSnapshot, - ) -> impl '_ + Iterator { + ) -> impl 'a + Iterator { let start = snapshot.anchor_before(Point::new(range.start.0, 0)); let mut cursor = self.creases.cursor::(snapshot); cursor.seek(&start, Bias::Left, snapshot); @@ -291,7 +291,7 @@ impl sum_tree::Summary for ItemSummary { impl sum_tree::Item for CreaseItem { type Summary = ItemSummary; - fn summary(&self) -> Self::Summary { + fn summary(&self, _cx: &MultiBufferSnapshot) -> Self::Summary { ItemSummary { range: self.crease.range.clone(), } diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 37983030b8..5eb26ff969 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -944,7 +944,7 @@ struct TransformSummary { impl sum_tree::Item for Transform { type Summary = TransformSummary; - fn summary(&self) -> Self::Summary { + fn summary(&self, _cx: &()) -> Self::Summary { self.summary.clone() } } @@ -1004,7 +1004,7 @@ impl Default for FoldRange { impl sum_tree::Item for Fold { type Summary = FoldSummary; - fn summary(&self) -> Self::Summary { + fn summary(&self, _cx: &MultiBufferSnapshot) -> Self::Summary { FoldSummary { start: self.range.start, end: self.range.end, diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 712db45e3f..d4e39f2df9 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -74,7 +74,7 @@ impl Inlay { impl sum_tree::Item for Transform { type Summary = TransformSummary; - fn summary(&self) -> Self::Summary { + fn summary(&self, _cx: &()) -> Self::Summary { match self { Transform::Isomorphic(summary) => TransformSummary { input: summary.clone(), diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 564bba2158..dc4d93058c 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -917,7 +917,7 @@ impl Transform { impl sum_tree::Item for Transform { type Summary = TransformSummary; - fn summary(&self) -> Self::Summary { + fn summary(&self, _cx: &()) -> Self::Summary { self.summary.clone() } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b1a3d95a0d..d06f66184b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -61,17 +61,16 @@ use debounced_delay::DebouncedDelay; use display_map::*; pub use display_map::{DisplayPoint, FoldPlaceholder}; pub use editor_settings::{ - CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine, SearchSettings, + CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine, SearchSettings, ShowScrollbar, }; pub use editor_settings_controls::*; use element::LineWithInvisibles; pub use element::{ CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition, }; -use futures::FutureExt; +use futures::{future, FutureExt}; use fuzzy::{StringMatch, StringMatchCandidate}; use git::blame::GitBlame; -use git::diff_hunk_to_display; use gpui::{ div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry, @@ -84,8 +83,8 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; -use hunk_diff::ExpandedHunks; pub(crate) use hunk_diff::HoveredHunk; +use hunk_diff::{diff_hunk_to_display, ExpandedHunks}; use indent_guides::ActiveIndentGuidesState; use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy}; pub use inline_completion_provider::*; @@ -99,7 +98,9 @@ use language::{ }; use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange}; use linked_editing_ranges::refresh_linked_ranges; -use proposed_changes_editor::{ProposedChangesBuffer, ProposedChangesEditor}; +pub use proposed_changes_editor::{ + ProposedChangesBuffer, ProposedChangesEditor, ProposedChangesEditorToolbar, +}; use similar::{ChangeTag, TextDiff}; use task::{ResolvedTask, TaskTemplate, TaskVariables}; @@ -122,8 +123,8 @@ use ordered_float::OrderedFloat; use parking_lot::{Mutex, RwLock}; use project::project_settings::{GitGutterSetting, ProjectSettings}; use project::{ - CodeAction, Completion, CompletionIntent, FormatTrigger, Item, Location, Project, ProjectPath, - ProjectTransaction, TaskSourceKind, + lsp_store::FormatTrigger, CodeAction, Completion, CompletionIntent, Item, Location, Project, + ProjectPath, ProjectTransaction, TaskSourceKind, }; use rand::prelude::*; use rpc::{proto::*, ErrorExt}; @@ -155,7 +156,7 @@ use theme::{ }; use ui::{ h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconName, IconSize, - ListItem, Popover, Tooltip, + ListItem, Popover, PopoverMenuHandle, Tooltip, }; use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::item::{ItemHandle, PreviewTabsSettings}; @@ -375,12 +376,20 @@ pub enum EditorMode { Full, } -#[derive(Clone, Debug)] +#[derive(Copy, Clone, Debug)] pub enum SoftWrap { + /// Prefer not to wrap at all. + /// + /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps. + /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible. + GitDiff, + /// Prefer a single line generally, unless an overly long line is encountered. None, - PreferLine, + /// Soft wrap lines that exceed the editor width. EditorWidth, + /// Soft wrap lines at the preferred line length. Column(u32), + /// Soft wrap line at the preferred line length or the editor width (whichever is smaller). Bounded(u32), } @@ -563,14 +572,15 @@ pub struct Editor { nav_history: Option, context_menu: RwLock>, mouse_context_menu: Option, + hunk_controls_menu_handle: PopoverMenuHandle, completion_tasks: Vec<(CompletionId, Task>)>, signature_help_state: SignatureHelpState, auto_signature_help: Option, find_all_references_task_sources: Vec, next_completion_id: CompletionId, completion_documentation_pre_resolve_debounce: DebouncedDelay, - available_code_actions: Option<(Location, Arc<[CodeAction]>)>, - code_actions_task: Option>, + available_code_actions: Option<(Location, Arc<[AvailableCodeAction]>)>, + code_actions_task: Option>>, document_highlights_task: Option>, linked_editing_range_task: Option>>, linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges, @@ -590,6 +600,7 @@ pub struct Editor { gutter_hovered: bool, hovered_link_state: Option, inline_completion_provider: Option, + code_action_providers: Vec>, active_inline_completion: Option, // enable_inline_completions is a switch that Vim can use to disable // inline completions based on its mode. @@ -660,7 +671,7 @@ pub struct EditorSnapshot { show_git_diff_gutter: Option, show_code_actions: Option, show_runnables: Option, - render_git_blame_gutter: bool, + git_blame_gutter_max_author_length: Option, pub display_snapshot: DisplaySnapshot, pub placeholder_text: Option>, is_focused: bool, @@ -670,7 +681,7 @@ pub struct EditorSnapshot { gutter_hovered: bool, } -const GIT_BLAME_GUTTER_WIDTH_CHARS: f32 = 53.; +const GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED: usize = 20; #[derive(Default, Debug, Clone, Copy)] pub struct GutterDimensions { @@ -810,8 +821,8 @@ impl SelectionHistory { struct RowHighlight { index: usize, - range: RangeInclusive, - color: Option, + range: Range, + color: Hsla, should_autoscroll: bool, } @@ -1217,6 +1228,10 @@ impl CompletionsMenu { None }; + let color_swatch = completion + .color() + .map(|color| div().size_4().bg(color).rounded_sm()); + div().min_w(px(220.)).max_w(px(540.)).child( ListItem::new(mat.candidate_id) .inset(true) @@ -1232,6 +1247,7 @@ impl CompletionsMenu { task.detach_and_log_err(cx) } })) + .start_slot::
(color_swatch) .child(h_flex().overflow_hidden().child(completion_label)) .end_slot::