Compare commits
53 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0b0540377c | ||
![]() |
00789bf6ee | ||
![]() |
5428e71d5b | ||
![]() |
2b316b6734 | ||
![]() |
4c98834fce | ||
![]() |
4ee7c8a77e | ||
![]() |
2970d8c659 | ||
![]() |
0d2f63b444 | ||
![]() |
16da73baac | ||
![]() |
11681ec240 | ||
![]() |
cfd1ea4c27 | ||
![]() |
3328d4254d | ||
![]() |
1b4d5cc816 | ||
![]() |
6e1abaa781 | ||
![]() |
6c883ee2bf | ||
![]() |
afb208cfd8 | ||
![]() |
76ca96c1bf | ||
![]() |
09e9ef4705 | ||
![]() |
f7fefa3406 | ||
![]() |
5f4734749c | ||
![]() |
332a02ab0a | ||
![]() |
1879ef5ddb | ||
![]() |
8a181b2745 | ||
![]() |
0be6cdc5f6 | ||
![]() |
5067174d9b | ||
![]() |
2c312cef7f | ||
![]() |
6c90e882cc | ||
![]() |
de4cf6e423 | ||
![]() |
a72432d968 | ||
![]() |
0220c5c92e | ||
![]() |
b13d048737 | ||
![]() |
4659eaf392 | ||
![]() |
008e905b44 | ||
![]() |
e24aa382a4 | ||
![]() |
a1ad6af1b1 | ||
![]() |
d6223feb34 | ||
![]() |
ff196875d9 | ||
![]() |
9c1d47bc59 | ||
![]() |
12d9fdc4e3 | ||
![]() |
33804db0ef | ||
![]() |
d50af2f39c | ||
![]() |
8c460ba15c | ||
![]() |
00352aa185 | ||
![]() |
4d465099eb | ||
![]() |
f31762f19b | ||
![]() |
6dbd498cea | ||
![]() |
da8da08bc3 | ||
![]() |
526bdf6a03 | ||
![]() |
e2be6611db | ||
![]() |
52fad99449 | ||
![]() |
40642aa3ac | ||
![]() |
20efc2333e | ||
![]() |
73421006d5 |
75 changed files with 1293 additions and 513 deletions
35
.github/actionlint.yml
vendored
35
.github/actionlint.yml
vendored
|
@ -5,25 +5,28 @@ self-hosted-runner:
|
||||||
# GitHub-hosted Runners
|
# GitHub-hosted Runners
|
||||||
- github-8vcpu-ubuntu-2404
|
- github-8vcpu-ubuntu-2404
|
||||||
- github-16vcpu-ubuntu-2404
|
- github-16vcpu-ubuntu-2404
|
||||||
|
- github-32vcpu-ubuntu-2404
|
||||||
|
- github-8vcpu-ubuntu-2204
|
||||||
|
- github-16vcpu-ubuntu-2204
|
||||||
|
- github-32vcpu-ubuntu-2204
|
||||||
|
- github-16vcpu-ubuntu-2204-arm
|
||||||
- windows-2025-16
|
- windows-2025-16
|
||||||
- windows-2025-32
|
- windows-2025-32
|
||||||
- windows-2025-64
|
- windows-2025-64
|
||||||
# Buildjet Ubuntu 20.04 - AMD x86_64
|
# Namespace Ubuntu 20.04 (Release builds)
|
||||||
- buildjet-2vcpu-ubuntu-2004
|
- namespace-profile-16x32-ubuntu-2004
|
||||||
- buildjet-4vcpu-ubuntu-2004
|
- namespace-profile-32x64-ubuntu-2004
|
||||||
- buildjet-8vcpu-ubuntu-2004
|
- namespace-profile-16x32-ubuntu-2004-arm
|
||||||
- buildjet-16vcpu-ubuntu-2004
|
- namespace-profile-32x64-ubuntu-2004-arm
|
||||||
- buildjet-32vcpu-ubuntu-2004
|
# Namespace Ubuntu 22.04 (Everything else)
|
||||||
# Buildjet Ubuntu 22.04 - AMD x86_64
|
- namespace-profile-2x4-ubuntu-2204
|
||||||
- buildjet-2vcpu-ubuntu-2204
|
- namespace-profile-4x8-ubuntu-2204
|
||||||
- buildjet-4vcpu-ubuntu-2204
|
- namespace-profile-8x16-ubuntu-2204
|
||||||
- buildjet-8vcpu-ubuntu-2204
|
- namespace-profile-16x32-ubuntu-2204
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- namespace-profile-32x64-ubuntu-2204
|
||||||
- buildjet-32vcpu-ubuntu-2204
|
# Namespace Limited Preview
|
||||||
# Buildjet Ubuntu 22.04 - Graviton aarch64
|
- namespace-profile-8x16-ubuntu-2004-arm-m4
|
||||||
- buildjet-8vcpu-ubuntu-2204-arm
|
- namespace-profile-8x32-ubuntu-2004-arm-m4
|
||||||
- buildjet-16vcpu-ubuntu-2204-arm
|
|
||||||
- buildjet-32vcpu-ubuntu-2204-arm
|
|
||||||
# Self Hosted Runners
|
# Self Hosted Runners
|
||||||
- self-mini-macos
|
- self-mini-macos
|
||||||
- self-32vcpu-windows-2022
|
- self-32vcpu-windows-2022
|
||||||
|
|
2
.github/actions/build_docs/action.yml
vendored
2
.github/actions/build_docs/action.yml
vendored
|
@ -13,7 +13,7 @@ runs:
|
||||||
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
cache-provider: "buildjet"
|
# cache-provider: "buildjet"
|
||||||
|
|
||||||
- name: Install Linux dependencies
|
- name: Install Linux dependencies
|
||||||
shell: bash -euxo pipefail {0}
|
shell: bash -euxo pipefail {0}
|
||||||
|
|
2
.github/workflows/bump_patch_version.yml
vendored
2
.github/workflows/bump_patch_version.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
||||||
bump_patch_version:
|
bump_patch_version:
|
||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- namespace-profile-16x32-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
61
.github/workflows/ci.yml
vendored
61
.github/workflows/ci.yml
vendored
|
@ -137,7 +137,7 @@ jobs:
|
||||||
github.repository_owner == 'zed-industries' &&
|
github.repository_owner == 'zed-industries' &&
|
||||||
needs.job_spec.outputs.run_tests == 'true'
|
needs.job_spec.outputs.run_tests == 'true'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-8vcpu-ubuntu-2204
|
- namespace-profile-8x16-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
@ -168,7 +168,7 @@ jobs:
|
||||||
needs: [job_spec]
|
needs: [job_spec]
|
||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-8vcpu-ubuntu-2204
|
- namespace-profile-4x8-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
@ -221,7 +221,7 @@ jobs:
|
||||||
github.repository_owner == 'zed-industries' &&
|
github.repository_owner == 'zed-industries' &&
|
||||||
(needs.job_spec.outputs.run_tests == 'true' || needs.job_spec.outputs.run_docs == 'true')
|
(needs.job_spec.outputs.run_tests == 'true' || needs.job_spec.outputs.run_docs == 'true')
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-8vcpu-ubuntu-2204
|
- namespace-profile-8x16-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
@ -328,7 +328,7 @@ jobs:
|
||||||
github.repository_owner == 'zed-industries' &&
|
github.repository_owner == 'zed-industries' &&
|
||||||
needs.job_spec.outputs.run_tests == 'true'
|
needs.job_spec.outputs.run_tests == 'true'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- namespace-profile-16x32-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Add Rust to the PATH
|
- name: Add Rust to the PATH
|
||||||
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||||
|
@ -342,7 +342,7 @@ jobs:
|
||||||
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
cache-provider: "buildjet"
|
# cache-provider: "buildjet"
|
||||||
|
|
||||||
- name: Install Linux dependencies
|
- name: Install Linux dependencies
|
||||||
run: ./script/linux
|
run: ./script/linux
|
||||||
|
@ -380,7 +380,7 @@ jobs:
|
||||||
github.repository_owner == 'zed-industries' &&
|
github.repository_owner == 'zed-industries' &&
|
||||||
needs.job_spec.outputs.run_tests == 'true'
|
needs.job_spec.outputs.run_tests == 'true'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-8vcpu-ubuntu-2204
|
- namespace-profile-16x32-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Add Rust to the PATH
|
- name: Add Rust to the PATH
|
||||||
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||||
|
@ -394,7 +394,7 @@ jobs:
|
||||||
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
cache-provider: "buildjet"
|
# cache-provider: "buildjet"
|
||||||
|
|
||||||
- name: Install Clang & Mold
|
- name: Install Clang & Mold
|
||||||
run: ./script/remote-server && ./script/install-mold 2.34.0
|
run: ./script/remote-server && ./script/install-mold 2.34.0
|
||||||
|
@ -511,8 +511,8 @@ jobs:
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-mini-macos
|
- self-mini-macos
|
||||||
if: |
|
if: |
|
||||||
startsWith(github.ref, 'refs/tags/v')
|
( startsWith(github.ref, 'refs/tags/v')
|
||||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
|
||||||
needs: [macos_tests]
|
needs: [macos_tests]
|
||||||
env:
|
env:
|
||||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||||
|
@ -526,6 +526,11 @@ jobs:
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
|
- name: Setup Sentry CLI
|
||||||
|
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||||
|
with:
|
||||||
|
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
|
@ -597,10 +602,10 @@ jobs:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: Linux x86_x64 release bundle
|
name: Linux x86_x64 release bundle
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2004 # ubuntu 20.04 for minimal glibc
|
- namespace-profile-16x32-ubuntu-2004 # ubuntu 20.04 for minimal glibc
|
||||||
if: |
|
if: |
|
||||||
startsWith(github.ref, 'refs/tags/v')
|
( startsWith(github.ref, 'refs/tags/v')
|
||||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
|
||||||
needs: [linux_tests]
|
needs: [linux_tests]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
|
@ -611,6 +616,11 @@ jobs:
|
||||||
- name: Install Linux dependencies
|
- name: Install Linux dependencies
|
||||||
run: ./script/linux && ./script/install-mold 2.34.0
|
run: ./script/linux && ./script/install-mold 2.34.0
|
||||||
|
|
||||||
|
- name: Setup Sentry CLI
|
||||||
|
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||||
|
with:
|
||||||
|
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
||||||
- name: Determine version and release channel
|
- name: Determine version and release channel
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
run: |
|
run: |
|
||||||
|
@ -650,7 +660,7 @@ jobs:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: Linux arm64 release bundle
|
name: Linux arm64 release bundle
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-32vcpu-ubuntu-2204-arm
|
- namespace-profile-8x32-ubuntu-2004-arm-m4 # ubuntu 20.04 for minimal glibc
|
||||||
if: |
|
if: |
|
||||||
startsWith(github.ref, 'refs/tags/v')
|
startsWith(github.ref, 'refs/tags/v')
|
||||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||||
|
@ -664,6 +674,11 @@ jobs:
|
||||||
- name: Install Linux dependencies
|
- name: Install Linux dependencies
|
||||||
run: ./script/linux
|
run: ./script/linux
|
||||||
|
|
||||||
|
- name: Setup Sentry CLI
|
||||||
|
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||||
|
with:
|
||||||
|
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
||||||
- name: Determine version and release channel
|
- name: Determine version and release channel
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
run: |
|
run: |
|
||||||
|
@ -703,10 +718,8 @@ jobs:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: github-8vcpu-ubuntu-2404
|
runs-on: github-8vcpu-ubuntu-2404
|
||||||
if: |
|
if: |
|
||||||
false && (
|
false && ( startsWith(github.ref, 'refs/tags/v')
|
||||||
startsWith(github.ref, 'refs/tags/v')
|
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
|
||||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
|
||||||
)
|
|
||||||
needs: [linux_tests]
|
needs: [linux_tests]
|
||||||
name: Build Zed on FreeBSD
|
name: Build Zed on FreeBSD
|
||||||
steps:
|
steps:
|
||||||
|
@ -791,6 +804,11 @@ jobs:
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
|
- name: Setup Sentry CLI
|
||||||
|
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||||
|
with:
|
||||||
|
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
||||||
- name: Determine version and release channel
|
- name: Determine version and release channel
|
||||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
@ -833,3 +851,12 @@ jobs:
|
||||||
run: gh release edit "$GITHUB_REF_NAME" --draft=false
|
run: gh release edit "$GITHUB_REF_NAME" --draft=false
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create Sentry release
|
||||||
|
uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c # v3
|
||||||
|
env:
|
||||||
|
SENTRY_ORG: zed-dev
|
||||||
|
SENTRY_PROJECT: zed
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
with:
|
||||||
|
environment: production
|
||||||
|
|
2
.github/workflows/deploy_cloudflare.yml
vendored
2
.github/workflows/deploy_cloudflare.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
||||||
deploy-docs:
|
deploy-docs:
|
||||||
name: Deploy Docs
|
name: Deploy Docs
|
||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on: buildjet-16vcpu-ubuntu-2204
|
runs-on: namespace-profile-16x32-ubuntu-2204
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
|
|
4
.github/workflows/deploy_collab.yml
vendored
4
.github/workflows/deploy_collab.yml
vendored
|
@ -61,7 +61,7 @@ jobs:
|
||||||
- style
|
- style
|
||||||
- tests
|
- tests
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- namespace-profile-16x32-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Install doctl
|
- name: Install doctl
|
||||||
uses: digitalocean/action-doctl@v2
|
uses: digitalocean/action-doctl@v2
|
||||||
|
@ -94,7 +94,7 @@ jobs:
|
||||||
needs:
|
needs:
|
||||||
- publish
|
- publish
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- namespace-profile-16x32-ubuntu-2204
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
|
|
4
.github/workflows/eval.yml
vendored
4
.github/workflows/eval.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
||||||
github.repository_owner == 'zed-industries' &&
|
github.repository_owner == 'zed-industries' &&
|
||||||
(github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-eval'))
|
(github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-eval'))
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- namespace-profile-16x32-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Add Rust to the PATH
|
- name: Add Rust to the PATH
|
||||||
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||||
|
@ -46,7 +46,7 @@ jobs:
|
||||||
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
cache-provider: "buildjet"
|
# cache-provider: "buildjet"
|
||||||
|
|
||||||
- name: Install Linux dependencies
|
- name: Install Linux dependencies
|
||||||
run: ./script/linux
|
run: ./script/linux
|
||||||
|
|
2
.github/workflows/nix.yml
vendored
2
.github/workflows/nix.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
system:
|
system:
|
||||||
- os: x86 Linux
|
- os: x86 Linux
|
||||||
runner: buildjet-16vcpu-ubuntu-2204
|
runner: namespace-profile-16x32-ubuntu-2204
|
||||||
install_nix: true
|
install_nix: true
|
||||||
- os: arm Mac
|
- os: arm Mac
|
||||||
runner: [macOS, ARM64, test]
|
runner: [macOS, ARM64, test]
|
||||||
|
|
2
.github/workflows/randomized_tests.yml
vendored
2
.github/workflows/randomized_tests.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
||||||
name: Run randomized tests
|
name: Run randomized tests
|
||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- namespace-profile-16x32-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
|
|
13
.github/workflows/release_nightly.yml
vendored
13
.github/workflows/release_nightly.yml
vendored
|
@ -128,7 +128,7 @@ jobs:
|
||||||
name: Create a Linux *.tar.gz bundle for x86
|
name: Create a Linux *.tar.gz bundle for x86
|
||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2004
|
- namespace-profile-16x32-ubuntu-2004 # ubuntu 20.04 for minimal glibc
|
||||||
needs: tests
|
needs: tests
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
|
@ -168,7 +168,7 @@ jobs:
|
||||||
name: Create a Linux *.tar.gz bundle for ARM
|
name: Create a Linux *.tar.gz bundle for ARM
|
||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-32vcpu-ubuntu-2204-arm
|
- namespace-profile-8x32-ubuntu-2004-arm-m4 # ubuntu 20.04 for minimal glibc
|
||||||
needs: tests
|
needs: tests
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
|
@ -316,3 +316,12 @@ jobs:
|
||||||
git config user.email github-actions@github.com
|
git config user.email github-actions@github.com
|
||||||
git tag -f nightly
|
git tag -f nightly
|
||||||
git push origin nightly --force
|
git push origin nightly --force
|
||||||
|
|
||||||
|
- name: Create Sentry release
|
||||||
|
uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c # v3
|
||||||
|
env:
|
||||||
|
SENTRY_ORG: zed-dev
|
||||||
|
SENTRY_PROJECT: zed
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
with:
|
||||||
|
environment: production
|
||||||
|
|
4
.github/workflows/unit_evals.yml
vendored
4
.github/workflows/unit_evals.yml
vendored
|
@ -23,7 +23,7 @@ jobs:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: Run unit evals
|
name: Run unit evals
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- namespace-profile-16x32-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Add Rust to the PATH
|
- name: Add Rust to the PATH
|
||||||
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||||
|
@ -37,7 +37,7 @@ jobs:
|
||||||
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
cache-provider: "buildjet"
|
# cache-provider: "buildjet"
|
||||||
|
|
||||||
- name: Install Linux dependencies
|
- name: Install Linux dependencies
|
||||||
run: ./script/linux
|
run: ./script/linux
|
||||||
|
|
63
Cargo.lock
generated
63
Cargo.lock
generated
|
@ -1411,7 +1411,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
"nom",
|
"nom 7.1.3",
|
||||||
"num-rational",
|
"num-rational",
|
||||||
"v_frame",
|
"v_frame",
|
||||||
]
|
]
|
||||||
|
@ -2785,7 +2785,7 @@ version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nom",
|
"nom 7.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3071,17 +3071,22 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cloud_api_types",
|
"cloud_api_types",
|
||||||
"futures 0.3.31",
|
"futures 0.3.31",
|
||||||
|
"gpui",
|
||||||
|
"gpui_tokio",
|
||||||
"http_client",
|
"http_client",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
|
"yawc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cloud_api_types"
|
name = "cloud_api_types"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"ciborium",
|
||||||
"cloud_llm_client",
|
"cloud_llm_client",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -3999,6 +4004,9 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"minidumper",
|
"minidumper",
|
||||||
"paths",
|
"paths",
|
||||||
|
"release_channel",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"smol",
|
"smol",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
@ -9118,6 +9126,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"client",
|
"client",
|
||||||
|
"cloud_api_types",
|
||||||
"cloud_llm_client",
|
"cloud_llm_client",
|
||||||
"collections",
|
"collections",
|
||||||
"futures 0.3.31",
|
"futures 0.3.31",
|
||||||
|
@ -10580,6 +10589,15 @@ dependencies = [
|
||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "8.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "noop_proc_macro"
|
name = "noop_proc_macro"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -11169,6 +11187,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures 0.3.31",
|
"futures 0.3.31",
|
||||||
"http_client",
|
"http_client",
|
||||||
|
"log",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -15401,7 +15420,7 @@ version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790"
|
checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nom",
|
"nom 7.1.3",
|
||||||
"unicode_categories",
|
"unicode_categories",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -16604,9 +16623,8 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiktoken-rs"
|
name = "tiktoken-rs"
|
||||||
version = "0.7.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/zed-industries/tiktoken-rs?rev=30c32a4522751699adeda0d5840c71c3b75ae73d#30c32a4522751699adeda0d5840c71c3b75ae73d"
|
||||||
checksum = "25563eeba904d770acf527e8b370fe9a5547bacd20ff84a0b6c3bc41288e5625"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
@ -19977,7 +19995,7 @@ dependencies = [
|
||||||
"naga",
|
"naga",
|
||||||
"nix 0.28.0",
|
"nix 0.28.0",
|
||||||
"nix 0.29.0",
|
"nix 0.29.0",
|
||||||
"nom",
|
"nom 7.1.3",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-bigint-dig",
|
"num-bigint-dig",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
|
@ -20312,6 +20330,34 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yawc"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "git+https://github.com/deviant-forks/yawc?rev=1899688f3e69ace4545aceb97b2a13881cf26142#1899688f3e69ace4545aceb97b2a13881cf26142"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"bytes 1.10.1",
|
||||||
|
"flate2",
|
||||||
|
"futures 0.3.31",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper 1.6.0",
|
||||||
|
"hyper-util",
|
||||||
|
"js-sys",
|
||||||
|
"nom 8.0.0",
|
||||||
|
"pin-project",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"sha1",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls 0.26.2",
|
||||||
|
"tokio-util",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"webpki-roots",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yazi"
|
name = "yazi"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -20418,7 +20464,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.199.0"
|
version = "0.199.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activity_indicator",
|
"activity_indicator",
|
||||||
"agent",
|
"agent",
|
||||||
|
@ -20821,6 +20867,7 @@ dependencies = [
|
||||||
"menu",
|
"menu",
|
||||||
"postage",
|
"postage",
|
||||||
"project",
|
"project",
|
||||||
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
"release_channel",
|
"release_channel",
|
||||||
"reqwest_client",
|
"reqwest_client",
|
||||||
|
|
|
@ -461,6 +461,7 @@ bytes = "1.0"
|
||||||
cargo_metadata = "0.19"
|
cargo_metadata = "0.19"
|
||||||
cargo_toml = "0.21"
|
cargo_toml = "0.21"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
ciborium = "0.2"
|
||||||
circular-buffer = "1.0"
|
circular-buffer = "1.0"
|
||||||
clap = { version = "4.4", features = ["derive"] }
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
cocoa = "0.26"
|
cocoa = "0.26"
|
||||||
|
@ -600,7 +601,7 @@ sysinfo = "0.31.0"
|
||||||
take-until = "0.2.0"
|
take-until = "0.2.0"
|
||||||
tempfile = "3.20.0"
|
tempfile = "3.20.0"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
tiktoken-rs = "0.7.0"
|
tiktoken-rs = { git = "https://github.com/zed-industries/tiktoken-rs", rev = "30c32a4522751699adeda0d5840c71c3b75ae73d" }
|
||||||
time = { version = "0.3", features = [
|
time = { version = "0.3", features = [
|
||||||
"macros",
|
"macros",
|
||||||
"parsing",
|
"parsing",
|
||||||
|
@ -660,6 +661,9 @@ which = "6.0.0"
|
||||||
windows-core = "0.61"
|
windows-core = "0.61"
|
||||||
wit-component = "0.221"
|
wit-component = "0.221"
|
||||||
workspace-hack = "0.1.0"
|
workspace-hack = "0.1.0"
|
||||||
|
# We can switch back to the published version once https://github.com/infinitefield/yawc/pull/16 is merged and a new
|
||||||
|
# version is released.
|
||||||
|
yawc = { git = "https://github.com/deviant-forks/yawc", rev = "1899688f3e69ace4545aceb97b2a13881cf26142" }
|
||||||
zstd = "0.11"
|
zstd = "0.11"
|
||||||
|
|
||||||
[workspace.dependencies.async-stripe]
|
[workspace.dependencies.async-stripe]
|
||||||
|
|
|
@ -14,7 +14,9 @@ use async_tungstenite::tungstenite::{
|
||||||
};
|
};
|
||||||
use clock::SystemClock;
|
use clock::SystemClock;
|
||||||
use cloud_api_client::CloudApiClient;
|
use cloud_api_client::CloudApiClient;
|
||||||
|
use cloud_api_client::websocket_protocol::MessageToClient;
|
||||||
use credentials_provider::CredentialsProvider;
|
use credentials_provider::CredentialsProvider;
|
||||||
|
use feature_flags::FeatureFlagAppExt as _;
|
||||||
use futures::{
|
use futures::{
|
||||||
AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt, TryFutureExt as _, TryStreamExt,
|
AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt, TryFutureExt as _, TryStreamExt,
|
||||||
channel::oneshot, future::BoxFuture,
|
channel::oneshot, future::BoxFuture,
|
||||||
|
@ -191,6 +193,8 @@ pub fn init(client: &Arc<Client>, cx: &mut App) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type MessageToClientHandler = Box<dyn Fn(&MessageToClient, &mut App) + Send + Sync + 'static>;
|
||||||
|
|
||||||
struct GlobalClient(Arc<Client>);
|
struct GlobalClient(Arc<Client>);
|
||||||
|
|
||||||
impl Global for GlobalClient {}
|
impl Global for GlobalClient {}
|
||||||
|
@ -204,6 +208,7 @@ pub struct Client {
|
||||||
credentials_provider: ClientCredentialsProvider,
|
credentials_provider: ClientCredentialsProvider,
|
||||||
state: RwLock<ClientState>,
|
state: RwLock<ClientState>,
|
||||||
handler_set: parking_lot::Mutex<ProtoMessageHandlerSet>,
|
handler_set: parking_lot::Mutex<ProtoMessageHandlerSet>,
|
||||||
|
message_to_client_handlers: parking_lot::Mutex<Vec<MessageToClientHandler>>,
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
@ -553,6 +558,7 @@ impl Client {
|
||||||
credentials_provider: ClientCredentialsProvider::new(cx),
|
credentials_provider: ClientCredentialsProvider::new(cx),
|
||||||
state: Default::default(),
|
state: Default::default(),
|
||||||
handler_set: Default::default(),
|
handler_set: Default::default(),
|
||||||
|
message_to_client_handlers: parking_lot::Mutex::new(Vec::new()),
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
authenticate: Default::default(),
|
authenticate: Default::default(),
|
||||||
|
@ -933,23 +939,77 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a sign-in and also connects to Collab.
|
/// Establishes a WebSocket connection with Cloud for receiving updates from the server.
|
||||||
|
async fn connect_to_cloud(self: &Arc<Self>, cx: &AsyncApp) -> Result<()> {
|
||||||
|
let connect_task = cx.update({
|
||||||
|
let cloud_client = self.cloud_client.clone();
|
||||||
|
move |cx| cloud_client.connect(cx)
|
||||||
|
})??;
|
||||||
|
let connection = connect_task.await?;
|
||||||
|
|
||||||
|
let (mut messages, task) = cx.update(|cx| connection.spawn(cx))?;
|
||||||
|
task.detach();
|
||||||
|
|
||||||
|
cx.spawn({
|
||||||
|
let this = self.clone();
|
||||||
|
async move |cx| {
|
||||||
|
while let Some(message) = messages.next().await {
|
||||||
|
if let Some(message) = message.log_err() {
|
||||||
|
this.handle_message_to_client(message, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a sign-in and also (optionally) connects to Collab.
|
||||||
///
|
///
|
||||||
/// This is called in places where we *don't* need to connect in the future. We will replace these calls with calls
|
/// Only Zed staff automatically connect to Collab.
|
||||||
/// to `sign_in` when we're ready to remove auto-connection to Collab.
|
|
||||||
pub async fn sign_in_with_optional_connect(
|
pub async fn sign_in_with_optional_connect(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
try_provider: bool,
|
try_provider: bool,
|
||||||
cx: &AsyncApp,
|
cx: &AsyncApp,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let (is_staff_tx, is_staff_rx) = oneshot::channel::<bool>();
|
||||||
|
let mut is_staff_tx = Some(is_staff_tx);
|
||||||
|
cx.update(|cx| {
|
||||||
|
cx.on_flags_ready(move |state, _cx| {
|
||||||
|
if let Some(is_staff_tx) = is_staff_tx.take() {
|
||||||
|
is_staff_tx.send(state.is_staff).log_err();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
|
||||||
let credentials = self.sign_in(try_provider, cx).await?;
|
let credentials = self.sign_in(try_provider, cx).await?;
|
||||||
|
|
||||||
let connect_result = match self.connect_with_credentials(credentials, cx).await {
|
self.connect_to_cloud(cx).await.log_err();
|
||||||
ConnectionResult::Timeout => Err(anyhow!("connection timed out")),
|
|
||||||
ConnectionResult::ConnectionReset => Err(anyhow!("connection reset")),
|
cx.update(move |cx| {
|
||||||
ConnectionResult::Result(result) => result.context("client auth and connect"),
|
cx.spawn({
|
||||||
};
|
let client = self.clone();
|
||||||
connect_result.log_err();
|
async move |cx| {
|
||||||
|
let is_staff = is_staff_rx.await?;
|
||||||
|
if is_staff {
|
||||||
|
match client.connect_with_credentials(credentials, cx).await {
|
||||||
|
ConnectionResult::Timeout => Err(anyhow!("connection timed out")),
|
||||||
|
ConnectionResult::ConnectionReset => Err(anyhow!("connection reset")),
|
||||||
|
ConnectionResult::Result(result) => {
|
||||||
|
result.context("client auth and connect")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1622,6 +1682,24 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_message_to_client_handler(
|
||||||
|
self: &Arc<Client>,
|
||||||
|
handler: impl Fn(&MessageToClient, &mut App) + Send + Sync + 'static,
|
||||||
|
) {
|
||||||
|
self.message_to_client_handlers
|
||||||
|
.lock()
|
||||||
|
.push(Box::new(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_message_to_client(self: &Arc<Client>, message: MessageToClient, cx: &AsyncApp) {
|
||||||
|
cx.update(|cx| {
|
||||||
|
for handler in self.message_to_client_handlers.lock().iter() {
|
||||||
|
handler(&message, cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn telemetry(&self) -> &Arc<Telemetry> {
|
pub fn telemetry(&self) -> &Arc<Telemetry> {
|
||||||
&self.telemetry
|
&self.telemetry
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use super::{Client, Status, TypedEnvelope, proto};
|
use super::{Client, Status, TypedEnvelope, proto};
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use cloud_api_client::websocket_protocol::MessageToClient;
|
||||||
use cloud_api_client::{GetAuthenticatedUserResponse, PlanInfo};
|
use cloud_api_client::{GetAuthenticatedUserResponse, PlanInfo};
|
||||||
use cloud_llm_client::{
|
use cloud_llm_client::{
|
||||||
EDIT_PREDICTIONS_USAGE_AMOUNT_HEADER_NAME, EDIT_PREDICTIONS_USAGE_LIMIT_HEADER_NAME,
|
EDIT_PREDICTIONS_USAGE_AMOUNT_HEADER_NAME, EDIT_PREDICTIONS_USAGE_LIMIT_HEADER_NAME,
|
||||||
|
@ -181,6 +182,12 @@ impl UserStore {
|
||||||
client.add_message_handler(cx.weak_entity(), Self::handle_update_invite_info),
|
client.add_message_handler(cx.weak_entity(), Self::handle_update_invite_info),
|
||||||
client.add_message_handler(cx.weak_entity(), Self::handle_show_contacts),
|
client.add_message_handler(cx.weak_entity(), Self::handle_show_contacts),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
client.add_message_to_client_handler({
|
||||||
|
let this = cx.weak_entity();
|
||||||
|
move |message, cx| Self::handle_message_to_client(this.clone(), message, cx)
|
||||||
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
users: Default::default(),
|
users: Default::default(),
|
||||||
by_github_login: Default::default(),
|
by_github_login: Default::default(),
|
||||||
|
@ -219,17 +226,35 @@ impl UserStore {
|
||||||
match status {
|
match status {
|
||||||
Status::Authenticated | Status::Connected { .. } => {
|
Status::Authenticated | Status::Connected { .. } => {
|
||||||
if let Some(user_id) = client.user_id() {
|
if let Some(user_id) = client.user_id() {
|
||||||
let response = client.cloud_client().get_authenticated_user().await;
|
let response = client
|
||||||
let mut current_user = None;
|
.cloud_client()
|
||||||
|
.get_authenticated_user()
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
|
||||||
|
let current_user_and_response = if let Some(response) = response {
|
||||||
|
let user = Arc::new(User {
|
||||||
|
id: user_id,
|
||||||
|
github_login: response.user.github_login.clone().into(),
|
||||||
|
avatar_uri: response.user.avatar_url.clone().into(),
|
||||||
|
name: response.user.name.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Some((user, response))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
current_user_tx
|
||||||
|
.send(
|
||||||
|
current_user_and_response
|
||||||
|
.as_ref()
|
||||||
|
.map(|(user, _)| user.clone()),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
if let Some(response) = response.log_err() {
|
if let Some((user, response)) = current_user_and_response {
|
||||||
let user = Arc::new(User {
|
|
||||||
id: user_id,
|
|
||||||
github_login: response.user.github_login.clone().into(),
|
|
||||||
avatar_uri: response.user.avatar_url.clone().into(),
|
|
||||||
name: response.user.name.clone(),
|
|
||||||
});
|
|
||||||
current_user = Some(user.clone());
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.by_github_login
|
this.by_github_login
|
||||||
.insert(user.github_login.clone(), user_id);
|
.insert(user.github_login.clone(), user_id);
|
||||||
|
@ -240,7 +265,6 @@ impl UserStore {
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
})??;
|
})??;
|
||||||
current_user_tx.send(current_user).await.ok();
|
|
||||||
|
|
||||||
this.update(cx, |_, cx| cx.notify())?;
|
this.update(cx, |_, cx| cx.notify())?;
|
||||||
}
|
}
|
||||||
|
@ -813,6 +837,32 @@ impl UserStore {
|
||||||
cx.emit(Event::PrivateUserInfoUpdated);
|
cx.emit(Event::PrivateUserInfoUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_message_to_client(this: WeakEntity<Self>, message: &MessageToClient, cx: &App) {
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
match message {
|
||||||
|
MessageToClient::UserUpdated => {
|
||||||
|
let cloud_client = cx
|
||||||
|
.update(|cx| {
|
||||||
|
this.read_with(cx, |this, _cx| {
|
||||||
|
this.client.upgrade().map(|client| client.cloud_client())
|
||||||
|
})
|
||||||
|
})??
|
||||||
|
.ok_or(anyhow::anyhow!("Failed to get Cloud client"))?;
|
||||||
|
|
||||||
|
let response = cloud_client.get_authenticated_user().await?;
|
||||||
|
cx.update(|cx| {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.update_authenticated_user(response, cx);
|
||||||
|
})
|
||||||
|
})??;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn watch_current_user(&self) -> watch::Receiver<Option<Arc<User>>> {
|
pub fn watch_current_user(&self) -> watch::Receiver<Option<Arc<User>>> {
|
||||||
self.current_user.clone()
|
self.current_user.clone()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,10 @@ path = "src/cloud_api_client.rs"
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
cloud_api_types.workspace = true
|
cloud_api_types.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
gpui.workspace = true
|
||||||
|
gpui_tokio.workspace = true
|
||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
yawc.workspace = true
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
|
mod websocket;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{Context, Result, anyhow};
|
use anyhow::{Context, Result, anyhow};
|
||||||
|
use cloud_api_types::websocket_protocol::{PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER_NAME};
|
||||||
pub use cloud_api_types::*;
|
pub use cloud_api_types::*;
|
||||||
use futures::AsyncReadExt as _;
|
use futures::AsyncReadExt as _;
|
||||||
|
use gpui::{App, Task};
|
||||||
|
use gpui_tokio::Tokio;
|
||||||
use http_client::http::request;
|
use http_client::http::request;
|
||||||
use http_client::{AsyncBody, HttpClientWithUrl, Method, Request, StatusCode};
|
use http_client::{AsyncBody, HttpClientWithUrl, Method, Request, StatusCode};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
use yawc::WebSocket;
|
||||||
|
|
||||||
|
use crate::websocket::Connection;
|
||||||
|
|
||||||
struct Credentials {
|
struct Credentials {
|
||||||
user_id: u32,
|
user_id: u32,
|
||||||
|
@ -78,6 +86,41 @@ impl CloudApiClient {
|
||||||
Ok(serde_json::from_str(&body)?)
|
Ok(serde_json::from_str(&body)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn connect(&self, cx: &App) -> Result<Task<Result<Connection>>> {
|
||||||
|
let mut connect_url = self
|
||||||
|
.http_client
|
||||||
|
.build_zed_cloud_url("/client/users/connect", &[])?;
|
||||||
|
connect_url
|
||||||
|
.set_scheme(match connect_url.scheme() {
|
||||||
|
"https" => "wss",
|
||||||
|
"http" => "ws",
|
||||||
|
scheme => Err(anyhow!("invalid URL scheme: {scheme}"))?,
|
||||||
|
})
|
||||||
|
.map_err(|_| anyhow!("failed to set URL scheme"))?;
|
||||||
|
|
||||||
|
let credentials = self.credentials.read();
|
||||||
|
let credentials = credentials.as_ref().context("no credentials provided")?;
|
||||||
|
let authorization_header = format!("{} {}", credentials.user_id, credentials.access_token);
|
||||||
|
|
||||||
|
Ok(cx.spawn(async move |cx| {
|
||||||
|
let handle = cx
|
||||||
|
.update(|cx| Tokio::handle(cx))
|
||||||
|
.ok()
|
||||||
|
.context("failed to get Tokio handle")?;
|
||||||
|
let _guard = handle.enter();
|
||||||
|
|
||||||
|
let ws = WebSocket::connect(connect_url)
|
||||||
|
.with_request(
|
||||||
|
request::Builder::new()
|
||||||
|
.header("Authorization", authorization_header)
|
||||||
|
.header(PROTOCOL_VERSION_HEADER_NAME, PROTOCOL_VERSION.to_string()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Connection::new(ws))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn accept_terms_of_service(&self) -> Result<AcceptTermsOfServiceResponse> {
|
pub async fn accept_terms_of_service(&self) -> Result<AcceptTermsOfServiceResponse> {
|
||||||
let request = self.build_request(
|
let request = self.build_request(
|
||||||
Request::builder().method(Method::POST).uri(
|
Request::builder().method(Method::POST).uri(
|
||||||
|
|
73
crates/cloud_api_client/src/websocket.rs
Normal file
73
crates/cloud_api_client/src/websocket.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use cloud_api_types::websocket_protocol::MessageToClient;
|
||||||
|
use futures::channel::mpsc::unbounded;
|
||||||
|
use futures::stream::{SplitSink, SplitStream};
|
||||||
|
use futures::{FutureExt as _, SinkExt as _, Stream, StreamExt as _, TryStreamExt as _, pin_mut};
|
||||||
|
use gpui::{App, BackgroundExecutor, Task};
|
||||||
|
use yawc::WebSocket;
|
||||||
|
use yawc::frame::{FrameView, OpCode};
|
||||||
|
|
||||||
|
const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(1);
|
||||||
|
|
||||||
|
pub type MessageStream = Pin<Box<dyn Stream<Item = Result<MessageToClient>>>>;
|
||||||
|
|
||||||
|
pub struct Connection {
|
||||||
|
tx: SplitSink<WebSocket, FrameView>,
|
||||||
|
rx: SplitStream<WebSocket>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
pub fn new(ws: WebSocket) -> Self {
|
||||||
|
let (tx, rx) = ws.split();
|
||||||
|
|
||||||
|
Self { tx, rx }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn(self, cx: &App) -> (MessageStream, Task<()>) {
|
||||||
|
let (mut tx, rx) = (self.tx, self.rx);
|
||||||
|
|
||||||
|
let (message_tx, message_rx) = unbounded();
|
||||||
|
|
||||||
|
let handle_io = |executor: BackgroundExecutor| async move {
|
||||||
|
// Send messages on this frequency so the connection isn't closed.
|
||||||
|
let keepalive_timer = executor.timer(KEEPALIVE_INTERVAL).fuse();
|
||||||
|
futures::pin_mut!(keepalive_timer);
|
||||||
|
|
||||||
|
let rx = rx.fuse();
|
||||||
|
pin_mut!(rx);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
futures::select_biased! {
|
||||||
|
_ = keepalive_timer => {
|
||||||
|
let _ = tx.send(FrameView::ping(Vec::new())).await;
|
||||||
|
|
||||||
|
keepalive_timer.set(executor.timer(KEEPALIVE_INTERVAL).fuse());
|
||||||
|
}
|
||||||
|
frame = rx.next() => {
|
||||||
|
let Some(frame) = frame else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
match frame.opcode {
|
||||||
|
OpCode::Binary => {
|
||||||
|
let message_result = MessageToClient::deserialize(&frame.payload);
|
||||||
|
message_tx.unbounded_send(message_result).ok();
|
||||||
|
}
|
||||||
|
OpCode::Close => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let task = cx.spawn(async move |cx| handle_io(cx.background_executor().clone()).await);
|
||||||
|
|
||||||
|
(message_rx.into_stream().boxed(), task)
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,9 @@ workspace = true
|
||||||
path = "src/cloud_api_types.rs"
|
path = "src/cloud_api_types.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
|
ciborium.workspace = true
|
||||||
cloud_llm_client.workspace = true
|
cloud_llm_client.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
mod timestamp;
|
mod timestamp;
|
||||||
|
pub mod websocket_protocol;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
28
crates/cloud_api_types/src/websocket_protocol.rs
Normal file
28
crates/cloud_api_types/src/websocket_protocol.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use anyhow::{Context as _, Result};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// The version of the Cloud WebSocket protocol.
|
||||||
|
pub const PROTOCOL_VERSION: u32 = 0;
|
||||||
|
|
||||||
|
/// The name of the header used to indicate the protocol version in use.
|
||||||
|
pub const PROTOCOL_VERSION_HEADER_NAME: &str = "x-zed-protocol-version";
|
||||||
|
|
||||||
|
/// A message from Cloud to the Zed client.
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum MessageToClient {
|
||||||
|
/// The user was updated and should be refreshed.
|
||||||
|
UserUpdated,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageToClient {
|
||||||
|
pub fn serialize(&self) -> Result<Vec<u8>> {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
ciborium::into_writer(self, &mut buffer).context("failed to serialize message")?;
|
||||||
|
|
||||||
|
Ok(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(data: &[u8]) -> Result<Self> {
|
||||||
|
ciborium::from_reader(data).context("failed to deserialize message")
|
||||||
|
}
|
||||||
|
}
|
|
@ -699,7 +699,10 @@ impl Database {
|
||||||
language_server::Column::ProjectId,
|
language_server::Column::ProjectId,
|
||||||
language_server::Column::Id,
|
language_server::Column::Id,
|
||||||
])
|
])
|
||||||
.update_column(language_server::Column::Name)
|
.update_columns([
|
||||||
|
language_server::Column::Name,
|
||||||
|
language_server::Column::Capabilities,
|
||||||
|
])
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
.exec(&*tx)
|
.exec(&*tx)
|
||||||
|
|
|
@ -3053,7 +3053,7 @@ impl Render for CollabPanel {
|
||||||
.on_action(cx.listener(CollabPanel::move_channel_down))
|
.on_action(cx.listener(CollabPanel::move_channel_down))
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(if self.user_store.read(cx).current_user().is_none() {
|
.child(if !self.client.status().borrow().is_connected() {
|
||||||
self.render_signed_out(cx)
|
self.render_signed_out(cx)
|
||||||
} else {
|
} else {
|
||||||
self.render_signed_in(window, cx)
|
self.render_signed_in(window, cx)
|
||||||
|
|
|
@ -21,7 +21,7 @@ use language::{
|
||||||
point_from_lsp, point_to_lsp,
|
point_from_lsp, point_to_lsp,
|
||||||
};
|
};
|
||||||
use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId, LanguageServerName};
|
use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId, LanguageServerName};
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::{NodeRuntime, VersionCheck};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::DisableAiSettings;
|
use project::DisableAiSettings;
|
||||||
use request::StatusNotification;
|
use request::StatusNotification;
|
||||||
|
@ -1169,9 +1169,8 @@ async fn get_copilot_lsp(fs: Arc<dyn Fs>, node_runtime: NodeRuntime) -> anyhow::
|
||||||
const SERVER_PATH: &str =
|
const SERVER_PATH: &str =
|
||||||
"node_modules/@github/copilot-language-server/dist/language-server.js";
|
"node_modules/@github/copilot-language-server/dist/language-server.js";
|
||||||
|
|
||||||
let latest_version = node_runtime
|
// pinning it: https://github.com/zed-industries/zed/issues/36093
|
||||||
.npm_package_latest_version(PACKAGE_NAME)
|
const PINNED_VERSION: &str = "1.354";
|
||||||
.await?;
|
|
||||||
let server_path = paths::copilot_dir().join(SERVER_PATH);
|
let server_path = paths::copilot_dir().join(SERVER_PATH);
|
||||||
|
|
||||||
fs.create_dir(paths::copilot_dir()).await?;
|
fs.create_dir(paths::copilot_dir()).await?;
|
||||||
|
@ -1181,12 +1180,13 @@ async fn get_copilot_lsp(fs: Arc<dyn Fs>, node_runtime: NodeRuntime) -> anyhow::
|
||||||
PACKAGE_NAME,
|
PACKAGE_NAME,
|
||||||
&server_path,
|
&server_path,
|
||||||
paths::copilot_dir(),
|
paths::copilot_dir(),
|
||||||
&latest_version,
|
&PINNED_VERSION,
|
||||||
|
VersionCheck::VersionMismatch,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
if should_install {
|
if should_install {
|
||||||
node_runtime
|
node_runtime
|
||||||
.npm_install_packages(paths::copilot_dir(), &[(PACKAGE_NAME, &latest_version)])
|
.npm_install_packages(paths::copilot_dir(), &[(PACKAGE_NAME, &PINNED_VERSION)])
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,10 @@ crash-handler.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
minidumper.workspace = true
|
minidumper.workspace = true
|
||||||
paths.workspace = true
|
paths.workspace = true
|
||||||
|
release_channel.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
use crash_handler::CrashHandler;
|
use crash_handler::CrashHandler;
|
||||||
use log::info;
|
use log::info;
|
||||||
use minidumper::{Client, LoopAction, MinidumpBinary};
|
use minidumper::{Client, LoopAction, MinidumpBinary};
|
||||||
|
use release_channel::{RELEASE_CHANNEL, ReleaseChannel};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
fs::File,
|
fs::{self, File},
|
||||||
io,
|
io,
|
||||||
|
panic::Location,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::{self, Command},
|
process::{self, Command},
|
||||||
sync::{
|
sync::{
|
||||||
OnceLock,
|
Arc, OnceLock,
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
},
|
},
|
||||||
thread,
|
thread,
|
||||||
|
@ -17,12 +20,17 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
// set once the crash handler has initialized and the client has connected to it
|
// set once the crash handler has initialized and the client has connected to it
|
||||||
pub static CRASH_HANDLER: AtomicBool = AtomicBool::new(false);
|
pub static CRASH_HANDLER: OnceLock<Arc<Client>> = OnceLock::new();
|
||||||
// set when the first minidump request is made to avoid generating duplicate crash reports
|
// set when the first minidump request is made to avoid generating duplicate crash reports
|
||||||
pub static REQUESTED_MINIDUMP: AtomicBool = AtomicBool::new(false);
|
pub static REQUESTED_MINIDUMP: AtomicBool = AtomicBool::new(false);
|
||||||
const CRASH_HANDLER_TIMEOUT: Duration = Duration::from_secs(60);
|
const CRASH_HANDLER_PING_TIMEOUT: Duration = Duration::from_secs(60);
|
||||||
|
const CRASH_HANDLER_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
|
pub async fn init(crash_init: InitCrashHandler) {
|
||||||
|
if *RELEASE_CHANNEL == ReleaseChannel::Dev && env::var("ZED_GENERATE_MINIDUMPS").is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn init(id: String) {
|
|
||||||
let exe = env::current_exe().expect("unable to find ourselves");
|
let exe = env::current_exe().expect("unable to find ourselves");
|
||||||
let zed_pid = process::id();
|
let zed_pid = process::id();
|
||||||
// TODO: we should be able to get away with using 1 crash-handler process per machine,
|
// TODO: we should be able to get away with using 1 crash-handler process per machine,
|
||||||
|
@ -53,9 +61,11 @@ pub async fn init(id: String) {
|
||||||
smol::Timer::after(retry_frequency).await;
|
smol::Timer::after(retry_frequency).await;
|
||||||
}
|
}
|
||||||
let client = maybe_client.unwrap();
|
let client = maybe_client.unwrap();
|
||||||
client.send_message(1, id).unwrap(); // set session id on the server
|
client
|
||||||
|
.send_message(1, serde_json::to_vec(&crash_init).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let client = std::sync::Arc::new(client);
|
let client = Arc::new(client);
|
||||||
let handler = crash_handler::CrashHandler::attach(unsafe {
|
let handler = crash_handler::CrashHandler::attach(unsafe {
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
crash_handler::make_crash_event(move |crash_context: &crash_handler::CrashContext| {
|
crash_handler::make_crash_event(move |crash_context: &crash_handler::CrashContext| {
|
||||||
|
@ -64,7 +74,6 @@ pub async fn init(id: String) {
|
||||||
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
client.send_message(2, "mistakes were made").unwrap();
|
|
||||||
client.ping().unwrap();
|
client.ping().unwrap();
|
||||||
client.request_dump(crash_context).is_ok()
|
client.request_dump(crash_context).is_ok()
|
||||||
} else {
|
} else {
|
||||||
|
@ -79,7 +88,7 @@ pub async fn init(id: String) {
|
||||||
{
|
{
|
||||||
handler.set_ptracer(Some(server_pid));
|
handler.set_ptracer(Some(server_pid));
|
||||||
}
|
}
|
||||||
CRASH_HANDLER.store(true, Ordering::Release);
|
CRASH_HANDLER.set(client.clone()).ok();
|
||||||
std::mem::forget(handler);
|
std::mem::forget(handler);
|
||||||
info!("crash handler registered");
|
info!("crash handler registered");
|
||||||
|
|
||||||
|
@ -90,14 +99,43 @@ pub async fn init(id: String) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CrashServer {
|
pub struct CrashServer {
|
||||||
session_id: OnceLock<String>,
|
initialization_params: OnceLock<InitCrashHandler>,
|
||||||
|
panic_info: OnceLock<CrashPanic>,
|
||||||
|
has_connection: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct CrashInfo {
|
||||||
|
pub init: InitCrashHandler,
|
||||||
|
pub panic: Option<CrashPanic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct InitCrashHandler {
|
||||||
|
pub session_id: String,
|
||||||
|
pub zed_version: String,
|
||||||
|
pub release_channel: String,
|
||||||
|
pub commit_sha: String,
|
||||||
|
// pub gpu: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
pub struct CrashPanic {
|
||||||
|
pub message: String,
|
||||||
|
pub span: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl minidumper::ServerHandler for CrashServer {
|
impl minidumper::ServerHandler for CrashServer {
|
||||||
fn create_minidump_file(&self) -> Result<(File, PathBuf), io::Error> {
|
fn create_minidump_file(&self) -> Result<(File, PathBuf), io::Error> {
|
||||||
let err_message = "Need to send a message with the ID upon starting the crash handler";
|
let err_message = "Missing initialization data";
|
||||||
let dump_path = paths::logs_dir()
|
let dump_path = paths::logs_dir()
|
||||||
.join(self.session_id.get().expect(err_message))
|
.join(
|
||||||
|
&self
|
||||||
|
.initialization_params
|
||||||
|
.get()
|
||||||
|
.expect(err_message)
|
||||||
|
.session_id,
|
||||||
|
)
|
||||||
.with_extension("dmp");
|
.with_extension("dmp");
|
||||||
let file = File::create(&dump_path)?;
|
let file = File::create(&dump_path)?;
|
||||||
Ok((file, dump_path))
|
Ok((file, dump_path))
|
||||||
|
@ -114,35 +152,71 @@ impl minidumper::ServerHandler for CrashServer {
|
||||||
info!("failed to write minidump: {:#}", e);
|
info!("failed to write minidump: {:#}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let crash_info = CrashInfo {
|
||||||
|
init: self
|
||||||
|
.initialization_params
|
||||||
|
.get()
|
||||||
|
.expect("not initialized")
|
||||||
|
.clone(),
|
||||||
|
panic: self.panic_info.get().cloned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let crash_data_path = paths::logs_dir()
|
||||||
|
.join(&crash_info.init.session_id)
|
||||||
|
.with_extension("json");
|
||||||
|
|
||||||
|
fs::write(crash_data_path, serde_json::to_vec(&crash_info).unwrap()).ok();
|
||||||
|
|
||||||
LoopAction::Exit
|
LoopAction::Exit
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_message(&self, kind: u32, buffer: Vec<u8>) {
|
fn on_message(&self, kind: u32, buffer: Vec<u8>) {
|
||||||
let message = String::from_utf8(buffer).expect("invalid utf-8");
|
match kind {
|
||||||
info!("kind: {kind}, message: {message}",);
|
1 => {
|
||||||
if kind == 1 {
|
let init_data =
|
||||||
self.session_id
|
serde_json::from_slice::<InitCrashHandler>(&buffer).expect("invalid init data");
|
||||||
.set(message)
|
self.initialization_params
|
||||||
.expect("session id already initialized");
|
.set(init_data)
|
||||||
|
.expect("already initialized");
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
let panic_data =
|
||||||
|
serde_json::from_slice::<CrashPanic>(&buffer).expect("invalid panic data");
|
||||||
|
self.panic_info.set(panic_data).expect("already panicked");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("invalid message kind");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_client_disconnected(&self, clients: usize) -> LoopAction {
|
fn on_client_disconnected(&self, _clients: usize) -> LoopAction {
|
||||||
info!("client disconnected, {clients} remaining");
|
LoopAction::Exit
|
||||||
if clients == 0 {
|
}
|
||||||
LoopAction::Exit
|
|
||||||
} else {
|
fn on_client_connected(&self, _clients: usize) -> LoopAction {
|
||||||
LoopAction::Continue
|
self.has_connection.store(true, Ordering::SeqCst);
|
||||||
}
|
LoopAction::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_panic() {
|
pub fn handle_panic(message: String, span: Option<&Location>) {
|
||||||
|
let span = span
|
||||||
|
.map(|loc| format!("{}:{}", loc.file(), loc.line()))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
// wait 500ms for the crash handler process to start up
|
// wait 500ms for the crash handler process to start up
|
||||||
// if it's still not there just write panic info and no minidump
|
// if it's still not there just write panic info and no minidump
|
||||||
let retry_frequency = Duration::from_millis(100);
|
let retry_frequency = Duration::from_millis(100);
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
if CRASH_HANDLER.load(Ordering::Acquire) {
|
if let Some(client) = CRASH_HANDLER.get() {
|
||||||
|
client
|
||||||
|
.send_message(
|
||||||
|
2,
|
||||||
|
serde_json::to_vec(&CrashPanic { message, span }).unwrap(),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
log::error!("triggering a crash to generate a minidump...");
|
log::error!("triggering a crash to generate a minidump...");
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
CrashHandler.simulate_signal(crash_handler::Signal::Trap as u32);
|
CrashHandler.simulate_signal(crash_handler::Signal::Trap as u32);
|
||||||
|
@ -159,14 +233,30 @@ pub fn crash_server(socket: &Path) {
|
||||||
log::info!("Couldn't create socket, there may already be a running crash server");
|
log::info!("Couldn't create socket, there may already be a running crash server");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let ab = AtomicBool::new(false);
|
|
||||||
|
let shutdown = Arc::new(AtomicBool::new(false));
|
||||||
|
let has_connection = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
std::thread::spawn({
|
||||||
|
let shutdown = shutdown.clone();
|
||||||
|
let has_connection = has_connection.clone();
|
||||||
|
move || {
|
||||||
|
std::thread::sleep(CRASH_HANDLER_CONNECT_TIMEOUT);
|
||||||
|
if !has_connection.load(Ordering::SeqCst) {
|
||||||
|
shutdown.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server
|
server
|
||||||
.run(
|
.run(
|
||||||
Box::new(CrashServer {
|
Box::new(CrashServer {
|
||||||
session_id: OnceLock::new(),
|
initialization_params: OnceLock::new(),
|
||||||
|
panic_info: OnceLock::new(),
|
||||||
|
has_connection,
|
||||||
}),
|
}),
|
||||||
&ab,
|
&shutdown,
|
||||||
Some(CRASH_HANDLER_TIMEOUT),
|
Some(CRASH_HANDLER_PING_TIMEOUT),
|
||||||
)
|
)
|
||||||
.expect("failed to run server");
|
.expect("failed to run server");
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,6 +152,9 @@ impl PythonDebugAdapter {
|
||||||
maybe!(async move {
|
maybe!(async move {
|
||||||
let response = latest_release.filter(|response| response.status().is_success())?;
|
let response = latest_release.filter(|response| response.status().is_success())?;
|
||||||
|
|
||||||
|
let download_dir = debug_adapters_dir().join(Self::ADAPTER_NAME);
|
||||||
|
std::fs::create_dir_all(&download_dir).ok()?;
|
||||||
|
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
response
|
response
|
||||||
.into_body()
|
.into_body()
|
||||||
|
|
|
@ -8028,12 +8028,20 @@ impl Element for EditorElement {
|
||||||
autoscroll_containing_element,
|
autoscroll_containing_element,
|
||||||
needs_horizontal_autoscroll,
|
needs_horizontal_autoscroll,
|
||||||
) = self.editor.update(cx, |editor, cx| {
|
) = self.editor.update(cx, |editor, cx| {
|
||||||
let autoscroll_request = editor.autoscroll_request();
|
let autoscroll_request = editor.scroll_manager.take_autoscroll_request();
|
||||||
|
|
||||||
let autoscroll_containing_element =
|
let autoscroll_containing_element =
|
||||||
autoscroll_request.is_some() || editor.has_pending_selection();
|
autoscroll_request.is_some() || editor.has_pending_selection();
|
||||||
|
|
||||||
let (needs_horizontal_autoscroll, was_scrolled) = editor
|
let (needs_horizontal_autoscroll, was_scrolled) = editor
|
||||||
.autoscroll_vertically(bounds, line_height, max_scroll_top, window, cx);
|
.autoscroll_vertically(
|
||||||
|
bounds,
|
||||||
|
line_height,
|
||||||
|
max_scroll_top,
|
||||||
|
autoscroll_request,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
if was_scrolled.0 {
|
if was_scrolled.0 {
|
||||||
snapshot = editor.snapshot(window, cx);
|
snapshot = editor.snapshot(window, cx);
|
||||||
}
|
}
|
||||||
|
@ -8423,7 +8431,11 @@ impl Element for EditorElement {
|
||||||
Ok(blocks) => blocks,
|
Ok(blocks) => blocks,
|
||||||
Err(resized_blocks) => {
|
Err(resized_blocks) => {
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.resize_blocks(resized_blocks, autoscroll_request, cx)
|
editor.resize_blocks(
|
||||||
|
resized_blocks,
|
||||||
|
autoscroll_request.map(|(autoscroll, _)| autoscroll),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
return self.prepaint(None, _inspector_id, bounds, &mut (), window, cx);
|
return self.prepaint(None, _inspector_id, bounds, &mut (), window, cx);
|
||||||
}
|
}
|
||||||
|
@ -8468,6 +8480,7 @@ impl Element for EditorElement {
|
||||||
scroll_width,
|
scroll_width,
|
||||||
em_advance,
|
em_advance,
|
||||||
&line_layouts,
|
&line_layouts,
|
||||||
|
autoscroll_request,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -348,8 +348,8 @@ impl ScrollManager {
|
||||||
self.show_scrollbars
|
self.show_scrollbars
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn autoscroll_request(&self) -> Option<Autoscroll> {
|
pub fn take_autoscroll_request(&mut self) -> Option<(Autoscroll, bool)> {
|
||||||
self.autoscroll_request.map(|(autoscroll, _)| autoscroll)
|
self.autoscroll_request.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_scrollbar_state(&self) -> Option<&ActiveScrollbarState> {
|
pub fn active_scrollbar_state(&self) -> Option<&ActiveScrollbarState> {
|
||||||
|
|
|
@ -102,15 +102,12 @@ impl AutoscrollStrategy {
|
||||||
pub(crate) struct NeedsHorizontalAutoscroll(pub(crate) bool);
|
pub(crate) struct NeedsHorizontalAutoscroll(pub(crate) bool);
|
||||||
|
|
||||||
impl Editor {
|
impl Editor {
|
||||||
pub fn autoscroll_request(&self) -> Option<Autoscroll> {
|
|
||||||
self.scroll_manager.autoscroll_request()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn autoscroll_vertically(
|
pub(crate) fn autoscroll_vertically(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
max_scroll_top: f32,
|
max_scroll_top: f32,
|
||||||
|
autoscroll_request: Option<(Autoscroll, bool)>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) -> (NeedsHorizontalAutoscroll, WasScrolled) {
|
) -> (NeedsHorizontalAutoscroll, WasScrolled) {
|
||||||
|
@ -137,7 +134,7 @@ impl Editor {
|
||||||
WasScrolled(false)
|
WasScrolled(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some((autoscroll, local)) = self.scroll_manager.autoscroll_request.take() else {
|
let Some((autoscroll, local)) = autoscroll_request else {
|
||||||
return (NeedsHorizontalAutoscroll(false), editor_was_scrolled);
|
return (NeedsHorizontalAutoscroll(false), editor_was_scrolled);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -284,9 +281,12 @@ impl Editor {
|
||||||
scroll_width: Pixels,
|
scroll_width: Pixels,
|
||||||
em_advance: Pixels,
|
em_advance: Pixels,
|
||||||
layouts: &[LineWithInvisibles],
|
layouts: &[LineWithInvisibles],
|
||||||
|
autoscroll_request: Option<(Autoscroll, bool)>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Option<gpui::Point<f32>> {
|
) -> Option<gpui::Point<f32>> {
|
||||||
|
let (_, local) = autoscroll_request?;
|
||||||
|
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
let selections = self.selections.all::<Point>(cx);
|
let selections = self.selections.all::<Point>(cx);
|
||||||
let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
|
let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
|
||||||
|
@ -335,10 +335,10 @@ impl Editor {
|
||||||
|
|
||||||
let was_scrolled = if target_left < scroll_left {
|
let was_scrolled = if target_left < scroll_left {
|
||||||
scroll_position.x = target_left / em_advance;
|
scroll_position.x = target_left / em_advance;
|
||||||
self.set_scroll_position_internal(scroll_position, true, true, window, cx)
|
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
|
||||||
} else if target_right > scroll_right {
|
} else if target_right > scroll_right {
|
||||||
scroll_position.x = (target_right - viewport_width) / em_advance;
|
scroll_position.x = (target_right - viewport_width) / em_advance;
|
||||||
self.set_scroll_position_internal(scroll_position, true, true, window, cx)
|
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
|
||||||
} else {
|
} else {
|
||||||
WasScrolled(false)
|
WasScrolled(false)
|
||||||
};
|
};
|
||||||
|
|
|
@ -158,6 +158,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OnFlagsReady {
|
||||||
|
pub is_staff: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait FeatureFlagAppExt {
|
pub trait FeatureFlagAppExt {
|
||||||
fn wait_for_flag<T: FeatureFlag>(&mut self) -> WaitForFlag;
|
fn wait_for_flag<T: FeatureFlag>(&mut self) -> WaitForFlag;
|
||||||
|
|
||||||
|
@ -169,6 +174,10 @@ pub trait FeatureFlagAppExt {
|
||||||
fn has_flag<T: FeatureFlag>(&self) -> bool;
|
fn has_flag<T: FeatureFlag>(&self) -> bool;
|
||||||
fn is_staff(&self) -> bool;
|
fn is_staff(&self) -> bool;
|
||||||
|
|
||||||
|
fn on_flags_ready<F>(&mut self, callback: F) -> Subscription
|
||||||
|
where
|
||||||
|
F: FnMut(OnFlagsReady, &mut App) + 'static;
|
||||||
|
|
||||||
fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
|
fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
|
||||||
where
|
where
|
||||||
F: FnMut(bool, &mut App) + 'static;
|
F: FnMut(bool, &mut App) + 'static;
|
||||||
|
@ -198,6 +207,21 @@ impl FeatureFlagAppExt for App {
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_flags_ready<F>(&mut self, mut callback: F) -> Subscription
|
||||||
|
where
|
||||||
|
F: FnMut(OnFlagsReady, &mut App) + 'static,
|
||||||
|
{
|
||||||
|
self.observe_global::<FeatureFlags>(move |cx| {
|
||||||
|
let feature_flags = cx.global::<FeatureFlags>();
|
||||||
|
callback(
|
||||||
|
OnFlagsReady {
|
||||||
|
is_staff: feature_flags.staff,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn observe_flag<T: FeatureFlag, F>(&mut self, mut callback: F) -> Subscription
|
fn observe_flag<T: FeatureFlag, F>(&mut self, mut callback: F) -> Subscription
|
||||||
where
|
where
|
||||||
F: FnMut(bool, &mut App) + 'static,
|
F: FnMut(bool, &mut App) + 'static,
|
||||||
|
|
|
@ -314,6 +314,15 @@ impl MetalRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_path_intermediate_textures(&mut self, size: Size<DevicePixels>) {
|
fn update_path_intermediate_textures(&mut self, size: Size<DevicePixels>) {
|
||||||
|
// We are uncertain when this happens, but sometimes size can be 0 here. Most likely before
|
||||||
|
// the layout pass on window creation. Zero-sized texture creation causes SIGABRT.
|
||||||
|
// https://github.com/zed-industries/zed/issues/36229
|
||||||
|
if size.width.0 <= 0 || size.height.0 <= 0 {
|
||||||
|
self.path_intermediate_texture = None;
|
||||||
|
self.path_intermediate_msaa_texture = None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let texture_descriptor = metal::TextureDescriptor::new();
|
let texture_descriptor = metal::TextureDescriptor::new();
|
||||||
texture_descriptor.set_width(size.width.0 as u64);
|
texture_descriptor.set_width(size.width.0 as u64);
|
||||||
texture_descriptor.set_height(size.height.0 as u64);
|
texture_descriptor.set_height(size.height.0 as u64);
|
||||||
|
|
|
@ -71,11 +71,19 @@ pub async fn latest_github_release(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
releases
|
let mut release = releases
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|release| !require_assets || !release.assets.is_empty())
|
.filter(|release| !require_assets || !release.assets.is_empty())
|
||||||
.find(|release| release.pre_release == pre_release)
|
.find(|release| release.pre_release == pre_release)
|
||||||
.context("finding a prerelease")
|
.context("finding a prerelease")?;
|
||||||
|
release.assets.iter_mut().for_each(|asset| {
|
||||||
|
if let Some(digest) = &mut asset.digest {
|
||||||
|
if let Some(stripped) = digest.strip_prefix("sha256:") {
|
||||||
|
*digest = stripped.to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(release)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_release_by_tag_name(
|
pub async fn get_release_by_tag_name(
|
||||||
|
|
|
@ -20,6 +20,7 @@ anthropic = { workspace = true, features = ["schemars"] }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
base64.workspace = true
|
base64.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
|
cloud_api_types.workspace = true
|
||||||
cloud_llm_client.workspace = true
|
cloud_llm_client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
|
|
@ -3,11 +3,9 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use client::Client;
|
use client::Client;
|
||||||
|
use cloud_api_types::websocket_protocol::MessageToClient;
|
||||||
use cloud_llm_client::Plan;
|
use cloud_llm_client::Plan;
|
||||||
use gpui::{
|
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Global, ReadGlobal as _};
|
||||||
App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Global, ReadGlobal as _,
|
|
||||||
};
|
|
||||||
use proto::TypedEnvelope;
|
|
||||||
use smol::lock::{RwLock, RwLockUpgradableReadGuard, RwLockWriteGuard};
|
use smol::lock::{RwLock, RwLockUpgradableReadGuard, RwLockWriteGuard};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -82,9 +80,7 @@ impl Global for GlobalRefreshLlmTokenListener {}
|
||||||
|
|
||||||
pub struct RefreshLlmTokenEvent;
|
pub struct RefreshLlmTokenEvent;
|
||||||
|
|
||||||
pub struct RefreshLlmTokenListener {
|
pub struct RefreshLlmTokenListener;
|
||||||
_llm_token_subscription: client::Subscription,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<RefreshLlmTokenEvent> for RefreshLlmTokenListener {}
|
impl EventEmitter<RefreshLlmTokenEvent> for RefreshLlmTokenListener {}
|
||||||
|
|
||||||
|
@ -99,17 +95,21 @@ impl RefreshLlmTokenListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(client: Arc<Client>, cx: &mut Context<Self>) -> Self {
|
fn new(client: Arc<Client>, cx: &mut Context<Self>) -> Self {
|
||||||
Self {
|
client.add_message_to_client_handler({
|
||||||
_llm_token_subscription: client
|
let this = cx.entity();
|
||||||
.add_message_handler(cx.weak_entity(), Self::handle_refresh_llm_token),
|
move |message, cx| {
|
||||||
}
|
Self::handle_refresh_llm_token(this.clone(), message, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_refresh_llm_token(
|
fn handle_refresh_llm_token(this: Entity<Self>, message: &MessageToClient, cx: &mut App) {
|
||||||
this: Entity<Self>,
|
match message {
|
||||||
_: TypedEnvelope<proto::RefreshLlmToken>,
|
MessageToClient::UserUpdated => {
|
||||||
mut cx: AsyncApp,
|
this.update(cx, |_this, cx| cx.emit(RefreshLlmTokenEvent));
|
||||||
) -> Result<()> {
|
}
|
||||||
this.update(&mut cx, |_this, cx| cx.emit(RefreshLlmTokenEvent))
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -941,6 +941,8 @@ impl LanguageModel for CloudLanguageModel {
|
||||||
request,
|
request,
|
||||||
model.id(),
|
model.id(),
|
||||||
model.supports_parallel_tool_calls(),
|
model.supports_parallel_tool_calls(),
|
||||||
|
model.supports_prompt_cache_key(),
|
||||||
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let llm_api_token = self.llm_api_token.clone();
|
let llm_api_token = self.llm_api_token.clone();
|
||||||
|
|
|
@ -14,7 +14,7 @@ use language_model::{
|
||||||
RateLimiter, Role, StopReason, TokenUsage,
|
RateLimiter, Role, StopReason, TokenUsage,
|
||||||
};
|
};
|
||||||
use menu;
|
use menu;
|
||||||
use open_ai::{ImageUrl, Model, ResponseStreamEvent, stream_completion};
|
use open_ai::{ImageUrl, Model, ReasoningEffort, ResponseStreamEvent, stream_completion};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
|
@ -45,6 +45,7 @@ pub struct AvailableModel {
|
||||||
pub max_tokens: u64,
|
pub max_tokens: u64,
|
||||||
pub max_output_tokens: Option<u64>,
|
pub max_output_tokens: Option<u64>,
|
||||||
pub max_completion_tokens: Option<u64>,
|
pub max_completion_tokens: Option<u64>,
|
||||||
|
pub reasoning_effort: Option<ReasoningEffort>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OpenAiLanguageModelProvider {
|
pub struct OpenAiLanguageModelProvider {
|
||||||
|
@ -213,6 +214,7 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider {
|
||||||
max_tokens: model.max_tokens,
|
max_tokens: model.max_tokens,
|
||||||
max_output_tokens: model.max_output_tokens,
|
max_output_tokens: model.max_output_tokens,
|
||||||
max_completion_tokens: model.max_completion_tokens,
|
max_completion_tokens: model.max_completion_tokens,
|
||||||
|
reasoning_effort: model.reasoning_effort.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -301,7 +303,25 @@ impl LanguageModel for OpenAiLanguageModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_images(&self) -> bool {
|
fn supports_images(&self) -> bool {
|
||||||
false
|
use open_ai::Model;
|
||||||
|
match &self.model {
|
||||||
|
Model::FourOmni
|
||||||
|
| Model::FourOmniMini
|
||||||
|
| Model::FourPointOne
|
||||||
|
| Model::FourPointOneMini
|
||||||
|
| Model::FourPointOneNano
|
||||||
|
| Model::Five
|
||||||
|
| Model::FiveMini
|
||||||
|
| Model::FiveNano
|
||||||
|
| Model::O1
|
||||||
|
| Model::O3
|
||||||
|
| Model::O4Mini => true,
|
||||||
|
Model::ThreePointFiveTurbo
|
||||||
|
| Model::Four
|
||||||
|
| Model::FourTurbo
|
||||||
|
| Model::O3Mini
|
||||||
|
| Model::Custom { .. } => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_tool_choice(&self, choice: LanguageModelToolChoice) -> bool {
|
fn supports_tool_choice(&self, choice: LanguageModelToolChoice) -> bool {
|
||||||
|
@ -350,7 +370,9 @@ impl LanguageModel for OpenAiLanguageModel {
|
||||||
request,
|
request,
|
||||||
self.model.id(),
|
self.model.id(),
|
||||||
self.model.supports_parallel_tool_calls(),
|
self.model.supports_parallel_tool_calls(),
|
||||||
|
self.model.supports_prompt_cache_key(),
|
||||||
self.max_output_tokens(),
|
self.max_output_tokens(),
|
||||||
|
self.model.reasoning_effort(),
|
||||||
);
|
);
|
||||||
let completions = self.stream_completion(request, cx);
|
let completions = self.stream_completion(request, cx);
|
||||||
async move {
|
async move {
|
||||||
|
@ -365,7 +387,9 @@ pub fn into_open_ai(
|
||||||
request: LanguageModelRequest,
|
request: LanguageModelRequest,
|
||||||
model_id: &str,
|
model_id: &str,
|
||||||
supports_parallel_tool_calls: bool,
|
supports_parallel_tool_calls: bool,
|
||||||
|
supports_prompt_cache_key: bool,
|
||||||
max_output_tokens: Option<u64>,
|
max_output_tokens: Option<u64>,
|
||||||
|
reasoning_effort: Option<ReasoningEffort>,
|
||||||
) -> open_ai::Request {
|
) -> open_ai::Request {
|
||||||
let stream = !model_id.starts_with("o1-");
|
let stream = !model_id.starts_with("o1-");
|
||||||
|
|
||||||
|
@ -455,6 +479,11 @@ pub fn into_open_ai(
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
prompt_cache_key: if supports_prompt_cache_key {
|
||||||
|
request.thread_id
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
tools: request
|
tools: request
|
||||||
.tools
|
.tools
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -471,6 +500,7 @@ pub fn into_open_ai(
|
||||||
LanguageModelToolChoice::Any => open_ai::ToolChoice::Required,
|
LanguageModelToolChoice::Any => open_ai::ToolChoice::Required,
|
||||||
LanguageModelToolChoice::None => open_ai::ToolChoice::None,
|
LanguageModelToolChoice::None => open_ai::ToolChoice::None,
|
||||||
}),
|
}),
|
||||||
|
reasoning_effort,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,6 +704,10 @@ pub fn count_open_ai_tokens(
|
||||||
| Model::O3
|
| Model::O3
|
||||||
| Model::O3Mini
|
| Model::O3Mini
|
||||||
| Model::O4Mini => tiktoken_rs::num_tokens_from_messages(model.id(), &messages),
|
| Model::O4Mini => tiktoken_rs::num_tokens_from_messages(model.id(), &messages),
|
||||||
|
// GPT-5 models don't have tiktoken support yet; fall back on gpt-4o tokenizer
|
||||||
|
Model::Five | Model::FiveMini | Model::FiveNano => {
|
||||||
|
tiktoken_rs::num_tokens_from_messages("gpt-4o", &messages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.map(|tokens| tokens as u64)
|
.map(|tokens| tokens as u64)
|
||||||
})
|
})
|
||||||
|
|
|
@ -355,7 +355,16 @@ impl LanguageModel for OpenAiCompatibleLanguageModel {
|
||||||
LanguageModelCompletionError,
|
LanguageModelCompletionError,
|
||||||
>,
|
>,
|
||||||
> {
|
> {
|
||||||
let request = into_open_ai(request, &self.model.name, true, self.max_output_tokens());
|
let supports_parallel_tool_call = true;
|
||||||
|
let supports_prompt_cache_key = false;
|
||||||
|
let request = into_open_ai(
|
||||||
|
request,
|
||||||
|
&self.model.name,
|
||||||
|
supports_parallel_tool_call,
|
||||||
|
supports_prompt_cache_key,
|
||||||
|
self.max_output_tokens(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
let completions = self.stream_completion(request, cx);
|
let completions = self.stream_completion(request, cx);
|
||||||
async move {
|
async move {
|
||||||
let mapper = OpenAiEventMapper::new();
|
let mapper = OpenAiEventMapper::new();
|
||||||
|
|
|
@ -355,7 +355,9 @@ impl LanguageModel for VercelLanguageModel {
|
||||||
request,
|
request,
|
||||||
self.model.id(),
|
self.model.id(),
|
||||||
self.model.supports_parallel_tool_calls(),
|
self.model.supports_parallel_tool_calls(),
|
||||||
|
self.model.supports_prompt_cache_key(),
|
||||||
self.max_output_tokens(),
|
self.max_output_tokens(),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
let completions = self.stream_completion(request, cx);
|
let completions = self.stream_completion(request, cx);
|
||||||
async move {
|
async move {
|
||||||
|
|
|
@ -359,7 +359,9 @@ impl LanguageModel for XAiLanguageModel {
|
||||||
request,
|
request,
|
||||||
self.model.id(),
|
self.model.id(),
|
||||||
self.model.supports_parallel_tool_calls(),
|
self.model.supports_parallel_tool_calls(),
|
||||||
|
self.model.supports_prompt_cache_key(),
|
||||||
self.max_output_tokens(),
|
self.max_output_tokens(),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
let completions = self.stream_completion(request, cx);
|
let completions = self.stream_completion(request, cx);
|
||||||
async move {
|
async move {
|
||||||
|
|
|
@ -71,8 +71,11 @@ impl super::LspAdapter for CLspAdapter {
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
delegate: &dyn LspAdapterDelegate,
|
delegate: &dyn LspAdapterDelegate,
|
||||||
) -> Result<LanguageServerBinary> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let GitHubLspBinaryVersion { name, url, digest } =
|
let GitHubLspBinaryVersion {
|
||||||
&*version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
name,
|
||||||
|
url,
|
||||||
|
digest: expected_digest,
|
||||||
|
} = *version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||||
let version_dir = container_dir.join(format!("clangd_{name}"));
|
let version_dir = container_dir.join(format!("clangd_{name}"));
|
||||||
let binary_path = version_dir.join("bin/clangd");
|
let binary_path = version_dir.join("bin/clangd");
|
||||||
|
|
||||||
|
@ -99,7 +102,9 @@ impl super::LspAdapter for CLspAdapter {
|
||||||
log::warn!("Unable to run {binary_path:?} asset, redownloading: {err}",)
|
log::warn!("Unable to run {binary_path:?} asset, redownloading: {err}",)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
if let (Some(actual_digest), Some(expected_digest)) = (&metadata.digest, digest) {
|
if let (Some(actual_digest), Some(expected_digest)) =
|
||||||
|
(&metadata.digest, &expected_digest)
|
||||||
|
{
|
||||||
if actual_digest == expected_digest {
|
if actual_digest == expected_digest {
|
||||||
if validity_check().await.is_ok() {
|
if validity_check().await.is_ok() {
|
||||||
return Ok(binary);
|
return Ok(binary);
|
||||||
|
@ -115,8 +120,8 @@ impl super::LspAdapter for CLspAdapter {
|
||||||
}
|
}
|
||||||
download_server_binary(
|
download_server_binary(
|
||||||
delegate,
|
delegate,
|
||||||
url,
|
&url,
|
||||||
digest.as_deref(),
|
expected_digest.as_deref(),
|
||||||
&container_dir,
|
&container_dir,
|
||||||
AssetKind::Zip,
|
AssetKind::Zip,
|
||||||
)
|
)
|
||||||
|
@ -125,7 +130,7 @@ impl super::LspAdapter for CLspAdapter {
|
||||||
GithubBinaryMetadata::write_to_file(
|
GithubBinaryMetadata::write_to_file(
|
||||||
&GithubBinaryMetadata {
|
&GithubBinaryMetadata {
|
||||||
metadata_version: 1,
|
metadata_version: 1,
|
||||||
digest: digest.clone(),
|
digest: expected_digest,
|
||||||
},
|
},
|
||||||
&metadata_path,
|
&metadata_path,
|
||||||
)
|
)
|
||||||
|
|
|
@ -103,7 +103,13 @@ impl LspAdapter for CssLspAdapter {
|
||||||
|
|
||||||
let should_install_language_server = self
|
let should_install_language_server = self
|
||||||
.node
|
.node
|
||||||
.should_install_npm_package(Self::PACKAGE_NAME, &server_path, &container_dir, &version)
|
.should_install_npm_package(
|
||||||
|
Self::PACKAGE_NAME,
|
||||||
|
&server_path,
|
||||||
|
&container_dir,
|
||||||
|
&version,
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if should_install_language_server {
|
if should_install_language_server {
|
||||||
|
|
|
@ -18,9 +18,8 @@ impl GithubBinaryMetadata {
|
||||||
let metadata_content = async_fs::read_to_string(metadata_path)
|
let metadata_content = async_fs::read_to_string(metadata_path)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("reading metadata file at {metadata_path:?}"))?;
|
.with_context(|| format!("reading metadata file at {metadata_path:?}"))?;
|
||||||
let metadata: GithubBinaryMetadata = serde_json::from_str(&metadata_content)
|
serde_json::from_str(&metadata_content)
|
||||||
.with_context(|| format!("parsing metadata file at {metadata_path:?}"))?;
|
.with_context(|| format!("parsing metadata file at {metadata_path:?}"))
|
||||||
Ok(metadata)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn write_to_file(&self, metadata_path: &Path) -> Result<()> {
|
pub(crate) async fn write_to_file(&self, metadata_path: &Path) -> Result<()> {
|
||||||
|
@ -62,6 +61,7 @@ pub(crate) async fn download_server_binary(
|
||||||
format!("saving archive contents into the temporary file for {url}",)
|
format!("saving archive contents into the temporary file for {url}",)
|
||||||
})?;
|
})?;
|
||||||
let asset_sha_256 = format!("{:x}", writer.hasher.finalize());
|
let asset_sha_256 = format!("{:x}", writer.hasher.finalize());
|
||||||
|
|
||||||
anyhow::ensure!(
|
anyhow::ensure!(
|
||||||
asset_sha_256 == expected_sha_256,
|
asset_sha_256 == expected_sha_256,
|
||||||
"{url} asset got SHA-256 mismatch. Expected: {expected_sha_256}, Got: {asset_sha_256}",
|
"{url} asset got SHA-256 mismatch. Expected: {expected_sha_256}, Got: {asset_sha_256}",
|
||||||
|
|
|
@ -340,7 +340,13 @@ impl LspAdapter for JsonLspAdapter {
|
||||||
|
|
||||||
let should_install_language_server = self
|
let should_install_language_server = self
|
||||||
.node
|
.node
|
||||||
.should_install_npm_package(Self::PACKAGE_NAME, &server_path, &container_dir, &version)
|
.should_install_npm_package(
|
||||||
|
Self::PACKAGE_NAME,
|
||||||
|
&server_path,
|
||||||
|
&container_dir,
|
||||||
|
&version,
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if should_install_language_server {
|
if should_install_language_server {
|
||||||
|
|
|
@ -206,6 +206,7 @@ impl LspAdapter for PythonLspAdapter {
|
||||||
&server_path,
|
&server_path,
|
||||||
&container_dir,
|
&container_dir,
|
||||||
&version,
|
&version,
|
||||||
|
Default::default(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ use std::{
|
||||||
sync::{Arc, LazyLock},
|
sync::{Arc, LazyLock},
|
||||||
};
|
};
|
||||||
use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
|
use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
|
||||||
use util::fs::make_file_executable;
|
use util::fs::{make_file_executable, remove_matching};
|
||||||
use util::merge_json_value_into;
|
use util::merge_json_value_into;
|
||||||
use util::{ResultExt, maybe};
|
use util::{ResultExt, maybe};
|
||||||
|
|
||||||
|
@ -161,13 +161,13 @@ impl LspAdapter for RustLspAdapter {
|
||||||
let asset_name = Self::build_asset_name();
|
let asset_name = Self::build_asset_name();
|
||||||
let asset = release
|
let asset = release
|
||||||
.assets
|
.assets
|
||||||
.iter()
|
.into_iter()
|
||||||
.find(|asset| asset.name == asset_name)
|
.find(|asset| asset.name == asset_name)
|
||||||
.with_context(|| format!("no asset found matching `{asset_name:?}`"))?;
|
.with_context(|| format!("no asset found matching `{asset_name:?}`"))?;
|
||||||
Ok(Box::new(GitHubLspBinaryVersion {
|
Ok(Box::new(GitHubLspBinaryVersion {
|
||||||
name: release.tag_name,
|
name: release.tag_name,
|
||||||
url: asset.browser_download_url.clone(),
|
url: asset.browser_download_url,
|
||||||
digest: asset.digest.clone(),
|
digest: asset.digest,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,11 +177,11 @@ impl LspAdapter for RustLspAdapter {
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
delegate: &dyn LspAdapterDelegate,
|
delegate: &dyn LspAdapterDelegate,
|
||||||
) -> Result<LanguageServerBinary> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let GitHubLspBinaryVersion { name, url, digest } =
|
let GitHubLspBinaryVersion {
|
||||||
&*version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
name,
|
||||||
let expected_digest = digest
|
url,
|
||||||
.as_ref()
|
digest: expected_digest,
|
||||||
.and_then(|digest| digest.strip_prefix("sha256:"));
|
} = *version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||||
let destination_path = container_dir.join(format!("rust-analyzer-{name}"));
|
let destination_path = container_dir.join(format!("rust-analyzer-{name}"));
|
||||||
let server_path = match Self::GITHUB_ASSET_KIND {
|
let server_path = match Self::GITHUB_ASSET_KIND {
|
||||||
AssetKind::TarGz | AssetKind::Gz => destination_path.clone(), // Tar and gzip extract in place.
|
AssetKind::TarGz | AssetKind::Gz => destination_path.clone(), // Tar and gzip extract in place.
|
||||||
|
@ -212,7 +212,7 @@ impl LspAdapter for RustLspAdapter {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
if let (Some(actual_digest), Some(expected_digest)) =
|
if let (Some(actual_digest), Some(expected_digest)) =
|
||||||
(&metadata.digest, expected_digest)
|
(&metadata.digest, &expected_digest)
|
||||||
{
|
{
|
||||||
if actual_digest == expected_digest {
|
if actual_digest == expected_digest {
|
||||||
if validity_check().await.is_ok() {
|
if validity_check().await.is_ok() {
|
||||||
|
@ -228,20 +228,20 @@ impl LspAdapter for RustLspAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = fs::remove_dir_all(&destination_path).await;
|
|
||||||
download_server_binary(
|
download_server_binary(
|
||||||
delegate,
|
delegate,
|
||||||
url,
|
&url,
|
||||||
expected_digest,
|
expected_digest.as_deref(),
|
||||||
&destination_path,
|
&destination_path,
|
||||||
Self::GITHUB_ASSET_KIND,
|
Self::GITHUB_ASSET_KIND,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
make_file_executable(&server_path).await?;
|
make_file_executable(&server_path).await?;
|
||||||
|
remove_matching(&container_dir, |path| server_path != path).await;
|
||||||
GithubBinaryMetadata::write_to_file(
|
GithubBinaryMetadata::write_to_file(
|
||||||
&GithubBinaryMetadata {
|
&GithubBinaryMetadata {
|
||||||
metadata_version: 1,
|
metadata_version: 1,
|
||||||
digest: expected_digest.map(ToString::to_string),
|
digest: expected_digest,
|
||||||
},
|
},
|
||||||
&metadata_path,
|
&metadata_path,
|
||||||
)
|
)
|
||||||
|
|
|
@ -108,7 +108,13 @@ impl LspAdapter for TailwindLspAdapter {
|
||||||
|
|
||||||
let should_install_language_server = self
|
let should_install_language_server = self
|
||||||
.node
|
.node
|
||||||
.should_install_npm_package(Self::PACKAGE_NAME, &server_path, &container_dir, &version)
|
.should_install_npm_package(
|
||||||
|
Self::PACKAGE_NAME,
|
||||||
|
&server_path,
|
||||||
|
&container_dir,
|
||||||
|
&version,
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if should_install_language_server {
|
if should_install_language_server {
|
||||||
|
|
|
@ -589,6 +589,7 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||||
&server_path,
|
&server_path,
|
||||||
&container_dir,
|
&container_dir,
|
||||||
version.typescript_version.as_str(),
|
version.typescript_version.as_str(),
|
||||||
|
Default::default(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,7 @@ impl LspAdapter for VtslsLspAdapter {
|
||||||
&server_path,
|
&server_path,
|
||||||
&container_dir,
|
&container_dir,
|
||||||
&latest_version.server_version,
|
&latest_version.server_version,
|
||||||
|
Default::default(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
@ -129,6 +130,7 @@ impl LspAdapter for VtslsLspAdapter {
|
||||||
&container_dir.join(Self::TYPESCRIPT_TSDK_PATH),
|
&container_dir.join(Self::TYPESCRIPT_TSDK_PATH),
|
||||||
&container_dir,
|
&container_dir,
|
||||||
&latest_version.typescript_version,
|
&latest_version.typescript_version,
|
||||||
|
Default::default(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|
|
@ -104,7 +104,13 @@ impl LspAdapter for YamlLspAdapter {
|
||||||
|
|
||||||
let should_install_language_server = self
|
let should_install_language_server = self
|
||||||
.node
|
.node
|
||||||
.should_install_npm_package(Self::PACKAGE_NAME, &server_path, &container_dir, &version)
|
.should_install_npm_package(
|
||||||
|
Self::PACKAGE_NAME,
|
||||||
|
&server_path,
|
||||||
|
&container_dir,
|
||||||
|
&version,
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if should_install_language_server {
|
if should_install_language_server {
|
||||||
|
|
|
@ -13,14 +13,15 @@ use parking_lot::Mutex;
|
||||||
use smol::io::BufReader;
|
use smol::io::BufReader;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyNotification, AnyResponse, CONTENT_LEN_HEADER, IoHandler, IoKind, RequestId, ResponseHandler,
|
AnyResponse, CONTENT_LEN_HEADER, IoHandler, IoKind, NotificationOrRequest, RequestId,
|
||||||
|
ResponseHandler,
|
||||||
};
|
};
|
||||||
|
|
||||||
const HEADER_DELIMITER: &[u8; 4] = b"\r\n\r\n";
|
const HEADER_DELIMITER: &[u8; 4] = b"\r\n\r\n";
|
||||||
/// Handler for stdout of language server.
|
/// Handler for stdout of language server.
|
||||||
pub struct LspStdoutHandler {
|
pub struct LspStdoutHandler {
|
||||||
pub(super) loop_handle: Task<Result<()>>,
|
pub(super) loop_handle: Task<Result<()>>,
|
||||||
pub(super) notifications_channel: UnboundedReceiver<AnyNotification>,
|
pub(super) incoming_messages: UnboundedReceiver<NotificationOrRequest>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_headers<Stdout>(reader: &mut BufReader<Stdout>, buffer: &mut Vec<u8>) -> Result<()>
|
async fn read_headers<Stdout>(reader: &mut BufReader<Stdout>, buffer: &mut Vec<u8>) -> Result<()>
|
||||||
|
@ -54,13 +55,13 @@ impl LspStdoutHandler {
|
||||||
let loop_handle = cx.spawn(Self::handler(stdout, tx, response_handlers, io_handlers));
|
let loop_handle = cx.spawn(Self::handler(stdout, tx, response_handlers, io_handlers));
|
||||||
Self {
|
Self {
|
||||||
loop_handle,
|
loop_handle,
|
||||||
notifications_channel,
|
incoming_messages: notifications_channel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handler<Input>(
|
async fn handler<Input>(
|
||||||
stdout: Input,
|
stdout: Input,
|
||||||
notifications_sender: UnboundedSender<AnyNotification>,
|
notifications_sender: UnboundedSender<NotificationOrRequest>,
|
||||||
response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>,
|
response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>,
|
||||||
io_handlers: Arc<Mutex<HashMap<i32, IoHandler>>>,
|
io_handlers: Arc<Mutex<HashMap<i32, IoHandler>>>,
|
||||||
) -> anyhow::Result<()>
|
) -> anyhow::Result<()>
|
||||||
|
@ -96,7 +97,7 @@ impl LspStdoutHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(msg) = serde_json::from_slice::<AnyNotification>(&buffer) {
|
if let Ok(msg) = serde_json::from_slice::<NotificationOrRequest>(&buffer) {
|
||||||
notifications_sender.unbounded_send(msg)?;
|
notifications_sender.unbounded_send(msg)?;
|
||||||
} else if let Ok(AnyResponse {
|
} else if let Ok(AnyResponse {
|
||||||
id, error, result, ..
|
id, error, result, ..
|
||||||
|
|
|
@ -242,7 +242,7 @@ struct Notification<'a, T> {
|
||||||
|
|
||||||
/// Language server RPC notification message before it is deserialized into a concrete type.
|
/// Language server RPC notification message before it is deserialized into a concrete type.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
struct AnyNotification {
|
struct NotificationOrRequest {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
id: Option<RequestId>,
|
id: Option<RequestId>,
|
||||||
method: String,
|
method: String,
|
||||||
|
@ -252,7 +252,10 @@ struct AnyNotification {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct Error {
|
struct Error {
|
||||||
|
code: i64,
|
||||||
message: String,
|
message: String,
|
||||||
|
#[serde(default)]
|
||||||
|
data: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait LspRequestFuture<O>: Future<Output = ConnectionResult<O>> {
|
pub trait LspRequestFuture<O>: Future<Output = ConnectionResult<O>> {
|
||||||
|
@ -364,6 +367,7 @@ impl LanguageServer {
|
||||||
notification.method,
|
notification.method,
|
||||||
serde_json::to_string_pretty(¬ification.params).unwrap(),
|
serde_json::to_string_pretty(¬ification.params).unwrap(),
|
||||||
);
|
);
|
||||||
|
false
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -389,7 +393,7 @@ impl LanguageServer {
|
||||||
Stdin: AsyncWrite + Unpin + Send + 'static,
|
Stdin: AsyncWrite + Unpin + Send + 'static,
|
||||||
Stdout: AsyncRead + Unpin + Send + 'static,
|
Stdout: AsyncRead + Unpin + Send + 'static,
|
||||||
Stderr: AsyncRead + Unpin + Send + 'static,
|
Stderr: AsyncRead + Unpin + Send + 'static,
|
||||||
F: FnMut(AnyNotification) + 'static + Send + Sync + Clone,
|
F: Fn(&NotificationOrRequest) -> bool + 'static + Send + Sync + Clone,
|
||||||
{
|
{
|
||||||
let (outbound_tx, outbound_rx) = channel::unbounded::<String>();
|
let (outbound_tx, outbound_rx) = channel::unbounded::<String>();
|
||||||
let (output_done_tx, output_done_rx) = barrier::channel();
|
let (output_done_tx, output_done_rx) = barrier::channel();
|
||||||
|
@ -400,14 +404,34 @@ impl LanguageServer {
|
||||||
let io_handlers = Arc::new(Mutex::new(HashMap::default()));
|
let io_handlers = Arc::new(Mutex::new(HashMap::default()));
|
||||||
|
|
||||||
let stdout_input_task = cx.spawn({
|
let stdout_input_task = cx.spawn({
|
||||||
let on_unhandled_notification = on_unhandled_notification.clone();
|
let unhandled_notification_wrapper = {
|
||||||
|
let response_channel = outbound_tx.clone();
|
||||||
|
async move |msg: NotificationOrRequest| {
|
||||||
|
let did_handle = on_unhandled_notification(&msg);
|
||||||
|
if !did_handle && let Some(message_id) = msg.id {
|
||||||
|
let response = AnyResponse {
|
||||||
|
jsonrpc: JSON_RPC_VERSION,
|
||||||
|
id: message_id,
|
||||||
|
error: Some(Error {
|
||||||
|
code: -32601,
|
||||||
|
message: format!("Unrecognized method `{}`", msg.method),
|
||||||
|
data: None,
|
||||||
|
}),
|
||||||
|
result: None,
|
||||||
|
};
|
||||||
|
if let Ok(response) = serde_json::to_string(&response) {
|
||||||
|
response_channel.send(response).await.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
let notification_handlers = notification_handlers.clone();
|
let notification_handlers = notification_handlers.clone();
|
||||||
let response_handlers = response_handlers.clone();
|
let response_handlers = response_handlers.clone();
|
||||||
let io_handlers = io_handlers.clone();
|
let io_handlers = io_handlers.clone();
|
||||||
async move |cx| {
|
async move |cx| {
|
||||||
Self::handle_input(
|
Self::handle_incoming_messages(
|
||||||
stdout,
|
stdout,
|
||||||
on_unhandled_notification,
|
unhandled_notification_wrapper,
|
||||||
notification_handlers,
|
notification_handlers,
|
||||||
response_handlers,
|
response_handlers,
|
||||||
io_handlers,
|
io_handlers,
|
||||||
|
@ -433,7 +457,7 @@ impl LanguageServer {
|
||||||
stdout.or(stderr)
|
stdout.or(stderr)
|
||||||
});
|
});
|
||||||
let output_task = cx.background_spawn({
|
let output_task = cx.background_spawn({
|
||||||
Self::handle_output(
|
Self::handle_outgoing_messages(
|
||||||
stdin,
|
stdin,
|
||||||
outbound_rx,
|
outbound_rx,
|
||||||
output_done_tx,
|
output_done_tx,
|
||||||
|
@ -479,9 +503,9 @@ impl LanguageServer {
|
||||||
self.code_action_kinds.clone()
|
self.code_action_kinds.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_input<Stdout, F>(
|
async fn handle_incoming_messages<Stdout>(
|
||||||
stdout: Stdout,
|
stdout: Stdout,
|
||||||
mut on_unhandled_notification: F,
|
on_unhandled_notification: impl AsyncFn(NotificationOrRequest) + 'static + Send,
|
||||||
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
||||||
response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>,
|
response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>,
|
||||||
io_handlers: Arc<Mutex<HashMap<i32, IoHandler>>>,
|
io_handlers: Arc<Mutex<HashMap<i32, IoHandler>>>,
|
||||||
|
@ -489,7 +513,6 @@ impl LanguageServer {
|
||||||
) -> anyhow::Result<()>
|
) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
Stdout: AsyncRead + Unpin + Send + 'static,
|
Stdout: AsyncRead + Unpin + Send + 'static,
|
||||||
F: FnMut(AnyNotification) + 'static + Send,
|
|
||||||
{
|
{
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
let stdout = BufReader::new(stdout);
|
let stdout = BufReader::new(stdout);
|
||||||
|
@ -506,15 +529,19 @@ impl LanguageServer {
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
while let Some(msg) = input_handler.notifications_channel.next().await {
|
while let Some(msg) = input_handler.incoming_messages.next().await {
|
||||||
{
|
let unhandled_message = {
|
||||||
let mut notification_handlers = notification_handlers.lock();
|
let mut notification_handlers = notification_handlers.lock();
|
||||||
if let Some(handler) = notification_handlers.get_mut(msg.method.as_str()) {
|
if let Some(handler) = notification_handlers.get_mut(msg.method.as_str()) {
|
||||||
handler(msg.id, msg.params.unwrap_or(Value::Null), cx);
|
handler(msg.id, msg.params.unwrap_or(Value::Null), cx);
|
||||||
|
None
|
||||||
} else {
|
} else {
|
||||||
drop(notification_handlers);
|
Some(msg)
|
||||||
on_unhandled_notification(msg);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(msg) = unhandled_message {
|
||||||
|
on_unhandled_notification(msg).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't starve the main thread when receiving lots of notifications at once.
|
// Don't starve the main thread when receiving lots of notifications at once.
|
||||||
|
@ -558,7 +585,7 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_output<Stdin>(
|
async fn handle_outgoing_messages<Stdin>(
|
||||||
stdin: Stdin,
|
stdin: Stdin,
|
||||||
outbound_rx: channel::Receiver<String>,
|
outbound_rx: channel::Receiver<String>,
|
||||||
output_done_tx: barrier::Sender,
|
output_done_tx: barrier::Sender,
|
||||||
|
@ -1036,7 +1063,9 @@ impl LanguageServer {
|
||||||
jsonrpc: JSON_RPC_VERSION,
|
jsonrpc: JSON_RPC_VERSION,
|
||||||
id,
|
id,
|
||||||
value: LspResult::Error(Some(Error {
|
value: LspResult::Error(Some(Error {
|
||||||
|
code: lsp_types::error_codes::REQUEST_FAILED,
|
||||||
message: error.to_string(),
|
message: error.to_string(),
|
||||||
|
data: None,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1057,7 +1086,9 @@ impl LanguageServer {
|
||||||
id,
|
id,
|
||||||
result: None,
|
result: None,
|
||||||
error: Some(Error {
|
error: Some(Error {
|
||||||
|
code: -32700, // Parse error
|
||||||
message: error.to_string(),
|
message: error.to_string(),
|
||||||
|
data: None,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
if let Some(response) = serde_json::to_string(&response).log_err() {
|
if let Some(response) = serde_json::to_string(&response).log_err() {
|
||||||
|
@ -1559,7 +1590,7 @@ impl FakeLanguageServer {
|
||||||
root,
|
root,
|
||||||
Some(workspace_folders.clone()),
|
Some(workspace_folders.clone()),
|
||||||
cx,
|
cx,
|
||||||
|_| {},
|
|_| false,
|
||||||
);
|
);
|
||||||
server.process_name = process_name;
|
server.process_name = process_name;
|
||||||
let fake = FakeLanguageServer {
|
let fake = FakeLanguageServer {
|
||||||
|
@ -1582,9 +1613,10 @@ impl FakeLanguageServer {
|
||||||
notifications_tx
|
notifications_tx
|
||||||
.try_send((
|
.try_send((
|
||||||
msg.method.to_string(),
|
msg.method.to_string(),
|
||||||
msg.params.unwrap_or(Value::Null).to_string(),
|
msg.params.as_ref().unwrap_or(&Value::Null).to_string(),
|
||||||
))
|
))
|
||||||
.ok();
|
.ok();
|
||||||
|
true
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
server.process_name = name.as_str().into();
|
server.process_name = name.as_str().into();
|
||||||
|
@ -1862,7 +1894,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_deserialize_string_digit_id() {
|
fn test_deserialize_string_digit_id() {
|
||||||
let json = r#"{"jsonrpc":"2.0","id":"2","method":"workspace/configuration","params":{"items":[{"scopeUri":"file:///Users/mph/Devel/personal/hello-scala/","section":"metals"}]}}"#;
|
let json = r#"{"jsonrpc":"2.0","id":"2","method":"workspace/configuration","params":{"items":[{"scopeUri":"file:///Users/mph/Devel/personal/hello-scala/","section":"metals"}]}}"#;
|
||||||
let notification = serde_json::from_str::<AnyNotification>(json)
|
let notification = serde_json::from_str::<NotificationOrRequest>(json)
|
||||||
.expect("message with string id should be parsed");
|
.expect("message with string id should be parsed");
|
||||||
let expected_id = RequestId::Str("2".to_string());
|
let expected_id = RequestId::Str("2".to_string());
|
||||||
assert_eq!(notification.id, Some(expected_id));
|
assert_eq!(notification.id, Some(expected_id));
|
||||||
|
@ -1871,7 +1903,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_deserialize_string_id() {
|
fn test_deserialize_string_id() {
|
||||||
let json = r#"{"jsonrpc":"2.0","id":"anythingAtAll","method":"workspace/configuration","params":{"items":[{"scopeUri":"file:///Users/mph/Devel/personal/hello-scala/","section":"metals"}]}}"#;
|
let json = r#"{"jsonrpc":"2.0","id":"anythingAtAll","method":"workspace/configuration","params":{"items":[{"scopeUri":"file:///Users/mph/Devel/personal/hello-scala/","section":"metals"}]}}"#;
|
||||||
let notification = serde_json::from_str::<AnyNotification>(json)
|
let notification = serde_json::from_str::<NotificationOrRequest>(json)
|
||||||
.expect("message with string id should be parsed");
|
.expect("message with string id should be parsed");
|
||||||
let expected_id = RequestId::Str("anythingAtAll".to_string());
|
let expected_id = RequestId::Str("anythingAtAll".to_string());
|
||||||
assert_eq!(notification.id, Some(expected_id));
|
assert_eq!(notification.id, Some(expected_id));
|
||||||
|
@ -1880,7 +1912,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_deserialize_int_id() {
|
fn test_deserialize_int_id() {
|
||||||
let json = r#"{"jsonrpc":"2.0","id":2,"method":"workspace/configuration","params":{"items":[{"scopeUri":"file:///Users/mph/Devel/personal/hello-scala/","section":"metals"}]}}"#;
|
let json = r#"{"jsonrpc":"2.0","id":2,"method":"workspace/configuration","params":{"items":[{"scopeUri":"file:///Users/mph/Devel/personal/hello-scala/","section":"metals"}]}}"#;
|
||||||
let notification = serde_json::from_str::<AnyNotification>(json)
|
let notification = serde_json::from_str::<NotificationOrRequest>(json)
|
||||||
.expect("message with string id should be parsed");
|
.expect("message with string id should be parsed");
|
||||||
let expected_id = RequestId::Int(2);
|
let expected_id = RequestId::Int(2);
|
||||||
assert_eq!(notification.id, Some(expected_id));
|
assert_eq!(notification.id, Some(expected_id));
|
||||||
|
|
|
@ -29,6 +29,15 @@ pub struct NodeBinaryOptions {
|
||||||
pub use_paths: Option<(PathBuf, PathBuf)>,
|
pub use_paths: Option<(PathBuf, PathBuf)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum VersionCheck {
|
||||||
|
/// Check whether the installed and requested version have a mismatch
|
||||||
|
VersionMismatch,
|
||||||
|
/// Only check whether the currently installed version is older than the newest one
|
||||||
|
#[default]
|
||||||
|
OlderVersion,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct NodeRuntime(Arc<Mutex<NodeRuntimeState>>);
|
pub struct NodeRuntime(Arc<Mutex<NodeRuntimeState>>);
|
||||||
|
|
||||||
|
@ -287,6 +296,7 @@ impl NodeRuntime {
|
||||||
local_executable_path: &Path,
|
local_executable_path: &Path,
|
||||||
local_package_directory: &Path,
|
local_package_directory: &Path,
|
||||||
latest_version: &str,
|
latest_version: &str,
|
||||||
|
version_check: VersionCheck,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// In the case of the local system not having the package installed,
|
// In the case of the local system not having the package installed,
|
||||||
// or in the instances where we fail to parse package.json data,
|
// or in the instances where we fail to parse package.json data,
|
||||||
|
@ -311,7 +321,10 @@ impl NodeRuntime {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
installed_version < latest_version
|
match version_check {
|
||||||
|
VersionCheck::VersionMismatch => installed_version != latest_version,
|
||||||
|
VersionCheck::OlderVersion => installed_version < latest_version,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ anyhow.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
schemars = { workspace = true, optional = true }
|
schemars = { workspace = true, optional = true }
|
||||||
|
log.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
strum.workspace = true
|
strum.workspace = true
|
||||||
|
|
|
@ -74,6 +74,12 @@ pub enum Model {
|
||||||
O3,
|
O3,
|
||||||
#[serde(rename = "o4-mini")]
|
#[serde(rename = "o4-mini")]
|
||||||
O4Mini,
|
O4Mini,
|
||||||
|
#[serde(rename = "gpt-5")]
|
||||||
|
Five,
|
||||||
|
#[serde(rename = "gpt-5-mini")]
|
||||||
|
FiveMini,
|
||||||
|
#[serde(rename = "gpt-5-nano")]
|
||||||
|
FiveNano,
|
||||||
|
|
||||||
#[serde(rename = "custom")]
|
#[serde(rename = "custom")]
|
||||||
Custom {
|
Custom {
|
||||||
|
@ -83,11 +89,13 @@ pub enum Model {
|
||||||
max_tokens: u64,
|
max_tokens: u64,
|
||||||
max_output_tokens: Option<u64>,
|
max_output_tokens: Option<u64>,
|
||||||
max_completion_tokens: Option<u64>,
|
max_completion_tokens: Option<u64>,
|
||||||
|
reasoning_effort: Option<ReasoningEffort>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn default_fast() -> Self {
|
pub fn default_fast() -> Self {
|
||||||
|
// TODO: Replace with FiveMini since all other models are deprecated
|
||||||
Self::FourPointOneMini
|
Self::FourPointOneMini
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +113,9 @@ impl Model {
|
||||||
"o3-mini" => Ok(Self::O3Mini),
|
"o3-mini" => Ok(Self::O3Mini),
|
||||||
"o3" => Ok(Self::O3),
|
"o3" => Ok(Self::O3),
|
||||||
"o4-mini" => Ok(Self::O4Mini),
|
"o4-mini" => Ok(Self::O4Mini),
|
||||||
|
"gpt-5" => Ok(Self::Five),
|
||||||
|
"gpt-5-mini" => Ok(Self::FiveMini),
|
||||||
|
"gpt-5-nano" => Ok(Self::FiveNano),
|
||||||
invalid_id => anyhow::bail!("invalid model id '{invalid_id}'"),
|
invalid_id => anyhow::bail!("invalid model id '{invalid_id}'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,6 +134,9 @@ impl Model {
|
||||||
Self::O3Mini => "o3-mini",
|
Self::O3Mini => "o3-mini",
|
||||||
Self::O3 => "o3",
|
Self::O3 => "o3",
|
||||||
Self::O4Mini => "o4-mini",
|
Self::O4Mini => "o4-mini",
|
||||||
|
Self::Five => "gpt-5",
|
||||||
|
Self::FiveMini => "gpt-5-mini",
|
||||||
|
Self::FiveNano => "gpt-5-nano",
|
||||||
Self::Custom { name, .. } => name,
|
Self::Custom { name, .. } => name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,6 +155,9 @@ impl Model {
|
||||||
Self::O3Mini => "o3-mini",
|
Self::O3Mini => "o3-mini",
|
||||||
Self::O3 => "o3",
|
Self::O3 => "o3",
|
||||||
Self::O4Mini => "o4-mini",
|
Self::O4Mini => "o4-mini",
|
||||||
|
Self::Five => "gpt-5",
|
||||||
|
Self::FiveMini => "gpt-5-mini",
|
||||||
|
Self::FiveNano => "gpt-5-nano",
|
||||||
Self::Custom {
|
Self::Custom {
|
||||||
name, display_name, ..
|
name, display_name, ..
|
||||||
} => display_name.as_ref().unwrap_or(name),
|
} => display_name.as_ref().unwrap_or(name),
|
||||||
|
@ -161,6 +178,9 @@ impl Model {
|
||||||
Self::O3Mini => 200_000,
|
Self::O3Mini => 200_000,
|
||||||
Self::O3 => 200_000,
|
Self::O3 => 200_000,
|
||||||
Self::O4Mini => 200_000,
|
Self::O4Mini => 200_000,
|
||||||
|
Self::Five => 272_000,
|
||||||
|
Self::FiveMini => 272_000,
|
||||||
|
Self::FiveNano => 272_000,
|
||||||
Self::Custom { max_tokens, .. } => *max_tokens,
|
Self::Custom { max_tokens, .. } => *max_tokens,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,6 +202,18 @@ impl Model {
|
||||||
Self::O3Mini => Some(100_000),
|
Self::O3Mini => Some(100_000),
|
||||||
Self::O3 => Some(100_000),
|
Self::O3 => Some(100_000),
|
||||||
Self::O4Mini => Some(100_000),
|
Self::O4Mini => Some(100_000),
|
||||||
|
Self::Five => Some(128_000),
|
||||||
|
Self::FiveMini => Some(128_000),
|
||||||
|
Self::FiveNano => Some(128_000),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reasoning_effort(&self) -> Option<ReasoningEffort> {
|
||||||
|
match self {
|
||||||
|
Self::Custom {
|
||||||
|
reasoning_effort, ..
|
||||||
|
} => reasoning_effort.to_owned(),
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,10 +229,20 @@ impl Model {
|
||||||
| Self::FourOmniMini
|
| Self::FourOmniMini
|
||||||
| Self::FourPointOne
|
| Self::FourPointOne
|
||||||
| Self::FourPointOneMini
|
| Self::FourPointOneMini
|
||||||
| Self::FourPointOneNano => true,
|
| Self::FourPointOneNano
|
||||||
|
| Self::Five
|
||||||
|
| Self::FiveMini
|
||||||
|
| Self::FiveNano => true,
|
||||||
Self::O1 | Self::O3 | Self::O3Mini | Self::O4Mini | Model::Custom { .. } => false,
|
Self::O1 | Self::O3 | Self::O3Mini | Self::O4Mini | Model::Custom { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether the given model supports the `prompt_cache_key` parameter.
|
||||||
|
///
|
||||||
|
/// If the model does not support the parameter, do not pass it up.
|
||||||
|
pub fn supports_prompt_cache_key(&self) -> bool {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -220,6 +262,10 @@ pub struct Request {
|
||||||
pub parallel_tool_calls: Option<bool>,
|
pub parallel_tool_calls: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub tools: Vec<ToolDefinition>,
|
pub tools: Vec<ToolDefinition>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub prompt_cache_key: Option<String>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub reasoning_effort: Option<ReasoningEffort>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -231,6 +277,16 @@ pub enum ToolChoice {
|
||||||
Other(ToolDefinition),
|
Other(ToolDefinition),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum ReasoningEffort {
|
||||||
|
Minimal,
|
||||||
|
Low,
|
||||||
|
Medium,
|
||||||
|
High,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
pub enum ToolDefinition {
|
pub enum ToolDefinition {
|
||||||
|
@ -421,7 +477,15 @@ pub async fn stream_completion(
|
||||||
Ok(ResponseStreamResult::Err { error }) => {
|
Ok(ResponseStreamResult::Err { error }) => {
|
||||||
Some(Err(anyhow!(error)))
|
Some(Err(anyhow!(error)))
|
||||||
}
|
}
|
||||||
Err(error) => Some(Err(anyhow!(error))),
|
Err(error) => {
|
||||||
|
log::error!(
|
||||||
|
"Failed to parse OpenAI response into ResponseStreamResult: `{}`\n\
|
||||||
|
Response: `{}`",
|
||||||
|
error,
|
||||||
|
line,
|
||||||
|
);
|
||||||
|
Some(Err(anyhow!(error)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7722,12 +7722,19 @@ impl LspStore {
|
||||||
pub(crate) fn set_language_server_statuses_from_proto(
|
pub(crate) fn set_language_server_statuses_from_proto(
|
||||||
&mut self,
|
&mut self,
|
||||||
language_servers: Vec<proto::LanguageServer>,
|
language_servers: Vec<proto::LanguageServer>,
|
||||||
|
server_capabilities: Vec<String>,
|
||||||
) {
|
) {
|
||||||
self.language_server_statuses = language_servers
|
self.language_server_statuses = language_servers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|server| {
|
.zip(server_capabilities)
|
||||||
|
.map(|(server, server_capabilities)| {
|
||||||
|
let server_id = LanguageServerId(server.id as usize);
|
||||||
|
if let Ok(server_capabilities) = serde_json::from_str(&server_capabilities) {
|
||||||
|
self.lsp_server_capabilities
|
||||||
|
.insert(server_id, server_capabilities);
|
||||||
|
}
|
||||||
(
|
(
|
||||||
LanguageServerId(server.id as usize),
|
server_id,
|
||||||
LanguageServerStatus {
|
LanguageServerStatus {
|
||||||
name: LanguageServerName::from_proto(server.name),
|
name: LanguageServerName::from_proto(server.name),
|
||||||
pending_work: Default::default(),
|
pending_work: Default::default(),
|
||||||
|
|
|
@ -1488,7 +1488,10 @@ impl Project {
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
lsp_store.set_language_server_statuses_from_proto(response.payload.language_servers);
|
lsp_store.set_language_server_statuses_from_proto(
|
||||||
|
response.payload.language_servers,
|
||||||
|
response.payload.language_server_capabilities,
|
||||||
|
);
|
||||||
lsp_store
|
lsp_store
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -2319,7 +2322,10 @@ impl Project {
|
||||||
self.set_worktrees_from_proto(message.worktrees, cx)?;
|
self.set_worktrees_from_proto(message.worktrees, cx)?;
|
||||||
self.set_collaborators_from_proto(message.collaborators, cx)?;
|
self.set_collaborators_from_proto(message.collaborators, cx)?;
|
||||||
self.lsp_store.update(cx, |lsp_store, _| {
|
self.lsp_store.update(cx, |lsp_store, _| {
|
||||||
lsp_store.set_language_server_statuses_from_proto(message.language_servers)
|
lsp_store.set_language_server_statuses_from_proto(
|
||||||
|
message.language_servers,
|
||||||
|
message.language_server_capabilities,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
self.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
|
self.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -84,11 +84,13 @@ message GetCrashFiles {
|
||||||
|
|
||||||
message GetCrashFilesResponse {
|
message GetCrashFilesResponse {
|
||||||
repeated CrashReport crashes = 1;
|
repeated CrashReport crashes = 1;
|
||||||
|
repeated string legacy_panics = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CrashReport {
|
message CrashReport {
|
||||||
optional string panic_contents = 1;
|
reserved 1, 2;
|
||||||
optional bytes minidump_contents = 2;
|
string metadata = 3;
|
||||||
|
bytes minidump_contents = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Extension {
|
message Extension {
|
||||||
|
|
|
@ -1484,20 +1484,17 @@ impl RemoteConnection for SshRemoteConnection {
|
||||||
identifier = &unique_identifier,
|
identifier = &unique_identifier,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(rust_log) = std::env::var("RUST_LOG").ok() {
|
for env_var in ["RUST_LOG", "RUST_BACKTRACE", "ZED_GENERATE_MINIDUMPS"] {
|
||||||
start_proxy_command = format!(
|
if let Some(value) = std::env::var(env_var).ok() {
|
||||||
"RUST_LOG={} {}",
|
start_proxy_command = format!(
|
||||||
shlex::try_quote(&rust_log).unwrap(),
|
"{}={} {} ",
|
||||||
start_proxy_command
|
env_var,
|
||||||
)
|
shlex::try_quote(&value).unwrap(),
|
||||||
}
|
start_proxy_command,
|
||||||
if let Some(rust_backtrace) = std::env::var("RUST_BACKTRACE").ok() {
|
);
|
||||||
start_proxy_command = format!(
|
}
|
||||||
"RUST_BACKTRACE={} {}",
|
|
||||||
shlex::try_quote(&rust_backtrace).unwrap(),
|
|
||||||
start_proxy_command
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if reconnect {
|
if reconnect {
|
||||||
start_proxy_command.push_str(" --reconnect");
|
start_proxy_command.push_str(" --reconnect");
|
||||||
}
|
}
|
||||||
|
@ -2229,8 +2226,7 @@ impl SshRemoteConnection {
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
run_cmd(Command::new("gzip").args(["-9", "-f", &bin_path.to_string_lossy()]))
|
run_cmd(Command::new("gzip").args(["-f", &bin_path.to_string_lossy()])).await?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
|
@ -2462,7 +2458,7 @@ impl ChannelClient {
|
||||||
},
|
},
|
||||||
async {
|
async {
|
||||||
smol::Timer::after(timeout).await;
|
smol::Timer::after(timeout).await;
|
||||||
anyhow::bail!("Timeout detected")
|
anyhow::bail!("Timed out resyncing remote client")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -2476,7 +2472,7 @@ impl ChannelClient {
|
||||||
},
|
},
|
||||||
async {
|
async {
|
||||||
smol::Timer::after(timeout).await;
|
smol::Timer::after(timeout).await;
|
||||||
anyhow::bail!("Timeout detected")
|
anyhow::bail!("Timed out pinging remote client")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -34,10 +34,10 @@ use smol::io::AsyncReadExt;
|
||||||
|
|
||||||
use smol::Async;
|
use smol::Async;
|
||||||
use smol::{net::unix::UnixListener, stream::StreamExt as _};
|
use smol::{net::unix::UnixListener, stream::StreamExt as _};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::sync::LazyLock;
|
||||||
use std::{env, thread};
|
use std::{env, thread};
|
||||||
use std::{
|
use std::{
|
||||||
io::Write,
|
io::Write,
|
||||||
|
@ -48,6 +48,13 @@ use std::{
|
||||||
use telemetry_events::LocationData;
|
use telemetry_events::LocationData;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
|
pub static VERSION: LazyLock<&str> = LazyLock::new(|| match *RELEASE_CHANNEL {
|
||||||
|
ReleaseChannel::Stable | ReleaseChannel::Preview => env!("ZED_PKG_VERSION"),
|
||||||
|
ReleaseChannel::Nightly | ReleaseChannel::Dev => {
|
||||||
|
option_env!("ZED_COMMIT_SHA").unwrap_or("missing-zed-commit-sha")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
fn init_logging_proxy() {
|
fn init_logging_proxy() {
|
||||||
env_logger::builder()
|
env_logger::builder()
|
||||||
.format(|buf, record| {
|
.format(|buf, record| {
|
||||||
|
@ -113,7 +120,6 @@ fn init_logging_server(log_file_path: PathBuf) -> Result<Receiver<Vec<u8>>> {
|
||||||
|
|
||||||
fn init_panic_hook(session_id: String) {
|
fn init_panic_hook(session_id: String) {
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
crashes::handle_panic();
|
|
||||||
let payload = info
|
let payload = info
|
||||||
.payload()
|
.payload()
|
||||||
.downcast_ref::<&str>()
|
.downcast_ref::<&str>()
|
||||||
|
@ -121,6 +127,8 @@ fn init_panic_hook(session_id: String) {
|
||||||
.or_else(|| info.payload().downcast_ref::<String>().cloned())
|
.or_else(|| info.payload().downcast_ref::<String>().cloned())
|
||||||
.unwrap_or_else(|| "Box<Any>".to_string());
|
.unwrap_or_else(|| "Box<Any>".to_string());
|
||||||
|
|
||||||
|
crashes::handle_panic(payload.clone(), info.location());
|
||||||
|
|
||||||
let backtrace = backtrace::Backtrace::new();
|
let backtrace = backtrace::Backtrace::new();
|
||||||
let mut backtrace = backtrace
|
let mut backtrace = backtrace
|
||||||
.frames()
|
.frames()
|
||||||
|
@ -150,14 +158,6 @@ fn init_panic_hook(session_id: String) {
|
||||||
(&backtrace).join("\n")
|
(&backtrace).join("\n")
|
||||||
);
|
);
|
||||||
|
|
||||||
let release_channel = *RELEASE_CHANNEL;
|
|
||||||
let version = match release_channel {
|
|
||||||
ReleaseChannel::Stable | ReleaseChannel::Preview => env!("ZED_PKG_VERSION"),
|
|
||||||
ReleaseChannel::Nightly | ReleaseChannel::Dev => {
|
|
||||||
option_env!("ZED_COMMIT_SHA").unwrap_or("missing-zed-commit-sha")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let panic_data = telemetry_events::Panic {
|
let panic_data = telemetry_events::Panic {
|
||||||
thread: thread_name.into(),
|
thread: thread_name.into(),
|
||||||
payload: payload.clone(),
|
payload: payload.clone(),
|
||||||
|
@ -165,9 +165,9 @@ fn init_panic_hook(session_id: String) {
|
||||||
file: location.file().into(),
|
file: location.file().into(),
|
||||||
line: location.line(),
|
line: location.line(),
|
||||||
}),
|
}),
|
||||||
app_version: format!("remote-server-{version}"),
|
app_version: format!("remote-server-{}", *VERSION),
|
||||||
app_commit_sha: option_env!("ZED_COMMIT_SHA").map(|sha| sha.into()),
|
app_commit_sha: option_env!("ZED_COMMIT_SHA").map(|sha| sha.into()),
|
||||||
release_channel: release_channel.dev_name().into(),
|
release_channel: RELEASE_CHANNEL.dev_name().into(),
|
||||||
target: env!("TARGET").to_owned().into(),
|
target: env!("TARGET").to_owned().into(),
|
||||||
os_name: telemetry::os_name(),
|
os_name: telemetry::os_name(),
|
||||||
os_version: Some(telemetry::os_version()),
|
os_version: Some(telemetry::os_version()),
|
||||||
|
@ -204,8 +204,8 @@ fn handle_crash_files_requests(project: &Entity<HeadlessProject>, client: &Arc<C
|
||||||
client.add_request_handler(
|
client.add_request_handler(
|
||||||
project.downgrade(),
|
project.downgrade(),
|
||||||
|_, _: TypedEnvelope<proto::GetCrashFiles>, _cx| async move {
|
|_, _: TypedEnvelope<proto::GetCrashFiles>, _cx| async move {
|
||||||
|
let mut legacy_panics = Vec::new();
|
||||||
let mut crashes = Vec::new();
|
let mut crashes = Vec::new();
|
||||||
let mut minidumps_by_session_id = HashMap::new();
|
|
||||||
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
|
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
|
||||||
while let Some(child) = children.next().await {
|
while let Some(child) = children.next().await {
|
||||||
let child = child?;
|
let child = child?;
|
||||||
|
@ -227,41 +227,31 @@ fn handle_crash_files_requests(project: &Entity<HeadlessProject>, client: &Arc<C
|
||||||
.await
|
.await
|
||||||
.context("error reading panic file")?;
|
.context("error reading panic file")?;
|
||||||
|
|
||||||
crashes.push(proto::CrashReport {
|
legacy_panics.push(file_contents);
|
||||||
panic_contents: Some(file_contents),
|
smol::fs::remove_file(&child_path)
|
||||||
minidump_contents: None,
|
.await
|
||||||
});
|
.context("error removing panic")
|
||||||
|
.log_err();
|
||||||
} else if extension == Some(OsStr::new("dmp")) {
|
} else if extension == Some(OsStr::new("dmp")) {
|
||||||
let session_id = child_path.file_stem().unwrap().to_string_lossy();
|
let mut json_path = child_path.clone();
|
||||||
minidumps_by_session_id
|
json_path.set_extension("json");
|
||||||
.insert(session_id.to_string(), smol::fs::read(&child_path).await?);
|
if let Ok(json_content) = smol::fs::read_to_string(&json_path).await {
|
||||||
}
|
crashes.push(CrashReport {
|
||||||
|
metadata: json_content,
|
||||||
// We've done what we can, delete the file
|
minidump_contents: smol::fs::read(&child_path).await?,
|
||||||
smol::fs::remove_file(&child_path)
|
});
|
||||||
.await
|
smol::fs::remove_file(&child_path).await.log_err();
|
||||||
.context("error removing panic")
|
smol::fs::remove_file(&json_path).await.log_err();
|
||||||
.log_err();
|
} else {
|
||||||
}
|
log::error!("Couldn't find json metadata for crash: {child_path:?}");
|
||||||
|
}
|
||||||
for crash in &mut crashes {
|
|
||||||
let panic: telemetry_events::Panic =
|
|
||||||
serde_json::from_str(crash.panic_contents.as_ref().unwrap())?;
|
|
||||||
if let dump @ Some(_) = minidumps_by_session_id.remove(&panic.session_id) {
|
|
||||||
crash.minidump_contents = dump;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
crashes.extend(
|
anyhow::Ok(proto::GetCrashFilesResponse {
|
||||||
minidumps_by_session_id
|
crashes,
|
||||||
.into_values()
|
legacy_panics,
|
||||||
.map(|dmp| CrashReport {
|
})
|
||||||
panic_contents: None,
|
|
||||||
minidump_contents: Some(dmp),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
anyhow::Ok(proto::GetCrashFilesResponse { crashes })
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -442,7 +432,12 @@ pub fn execute_run(
|
||||||
let app = gpui::Application::headless();
|
let app = gpui::Application::headless();
|
||||||
let id = std::process::id().to_string();
|
let id = std::process::id().to_string();
|
||||||
app.background_executor()
|
app.background_executor()
|
||||||
.spawn(crashes::init(id.clone()))
|
.spawn(crashes::init(crashes::InitCrashHandler {
|
||||||
|
session_id: id.clone(),
|
||||||
|
zed_version: VERSION.to_owned(),
|
||||||
|
release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(),
|
||||||
|
commit_sha: option_env!("ZED_COMMIT_SHA").unwrap_or("no_sha").to_owned(),
|
||||||
|
}))
|
||||||
.detach();
|
.detach();
|
||||||
init_panic_hook(id);
|
init_panic_hook(id);
|
||||||
let log_rx = init_logging_server(log_file)?;
|
let log_rx = init_logging_server(log_file)?;
|
||||||
|
@ -569,7 +564,13 @@ pub fn execute_proxy(identifier: String, is_reconnecting: bool) -> Result<()> {
|
||||||
let server_paths = ServerPaths::new(&identifier)?;
|
let server_paths = ServerPaths::new(&identifier)?;
|
||||||
|
|
||||||
let id = std::process::id().to_string();
|
let id = std::process::id().to_string();
|
||||||
smol::spawn(crashes::init(id.clone())).detach();
|
smol::spawn(crashes::init(crashes::InitCrashHandler {
|
||||||
|
session_id: id.clone(),
|
||||||
|
zed_version: VERSION.to_owned(),
|
||||||
|
release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(),
|
||||||
|
commit_sha: option_env!("ZED_COMMIT_SHA").unwrap_or("no_sha").to_owned(),
|
||||||
|
}))
|
||||||
|
.detach();
|
||||||
init_panic_hook(id);
|
init_panic_hook(id);
|
||||||
|
|
||||||
log::info!("starting proxy process. PID: {}", std::process::id());
|
log::info!("starting proxy process. PID: {}", std::process::id());
|
||||||
|
|
|
@ -928,14 +928,14 @@ impl<'a> KeybindUpdateTarget<'a> {
|
||||||
}
|
}
|
||||||
let action_name: Value = self.action_name.into();
|
let action_name: Value = self.action_name.into();
|
||||||
let value = match self.action_arguments {
|
let value = match self.action_arguments {
|
||||||
Some(args) => {
|
Some(args) if !args.is_empty() => {
|
||||||
let args = serde_json::from_str::<Value>(args)
|
let args = serde_json::from_str::<Value>(args)
|
||||||
.context("Failed to parse action arguments as JSON")?;
|
.context("Failed to parse action arguments as JSON")?;
|
||||||
serde_json::json!([action_name, args])
|
serde_json::json!([action_name, args])
|
||||||
}
|
}
|
||||||
None => action_name,
|
_ => action_name,
|
||||||
};
|
};
|
||||||
return Ok(value);
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keystrokes_unparsed(&self) -> String {
|
fn keystrokes_unparsed(&self) -> String {
|
||||||
|
@ -1084,6 +1084,24 @@ mod tests {
|
||||||
.unindent(),
|
.unindent(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
check_keymap_update(
|
||||||
|
"[]",
|
||||||
|
KeybindUpdateOperation::add(KeybindUpdateTarget {
|
||||||
|
keystrokes: &parse_keystrokes("ctrl-a"),
|
||||||
|
action_name: "zed::SomeAction",
|
||||||
|
context: None,
|
||||||
|
action_arguments: Some(""),
|
||||||
|
}),
|
||||||
|
r#"[
|
||||||
|
{
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-a": "zed::SomeAction"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]"#
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
check_keymap_update(
|
check_keymap_update(
|
||||||
r#"[
|
r#"[
|
||||||
{
|
{
|
||||||
|
|
|
@ -2148,7 +2148,8 @@ impl KeybindingEditorModal {
|
||||||
let action_arguments = self
|
let action_arguments = self
|
||||||
.action_arguments_editor
|
.action_arguments_editor
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|editor| editor.read(cx).editor.read(cx).text(cx));
|
.map(|arguments_editor| arguments_editor.read(cx).editor.read(cx).text(cx))
|
||||||
|
.filter(|args| !args.is_empty());
|
||||||
|
|
||||||
let value = action_arguments
|
let value = action_arguments
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -2259,29 +2260,11 @@ impl KeybindingEditorModal {
|
||||||
|
|
||||||
let create = self.creating;
|
let create = self.creating;
|
||||||
|
|
||||||
let status_toast = StatusToast::new(
|
|
||||||
format!(
|
|
||||||
"Saved edits to the {} action.",
|
|
||||||
&self.editing_keybind.action().humanized_name
|
|
||||||
),
|
|
||||||
cx,
|
|
||||||
move |this, _cx| {
|
|
||||||
this.icon(ToastIcon::new(IconName::Check).color(Color::Success))
|
|
||||||
.dismiss_button(true)
|
|
||||||
// .action("Undo", f) todo: wire the undo functionality
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
self.workspace
|
|
||||||
.update(cx, |workspace, cx| {
|
|
||||||
workspace.toggle_status_toast(status_toast, cx);
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let action_name = existing_keybind.action().name;
|
let action_name = existing_keybind.action().name;
|
||||||
|
let humanized_action_name = existing_keybind.action().humanized_name.clone();
|
||||||
|
|
||||||
if let Err(err) = save_keybinding_update(
|
match save_keybinding_update(
|
||||||
create,
|
create,
|
||||||
existing_keybind,
|
existing_keybind,
|
||||||
&action_mapping,
|
&action_mapping,
|
||||||
|
@ -2291,25 +2274,43 @@ impl KeybindingEditorModal {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
this.update(cx, |this, cx| {
|
Ok(_) => {
|
||||||
this.set_error(InputError::error(err), cx);
|
this.update(cx, |this, cx| {
|
||||||
})
|
this.keymap_editor.update(cx, |keymap, cx| {
|
||||||
.log_err();
|
keymap.previous_edit = Some(PreviousEdit::Keybinding {
|
||||||
} else {
|
action_mapping,
|
||||||
this.update(cx, |this, cx| {
|
action_name,
|
||||||
this.keymap_editor.update(cx, |keymap, cx| {
|
fallback: keymap
|
||||||
keymap.previous_edit = Some(PreviousEdit::Keybinding {
|
.table_interaction_state
|
||||||
action_mapping,
|
.read(cx)
|
||||||
action_name,
|
.get_scrollbar_offset(Axis::Vertical),
|
||||||
fallback: keymap
|
});
|
||||||
.table_interaction_state
|
let status_toast = StatusToast::new(
|
||||||
.read(cx)
|
format!("Saved edits to the {} action.", humanized_action_name),
|
||||||
.get_scrollbar_offset(Axis::Vertical),
|
cx,
|
||||||
})
|
move |this, _cx| {
|
||||||
});
|
this.icon(ToastIcon::new(IconName::Check).color(Color::Success))
|
||||||
cx.emit(DismissEvent);
|
.dismiss_button(true)
|
||||||
})
|
// .action("Undo", f) todo: wire the undo functionality
|
||||||
.ok();
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
workspace.toggle_status_toast(status_toast, cx);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
});
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_error(InputError::error(err), cx);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
@ -2906,7 +2907,7 @@ async fn save_keybinding_update(
|
||||||
|
|
||||||
let updated_keymap_contents =
|
let updated_keymap_contents =
|
||||||
settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
|
settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
|
||||||
.context("Failed to update keybinding")?;
|
.map_err(|err| anyhow::anyhow!("Could not save updated keybinding: {}", err))?;
|
||||||
fs.write(
|
fs.write(
|
||||||
paths::keymap_file().as_path(),
|
paths::keymap_file().as_path(),
|
||||||
updated_keymap_contents.as_bytes(),
|
updated_keymap_contents.as_bytes(),
|
||||||
|
|
|
@ -31,7 +31,7 @@ pub enum AnimationDirection {
|
||||||
FromTop,
|
FromTop,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DefaultAnimations: Styled + Sized {
|
pub trait DefaultAnimations: Styled + Sized + Element {
|
||||||
fn animate_in(
|
fn animate_in(
|
||||||
self,
|
self,
|
||||||
animation_type: AnimationDirection,
|
animation_type: AnimationDirection,
|
||||||
|
@ -44,8 +44,13 @@ pub trait DefaultAnimations: Styled + Sized {
|
||||||
AnimationDirection::FromTop => "animate_from_top",
|
AnimationDirection::FromTop => "animate_from_top",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let animation_id = self.id().map_or_else(
|
||||||
|
|| ElementId::from(animation_name),
|
||||||
|
|id| (id, animation_name).into(),
|
||||||
|
);
|
||||||
|
|
||||||
self.with_animation(
|
self.with_animation(
|
||||||
animation_name,
|
animation_id,
|
||||||
gpui::Animation::new(AnimationDuration::Fast.into()).with_easing(ease_out_quint()),
|
gpui::Animation::new(AnimationDuration::Fast.into()).with_easing(ease_out_quint()),
|
||||||
move |mut this, delta| {
|
move |mut this, delta| {
|
||||||
let start_opacity = 0.4;
|
let start_opacity = 0.4;
|
||||||
|
@ -91,7 +96,7 @@ pub trait DefaultAnimations: Styled + Sized {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Styled> DefaultAnimations for E {}
|
impl<E: Styled + Element> DefaultAnimations for E {}
|
||||||
|
|
||||||
// Don't use this directly, it only exists to show animation previews
|
// Don't use this directly, it only exists to show animation previews
|
||||||
#[derive(RegisterComponent)]
|
#[derive(RegisterComponent)]
|
||||||
|
@ -132,7 +137,7 @@ impl Component for Animation {
|
||||||
.left(px(offset))
|
.left(px(offset))
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.bg(gpui::red())
|
.bg(gpui::red())
|
||||||
.animate_in(AnimationDirection::FromBottom, false),
|
.animate_in_from_bottom(false),
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
|
@ -151,7 +156,7 @@ impl Component for Animation {
|
||||||
.left(px(offset))
|
.left(px(offset))
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.bg(gpui::blue())
|
.bg(gpui::blue())
|
||||||
.animate_in(AnimationDirection::FromTop, false),
|
.animate_in_from_top(false),
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
|
@ -170,7 +175,7 @@ impl Component for Animation {
|
||||||
.top(px(offset))
|
.top(px(offset))
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.bg(gpui::green())
|
.bg(gpui::green())
|
||||||
.animate_in(AnimationDirection::FromLeft, false),
|
.animate_in_from_left(false),
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
|
@ -189,7 +194,7 @@ impl Component for Animation {
|
||||||
.top(px(offset))
|
.top(px(offset))
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.bg(gpui::yellow())
|
.bg(gpui::yellow())
|
||||||
.animate_in(AnimationDirection::FromRight, false),
|
.animate_in_from_right(false),
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
|
@ -214,7 +219,7 @@ impl Component for Animation {
|
||||||
.left(px(offset))
|
.left(px(offset))
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.bg(gpui::red())
|
.bg(gpui::red())
|
||||||
.animate_in(AnimationDirection::FromBottom, true),
|
.animate_in_from_bottom(true),
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
|
@ -233,7 +238,7 @@ impl Component for Animation {
|
||||||
.left(px(offset))
|
.left(px(offset))
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.bg(gpui::blue())
|
.bg(gpui::blue())
|
||||||
.animate_in(AnimationDirection::FromTop, true),
|
.animate_in_from_top(true),
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
|
@ -252,7 +257,7 @@ impl Component for Animation {
|
||||||
.top(px(offset))
|
.top(px(offset))
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.bg(gpui::green())
|
.bg(gpui::green())
|
||||||
.animate_in(AnimationDirection::FromLeft, true),
|
.animate_in_from_left(true),
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
|
@ -271,7 +276,7 @@ impl Component for Animation {
|
||||||
.top(px(offset))
|
.top(px(offset))
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.bg(gpui::yellow())
|
.bg(gpui::yellow())
|
||||||
.animate_in(AnimationDirection::FromRight, true),
|
.animate_in_from_right(true),
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
|
|
|
@ -71,4 +71,8 @@ impl Model {
|
||||||
Model::Custom { .. } => false,
|
Model::Custom { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn supports_prompt_cache_key(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use gpui::{AnyView, DismissEvent, Entity, FocusHandle, ManagedView, Subscription, Task};
|
use gpui::{AnyView, DismissEvent, Entity, EntityId, FocusHandle, ManagedView, Subscription, Task};
|
||||||
use ui::{animation::DefaultAnimations, prelude::*};
|
use ui::{animation::DefaultAnimations, prelude::*};
|
||||||
use zed_actions::toast;
|
use zed_actions::toast;
|
||||||
|
|
||||||
|
@ -76,6 +76,7 @@ impl<V: ToastView> ToastViewHandle for Entity<V> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ActiveToast {
|
pub struct ActiveToast {
|
||||||
|
id: EntityId,
|
||||||
toast: Box<dyn ToastViewHandle>,
|
toast: Box<dyn ToastViewHandle>,
|
||||||
action: Option<ToastAction>,
|
action: Option<ToastAction>,
|
||||||
_subscriptions: [Subscription; 1],
|
_subscriptions: [Subscription; 1],
|
||||||
|
@ -113,9 +114,9 @@ impl ToastLayer {
|
||||||
V: ToastView,
|
V: ToastView,
|
||||||
{
|
{
|
||||||
if let Some(active_toast) = &self.active_toast {
|
if let Some(active_toast) = &self.active_toast {
|
||||||
let is_close = active_toast.toast.view().downcast::<V>().is_ok();
|
let show_new = active_toast.id != new_toast.entity_id();
|
||||||
let did_close = self.hide_toast(cx);
|
self.hide_toast(cx);
|
||||||
if is_close || !did_close {
|
if !show_new {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,11 +131,12 @@ impl ToastLayer {
|
||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
|
|
||||||
self.active_toast = Some(ActiveToast {
|
self.active_toast = Some(ActiveToast {
|
||||||
toast: Box::new(new_toast.clone()),
|
|
||||||
action,
|
|
||||||
_subscriptions: [cx.subscribe(&new_toast, |this, _, _: &DismissEvent, cx| {
|
_subscriptions: [cx.subscribe(&new_toast, |this, _, _: &DismissEvent, cx| {
|
||||||
this.hide_toast(cx);
|
this.hide_toast(cx);
|
||||||
})],
|
})],
|
||||||
|
id: new_toast.entity_id(),
|
||||||
|
toast: Box::new(new_toast),
|
||||||
|
action,
|
||||||
focus_handle,
|
focus_handle,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -143,11 +145,9 @@ impl ToastLayer {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hide_toast(&mut self, cx: &mut Context<Self>) -> bool {
|
pub fn hide_toast(&mut self, cx: &mut Context<Self>) {
|
||||||
self.active_toast.take();
|
self.active_toast.take();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_toast<V>(&self) -> Option<Entity<V>>
|
pub fn active_toast<V>(&self) -> Option<Entity<V>>
|
||||||
|
@ -218,11 +218,10 @@ impl Render for ToastLayer {
|
||||||
let Some(active_toast) = &self.active_toast else {
|
let Some(active_toast) = &self.active_toast else {
|
||||||
return div();
|
return div();
|
||||||
};
|
};
|
||||||
let handle = cx.weak_entity();
|
|
||||||
|
|
||||||
div().absolute().size_full().bottom_0().left_0().child(
|
div().absolute().size_full().bottom_0().left_0().child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("toast-layer-container")
|
.id(("toast-layer-container", active_toast.id))
|
||||||
.absolute()
|
.absolute()
|
||||||
.w_full()
|
.w_full()
|
||||||
.bottom(px(0.))
|
.bottom(px(0.))
|
||||||
|
@ -234,17 +233,14 @@ impl Render for ToastLayer {
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("active-toast-container")
|
.id("active-toast-container")
|
||||||
.occlude()
|
.occlude()
|
||||||
.on_hover(move |hover_start, _window, cx| {
|
.on_hover(cx.listener(|this, hover_start, _window, cx| {
|
||||||
let Some(this) = handle.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if *hover_start {
|
if *hover_start {
|
||||||
this.update(cx, |this, _| this.pause_dismiss_timer());
|
this.pause_dismiss_timer();
|
||||||
} else {
|
} else {
|
||||||
this.update(cx, |this, cx| this.restart_dismiss_timer(cx));
|
this.restart_dismiss_timer(cx);
|
||||||
}
|
}
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
})
|
}))
|
||||||
.on_click(|_, _, cx| {
|
.on_click(|_, _, cx| {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
})
|
})
|
||||||
|
|
|
@ -105,6 +105,10 @@ impl Model {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn supports_prompt_cache_key(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
pub fn supports_tool(&self) -> bool {
|
pub fn supports_tool(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Grok2Vision
|
Self::Grok2Vision
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
description = "The fast, collaborative code editor."
|
description = "The fast, collaborative code editor."
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.199.0"
|
version = "0.199.10"
|
||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
authors = ["Zed Team <hi@zed.dev>"]
|
authors = ["Zed Team <hi@zed.dev>"]
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
dev
|
stable
|
|
@ -8,6 +8,7 @@ use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
||||||
use client::{Client, ProxySettings, UserStore, parse_zed_link};
|
use client::{Client, ProxySettings, UserStore, parse_zed_link};
|
||||||
use collab_ui::channel_view::ChannelView;
|
use collab_ui::channel_view::ChannelView;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
use crashes::InitCrashHandler;
|
||||||
use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
|
use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use extension::ExtensionHostProxy;
|
use extension::ExtensionHostProxy;
|
||||||
|
@ -271,7 +272,15 @@ pub fn main() {
|
||||||
let session = app.background_executor().block(Session::new());
|
let session = app.background_executor().block(Session::new());
|
||||||
|
|
||||||
app.background_executor()
|
app.background_executor()
|
||||||
.spawn(crashes::init(session_id.clone()))
|
.spawn(crashes::init(InitCrashHandler {
|
||||||
|
session_id: session_id.clone(),
|
||||||
|
zed_version: app_version.to_string(),
|
||||||
|
release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(),
|
||||||
|
commit_sha: app_commit_sha
|
||||||
|
.as_ref()
|
||||||
|
.map(|sha| sha.full())
|
||||||
|
.unwrap_or_else(|| "no sha".to_owned()),
|
||||||
|
}))
|
||||||
.detach();
|
.detach();
|
||||||
reliability::init_panic_hook(
|
reliability::init_panic_hook(
|
||||||
app_version,
|
app_version,
|
||||||
|
|
|
@ -12,6 +12,7 @@ use gpui::{App, AppContext as _, SemanticVersion};
|
||||||
use http_client::{self, HttpClient, HttpClientWithUrl, HttpRequestExt, Method};
|
use http_client::{self, HttpClient, HttpClientWithUrl, HttpRequestExt, Method};
|
||||||
use paths::{crashes_dir, crashes_retired_dir};
|
use paths::{crashes_dir, crashes_retired_dir};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
use proto::{CrashReport, GetCrashFilesResponse};
|
||||||
use release_channel::{AppCommitSha, RELEASE_CHANNEL, ReleaseChannel};
|
use release_channel::{AppCommitSha, RELEASE_CHANNEL, ReleaseChannel};
|
||||||
use reqwest::multipart::{Form, Part};
|
use reqwest::multipart::{Form, Part};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
@ -51,10 +52,6 @@ pub fn init_panic_hook(
|
||||||
thread::yield_now();
|
thread::yield_now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crashes::handle_panic();
|
|
||||||
|
|
||||||
let thread = thread::current();
|
|
||||||
let thread_name = thread.name().unwrap_or("<unnamed>");
|
|
||||||
|
|
||||||
let payload = info
|
let payload = info
|
||||||
.payload()
|
.payload()
|
||||||
|
@ -63,6 +60,11 @@ pub fn init_panic_hook(
|
||||||
.or_else(|| info.payload().downcast_ref::<String>().cloned())
|
.or_else(|| info.payload().downcast_ref::<String>().cloned())
|
||||||
.unwrap_or_else(|| "Box<Any>".to_string());
|
.unwrap_or_else(|| "Box<Any>".to_string());
|
||||||
|
|
||||||
|
crashes::handle_panic(payload.clone(), info.location());
|
||||||
|
|
||||||
|
let thread = thread::current();
|
||||||
|
let thread_name = thread.name().unwrap_or("<unnamed>");
|
||||||
|
|
||||||
if *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
|
if *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
|
||||||
let location = info.location().unwrap();
|
let location = info.location().unwrap();
|
||||||
let backtrace = Backtrace::new();
|
let backtrace = Backtrace::new();
|
||||||
|
@ -214,45 +216,53 @@ pub fn init(
|
||||||
let installation_id = installation_id.clone();
|
let installation_id = installation_id.clone();
|
||||||
let system_id = system_id.clone();
|
let system_id = system_id.clone();
|
||||||
|
|
||||||
if let Some(ssh_client) = project.ssh_client() {
|
let Some(ssh_client) = project.ssh_client() else {
|
||||||
ssh_client.update(cx, |client, cx| {
|
return;
|
||||||
if TelemetrySettings::get_global(cx).diagnostics {
|
};
|
||||||
let request = client.proto_client().request(proto::GetCrashFiles {});
|
ssh_client.update(cx, |client, cx| {
|
||||||
cx.background_spawn(async move {
|
if !TelemetrySettings::get_global(cx).diagnostics {
|
||||||
let crash_files = request.await?;
|
return;
|
||||||
for crash in crash_files.crashes {
|
}
|
||||||
let mut panic: Option<Panic> = crash
|
let request = client.proto_client().request(proto::GetCrashFiles {});
|
||||||
.panic_contents
|
cx.background_spawn(async move {
|
||||||
.and_then(|s| serde_json::from_str(&s).log_err());
|
let GetCrashFilesResponse {
|
||||||
|
legacy_panics,
|
||||||
|
crashes,
|
||||||
|
} = request.await?;
|
||||||
|
|
||||||
if let Some(panic) = panic.as_mut() {
|
for panic in legacy_panics {
|
||||||
panic.session_id = session_id.clone();
|
if let Some(mut panic) = serde_json::from_str::<Panic>(&panic).log_err() {
|
||||||
panic.system_id = system_id.clone();
|
panic.session_id = session_id.clone();
|
||||||
panic.installation_id = installation_id.clone();
|
panic.system_id = system_id.clone();
|
||||||
}
|
panic.installation_id = installation_id.clone();
|
||||||
|
upload_panic(&http_client, &panic_report_url, panic, &mut None).await?;
|
||||||
if let Some(minidump) = crash.minidump_contents {
|
}
|
||||||
upload_minidump(
|
|
||||||
http_client.clone(),
|
|
||||||
minidump.clone(),
|
|
||||||
panic.as_ref(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(panic) = panic {
|
|
||||||
upload_panic(&http_client, &panic_report_url, panic, &mut None)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Some(endpoint) = MINIDUMP_ENDPOINT.as_ref() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
for CrashReport {
|
||||||
|
metadata,
|
||||||
|
minidump_contents,
|
||||||
|
} in crashes
|
||||||
|
{
|
||||||
|
if let Some(metadata) = serde_json::from_str(&metadata).log_err() {
|
||||||
|
upload_minidump(
|
||||||
|
http_client.clone(),
|
||||||
|
endpoint,
|
||||||
|
minidump_contents,
|
||||||
|
&metadata,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
}
|
.detach_and_log_err(cx);
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
@ -466,16 +476,18 @@ fn upload_panics_and_crashes(
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) {
|
) {
|
||||||
let telemetry_settings = *client::TelemetrySettings::get_global(cx);
|
if !client::TelemetrySettings::get_global(cx).diagnostics {
|
||||||
|
return;
|
||||||
|
}
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let most_recent_panic =
|
upload_previous_minidumps(http.clone()).await.warn_on_err();
|
||||||
upload_previous_panics(http.clone(), &panic_report_url, telemetry_settings)
|
let most_recent_panic = upload_previous_panics(http.clone(), &panic_report_url)
|
||||||
.await
|
|
||||||
.log_err()
|
|
||||||
.flatten();
|
|
||||||
upload_previous_crashes(http, most_recent_panic, installation_id, telemetry_settings)
|
|
||||||
.await
|
.await
|
||||||
.log_err()
|
.log_err()
|
||||||
|
.flatten();
|
||||||
|
upload_previous_crashes(http, most_recent_panic, installation_id)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
})
|
})
|
||||||
.detach()
|
.detach()
|
||||||
}
|
}
|
||||||
|
@ -484,7 +496,6 @@ fn upload_panics_and_crashes(
|
||||||
async fn upload_previous_panics(
|
async fn upload_previous_panics(
|
||||||
http: Arc<HttpClientWithUrl>,
|
http: Arc<HttpClientWithUrl>,
|
||||||
panic_report_url: &Url,
|
panic_report_url: &Url,
|
||||||
telemetry_settings: client::TelemetrySettings,
|
|
||||||
) -> anyhow::Result<Option<(i64, String)>> {
|
) -> anyhow::Result<Option<(i64, String)>> {
|
||||||
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
|
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
|
||||||
|
|
||||||
|
@ -507,58 +518,41 @@ async fn upload_previous_panics(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if telemetry_settings.diagnostics {
|
let panic_file_content = smol::fs::read_to_string(&child_path)
|
||||||
let panic_file_content = smol::fs::read_to_string(&child_path)
|
.await
|
||||||
.await
|
.context("error reading panic file")?;
|
||||||
.context("error reading panic file")?;
|
|
||||||
|
|
||||||
let panic: Option<Panic> = serde_json::from_str(&panic_file_content)
|
let panic: Option<Panic> = serde_json::from_str(&panic_file_content)
|
||||||
.log_err()
|
.log_err()
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
panic_file_content
|
panic_file_content
|
||||||
.lines()
|
.lines()
|
||||||
.next()
|
.next()
|
||||||
.and_then(|line| serde_json::from_str(line).ok())
|
.and_then(|line| serde_json::from_str(line).ok())
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
log::error!("failed to deserialize panic file {:?}", panic_file_content);
|
log::error!("failed to deserialize panic file {:?}", panic_file_content);
|
||||||
None
|
None
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(panic) = panic {
|
if let Some(panic) = panic
|
||||||
let minidump_path = paths::logs_dir()
|
&& upload_panic(&http, &panic_report_url, panic, &mut most_recent_panic).await?
|
||||||
.join(&panic.session_id)
|
{
|
||||||
.with_extension("dmp");
|
// We've done what we can, delete the file
|
||||||
if minidump_path.exists() {
|
fs::remove_file(child_path)
|
||||||
let minidump = smol::fs::read(&minidump_path)
|
.context("error removing panic")
|
||||||
.await
|
.log_err();
|
||||||
.context("Failed to read minidump")?;
|
|
||||||
if upload_minidump(http.clone(), minidump, Some(&panic))
|
|
||||||
.await
|
|
||||||
.log_err()
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
fs::remove_file(minidump_path).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !upload_panic(&http, &panic_report_url, panic, &mut most_recent_panic).await? {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've done what we can, delete the file
|
|
||||||
fs::remove_file(child_path)
|
|
||||||
.context("error removing panic")
|
|
||||||
.log_err();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if MINIDUMP_ENDPOINT.is_none() {
|
Ok(most_recent_panic)
|
||||||
return Ok(most_recent_panic);
|
}
|
||||||
}
|
|
||||||
|
pub async fn upload_previous_minidumps(http: Arc<HttpClientWithUrl>) -> anyhow::Result<()> {
|
||||||
|
let Some(minidump_endpoint) = MINIDUMP_ENDPOINT.as_ref() else {
|
||||||
|
return Err(anyhow::anyhow!("Minidump endpoint not set"));
|
||||||
|
};
|
||||||
|
|
||||||
// loop back over the directory again to upload any minidumps that are missing panics
|
|
||||||
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
|
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
|
||||||
while let Some(child) = children.next().await {
|
while let Some(child) = children.next().await {
|
||||||
let child = child?;
|
let child = child?;
|
||||||
|
@ -566,33 +560,35 @@ async fn upload_previous_panics(
|
||||||
if child_path.extension() != Some(OsStr::new("dmp")) {
|
if child_path.extension() != Some(OsStr::new("dmp")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if upload_minidump(
|
let mut json_path = child_path.clone();
|
||||||
http.clone(),
|
json_path.set_extension("json");
|
||||||
smol::fs::read(&child_path)
|
if let Ok(metadata) = serde_json::from_slice(&smol::fs::read(&json_path).await?) {
|
||||||
.await
|
if upload_minidump(
|
||||||
.context("Failed to read minidump")?,
|
http.clone(),
|
||||||
None,
|
&minidump_endpoint,
|
||||||
)
|
smol::fs::read(&child_path)
|
||||||
.await
|
.await
|
||||||
.log_err()
|
.context("Failed to read minidump")?,
|
||||||
.is_some()
|
&metadata,
|
||||||
{
|
)
|
||||||
fs::remove_file(child_path).ok();
|
.await
|
||||||
|
.log_err()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
fs::remove_file(child_path).ok();
|
||||||
|
fs::remove_file(json_path).ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
Ok(most_recent_panic)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn upload_minidump(
|
async fn upload_minidump(
|
||||||
http: Arc<HttpClientWithUrl>,
|
http: Arc<HttpClientWithUrl>,
|
||||||
|
endpoint: &str,
|
||||||
minidump: Vec<u8>,
|
minidump: Vec<u8>,
|
||||||
panic: Option<&Panic>,
|
metadata: &crashes::CrashInfo,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let minidump_endpoint = MINIDUMP_ENDPOINT
|
|
||||||
.to_owned()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Minidump endpoint not set"))?;
|
|
||||||
|
|
||||||
let mut form = Form::new()
|
let mut form = Form::new()
|
||||||
.part(
|
.part(
|
||||||
"upload_file_minidump",
|
"upload_file_minidump",
|
||||||
|
@ -600,18 +596,22 @@ async fn upload_minidump(
|
||||||
.file_name("minidump.dmp")
|
.file_name("minidump.dmp")
|
||||||
.mime_str("application/octet-stream")?,
|
.mime_str("application/octet-stream")?,
|
||||||
)
|
)
|
||||||
|
.text(
|
||||||
|
"sentry[tags][channel]",
|
||||||
|
metadata.init.release_channel.clone(),
|
||||||
|
)
|
||||||
|
.text("sentry[tags][version]", metadata.init.zed_version.clone())
|
||||||
|
.text("sentry[release]", metadata.init.commit_sha.clone())
|
||||||
.text("platform", "rust");
|
.text("platform", "rust");
|
||||||
if let Some(panic) = panic {
|
if let Some(panic_info) = metadata.panic.as_ref() {
|
||||||
form = form
|
form = form.text("sentry[logentry][formatted]", panic_info.message.clone());
|
||||||
.text(
|
form = form.text("span", panic_info.span.clone());
|
||||||
"sentry[release]",
|
// TODO: add gpu-context, feature-flag-context, and more of device-context like gpu
|
||||||
format!("{}-{}", panic.release_channel, panic.app_version),
|
// name, screen resolution, available ram, device model, etc
|
||||||
)
|
|
||||||
.text("sentry[logentry][formatted]", panic.payload.clone());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response_text = String::new();
|
let mut response_text = String::new();
|
||||||
let mut response = http.send_multipart_form(&minidump_endpoint, form).await?;
|
let mut response = http.send_multipart_form(endpoint, form).await?;
|
||||||
response
|
response
|
||||||
.body_mut()
|
.body_mut()
|
||||||
.read_to_string(&mut response_text)
|
.read_to_string(&mut response_text)
|
||||||
|
@ -661,11 +661,7 @@ async fn upload_previous_crashes(
|
||||||
http: Arc<HttpClientWithUrl>,
|
http: Arc<HttpClientWithUrl>,
|
||||||
most_recent_panic: Option<(i64, String)>,
|
most_recent_panic: Option<(i64, String)>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
telemetry_settings: client::TelemetrySettings,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if !telemetry_settings.diagnostics {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let last_uploaded = KEY_VALUE_STORE
|
let last_uploaded = KEY_VALUE_STORE
|
||||||
.read_kvp(LAST_CRASH_UPLOADED)?
|
.read_kvp(LAST_CRASH_UPLOADED)?
|
||||||
.unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this.
|
.unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this.
|
||||||
|
|
|
@ -5,11 +5,9 @@ use editor::Editor;
|
||||||
use gpui::{AnyWindowHandle, App, AppContext as _, Context, Entity, WeakEntity};
|
use gpui::{AnyWindowHandle, App, AppContext as _, Context, Entity, WeakEntity};
|
||||||
use language::language_settings::{EditPredictionProvider, all_language_settings};
|
use language::language_settings::{EditPredictionProvider, all_language_settings};
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use smol::stream::StreamExt;
|
|
||||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||||
use supermaven::{Supermaven, SupermavenCompletionProvider};
|
use supermaven::{Supermaven, SupermavenCompletionProvider};
|
||||||
use ui::Window;
|
use ui::Window;
|
||||||
use util::ResultExt;
|
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
use zeta::{ProviderDataCollection, ZetaEditPredictionProvider};
|
use zeta::{ProviderDataCollection, ZetaEditPredictionProvider};
|
||||||
|
|
||||||
|
@ -59,25 +57,20 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
|
||||||
cx.on_action(clear_zeta_edit_history);
|
cx.on_action(clear_zeta_edit_history);
|
||||||
|
|
||||||
let mut provider = all_language_settings(None, cx).edit_predictions.provider;
|
let mut provider = all_language_settings(None, cx).edit_predictions.provider;
|
||||||
cx.spawn({
|
cx.subscribe(&user_store, {
|
||||||
let user_store = user_store.clone();
|
|
||||||
let editors = editors.clone();
|
let editors = editors.clone();
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
|
move |user_store, event, cx| match event {
|
||||||
async move |cx| {
|
client::user::Event::PrivateUserInfoUpdated => {
|
||||||
let mut status = client.status();
|
assign_edit_prediction_providers(
|
||||||
while let Some(_status) = status.next().await {
|
&editors,
|
||||||
cx.update(|cx| {
|
provider,
|
||||||
assign_edit_prediction_providers(
|
&client,
|
||||||
&editors,
|
user_store.clone(),
|
||||||
provider,
|
cx,
|
||||||
&client,
|
);
|
||||||
user_store.clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
|
@ -26,6 +26,7 @@ collections.workspace = true
|
||||||
command_palette_hooks.workspace = true
|
command_palette_hooks.workspace = true
|
||||||
copilot.workspace = true
|
copilot.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
|
edit_prediction.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
feature_flags.workspace = true
|
feature_flags.workspace = true
|
||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
|
@ -33,13 +34,13 @@ futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
edit_prediction.workspace = true
|
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
language_model.workspace = true
|
language_model.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
|
rand.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
release_channel.workspace = true
|
release_channel.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
|
@ -432,6 +432,7 @@ impl Zeta {
|
||||||
body,
|
body,
|
||||||
editable_range,
|
editable_range,
|
||||||
} = gather_task.await?;
|
} = gather_task.await?;
|
||||||
|
let done_gathering_context_at = Instant::now();
|
||||||
|
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Events:\n{}\nExcerpt:\n{:?}",
|
"Events:\n{}\nExcerpt:\n{:?}",
|
||||||
|
@ -484,6 +485,7 @@ impl Zeta {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let received_response_at = Instant::now();
|
||||||
log::debug!("completion response: {}", &response.output_excerpt);
|
log::debug!("completion response: {}", &response.output_excerpt);
|
||||||
|
|
||||||
if let Some(usage) = usage {
|
if let Some(usage) = usage {
|
||||||
|
@ -495,7 +497,7 @@ impl Zeta {
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::process_completion_response(
|
let edit_prediction = Self::process_completion_response(
|
||||||
response,
|
response,
|
||||||
buffer,
|
buffer,
|
||||||
&snapshot,
|
&snapshot,
|
||||||
|
@ -508,7 +510,25 @@ impl Zeta {
|
||||||
buffer_snapshotted_at,
|
buffer_snapshotted_at,
|
||||||
&cx,
|
&cx,
|
||||||
)
|
)
|
||||||
.await
|
.await;
|
||||||
|
|
||||||
|
let finished_at = Instant::now();
|
||||||
|
|
||||||
|
// record latency for ~1% of requests
|
||||||
|
if rand::random::<u8>() <= 2 {
|
||||||
|
telemetry::event!(
|
||||||
|
"Edit Prediction Request",
|
||||||
|
context_latency = done_gathering_context_at
|
||||||
|
.duration_since(buffer_snapshotted_at)
|
||||||
|
.as_millis(),
|
||||||
|
request_latency = received_response_at
|
||||||
|
.duration_since(done_gathering_context_at)
|
||||||
|
.as_millis(),
|
||||||
|
process_latency = finished_at.duration_since(received_response_at).as_millis()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
edit_prediction
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -207,7 +207,7 @@ function prepare_binaries() {
|
||||||
rm -f target/${architecture}/${target_dir}/Zed.dwarf.gz
|
rm -f target/${architecture}/${target_dir}/Zed.dwarf.gz
|
||||||
|
|
||||||
echo "Gzipping dSYMs for $architecture"
|
echo "Gzipping dSYMs for $architecture"
|
||||||
gzip -f target/${architecture}/${target_dir}/Zed.dwarf
|
gzip -kf target/${architecture}/${target_dir}/Zed.dwarf
|
||||||
|
|
||||||
echo "Uploading dSYMs${architecture} for $architecture to by-uuid/${uuid}.dwarf.gz"
|
echo "Uploading dSYMs${architecture} for $architecture to by-uuid/${uuid}.dwarf.gz"
|
||||||
upload_to_blob_store_public \
|
upload_to_blob_store_public \
|
||||||
|
@ -367,19 +367,25 @@ else
|
||||||
gzip -f --stdout --best target/aarch64-apple-darwin/release/remote_server > target/zed-remote-server-macos-aarch64.gz
|
gzip -f --stdout --best target/aarch64-apple-darwin/release/remote_server > target/zed-remote-server-macos-aarch64.gz
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Upload debug info to sentry.io
|
function upload_debug_info() {
|
||||||
if ! command -v sentry-cli >/dev/null 2>&1; then
|
architecture=$1
|
||||||
echo "sentry-cli not found. skipping sentry upload."
|
|
||||||
echo "install with: 'curl -sL https://sentry.io/get-cli | bash'"
|
|
||||||
else
|
|
||||||
if [[ -n "${SENTRY_AUTH_TOKEN:-}" ]]; then
|
if [[ -n "${SENTRY_AUTH_TOKEN:-}" ]]; then
|
||||||
echo "Uploading zed debug symbols to sentry..."
|
echo "Uploading zed debug symbols to sentry..."
|
||||||
# note: this uploads the unstripped binary which is needed because it contains
|
# note: this uploads the unstripped binary which is needed because it contains
|
||||||
# .eh_frame data for stack unwinindg. see https://github.com/getsentry/symbolic/issues/783
|
# .eh_frame data for stack unwinindg. see https://github.com/getsentry/symbolic/issues/783
|
||||||
sentry-cli debug-files upload --include-sources --wait -p zed -o zed-dev \
|
sentry-cli debug-files upload --include-sources --wait -p zed -o zed-dev \
|
||||||
"target/x86_64-apple-darwin/${target_dir}/" \
|
"target/${architecture}/${target_dir}/zed" \
|
||||||
"target/aarch64-apple-darwin/${target_dir}/"
|
"target/${architecture}/${target_dir}/remote_server" \
|
||||||
|
"target/${architecture}/${target_dir}/zed.dwarf"
|
||||||
else
|
else
|
||||||
echo "missing SENTRY_AUTH_TOKEN. skipping sentry upload."
|
echo "missing SENTRY_AUTH_TOKEN. skipping sentry upload."
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if command -v sentry-cli >/dev/null 2>&1; then
|
||||||
|
upload_debug_info "aarch64-apple-darwin"
|
||||||
|
upload_debug_info "x86_64-apple-darwin"
|
||||||
|
else
|
||||||
|
echo "sentry-cli not found. skipping sentry upload."
|
||||||
|
echo "install with: 'curl -sL https://sentry.io/get-cli | bash'"
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -305,7 +305,7 @@ scopeguard = { version = "1" }
|
||||||
security-framework = { version = "3", features = ["OSX_10_14"] }
|
security-framework = { version = "3", features = ["OSX_10_14"] }
|
||||||
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
|
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
|
||||||
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] }
|
||||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||||
tower = { version = "0.5", default-features = false, features = ["timeout", "util"] }
|
tower = { version = "0.5", default-features = false, features = ["timeout", "util"] }
|
||||||
|
@ -334,7 +334,7 @@ scopeguard = { version = "1" }
|
||||||
security-framework = { version = "3", features = ["OSX_10_14"] }
|
security-framework = { version = "3", features = ["OSX_10_14"] }
|
||||||
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
|
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
|
||||||
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] }
|
||||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||||
tower = { version = "0.5", default-features = false, features = ["timeout", "util"] }
|
tower = { version = "0.5", default-features = false, features = ["timeout", "util"] }
|
||||||
|
@ -362,7 +362,7 @@ scopeguard = { version = "1" }
|
||||||
security-framework = { version = "3", features = ["OSX_10_14"] }
|
security-framework = { version = "3", features = ["OSX_10_14"] }
|
||||||
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
|
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
|
||||||
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] }
|
||||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||||
tower = { version = "0.5", default-features = false, features = ["timeout", "util"] }
|
tower = { version = "0.5", default-features = false, features = ["timeout", "util"] }
|
||||||
|
@ -391,7 +391,7 @@ scopeguard = { version = "1" }
|
||||||
security-framework = { version = "3", features = ["OSX_10_14"] }
|
security-framework = { version = "3", features = ["OSX_10_14"] }
|
||||||
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
|
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
|
||||||
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] }
|
||||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||||
tower = { version = "0.5", default-features = false, features = ["timeout", "util"] }
|
tower = { version = "0.5", default-features = false, features = ["timeout", "util"] }
|
||||||
|
@ -429,7 +429,7 @@ rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs",
|
||||||
scopeguard = { version = "1" }
|
scopeguard = { version = "1" }
|
||||||
syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] }
|
syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] }
|
||||||
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] }
|
||||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||||
toml_datetime = { version = "0.6", default-features = false, features = ["serde"] }
|
toml_datetime = { version = "0.6", default-features = false, features = ["serde"] }
|
||||||
|
@ -468,7 +468,7 @@ rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["ev
|
||||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
||||||
scopeguard = { version = "1" }
|
scopeguard = { version = "1" }
|
||||||
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] }
|
||||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||||
toml_datetime = { version = "0.6", default-features = false, features = ["serde"] }
|
toml_datetime = { version = "0.6", default-features = false, features = ["serde"] }
|
||||||
|
@ -509,7 +509,7 @@ rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs",
|
||||||
scopeguard = { version = "1" }
|
scopeguard = { version = "1" }
|
||||||
syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] }
|
syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] }
|
||||||
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] }
|
||||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||||
toml_datetime = { version = "0.6", default-features = false, features = ["serde"] }
|
toml_datetime = { version = "0.6", default-features = false, features = ["serde"] }
|
||||||
|
@ -548,7 +548,7 @@ rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["ev
|
||||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
||||||
scopeguard = { version = "1" }
|
scopeguard = { version = "1" }
|
||||||
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] }
|
||||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||||
toml_datetime = { version = "0.6", default-features = false, features = ["serde"] }
|
toml_datetime = { version = "0.6", default-features = false, features = ["serde"] }
|
||||||
|
@ -568,7 +568,7 @@ ring = { version = "0.17", features = ["std"] }
|
||||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event"] }
|
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event"] }
|
||||||
scopeguard = { version = "1" }
|
scopeguard = { version = "1" }
|
||||||
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] }
|
||||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||||
tower = { version = "0.5", default-features = false, features = ["timeout", "util"] }
|
tower = { version = "0.5", default-features = false, features = ["timeout", "util"] }
|
||||||
|
@ -592,7 +592,7 @@ ring = { version = "0.17", features = ["std"] }
|
||||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event"] }
|
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event"] }
|
||||||
scopeguard = { version = "1" }
|
scopeguard = { version = "1" }
|
||||||
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] }
|
||||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||||
tower = { version = "0.5", default-features = false, features = ["timeout", "util"] }
|
tower = { version = "0.5", default-features = false, features = ["timeout", "util"] }
|
||||||
|
@ -636,7 +636,7 @@ rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs",
|
||||||
scopeguard = { version = "1" }
|
scopeguard = { version = "1" }
|
||||||
syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] }
|
syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] }
|
||||||
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] }
|
||||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||||
toml_datetime = { version = "0.6", default-features = false, features = ["serde"] }
|
toml_datetime = { version = "0.6", default-features = false, features = ["serde"] }
|
||||||
|
@ -675,7 +675,7 @@ rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["ev
|
||||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
||||||
scopeguard = { version = "1" }
|
scopeguard = { version = "1" }
|
||||||
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
sync_wrapper = { version = "1", default-features = false, features = ["futures"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "ring"] }
|
||||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||||
toml_datetime = { version = "0.6", default-features = false, features = ["serde"] }
|
toml_datetime = { version = "0.6", default-features = false, features = ["serde"] }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue