Compare commits

..

7 Commits

Author SHA1 Message Date
Max Brunsfeld
c4ba3c3dbc Add uncommitted diff field to zeta example files 2025-11-01 12:36:51 -07:00
Max Brunsfeld
3ff62895cb Clippy 2025-11-01 12:21:25 -07:00
Agus Zubiaga
ad3208536a Fix extension match 2025-10-31 18:28:43 -03:00
Agus Zubiaga
db0569cd21 Add cursor position section
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2025-10-31 15:47:17 -03:00
Agus Zubiaga
d2ab48aa4b Remove dbg 2025-10-30 20:54:39 -03:00
Agus Zubiaga
e2eaee1f1d Parse md example format 2025-10-30 20:51:40 -03:00
Agus Zubiaga
2b13b84ff2 Add convert-example command to zeta-cli 2025-10-30 20:27:58 -03:00
681 changed files with 15743 additions and 40162 deletions

View File

@@ -39,21 +39,3 @@ body:
Output of "zed: copy system specs into clipboard"
validations:
required: true
- type: textarea
attributes:
label: If applicable, attach your `Zed.log` file to this issue.
description: |
From the command palette, run `zed: open log` to see the last 1000 lines.
Or run `zed: reveal log in file manager` to reveal the log file itself.
value: |
<details><summary>Zed.log</summary>
<!-- Paste your log inside the code block. -->
```log
```
</details>
validations:
required: false

View File

@@ -33,21 +33,3 @@ body:
Output of "zed: copy system specs into clipboard"
validations:
required: true
- type: textarea
attributes:
label: If applicable, attach your `Zed.log` file to this issue.
description: |
From the command palette, run `zed: open log` to see the last 1000 lines.
Or run `zed: reveal log in file manager` to reveal the log file itself.
value: |
<details><summary>Zed.log</summary>
<!-- Paste your log inside the code block. -->
```log
```
</details>
validations:
required: false

View File

@@ -33,21 +33,3 @@ body:
Output of "zed: copy system specs into clipboard"
validations:
required: true
- type: textarea
attributes:
label: If applicable, attach your `Zed.log` file to this issue.
description: |
From the command palette, run `zed: open log` to see the last 1000 lines.
Or run `zed: reveal log in file manager` to reveal the log file itself.
value: |
<details><summary>Zed.log</summary>
<!-- Paste your log inside the code block. -->
```log
```
</details>
validations:
required: false

View File

@@ -33,21 +33,3 @@ body:
Output of "zed: copy system specs into clipboard"
validations:
required: true
- type: textarea
attributes:
label: If applicable, attach your `Zed.log` file to this issue.
description: |
From the command palette, run `zed: open log` to see the last 1000 lines.
Or run `zed: reveal log in file manager` to reveal the log file itself.
value: |
<details><summary>Zed.log</summary>
<!-- Paste your log inside the code block. -->
```log
```
</details>
validations:
required: false

View File

@@ -56,20 +56,3 @@ body:
Output of "zed: copy system specs into clipboard"
validations:
required: true
- type: textarea
attributes:
label: If applicable, attach your `Zed.log` file to this issue.
description: |
From the command palette, run `zed: open log` to see the last 1000 lines.
Or run `zed: reveal log in file manager` to reveal the log file itself.
value: |
<details><summary>Zed.log</summary>
<!-- Paste your log inside the code block. -->
```log
```
</details>
validations:
required: false

View File

@@ -1,4 +1,4 @@
# yaml-language-server: $schema=https://www.schemastore.org/github-issue-config.json
# yaml-language-server: $schema=https://json.schemastore.org/github-issue-config.json
blank_issues_enabled: false
contact_links:
- name: Feature Request

View File

@@ -4,8 +4,10 @@ description: "Runs the tests"
runs:
using: "composite"
steps:
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Install Rust
shell: bash -euxo pipefail {0}
run: |
cargo install cargo-nextest --locked
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4

View File

@@ -11,8 +11,9 @@ runs:
using: "composite"
steps:
- name: Install test runner
shell: powershell
working-directory: ${{ inputs.working-directory }}
uses: taiki-e/install-action@nextest
run: cargo install cargo-nextest --locked
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4

View File

@@ -1,104 +0,0 @@
# Generated from xtask::workflows::after_release
# Rebuild with `cargo xtask workflows`.
name: after_release
on:
release:
types:
- published
jobs:
rebuild_releases_page:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: after_release::rebuild_releases_page::refresh_cloud_releases
run: curl -fX POST https://cloud.zed.dev/releases/refresh?expect_tag=${{ github.event.release.tag_name }}
shell: bash -euxo pipefail {0}
- name: after_release::rebuild_releases_page::redeploy_zed_dev
run: npm exec --yes -- vercel@37 --token="$VERCEL_TOKEN" --scope zed-industries redeploy https://zed.dev
shell: bash -euxo pipefail {0}
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
post_to_discord:
needs:
- rebuild_releases_page
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- id: get-release-url
name: after_release::post_to_discord::get_release_url
run: |
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
URL="https://zed.dev/releases/preview"
else
URL="https://zed.dev/releases/stable"
fi
echo "URL=$URL" >> "$GITHUB_OUTPUT"
shell: bash -euxo pipefail {0}
- id: get-content
name: after_release::post_to_discord::get_content
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757
with:
stringToTruncate: |
📣 Zed [${{ github.event.release.tag_name }}](<${{ steps.get-release-url.outputs.URL }}>) was just released!
${{ github.event.release.body }}
maxLength: 2000
truncationSymbol: '...'
- name: after_release::post_to_discord::discord_webhook_action
uses: tsickert/discord-webhook@c840d45a03a323fbc3f7507ac7769dbd91bfb164
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_RELEASE_NOTES }}
content: ${{ steps.get-content.outputs.string }}
publish_winget:
runs-on: self-32vcpu-windows-2022
steps:
- id: set-package-name
name: after_release::publish_winget::set_package_name
run: |
if ("${{ github.event.release.prerelease }}" -eq "true") {
$PACKAGE_NAME = "ZedIndustries.Zed.Preview"
} else {
$PACKAGE_NAME = "ZedIndustries.Zed"
}
echo "PACKAGE_NAME=$PACKAGE_NAME" >> $env:GITHUB_OUTPUT
shell: pwsh
- name: after_release::publish_winget::winget_releaser
uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f
with:
identifier: ${{ steps.set-package-name.outputs.PACKAGE_NAME }}
max-versions-to-keep: 5
token: ${{ secrets.WINGET_TOKEN }}
create_sentry_release:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: release::create_sentry_release
uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c
with:
environment: production
env:
SENTRY_ORG: zed-dev
SENTRY_PROJECT: zed
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
notify_on_failure:
needs:
- rebuild_releases_page
- post_to_discord
- publish_winget
- create_sentry_release
if: failure()
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: release::notify_on_failure::notify_slack
run: |-
curl -X POST -H 'Content-type: application/json'\
--data '{"text":"${{ github.workflow }} failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}' "$SLACK_WEBHOOK"
shell: bash -euxo pipefail {0}
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_WORKFLOW_FAILURES }}

View File

@@ -42,7 +42,7 @@ jobs:
exit 1
;;
esac
which cargo-set-version > /dev/null || cargo install cargo-edit -f --no-default-features --features "set-version"
which cargo-set-version > /dev/null || cargo install cargo-edit
output="$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')"
export GIT_COMMITTER_NAME="Zed Bot"
export GIT_COMMITTER_EMAIL="hi@zed.dev"

View File

@@ -1,44 +0,0 @@
# Generated from xtask::workflows::cherry_pick
# Rebuild with `cargo xtask workflows`.
name: cherry_pick
run-name: 'cherry_pick to ${{ inputs.channel }} #${{ inputs.pr_number }}'
on:
workflow_dispatch:
inputs:
commit:
description: commit
required: true
type: string
branch:
description: branch
required: true
type: string
channel:
description: channel
required: true
type: string
pr_number:
description: pr_number
required: true
type: string
jobs:
run_cherry_pick:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- id: get-app-token
name: cherry_pick::run_cherry_pick::authenticate_as_zippy
uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1
with:
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
- name: cherry_pick::run_cherry_pick::cherry_pick
run: ./script/cherry-pick ${{ inputs.branch }} ${{ inputs.commit }} ${{ inputs.channel }}
shell: bash -euxo pipefail {0}
env:
GIT_COMMITTER_NAME: Zed Zippy
GIT_COMMITTER_EMAIL: hi@zed.dev
GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}

841
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,841 @@
name: CI
on:
push:
tags:
- "v*"
concurrency:
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
jobs:
job_spec:
name: Decide which jobs to run
if: github.repository_owner == 'zed-industries'
outputs:
run_tests: ${{ steps.filter.outputs.run_tests }}
run_license: ${{ steps.filter.outputs.run_license }}
run_docs: ${{ steps.filter.outputs.run_docs }}
run_nix: ${{ steps.filter.outputs.run_nix }}
run_actionlint: ${{ steps.filter.outputs.run_actionlint }}
runs-on:
- namespace-profile-2x4-ubuntu-2404
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
# 350 is arbitrary; ~10days of history on main (5secs); full history is ~25secs
fetch-depth: ${{ github.ref == 'refs/heads/main' && 2 || 350 }}
- name: Fetch git history and generate output filters
id: filter
run: |
if [ -z "$GITHUB_BASE_REF" ]; then
echo "Not in a PR context (i.e., push to main/stable/preview)"
COMPARE_REV="$(git rev-parse HEAD~1)"
else
echo "In a PR context comparing to pull_request.base.ref"
git fetch origin "$GITHUB_BASE_REF" --depth=350
COMPARE_REV="$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)"
fi
CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" ${{ github.sha }})"
# Specify anything which should potentially skip full test suite in this regex:
# - docs/
# - script/update_top_ranking_issues/
# - .github/ISSUE_TEMPLATE/
# - .github/workflows/ (except .github/workflows/ci.yml)
SKIP_REGEX='^(docs/|script/update_top_ranking_issues/|\.github/(ISSUE_TEMPLATE|workflows/(?!ci)))'
echo "$CHANGED_FILES" | grep -qvP "$SKIP_REGEX" && \
echo "run_tests=true" >> "$GITHUB_OUTPUT" || \
echo "run_tests=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^docs/' && \
echo "run_docs=true" >> "$GITHUB_OUTPUT" || \
echo "run_docs=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^\.github/(workflows/|actions/|actionlint.yml)' && \
echo "run_actionlint=true" >> "$GITHUB_OUTPUT" || \
echo "run_actionlint=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^(Cargo.lock|script/.*licenses)' && \
echo "run_license=true" >> "$GITHUB_OUTPUT" || \
echo "run_license=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^(nix/|flake\.|Cargo\.|rust-toolchain.toml|\.cargo/config.toml)' && \
echo "$GITHUB_REF_NAME" | grep -qvP '^v[0-9]+\.[0-9]+\.[0-9x](-pre)?$' && \
echo "run_nix=true" >> "$GITHUB_OUTPUT" || \
echo "run_nix=false" >> "$GITHUB_OUTPUT"
migration_checks:
name: Check Postgres and Protobuf migrations, mergability
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
timeout-minutes: 60
runs-on:
- self-mini-macos
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
fetch-depth: 0 # fetch full history
- name: Remove untracked files
run: git clean -df
- name: Find modified migrations
shell: bash -euxo pipefail {0}
run: |
export SQUAWK_GITHUB_TOKEN=${{ github.token }}
. ./script/squawk
- name: Ensure fresh merge
shell: bash -euxo pipefail {0}
run: |
if [ -z "$GITHUB_BASE_REF" ];
then
echo "BUF_BASE_BRANCH=$(git merge-base origin/main HEAD)" >> "$GITHUB_ENV"
else
git checkout -B temp
git merge -q "origin/$GITHUB_BASE_REF" -m "merge main into temp"
echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> "$GITHUB_ENV"
fi
- uses: bufbuild/buf-setup-action@v1
with:
version: v1.29.0
- uses: bufbuild/buf-breaking-action@v1
with:
input: "crates/proto/proto/"
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/proto/proto/"
style:
timeout-minutes: 60
name: Check formatting and spelling
needs: [job_spec]
if: github.repository_owner == 'zed-industries'
runs-on:
- namespace-profile-4x8-ubuntu-2204
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
with:
version: 9
- name: Prettier Check on /docs
working-directory: ./docs
run: |
pnpm dlx "prettier@${PRETTIER_VERSION}" . --check || {
echo "To fix, run from the root of the Zed repo:"
echo " cd docs && pnpm dlx prettier@${PRETTIER_VERSION} . --write && cd .."
false
}
env:
PRETTIER_VERSION: 3.5.0
- name: Prettier Check on default.json
run: |
pnpm dlx "prettier@${PRETTIER_VERSION}" assets/settings/default.json --check || {
echo "To fix, run from the root of the Zed repo:"
echo " pnpm dlx prettier@${PRETTIER_VERSION} assets/settings/default.json --write"
false
}
env:
PRETTIER_VERSION: 3.5.0
# To support writing comments that they will certainly be revisited.
- name: Check for todo! and FIXME comments
run: script/check-todos
- name: Check modifier use in keymaps
run: script/check-keymaps
- name: Run style checks
uses: ./.github/actions/check_style
- name: Check for typos
uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1 # v1.38.1
with:
config: ./typos.toml
check_docs:
timeout-minutes: 60
name: Check docs
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
(needs.job_spec.outputs.run_tests == 'true' || needs.job_spec.outputs.run_docs == 'true')
runs-on:
- namespace-profile-8x16-ubuntu-2204
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Build docs
uses: ./.github/actions/build_docs
actionlint:
runs-on: namespace-profile-2x4-ubuntu-2404
if: github.repository_owner == 'zed-industries' && needs.job_spec.outputs.run_actionlint == 'true'
needs: [job_spec]
steps:
- uses: actions/checkout@v4
- name: Download actionlint
id: get_actionlint
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
shell: bash
- name: Check workflow files
run: ${{ steps.get_actionlint.outputs.executable }} -color
shell: bash
macos_tests:
timeout-minutes: 60
name: (macOS) Run Clippy and tests
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- self-mini-macos
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Check that Cargo.lock is up to date
run: |
cargo update --locked --workspace
- name: cargo clippy
run: ./script/clippy
- name: Install cargo-machete
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2
with:
command: install
args: cargo-machete@0.7.0
- name: Check unused dependencies
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2
with:
command: machete
- name: Check licenses
run: |
script/check-licenses
if [[ "${{ needs.job_spec.outputs.run_license }}" == "true" ]]; then
script/generate-licenses /tmp/zed_licenses_output
fi
- name: Check for new vulnerable dependencies
if: github.event_name == 'pull_request'
uses: actions/dependency-review-action@67d4f4bd7a9b17a0db54d2a7519187c65e339de8 # v4
with:
license-check: false
- name: Run tests
uses: ./.github/actions/run_tests
- name: Build collab
# we should do this on a linux x86 machinge
run: cargo build -p collab
- name: Build other binaries and features
run: |
cargo build --workspace --bins --examples
# Since the macOS runners are stateful, so we need to remove the config file to prevent potential bug.
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo
linux_tests:
timeout-minutes: 60
name: (Linux) Run Clippy and tests
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
# cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: cargo clippy
run: ./script/clippy
- name: Run tests
uses: ./.github/actions/run_tests
- name: Build other binaries and features
run: |
cargo build -p zed
cargo check -p workspace
cargo check -p gpui --examples
# Even the Linux runner is not stateful, in theory there is no need to do this cleanup.
# But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code
# to clean up the config file, Ive included the cleanup code here as a precaution.
# While its not strictly necessary at this moment, I believe its better to err on the side of caution.
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo
doctests:
# Nextest currently doesn't support doctests, so run them separately and in parallel.
timeout-minutes: 60
name: (Linux) Run doctests
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
# cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Run doctests
run: cargo test --workspace --doc --no-fail-fast
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo
build_remote_server:
timeout-minutes: 60
name: (Linux) Build Remote Server
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
# cache-provider: "buildjet"
- name: Install Clang & Mold
run: ./script/remote-server && ./script/install-mold 2.34.0
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Build Remote Server
run: cargo build -p remote_server
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo
windows_tests:
timeout-minutes: 60
name: (Windows) Run Clippy and tests
needs: [job_spec]
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on: [self-32vcpu-windows-2022]
steps:
- name: Environment Setup
run: |
$RunnerDir = Split-Path -Parent $env:RUNNER_WORKSPACE
Write-Output `
"RUSTUP_HOME=$RunnerDir\.rustup" `
"CARGO_HOME=$RunnerDir\.cargo" `
"PATH=$RunnerDir\.cargo\bin;$env:PATH" `
>> $env:GITHUB_ENV
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Configure CI
run: |
New-Item -ItemType Directory -Path "./../.cargo" -Force
Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
- name: cargo clippy
run: |
.\script\clippy.ps1
- name: Run tests
uses: ./.github/actions/run_tests_windows
- name: Build Zed
run: cargo build
- name: Limit target directory size
run: ./script/clear-target-dir-if-larger-than.ps1 250
- name: Clean CI config file
if: always()
run: Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
tests_pass:
name: Tests Pass
runs-on: namespace-profile-2x4-ubuntu-2404
needs:
- job_spec
- style
- check_docs
- actionlint
- migration_checks
# run_tests: If adding required tests, add them here and to script below.
- linux_tests
- build_remote_server
- macos_tests
- windows_tests
if: |
github.repository_owner == 'zed-industries' &&
always()
steps:
- name: Check all tests passed
run: |
# Check dependent jobs...
RET_CODE=0
# Always check style
[[ "${{ needs.style.result }}" != 'success' ]] && { RET_CODE=1; echo "style tests failed"; }
if [[ "${{ needs.job_spec.outputs.run_docs }}" == "true" ]]; then
[[ "${{ needs.check_docs.result }}" != 'success' ]] && { RET_CODE=1; echo "docs checks failed"; }
fi
if [[ "${{ needs.job_spec.outputs.run_actionlint }}" == "true" ]]; then
[[ "${{ needs.actionlint.result }}" != 'success' ]] && { RET_CODE=1; echo "actionlint checks failed"; }
fi
# Only check test jobs if they were supposed to run
if [[ "${{ needs.job_spec.outputs.run_tests }}" == "true" ]]; then
[[ "${{ needs.macos_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "macOS tests failed"; }
[[ "${{ needs.linux_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "Linux tests failed"; }
[[ "${{ needs.windows_tests.result }}" != 'success' ]] && { RET_CODE=1; echo "Windows tests failed"; }
[[ "${{ needs.build_remote_server.result }}" != 'success' ]] && { RET_CODE=1; echo "Remote server build failed"; }
# This check is intentionally disabled. See: https://github.com/zed-industries/zed/pull/28431
# [[ "${{ needs.migration_checks.result }}" != 'success' ]] && { RET_CODE=1; echo "Migration Checks failed"; }
fi
if [[ "$RET_CODE" -eq 0 ]]; then
echo "All tests passed successfully!"
fi
exit $RET_CODE
bundle-mac:
timeout-minutes: 120
name: Create a macOS bundle
runs-on:
- self-mini-macos
if: startsWith(github.ref, 'refs/tags/v')
needs: [macos_tests]
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "18"
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
# We need to fetch more than one commit so that `script/draft-release-notes`
# is able to diff between the current and previous tag.
#
# 25 was chosen arbitrarily.
fetch-depth: 25
clean: false
ref: ${{ github.ref }}
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 300
- name: Determine version and release channel
run: |
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
script/determine-release-channel
- name: Draft release notes
run: |
mkdir -p target/
# Ignore any errors that occur while drafting release notes to not fail the build.
script/draft-release-notes "$RELEASE_VERSION" "$RELEASE_CHANNEL" > target/release-notes.md || true
script/create-draft-release target/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create macOS app bundle (aarch64)
run: script/bundle-mac aarch64-apple-darwin
- name: Create macOS app bundle (x64)
run: script/bundle-mac x86_64-apple-darwin
- name: Rename binaries
run: |
mv target/aarch64-apple-darwin/release/Zed.dmg target/aarch64-apple-darwin/release/Zed-aarch64.dmg
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
- uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
name: Upload app bundle to release
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: |
target/zed-remote-server-macos-x86_64.gz
target/zed-remote-server-macos-aarch64.gz
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
bundle-linux-x86_x64:
timeout-minutes: 60
name: Linux x86_x64 release bundle
runs-on:
- namespace-profile-16x32-ubuntu-2004 # ubuntu 20.04 for minimal glibc
if: |
( startsWith(github.ref, 'refs/tags/v') )
needs: [linux_tests]
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Install Linux dependencies
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
run: |
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
script/determine-release-channel
- name: Create Linux .tar.gz bundle
run: script/bundle-linux
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: |
target/zed-remote-server-linux-x86_64.gz
target/release/zed-linux-x86_64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
bundle-linux-aarch64: # this runs on ubuntu22.04
timeout-minutes: 60
name: Linux arm64 release bundle
runs-on:
- namespace-profile-8x32-ubuntu-2004-arm-m4 # ubuntu 20.04 for minimal glibc
if: |
startsWith(github.ref, 'refs/tags/v')
needs: [linux_tests]
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Install Linux dependencies
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
run: |
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
script/determine-release-channel
- name: Create and upload Linux .tar.gz bundles
run: script/bundle-linux
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: |
target/zed-remote-server-linux-aarch64.gz
target/release/zed-linux-aarch64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
freebsd:
timeout-minutes: 60
runs-on: github-8vcpu-ubuntu-2404
if: |
false && ( startsWith(github.ref, 'refs/tags/v') )
needs: [linux_tests]
name: Build Zed on FreeBSD
steps:
- uses: actions/checkout@v4
- name: Build FreeBSD remote-server
id: freebsd-build
uses: vmactions/freebsd-vm@c3ae29a132c8ef1924775414107a97cac042aad5 # v1.2.0
with:
usesh: true
release: 13.5
copyback: true
prepare: |
pkg install -y \
bash curl jq git \
rustup-init cmake-core llvm-devel-lite pkgconf protobuf # ibx11 alsa-lib rust-bindgen-cli
run: |
freebsd-version
sysctl hw.model
sysctl hw.ncpu
sysctl hw.physmem
sysctl hw.usermem
git config --global --add safe.directory /home/runner/work/zed/zed
rustup-init --profile minimal --default-toolchain none -y
. "$HOME/.cargo/env"
./script/bundle-freebsd
mkdir -p out/
mv "target/zed-remote-server-freebsd-x86_64.gz" out/
rm -rf target/
cargo clean
- name: Upload Artifact to Workflow - zed-remote-server (run-bundling)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
with:
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-freebsd.gz
path: out/zed-remote-server-freebsd-x86_64.gz
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: |
out/zed-remote-server-freebsd-x86_64.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
nix-build:
name: Build with Nix
uses: ./.github/workflows/nix_build.yml
needs: [job_spec]
if: github.repository_owner == 'zed-industries' &&
(contains(github.event.pull_request.labels.*.name, 'run-nix') ||
needs.job_spec.outputs.run_nix == 'true')
secrets: inherit
bundle-windows-x64:
timeout-minutes: 120
name: Create a Windows installer for x86_64
runs-on: [self-32vcpu-windows-2022]
if: |
( startsWith(github.ref, 'refs/tags/v') )
needs: [windows_tests]
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com"
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
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
working-directory: ${{ env.ZED_WORKSPACE }}
run: |
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
script/determine-release-channel.ps1
- name: Build Zed installer
working-directory: ${{ env.ZED_WORKSPACE }}
run: script/bundle-windows.ps1
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: ${{ env.SETUP_PATH }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
bundle-windows-aarch64:
timeout-minutes: 120
name: Create a Windows installer for aarch64
runs-on: [self-32vcpu-windows-2022]
if: |
( startsWith(github.ref, 'refs/tags/v') )
needs: [windows_tests]
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com"
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
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
working-directory: ${{ env.ZED_WORKSPACE }}
run: |
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
script/determine-release-channel.ps1
- name: Build Zed installer
working-directory: ${{ env.ZED_WORKSPACE }}
run: script/bundle-windows.ps1 -Architecture aarch64
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: ${{ env.SETUP_PATH }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
auto-release-preview:
name: Auto release preview
if: |
false
&& startsWith(github.ref, 'refs/tags/v')
&& endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, bundle-windows-x64, bundle-windows-aarch64]
runs-on:
- self-mini-macos
steps:
- name: gh release
run: gh release edit "$GITHUB_REF_NAME" --draft=false
env:
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

View File

@@ -15,15 +15,14 @@ jobs:
stale-issue-message: >
Hi there! 👋
We're working to clean up our issue tracker by closing older bugs that might not be relevant anymore. If you are able to reproduce this issue in the latest version of Zed, please let us know by commenting on this issue, and it will be kept open. If you can't reproduce it, feel free to close the issue yourself. Otherwise, it will close automatically in 14 days.
We're working to clean up our issue tracker by closing older issues that might not be relevant anymore. If you are able to reproduce this issue in the latest version of Zed, please let us know by commenting on this issue, and we will keep it open. If you can't reproduce it, feel free to close the issue yourself. Otherwise, we'll close it in 7 days.
Thanks for your help!
close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, please open a new issue with a link to this issue."
days-before-stale: 60
days-before-close: 14
only-issue-types: "Bug,Crash"
days-before-stale: 120
days-before-close: 7
any-of-issue-labels: "bug,panic / crash"
operations-per-run: 1000
ascending: true
enable-statistics: true
stale-issue-label: "stale"
exempt-issue-labels: "never stale"

View File

@@ -0,0 +1,93 @@
# IF YOU UPDATE THE NAME OF ANY GITHUB SECRET, YOU MUST CHERRY PICK THE COMMIT
# TO BOTH STABLE AND PREVIEW CHANNELS
name: Release Actions
on:
release:
types: [published]
jobs:
discord_release:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
- name: Get release URL
id: get-release-url
run: |
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
URL="https://zed.dev/releases/preview"
else
URL="https://zed.dev/releases/stable"
fi
echo "URL=$URL" >> "$GITHUB_OUTPUT"
- name: Get content
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757 # v1.4.1
id: get-content
with:
stringToTruncate: |
📣 Zed [${{ github.event.release.tag_name }}](<${{ steps.get-release-url.outputs.URL }}>) was just released!
${{ github.event.release.body }}
maxLength: 2000
truncationSymbol: "..."
- name: Discord Webhook Action
uses: tsickert/discord-webhook@c840d45a03a323fbc3f7507ac7769dbd91bfb164 # v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_RELEASE_NOTES }}
content: ${{ steps.get-content.outputs.string }}
publish-winget:
runs-on:
- ubuntu-latest
steps:
- name: Set Package Name
id: set-package-name
run: |
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
PACKAGE_NAME=ZedIndustries.Zed.Preview
else
PACKAGE_NAME=ZedIndustries.Zed
fi
echo "PACKAGE_NAME=$PACKAGE_NAME" >> "$GITHUB_OUTPUT"
- uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f # v2
with:
identifier: ${{ steps.set-package-name.outputs.PACKAGE_NAME }}
max-versions-to-keep: 5
token: ${{ secrets.WINGET_TOKEN }}
send_release_notes_email:
if: false && github.repository_owner == 'zed-industries' && !github.event.release.prerelease
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Check if release was promoted from preview
id: check-promotion-from-preview
run: |
VERSION="${{ github.event.release.tag_name }}"
PREVIEW_TAG="${VERSION}-pre"
if git rev-parse "$PREVIEW_TAG" > /dev/null 2>&1; then
echo "was_promoted_from_preview=true" >> "$GITHUB_OUTPUT"
else
echo "was_promoted_from_preview=false" >> "$GITHUB_OUTPUT"
fi
- name: Send release notes email
if: steps.check-promotion-from-preview.outputs.was_promoted_from_preview == 'true'
run: |
TAG="${{ github.event.release.tag_name }}"
cat << 'EOF' > release_body.txt
${{ github.event.release.body }}
EOF
jq -n --arg tag "$TAG" --rawfile body release_body.txt '{version: $tag, markdown_body: $body}' \
> release_data.json
curl -X POST "https://zed.dev/api/send_release_notes_email" \
-H "Authorization: Bearer ${{ secrets.RELEASE_NOTES_API_TOKEN }}" \
-H "Content-Type: application/json" \
-d @release_data.json

View File

@@ -1,80 +0,0 @@
# Generated from xtask::workflows::compare_perf
# Rebuild with `cargo xtask workflows`.
name: compare_perf
on:
workflow_dispatch:
inputs:
head:
description: head
required: true
type: string
base:
description: base
required: true
type: string
crate_name:
description: crate_name
type: string
default: ''
jobs:
run_perf:
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: compare_perf::run_perf::install_hyperfine
uses: taiki-e/install-action@hyperfine
- name: steps::git_checkout
run: git fetch origin ${{ inputs.base }} && git checkout ${{ inputs.base }}
shell: bash -euxo pipefail {0}
- name: compare_perf::run_perf::cargo_perf_test
run: |2-
if [ -n "${{ inputs.crate_name }}" ]; then
cargo perf-test -p ${{ inputs.crate_name }} -- --json=${{ inputs.base }};
else
cargo perf-test -p vim -- --json=${{ inputs.base }};
fi
shell: bash -euxo pipefail {0}
- name: steps::git_checkout
run: git fetch origin ${{ inputs.head }} && git checkout ${{ inputs.head }}
shell: bash -euxo pipefail {0}
- name: compare_perf::run_perf::cargo_perf_test
run: |2-
if [ -n "${{ inputs.crate_name }}" ]; then
cargo perf-test -p ${{ inputs.crate_name }} -- --json=${{ inputs.head }};
else
cargo perf-test -p vim -- --json=${{ inputs.head }};
fi
shell: bash -euxo pipefail {0}
- name: compare_perf::run_perf::compare_runs
run: cargo perf-compare --save=results.md ${{ inputs.base }} ${{ inputs.head }}
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact results.md'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: results.md
path: results.md
if-no-files-found: error
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}

View File

@@ -29,10 +29,10 @@ jobs:
node-version: '20'
cache: pnpm
cache-dependency-path: script/danger/pnpm-lock.yaml
- name: danger::danger_job::install_deps
- name: danger::install_deps
run: pnpm install --dir script/danger
shell: bash -euxo pipefail {0}
- name: danger::danger_job::run
- name: danger::run
run: pnpm run --dir script/danger danger ci
shell: bash -euxo pipefail {0}
env:

View File

@@ -43,7 +43,9 @@ jobs:
fetch-depth: 0
- name: Install cargo nextest
uses: taiki-e/install-action@nextest
shell: bash -euxo pipefail {0}
run: |
cargo install cargo-nextest --locked
- name: Limit target directory size
shell: bash -euxo pipefail {0}

71
.github/workflows/eval.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
name: Run Agent Eval
on:
schedule:
- cron: "0 0 * * *"
pull_request:
branches:
- "**"
types: [synchronize, reopened, labeled]
workflow_dispatch:
concurrency:
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_EVAL_TELEMETRY: 1
jobs:
run_eval:
timeout-minutes: 60
name: Run Agent Eval
if: >
github.repository_owner == 'zed-industries' &&
(github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-eval'))
runs-on:
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
# cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Compile eval
run: cargo build --package=eval
- name: Run eval
run: cargo run --package=eval -- --repetitions=8 --concurrency=1
# Even the Linux runner is not stateful, in theory there is no need to do this cleanup.
# But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code
# to clean up the config file, Ive included the cleanup code here as a precaution.
# While its not strictly necessary at this moment, I believe its better to err on the side of caution.
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo

97
.github/workflows/nix_build.yml vendored Normal file
View File

@@ -0,0 +1,97 @@
# Generated from xtask::workflows::nix_build
# Rebuild with `cargo xtask workflows`.
name: nix_build
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: '1'
CARGO_INCREMENTAL: '0'
on:
pull_request:
branches:
- '**'
paths:
- nix/**
- flake.*
- Cargo.*
- rust-toolchain.toml
- .cargo/config.toml
push:
branches:
- main
- v[0-9]+.[0-9]+.x
paths:
- nix/**
- flake.*
- Cargo.*
- rust-toolchain.toml
- .cargo/config.toml
workflow_call: {}
jobs:
build_nix_linux_x86_64:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-32x64-ubuntu-2004
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
GIT_LFS_SKIP_SMUDGE: '1'
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: nix_build::install_nix
uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- name: nix_build::cachix_action
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
with:
name: zed
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
cachixArgs: -v
pushFilter: -zed-editor-[0-9.]*-nightly
- name: nix_build::build
run: nix build .#debug -L --accept-flake-config
shell: bash -euxo pipefail {0}
timeout-minutes: 60
continue-on-error: true
build_nix_mac_aarch64:
if: github.repository_owner == 'zed-industries'
runs-on: self-mini-macos
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
GIT_LFS_SKIP_SMUDGE: '1'
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: nix_build::set_path
run: |
echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH"
echo "/Users/administrator/.nix-profile/bin" >> "$GITHUB_PATH"
shell: bash -euxo pipefail {0}
- name: nix_build::cachix_action
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
with:
name: zed
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
cachixArgs: -v
pushFilter: -zed-editor-[0-9.]*-nightly
- name: nix_build::build
run: nix build .#debug -L --accept-flake-config
shell: bash -euxo pipefail {0}
- name: nix_build::limit_store
run: |-
if [ "$(du -sm /nix/store | cut -f1)" -gt 50000 ]; then
nix-collect-garbage -d || true
fi
shell: bash -euxo pipefail {0}
timeout-minutes: 60
continue-on-error: true
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true

View File

@@ -1,496 +0,0 @@
# Generated from xtask::workflows::release
# Rebuild with `cargo xtask workflows`.
name: release
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: '1'
on:
push:
tags:
- v*
jobs:
run_tests_mac:
if: github.repository_owner == 'zed-industries'
runs-on: self-mini-macos
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::clippy
run: ./script/clippy
shell: bash -euxo pipefail {0}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: steps::cargo_nextest
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
shell: bash -euxo pipefail {0}
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
timeout-minutes: 60
run_tests_linux:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::clippy
run: ./script/clippy
shell: bash -euxo pipefail {0}
- name: steps::cargo_install_nextest
uses: taiki-e/install-action@nextest
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 250
shell: bash -euxo pipefail {0}
- name: steps::cargo_nextest
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
shell: bash -euxo pipefail {0}
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
timeout-minutes: 60
run_tests_windows:
if: github.repository_owner == 'zed-industries'
runs-on: self-32vcpu-windows-2022
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
New-Item -ItemType Directory -Path "./../.cargo" -Force
Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
shell: pwsh
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::clippy
run: ./script/clippy.ps1
shell: pwsh
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than.ps1 250
shell: pwsh
- name: steps::cargo_nextest
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
shell: pwsh
- name: steps::cleanup_cargo_config
if: always()
run: |
Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
shell: pwsh
timeout-minutes: 60
check_scripts:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_tests::check_scripts::run_shellcheck
run: ./script/shellcheck-scripts error
shell: bash -euxo pipefail {0}
- id: get_actionlint
name: run_tests::check_scripts::download_actionlint
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
shell: bash -euxo pipefail {0}
- name: run_tests::check_scripts::run_actionlint
run: |
${{ steps.get_actionlint.outputs.executable }} -color
shell: bash -euxo pipefail {0}
- name: run_tests::check_scripts::check_xtask_workflows
run: |
cargo xtask workflows
if ! git diff --exit-code .github; then
echo "Error: .github directory has uncommitted changes after running 'cargo xtask workflows'"
echo "Please run 'cargo xtask workflows' locally and commit the changes"
exit 1
fi
shell: bash -euxo pipefail {0}
timeout-minutes: 60
create_draft_release:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
fetch-depth: 25
ref: ${{ github.ref }}
- name: script/determine-release-channel
run: script/determine-release-channel
shell: bash -euxo pipefail {0}
- name: mkdir -p target/
run: mkdir -p target/
shell: bash -euxo pipefail {0}
- name: release::create_draft_release::generate_release_notes
run: node --redirect-warnings=/dev/null ./script/draft-release-notes "$RELEASE_VERSION" "$RELEASE_CHANNEL" > target/release-notes.md
shell: bash -euxo pipefail {0}
- name: release::create_draft_release::create_release
run: script/create-draft-release target/release-notes.md
shell: bash -euxo pipefail {0}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
timeout-minutes: 60
bundle_linux_aarch64:
needs:
- run_tests_linux
- check_scripts
runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-linux-aarch64.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-linux-aarch64.tar.gz
path: target/release/zed-linux-aarch64.tar.gz
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-linux-aarch64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-linux-aarch64.gz
path: target/zed-remote-server-linux-aarch64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_linux_x86_64:
needs:
- run_tests_linux
- check_scripts
runs-on: namespace-profile-32x64-ubuntu-2004
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-linux-x86_64.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-linux-x86_64.tar.gz
path: target/release/zed-linux-x86_64.tar.gz
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-linux-x86_64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-linux-x86_64.gz
path: target/zed-remote-server-linux-x86_64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_mac_aarch64:
needs:
- run_tests_mac
- check_scripts
runs-on: self-mini-macos
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac::bundle_mac
run: ./script/bundle-mac aarch64-apple-darwin
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact Zed-aarch64.dmg'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-macos-aarch64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-macos-aarch64.gz
path: target/zed-remote-server-macos-aarch64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_mac_x86_64:
needs:
- run_tests_mac
- check_scripts
runs-on: self-mini-macos
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac::bundle_mac
run: ./script/bundle-mac x86_64-apple-darwin
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact Zed-x86_64.dmg'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-x86_64.dmg
path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-macos-x86_64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-macos-x86_64.gz
path: target/zed-remote-server-macos-x86_64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_windows_aarch64:
needs:
- run_tests_windows
- check_scripts
runs-on: self-32vcpu-windows-2022
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: run_bundling::bundle_windows::bundle_windows
run: script/bundle-windows.ps1 -Architecture aarch64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: '@actions/upload-artifact Zed-aarch64.exe'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-aarch64.exe
path: target/Zed-aarch64.exe
if-no-files-found: error
timeout-minutes: 60
bundle_windows_x86_64:
needs:
- run_tests_windows
- check_scripts
runs-on: self-32vcpu-windows-2022
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: run_bundling::bundle_windows::bundle_windows
run: script/bundle-windows.ps1 -Architecture x86_64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: '@actions/upload-artifact Zed-x86_64.exe'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-x86_64.exe
path: target/Zed-x86_64.exe
if-no-files-found: error
timeout-minutes: 60
upload_release_assets:
needs:
- create_draft_release
- bundle_linux_aarch64
- bundle_linux_x86_64
- bundle_mac_aarch64
- bundle_mac_x86_64
- bundle_windows_aarch64
- bundle_windows_x86_64
runs-on: namespace-profile-4x8-ubuntu-2204
steps:
- name: release::download_workflow_artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
path: ./artifacts/
- name: ls -lR ./artifacts
run: ls -lR ./artifacts
shell: bash -euxo pipefail {0}
- name: release::prep_release_artifacts
run: |-
mkdir -p release-artifacts/
mv ./artifacts/Zed-aarch64.dmg/Zed-aarch64.dmg release-artifacts/Zed-aarch64.dmg
mv ./artifacts/Zed-x86_64.dmg/Zed-x86_64.dmg release-artifacts/Zed-x86_64.dmg
mv ./artifacts/zed-linux-aarch64.tar.gz/zed-linux-aarch64.tar.gz release-artifacts/zed-linux-aarch64.tar.gz
mv ./artifacts/zed-linux-x86_64.tar.gz/zed-linux-x86_64.tar.gz release-artifacts/zed-linux-x86_64.tar.gz
mv ./artifacts/Zed-x86_64.exe/Zed-x86_64.exe release-artifacts/Zed-x86_64.exe
mv ./artifacts/Zed-aarch64.exe/Zed-aarch64.exe release-artifacts/Zed-aarch64.exe
mv ./artifacts/zed-remote-server-macos-aarch64.gz/zed-remote-server-macos-aarch64.gz release-artifacts/zed-remote-server-macos-aarch64.gz
mv ./artifacts/zed-remote-server-macos-x86_64.gz/zed-remote-server-macos-x86_64.gz release-artifacts/zed-remote-server-macos-x86_64.gz
mv ./artifacts/zed-remote-server-linux-aarch64.gz/zed-remote-server-linux-aarch64.gz release-artifacts/zed-remote-server-linux-aarch64.gz
mv ./artifacts/zed-remote-server-linux-x86_64.gz/zed-remote-server-linux-x86_64.gz release-artifacts/zed-remote-server-linux-x86_64.gz
shell: bash -euxo pipefail {0}
- name: gh release upload "$GITHUB_REF_NAME" --repo=zed-industries/zed release-artifacts/*
run: gh release upload "$GITHUB_REF_NAME" --repo=zed-industries/zed release-artifacts/*
shell: bash -euxo pipefail {0}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
auto_release_preview:
needs:
- upload_release_assets
if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: gh release edit "$GITHUB_REF_NAME" --repo=zed-industries/zed --draft=false
run: gh release edit "$GITHUB_REF_NAME" --repo=zed-industries/zed --draft=false
shell: bash -euxo pipefail {0}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
notify_on_failure:
needs:
- upload_release_assets
- auto_release_preview
if: failure()
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: release::notify_on_failure::notify_slack
run: |-
curl -X POST -H 'Content-type: application/json'\
--data '{"text":"${{ github.workflow }} failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}' "$SLACK_WEBHOOK"
shell: bash -euxo pipefail {0}
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_WORKFLOW_FAILURES }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true

View File

@@ -3,7 +3,12 @@
name: release_nightly
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: '0'
RUST_BACKTRACE: '1'
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
on:
push:
tags:
@@ -27,6 +32,41 @@ jobs:
run: ./script/clippy
shell: bash -euxo pipefail {0}
timeout-minutes: 60
run_tests_mac:
if: github.repository_owner == 'zed-industries'
runs-on: self-mini-macos
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::clippy
run: ./script/clippy
shell: bash -euxo pipefail {0}
- name: steps::cargo_install_nextest
run: cargo install cargo-nextest --locked
shell: bash -euxo pipefail {0}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: steps::cargo_nextest
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
shell: bash -euxo pipefail {0}
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
timeout-minutes: 60
run_tests_windows:
if: github.repository_owner == 'zed-industries'
runs-on: self-32vcpu-windows-2022
@@ -47,6 +87,9 @@ jobs:
- name: steps::clippy
run: ./script/clippy.ps1
shell: pwsh
- name: steps::cargo_install_nextest
run: cargo install cargo-nextest --locked
shell: pwsh
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than.ps1 250
shell: pwsh
@@ -59,115 +102,13 @@ jobs:
Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
shell: pwsh
timeout-minutes: 60
bundle_linux_aarch64:
bundle_mac_nightly_x86_64:
needs:
- check_style
- run_tests_windows
runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
run: |
set -eu
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
shell: bash -euxo pipefail {0}
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-linux-aarch64.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-linux-aarch64.tar.gz
path: target/release/zed-linux-aarch64.tar.gz
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-linux-aarch64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-linux-aarch64.gz
path: target/zed-remote-server-linux-aarch64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_linux_x86_64:
needs:
- check_style
- run_tests_windows
runs-on: namespace-profile-32x64-ubuntu-2004
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
run: |
set -eu
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
shell: bash -euxo pipefail {0}
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-linux-x86_64.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-linux-x86_64.tar.gz
path: target/release/zed-linux-x86_64.tar.gz
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-linux-x86_64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-linux-x86_64.gz
path: target/zed-remote-server-linux-x86_64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_mac_aarch64:
needs:
- check_style
- run_tests_windows
- run_tests_mac
if: github.repository_owner == 'zed-industries'
runs-on: self-mini-macos
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
@@ -178,13 +119,6 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
run: |
set -eu
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
shell: bash -euxo pipefail {0}
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
@@ -196,84 +130,148 @@ jobs:
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac::bundle_mac
run: ./script/bundle-mac aarch64-apple-darwin
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact Zed-aarch64.dmg'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-macos-aarch64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-macos-aarch64.gz
path: target/zed-remote-server-macos-aarch64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_mac_x86_64:
needs:
- check_style
- run_tests_windows
runs-on: self-mini-macos
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
- name: release_nightly::set_release_channel_to_nightly
run: |
set -eu
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
shell: bash -euxo pipefail {0}
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac::bundle_mac
- name: run_bundling::bundle_mac
run: ./script/bundle-mac x86_64-apple-darwin
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact Zed-x86_64.dmg'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-x86_64.dmg
path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-macos-x86_64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-macos-x86_64.gz
path: target/zed-remote-server-macos-x86_64.gz
if-no-files-found: error
- name: release_nightly::upload_zed_nightly
run: script/upload-nightly macos x86_64
shell: bash -euxo pipefail {0}
timeout-minutes: 60
bundle_windows_aarch64:
bundle_mac_nightly_aarch64:
needs:
- check_style
- run_tests_mac
if: github.repository_owner == 'zed-industries'
runs-on: self-mini-macos
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: release_nightly::set_release_channel_to_nightly
run: |
set -eu
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac
run: ./script/bundle-mac aarch64-apple-darwin
shell: bash -euxo pipefail {0}
- name: release_nightly::upload_zed_nightly
run: script/upload-nightly macos aarch64
shell: bash -euxo pipefail {0}
timeout-minutes: 60
bundle_linux_nightly_x86_64:
needs:
- check_style
- run_tests_mac
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-32x64-ubuntu-2004
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: release_nightly::add_rust_to_path
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
shell: bash -euxo pipefail {0}
- name: ./script/linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: ./script/install-mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 100
shell: bash -euxo pipefail {0}
- name: release_nightly::set_release_channel_to_nightly
run: |
set -eu
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: release_nightly::upload_zed_nightly
run: script/upload-nightly linux-targz x86_64
shell: bash -euxo pipefail {0}
timeout-minutes: 60
bundle_linux_nightly_aarch64:
needs:
- check_style
- run_tests_mac
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: release_nightly::add_rust_to_path
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
shell: bash -euxo pipefail {0}
- name: ./script/linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 100
shell: bash -euxo pipefail {0}
- name: release_nightly::set_release_channel_to_nightly
run: |
set -eu
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: release_nightly::upload_zed_nightly
run: script/upload-nightly linux-targz aarch64
shell: bash -euxo pipefail {0}
timeout-minutes: 60
bundle_windows_nightly_x86_64:
needs:
- check_style
- run_tests_windows
if: github.repository_owner == 'zed-industries'
runs-on: self-32vcpu-windows-2022
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
@@ -288,7 +286,11 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: release_nightly::set_release_channel_to_nightly
run: |
$ErrorActionPreference = "Stop"
$version = git rev-parse --short HEAD
@@ -296,71 +298,61 @@ jobs:
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: run_bundling::bundle_windows::bundle_windows
run: script/bundle-windows.ps1 -Architecture aarch64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: '@actions/upload-artifact Zed-aarch64.exe'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-aarch64.exe
path: target/Zed-aarch64.exe
if-no-files-found: error
timeout-minutes: 60
bundle_windows_x86_64:
needs:
- check_style
- run_tests_windows
runs-on: self-32vcpu-windows-2022
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
run: |
$ErrorActionPreference = "Stop"
$version = git rev-parse --short HEAD
Write-Host "Publishing version: $version on release channel nightly"
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: run_bundling::bundle_windows::bundle_windows
- name: release_nightly::build_zed_installer
run: script/bundle-windows.ps1 -Architecture x86_64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: '@actions/upload-artifact Zed-x86_64.exe'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
- name: release_nightly::upload_zed_nightly_windows
run: script/upload-nightly.ps1 -Architecture x86_64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
timeout-minutes: 60
bundle_windows_nightly_aarch64:
needs:
- check_style
- run_tests_windows
if: github.repository_owner == 'zed-industries'
runs-on: self-32vcpu-windows-2022
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
name: Zed-x86_64.exe
path: target/Zed-x86_64.exe
if-no-files-found: error
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: release_nightly::set_release_channel_to_nightly
run: |
$ErrorActionPreference = "Stop"
$version = git rev-parse --short HEAD
Write-Host "Publishing version: $version on release channel nightly"
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: release_nightly::build_zed_installer
run: script/bundle-windows.ps1 -Architecture aarch64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: release_nightly::upload_zed_nightly_windows
run: script/upload-nightly.ps1 -Architecture aarch64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
timeout-minutes: 60
build_nix_linux_x86_64:
needs:
- check_style
- run_tests_windows
- run_tests_mac
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-32x64-ubuntu-2004
env:
@@ -373,17 +365,17 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: nix_build::build_nix::install_nix
- name: nix_build::install_nix
uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- name: nix_build::build_nix::cachix_action
- name: nix_build::cachix_action
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
with:
name: zed
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
cachixArgs: -v
- name: nix_build::build_nix::build
- name: nix_build::build
run: nix build .#default -L --accept-flake-config
shell: bash -euxo pipefail {0}
timeout-minutes: 60
@@ -391,7 +383,7 @@ jobs:
build_nix_mac_aarch64:
needs:
- check_style
- run_tests_windows
- run_tests_mac
if: github.repository_owner == 'zed-industries'
runs-on: self-mini-macos
env:
@@ -404,21 +396,21 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: nix_build::build_nix::set_path
- name: nix_build::set_path
run: |
echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH"
echo "/Users/administrator/.nix-profile/bin" >> "$GITHUB_PATH"
shell: bash -euxo pipefail {0}
- name: nix_build::build_nix::cachix_action
- name: nix_build::cachix_action
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
with:
name: zed
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
cachixArgs: -v
- name: nix_build::build_nix::build
- name: nix_build::build
run: nix build .#default -L --accept-flake-config
shell: bash -euxo pipefail {0}
- name: nix_build::build_nix::limit_store
- name: nix_build::limit_store
run: |-
if [ "$(du -sm /nix/store | cut -f1)" -gt 50000 ]; then
nix-collect-garbage -d || true
@@ -428,49 +420,21 @@ jobs:
continue-on-error: true
update_nightly_tag:
needs:
- bundle_linux_aarch64
- bundle_linux_x86_64
- bundle_mac_aarch64
- bundle_mac_x86_64
- bundle_windows_aarch64
- bundle_windows_x86_64
- bundle_mac_nightly_x86_64
- bundle_mac_nightly_aarch64
- bundle_linux_nightly_x86_64
- bundle_linux_nightly_aarch64
- bundle_windows_nightly_x86_64
- bundle_windows_nightly_aarch64
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-4x8-ubuntu-2204
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
fetch-depth: 0
- name: release::download_workflow_artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
path: ./artifacts/
- name: ls -lR ./artifacts
run: ls -lR ./artifacts
shell: bash -euxo pipefail {0}
- name: release::prep_release_artifacts
run: |-
mkdir -p release-artifacts/
mv ./artifacts/Zed-aarch64.dmg/Zed-aarch64.dmg release-artifacts/Zed-aarch64.dmg
mv ./artifacts/Zed-x86_64.dmg/Zed-x86_64.dmg release-artifacts/Zed-x86_64.dmg
mv ./artifacts/zed-linux-aarch64.tar.gz/zed-linux-aarch64.tar.gz release-artifacts/zed-linux-aarch64.tar.gz
mv ./artifacts/zed-linux-x86_64.tar.gz/zed-linux-x86_64.tar.gz release-artifacts/zed-linux-x86_64.tar.gz
mv ./artifacts/Zed-x86_64.exe/Zed-x86_64.exe release-artifacts/Zed-x86_64.exe
mv ./artifacts/Zed-aarch64.exe/Zed-aarch64.exe release-artifacts/Zed-aarch64.exe
mv ./artifacts/zed-remote-server-macos-aarch64.gz/zed-remote-server-macos-aarch64.gz release-artifacts/zed-remote-server-macos-aarch64.gz
mv ./artifacts/zed-remote-server-macos-x86_64.gz/zed-remote-server-macos-x86_64.gz release-artifacts/zed-remote-server-macos-x86_64.gz
mv ./artifacts/zed-remote-server-linux-aarch64.gz/zed-remote-server-linux-aarch64.gz release-artifacts/zed-remote-server-linux-aarch64.gz
mv ./artifacts/zed-remote-server-linux-x86_64.gz/zed-remote-server-linux-x86_64.gz release-artifacts/zed-remote-server-linux-x86_64.gz
shell: bash -euxo pipefail {0}
- name: ./script/upload-nightly
run: ./script/upload-nightly
shell: bash -euxo pipefail {0}
env:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
- name: release_nightly::update_nightly_tag_job::update_nightly_tag
- name: release_nightly::update_nightly_tag
run: |
if [ "$(git rev-parse nightly)" = "$(git rev-parse HEAD)" ]; then
echo "Nightly tag already points to current commit. Skipping tagging."
@@ -481,7 +445,7 @@ jobs:
git tag -f nightly
git push origin nightly --force
shell: bash -euxo pipefail {0}
- name: release::create_sentry_release
- name: release_nightly::create_sentry_release
uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c
with:
environment: production
@@ -490,21 +454,3 @@ jobs:
SENTRY_PROJECT: zed
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
timeout-minutes: 60
notify_on_failure:
needs:
- bundle_linux_aarch64
- bundle_linux_x86_64
- bundle_mac_aarch64
- bundle_mac_x86_64
- bundle_windows_aarch64
- bundle_windows_x86_64
if: failure()
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: release::notify_on_failure::notify_slack
run: |-
curl -X POST -H 'Content-type: application/json'\
--data '{"text":"${{ github.workflow }} failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}' "$SLACK_WEBHOOK"
shell: bash -euxo pipefail {0}
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_WORKFLOW_FAILURES }}

View File

@@ -1,67 +0,0 @@
# Generated from xtask::workflows::run_agent_evals
# Rebuild with `cargo xtask workflows`.
name: run_agent_evals
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: '0'
RUST_BACKTRACE: '1'
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_EVAL_TELEMETRY: '1'
MODEL_NAME: ${{ inputs.model_name }}
on:
workflow_dispatch:
inputs:
model_name:
description: model_name
required: true
type: string
jobs:
agent_evals:
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: cargo build --package=eval
run: cargo build --package=eval
shell: bash -euxo pipefail {0}
- name: run_agent_evals::agent_evals::run_eval
run: cargo run --package=eval -- --repetitions=8 --concurrency=1 --model "${MODEL_NAME}"
shell: bash -euxo pipefail {0}
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
timeout-minutes: 600
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true

View File

@@ -3,154 +3,22 @@
name: run_bundling
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: '0'
RUST_BACKTRACE: '1'
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
on:
pull_request:
types:
- labeled
- synchronize
jobs:
bundle_linux_aarch64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-linux-aarch64.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-linux-aarch64.tar.gz
path: target/release/zed-linux-aarch64.tar.gz
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-linux-aarch64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-linux-aarch64.gz
path: target/zed-remote-server-linux-aarch64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_linux_x86_64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: namespace-profile-32x64-ubuntu-2004
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-linux-x86_64.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-linux-x86_64.tar.gz
path: target/release/zed-linux-x86_64.tar.gz
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-linux-x86_64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-linux-x86_64.gz
path: target/zed-remote-server-linux-x86_64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_mac_aarch64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: self-mini-macos
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac::bundle_mac
run: ./script/bundle-mac aarch64-apple-darwin
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact Zed-aarch64.dmg'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-macos-aarch64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-macos-aarch64.gz
path: target/zed-remote-server-macos-aarch64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_mac_x86_64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: self-mini-macos
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
@@ -172,40 +40,66 @@ jobs:
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac::bundle_mac
- name: run_bundling::bundle_mac
run: ./script/bundle-mac x86_64-apple-darwin
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact Zed-x86_64.dmg'
- name: '@actions/upload-artifact Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-x86_64.dmg
path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
if-no-files-found: error
- name: '@actions/upload-artifact zed-remote-server-macos-x86_64.gz'
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
path: target/x86_64-apple-darwin/release/Zed.dmg
- name: '@actions/upload-artifact zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-macos-x86_64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-macos-x86_64.gz
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-macos-x86_64.gz
path: target/zed-remote-server-macos-x86_64.gz
if-no-files-found: error
timeout-minutes: 60
bundle_windows_aarch64:
bundle_mac_arm64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: self-32vcpu-windows-2022
runs-on: self-mini-macos
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '20'
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
- name: run_bundling::bundle_mac
run: ./script/bundle-mac aarch64-apple-darwin
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed.dmg
- name: '@actions/upload-artifact zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-macos-aarch64.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-macos-aarch64.gz
path: target/zed-remote-server-macos-aarch64.gz
timeout-minutes: 60
bundle_linux_x86_64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: namespace-profile-32x64-ubuntu-2004
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
@@ -215,16 +109,59 @@ jobs:
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: run_bundling::bundle_windows::bundle_windows
run: script/bundle-windows.ps1 -Architecture aarch64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: '@actions/upload-artifact Zed-aarch64.exe'
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-aarch64.exe
path: target/Zed-aarch64.exe
if-no-files-found: error
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
path: target/release/zed-*.tar.gz
- name: '@actions/upload-artifact zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
path: target/release/zed-remote-server-*.tar.gz
timeout-minutes: 60
bundle_linux_arm64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: ./script/bundle-linux
run: ./script/bundle-linux
shell: bash -euxo pipefail {0}
- name: '@actions/upload-artifact zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
path: target/release/zed-*.tar.gz
- name: '@actions/upload-artifact zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
path: target/release/zed-remote-server-*.tar.gz
timeout-minutes: 60
bundle_windows_x86_64:
if: |-
@@ -232,9 +169,6 @@ jobs:
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: self-32vcpu-windows-2022
env:
CARGO_INCREMENTAL: 0
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
@@ -253,16 +187,49 @@ jobs:
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: run_bundling::bundle_windows::bundle_windows
- name: run_bundling::bundle_windows
run: script/bundle-windows.ps1 -Architecture x86_64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: '@actions/upload-artifact Zed-x86_64.exe'
- name: '@actions/upload-artifact Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.exe'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed-x86_64.exe
path: target/Zed-x86_64.exe
if-no-files-found: error
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.exe
path: ${{ env.SETUP_PATH }}
timeout-minutes: 60
bundle_windows_arm64:
if: |-
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
runs-on: self-32vcpu-windows-2022
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_sentry
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: run_bundling::bundle_windows
run: script/bundle-windows.ps1 -Architecture aarch64
shell: pwsh
working-directory: ${{ env.ZED_WORKSPACE }}
- name: '@actions/upload-artifact Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.exe'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.exe
path: ${{ env.SETUP_PATH }}
timeout-minutes: 60
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}

View File

@@ -1,68 +0,0 @@
# Generated from xtask::workflows::run_cron_unit_evals
# Rebuild with `cargo xtask workflows`.
name: run_cron_unit_evals
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: '0'
RUST_BACKTRACE: '1'
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
on:
schedule:
- cron: 47 1 * * 2
workflow_dispatch: {}
jobs:
cron_unit_evals:
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: steps::cargo_install_nextest
uses: taiki-e/install-action@nextest
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 250
shell: bash -euxo pipefail {0}
- name: ./script/run-unit-evals
run: ./script/run-unit-evals
shell: bash -euxo pipefail {0}
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
- name: run_agent_evals::cron_unit_evals::send_failure_to_slack
if: ${{ failure() }}
uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52
with:
method: chat.postMessage
token: ${{ secrets.SLACK_APP_ZED_UNIT_EVALS_BOT_TOKEN }}
payload: |
channel: C04UDRNNJFQ
text: "Unit Evals Failed: https://github.com/zed-industries/zed/actions/runs/${{ github.run_id }}"
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true

View File

@@ -66,10 +66,6 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: steps::setup_pnpm
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
with:
@@ -113,6 +109,9 @@ jobs:
- name: steps::clippy
run: ./script/clippy.ps1
shell: pwsh
- name: steps::cargo_install_nextest
run: cargo install cargo-nextest --locked
shell: pwsh
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than.ps1 250
shell: pwsh
@@ -140,19 +139,12 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: steps::setup_node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
@@ -161,9 +153,10 @@ jobs:
run: ./script/clippy
shell: bash -euxo pipefail {0}
- name: steps::cargo_install_nextest
uses: taiki-e/install-action@nextest
run: cargo install cargo-nextest --locked
shell: bash -euxo pipefail {0}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 250
run: ./script/clear-target-dir-if-larger-than 100
shell: bash -euxo pipefail {0}
- name: steps::cargo_nextest
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
@@ -196,6 +189,9 @@ jobs:
- name: steps::clippy
run: ./script/clippy
shell: bash -euxo pipefail {0}
- name: steps::cargo_install_nextest
run: cargo install cargo-nextest --locked
shell: bash -euxo pipefail {0}
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
shell: bash -euxo pipefail {0}
@@ -218,19 +214,16 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
- name: steps::cache_rust_dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6
with:
cache: rust
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
@@ -262,19 +255,12 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: cargo build -p collab
run: cargo build -p collab
shell: bash -euxo pipefail {0}
@@ -287,6 +273,40 @@ jobs:
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
timeout-minutes: 60
check_postgres_and_protobuf_migrations:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_tests == 'true'
runs-on: self-mini-macos
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0
- name: run_tests::check_postgres_and_protobuf_migrations::remove_untracked_files
run: git clean -df
shell: bash -euxo pipefail {0}
- name: run_tests::check_postgres_and_protobuf_migrations::ensure_fresh_merge
run: |
if [ -z "$GITHUB_BASE_REF" ];
then
echo "BUF_BASE_BRANCH=$(git merge-base origin/main HEAD)" >> "$GITHUB_ENV"
else
git checkout -B temp
git merge -q "origin/$GITHUB_BASE_REF" -m "merge main into temp"
echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> "$GITHUB_ENV"
fi
shell: bash -euxo pipefail {0}
- name: run_tests::check_postgres_and_protobuf_migrations::bufbuild_setup_action
uses: bufbuild/buf-setup-action@v1
with:
version: v1.29.0
- name: run_tests::check_postgres_and_protobuf_migrations::bufbuild_breaking_action
uses: bufbuild/buf-breaking-action@v1
with:
input: crates/proto/proto/
against: https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/proto/proto/
timeout-minutes: 60
check_dependencies:
needs:
- orchestrate
@@ -297,10 +317,6 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: run_tests::check_dependencies::install_cargo_machete
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386
with:
@@ -334,10 +350,10 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
- name: steps::cache_rust_dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6
with:
cache: rust
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: run_tests::check_docs::lychee_link_check
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332
with:
@@ -350,9 +366,6 @@ jobs:
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: run_tests::check_docs::install_mdbook
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08
with:
@@ -379,10 +392,6 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: ./script/check-licenses
run: ./script/check-licenses
shell: bash -euxo pipefail {0}
@@ -435,18 +444,18 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: nix_build::build_nix::install_nix
- name: nix_build::install_nix
uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- name: nix_build::build_nix::cachix_action
- name: nix_build::cachix_action
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
with:
name: zed
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
cachixArgs: -v
pushFilter: -zed-editor-[0-9.]*-nightly
- name: nix_build::build_nix::build
- name: nix_build::build
run: nix build .#debug -L --accept-flake-config
shell: bash -euxo pipefail {0}
timeout-minutes: 60
@@ -466,22 +475,22 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: nix_build::build_nix::set_path
- name: nix_build::set_path
run: |
echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH"
echo "/Users/administrator/.nix-profile/bin" >> "$GITHUB_PATH"
shell: bash -euxo pipefail {0}
- name: nix_build::build_nix::cachix_action
- name: nix_build::cachix_action
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
with:
name: zed
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
cachixArgs: -v
pushFilter: -zed-editor-[0-9.]*-nightly
- name: nix_build::build_nix::build
- name: nix_build::build
run: nix build .#debug -L --accept-flake-config
shell: bash -euxo pipefail {0}
- name: nix_build::build_nix::limit_store
- name: nix_build::limit_store
run: |-
if [ "$(du -sm /nix/store | cut -f1)" -gt 50000 ]; then
nix-collect-garbage -d || true
@@ -489,40 +498,6 @@ jobs:
shell: bash -euxo pipefail {0}
timeout-minutes: 60
continue-on-error: true
check_postgres_and_protobuf_migrations:
needs:
- orchestrate
if: needs.orchestrate.outputs.run_tests == 'true'
runs-on: self-mini-macos
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0
- name: run_tests::check_postgres_and_protobuf_migrations::remove_untracked_files
run: git clean -df
shell: bash -euxo pipefail {0}
- name: run_tests::check_postgres_and_protobuf_migrations::ensure_fresh_merge
run: |
if [ -z "$GITHUB_BASE_REF" ];
then
echo "BUF_BASE_BRANCH=$(git merge-base origin/main HEAD)" >> "$GITHUB_ENV"
else
git checkout -B temp
git merge -q "origin/$GITHUB_BASE_REF" -m "merge main into temp"
echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> "$GITHUB_ENV"
fi
shell: bash -euxo pipefail {0}
- name: run_tests::check_postgres_and_protobuf_migrations::bufbuild_setup_action
uses: bufbuild/buf-setup-action@v1
with:
version: v1.29.0
- name: run_tests::check_postgres_and_protobuf_migrations::bufbuild_breaking_action
uses: bufbuild/buf-breaking-action@v1
with:
input: crates/proto/proto/
against: https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/proto/proto/
timeout-minutes: 60
tests_pass:
needs:
- orchestrate
@@ -532,6 +507,7 @@ jobs:
- run_tests_mac
- doctests
- check_workspace_binaries
- check_postgres_and_protobuf_migrations
- check_dependencies
- check_docs
- check_licenses
@@ -558,6 +534,7 @@ jobs:
check_result "run_tests_mac" "${{ needs.run_tests_mac.result }}"
check_result "doctests" "${{ needs.doctests.result }}"
check_result "check_workspace_binaries" "${{ needs.check_workspace_binaries.result }}"
check_result "check_postgres_and_protobuf_migrations" "${{ needs.check_postgres_and_protobuf_migrations.result }}"
check_result "check_dependencies" "${{ needs.check_dependencies.result }}"
check_result "check_docs" "${{ needs.check_docs.result }}"
check_result "check_licenses" "${{ needs.check_licenses.result }}"

View File

@@ -1,69 +0,0 @@
# Generated from xtask::workflows::run_unit_evals
# Rebuild with `cargo xtask workflows`.
name: run_unit_evals
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: '0'
RUST_BACKTRACE: '1'
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_EVAL_TELEMETRY: '1'
MODEL_NAME: ${{ inputs.model_name }}
on:
workflow_dispatch:
inputs:
model_name:
description: model_name
required: true
type: string
commit_sha:
description: commit_sha
required: true
type: string
jobs:
run_unit_evals:
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: steps::setup_cargo_config
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
shell: bash -euxo pipefail {0}
- name: steps::cache_rust_dependencies_namespace
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: rust
- name: steps::setup_linux
run: ./script/linux
shell: bash -euxo pipefail {0}
- name: steps::install_mold
run: ./script/install-mold
shell: bash -euxo pipefail {0}
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: steps::cargo_install_nextest
uses: taiki-e/install-action@nextest
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 250
shell: bash -euxo pipefail {0}
- name: ./script/run-unit-evals
run: ./script/run-unit-evals
shell: bash -euxo pipefail {0}
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
UNIT_EVAL_COMMIT: ${{ inputs.commit_sha }}
- name: steps::cleanup_cargo_config
if: always()
run: |
rm -rf ./../.cargo
shell: bash -euxo pipefail {0}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.run_id }}
cancel-in-progress: true

86
.github/workflows/unit_evals.yml vendored Normal file
View File

@@ -0,0 +1,86 @@
name: Run Unit Evals
on:
schedule:
# GitHub might drop jobs at busy times, so we choose a random time in the middle of the night.
- cron: "47 1 * * 2"
workflow_dispatch:
concurrency:
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
jobs:
unit_evals:
if: github.repository_owner == 'zed-industries'
timeout-minutes: 60
name: Run unit evals
runs-on:
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
# cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Install Rust
shell: bash -euxo pipefail {0}
run: |
cargo install cargo-nextest --locked
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "18"
- name: Limit target directory size
shell: bash -euxo pipefail {0}
run: script/clear-target-dir-if-larger-than 100
- name: Run unit evals
shell: bash -euxo pipefail {0}
run: cargo nextest run --workspace --no-fail-fast --features unit-eval --no-capture -E 'test(::eval_)'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Send failure message to Slack channel if needed
if: ${{ failure() }}
uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52
with:
method: chat.postMessage
token: ${{ secrets.SLACK_APP_ZED_UNIT_EVALS_BOT_TOKEN }}
payload: |
channel: C04UDRNNJFQ
text: "Unit Evals Failed: https://github.com/zed-industries/zed/actions/runs/${{ github.run_id }}"
# Even the Linux runner is not stateful, in theory there is no need to do this cleanup.
# But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code
# to clean up the config file, Ive included the cleanup code here as a precaution.
# While its not strictly necessary at this moment, I believe its better to err on the side of caution.
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo

204
Cargo.lock generated
View File

@@ -32,7 +32,6 @@ dependencies = [
"settings",
"smol",
"task",
"telemetry",
"tempfile",
"terminal",
"ui",
@@ -40,7 +39,6 @@ dependencies = [
"util",
"uuid",
"watch",
"zlog",
]
[[package]]
@@ -81,7 +79,6 @@ dependencies = [
"rand 0.9.2",
"serde_json",
"settings",
"telemetry",
"text",
"util",
"watch",
@@ -96,7 +93,6 @@ dependencies = [
"auto_update",
"editor",
"extension_host",
"fs",
"futures 0.3.31",
"gpui",
"language",
@@ -251,6 +247,7 @@ dependencies = [
"acp_tools",
"action_log",
"agent-client-protocol",
"agent_settings",
"anyhow",
"async-trait",
"client",
@@ -322,7 +319,6 @@ dependencies = [
"assistant_slash_command",
"assistant_slash_commands",
"assistant_text_thread",
"async-fs",
"audio",
"buffer_diff",
"chrono",
@@ -344,7 +340,6 @@ dependencies = [
"gpui",
"html_to_markdown",
"http_client",
"image",
"indoc",
"itertools 0.14.0",
"jsonschema",
@@ -1333,14 +1328,10 @@ version = "0.1.0"
dependencies = [
"anyhow",
"client",
"clock",
"ctor",
"db",
"futures 0.3.31",
"gpui",
"http_client",
"log",
"parking_lot",
"paths",
"release_channel",
"serde",
@@ -1348,10 +1339,8 @@ dependencies = [
"settings",
"smol",
"tempfile",
"util",
"which 6.0.3",
"workspace",
"zlog",
]
[[package]]
@@ -1361,7 +1350,6 @@ dependencies = [
"anyhow",
"log",
"simplelog",
"tempfile",
"windows 0.61.3",
"winresource",
]
@@ -1463,7 +1451,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d"
dependencies = [
"aws-lc-sys",
"untrusted 0.7.1",
"zeroize",
]
@@ -3209,7 +3196,6 @@ dependencies = [
"indoc",
"ordered-float 2.10.1",
"rustc-hash 2.1.1",
"schemars 1.0.4",
"serde",
"strum 0.27.2",
]
@@ -4542,15 +4528,12 @@ dependencies = [
"fs",
"futures 0.3.31",
"gpui",
"http_client",
"json_dotpath",
"language",
"log",
"node_runtime",
"paths",
"serde",
"serde_json",
"settings",
"smol",
"task",
"util",
@@ -4949,7 +4932,6 @@ dependencies = [
"editor",
"gpui",
"indoc",
"itertools 0.14.0",
"language",
"log",
"lsp",
@@ -5314,7 +5296,6 @@ dependencies = [
"serde_json",
"settings",
"supermaven",
"sweep_ai",
"telemetry",
"theme",
"ui",
@@ -5851,6 +5832,8 @@ name = "extension"
version = "0.1.0"
dependencies = [
"anyhow",
"async-compression",
"async-tar",
"async-trait",
"collections",
"dap",
@@ -5864,7 +5847,6 @@ dependencies = [
"lsp",
"parking_lot",
"pretty_assertions",
"proto",
"semantic_version",
"serde",
"serde_json",
@@ -6253,7 +6235,7 @@ dependencies = [
"futures-core",
"futures-sink",
"nanorand",
"spin 0.9.8",
"spin",
]
[[package]]
@@ -6364,9 +6346,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
[[package]]
name = "fork"
version = "0.4.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30268f1eefccc9d72f43692e8b89e659aeb52e84016c3b32b6e7e9f1c8f38f94"
checksum = "05dc8b302e04a1c27f4fe694439ef0f29779ca4edc205b7b58f00db04e29656d"
dependencies = [
"libc",
]
@@ -6418,7 +6400,7 @@ dependencies = [
"ignore",
"libc",
"log",
"notify 8.2.0",
"notify 8.0.0",
"objc",
"parking_lot",
"paths",
@@ -6975,7 +6957,7 @@ dependencies = [
[[package]]
name = "gh-workflow"
version = "0.8.0"
source = "git+https://github.com/zed-industries/gh-workflow?rev=3eaa84abca0778eb54272f45a312cb24f9a0b435#3eaa84abca0778eb54272f45a312cb24f9a0b435"
source = "git+https://github.com/zed-industries/gh-workflow?rev=0090c6b6ef82fff02bc8616645953e778d1acc08#0090c6b6ef82fff02bc8616645953e778d1acc08"
dependencies = [
"async-trait",
"derive_more 2.0.1",
@@ -6992,7 +6974,7 @@ dependencies = [
[[package]]
name = "gh-workflow-macros"
version = "0.8.0"
source = "git+https://github.com/zed-industries/gh-workflow?rev=3eaa84abca0778eb54272f45a312cb24f9a0b435#3eaa84abca0778eb54272f45a312cb24f9a0b435"
source = "git+https://github.com/zed-industries/gh-workflow?rev=0090c6b6ef82fff02bc8616645953e778d1acc08#0090c6b6ef82fff02bc8616645953e778d1acc08"
dependencies = [
"heck 0.5.0",
"quote",
@@ -7092,7 +7074,6 @@ dependencies = [
"serde_json",
"settings",
"url",
"urlencoding",
"util",
]
@@ -7105,6 +7086,7 @@ dependencies = [
"askpass",
"buffer_diff",
"call",
"chrono",
"cloud_llm_client",
"collections",
"command_palette_hooks",
@@ -7130,8 +7112,6 @@ dependencies = [
"picker",
"pretty_assertions",
"project",
"recent_projects",
"remote",
"schemars 1.0.4",
"serde",
"serde_json",
@@ -7292,7 +7272,6 @@ dependencies = [
"calloop",
"calloop-wayland-source",
"cbindgen",
"circular-buffer",
"cocoa 0.26.0",
"cocoa-foundation 0.2.0",
"collections",
@@ -7348,7 +7327,6 @@ dependencies = [
"slotmap",
"smallvec",
"smol",
"spin 0.10.0",
"stacksafe",
"strum 0.27.2",
"sum_tree",
@@ -7812,7 +7790,6 @@ dependencies = [
"parking_lot",
"serde",
"serde_json",
"serde_urlencoded",
"sha2",
"tempfile",
"url",
@@ -8663,25 +8640,23 @@ dependencies = [
[[package]]
name = "jupyter-protocol"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c047f6b5e551563af2ddb13dafed833f0ec5a5b0f9621d5ad740a9ff1e1095"
version = "0.6.0"
source = "git+https://github.com/ConradIrwin/runtimed?rev=7130c804216b6914355d15d0b91ea91f6babd734#7130c804216b6914355d15d0b91ea91f6babd734"
dependencies = [
"anyhow",
"async-trait",
"bytes 1.10.1",
"chrono",
"futures 0.3.31",
"serde",
"serde_json",
"thiserror 2.0.17",
"uuid",
]
[[package]]
name = "jupyter-websocket-client"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4197fa926a6b0bddfed7377d9fed3d00a0dec44a1501e020097bd26604699cae"
version = "0.9.0"
source = "git+https://github.com/ConradIrwin/runtimed?rev=7130c804216b6914355d15d0b91ea91f6babd734#7130c804216b6914355d15d0b91ea91f6babd734"
dependencies = [
"anyhow",
"async-trait",
@@ -8690,7 +8665,6 @@ dependencies = [
"jupyter-protocol",
"serde",
"serde_json",
"tokio",
"url",
"uuid",
]
@@ -8880,11 +8854,9 @@ dependencies = [
"icons",
"image",
"log",
"open_ai",
"open_router",
"parking_lot",
"proto",
"schemars 1.0.4",
"serde",
"serde_json",
"settings",
@@ -9049,7 +9021,6 @@ dependencies = [
"settings",
"smol",
"task",
"terminal",
"text",
"theme",
"toml 0.8.23",
@@ -9083,7 +9054,7 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [
"spin 0.9.8",
"spin",
]
[[package]]
@@ -9697,7 +9668,6 @@ dependencies = [
"settings",
"theme",
"ui",
"urlencoding",
"util",
"workspace",
]
@@ -10025,18 +9995,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniprofiler_ui"
version = "0.1.0"
dependencies = [
"gpui",
"serde_json",
"smol",
"util",
"workspace",
"zed_actions",
]
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@@ -10241,9 +10199,8 @@ dependencies = [
[[package]]
name = "nbformat"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89c7229d604d847227002715e1235cd84e81919285d904ccb290a42ecc409348"
version = "0.10.0"
source = "git+https://github.com/ConradIrwin/runtimed?rev=7130c804216b6914355d15d0b91ea91f6babd734#7130c804216b6914355d15d0b91ea91f6babd734"
dependencies = [
"anyhow",
"chrono",
@@ -10445,10 +10402,11 @@ dependencies = [
[[package]]
name = "notify"
version = "8.2.0"
source = "git+https://github.com/zed-industries/notify.git?rev=b4588b2e5aee68f4c0e100f140e808cbce7b1419#b4588b2e5aee68f4c0e100f140e808cbce7b1419"
version = "8.0.0"
source = "git+https://github.com/zed-industries/notify.git?rev=bbb9ea5ae52b253e095737847e367c30653a2e96#bbb9ea5ae52b253e095737847e367c30653a2e96"
dependencies = [
"bitflags 2.9.4",
"filetime",
"fsevent-sys 4.1.0",
"inotify 0.11.0",
"kqueue",
@@ -10457,7 +10415,7 @@ dependencies = [
"mio 1.1.0",
"notify-types",
"walkdir",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -10474,7 +10432,7 @@ dependencies = [
[[package]]
name = "notify-types"
version = "2.0.0"
source = "git+https://github.com/zed-industries/notify.git?rev=b4588b2e5aee68f4c0e100f140e808cbce7b1419#b4588b2e5aee68f4c0e100f140e808cbce7b1419"
source = "git+https://github.com/zed-industries/notify.git?rev=bbb9ea5ae52b253e095737847e367c30653a2e96#bbb9ea5ae52b253e095737847e367c30653a2e96"
[[package]]
name = "now"
@@ -10529,10 +10487,11 @@ dependencies = [
[[package]]
name = "num-bigint-dig"
version = "0.8.6"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
dependencies = [
"byteorder",
"lazy_static",
"libm",
"num-integer",
@@ -11035,7 +10994,6 @@ dependencies = [
"serde_json",
"settings",
"strum 0.27.2",
"thiserror 2.0.17",
]
[[package]]
@@ -13075,23 +13033,6 @@ dependencies = [
"zlog",
]
[[package]]
name = "project_benchmarks"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"client",
"futures 0.3.31",
"gpui",
"http_client",
"language",
"node_runtime",
"project",
"settings",
"watch",
]
[[package]]
name = "project_panel"
version = "0.1.0"
@@ -13119,7 +13060,6 @@ dependencies = [
"settings",
"smallvec",
"telemetry",
"tempfile",
"theme",
"ui",
"util",
@@ -14024,7 +13964,6 @@ dependencies = [
"gpui",
"gpui_tokio",
"http_client",
"image",
"json_schema_store",
"language",
"language_extension",
@@ -14038,7 +13977,6 @@ dependencies = [
"paths",
"pretty_assertions",
"project",
"prompt_store",
"proto",
"rayon",
"release_channel",
@@ -14284,7 +14222,7 @@ dependencies = [
"cfg-if",
"getrandom 0.2.16",
"libc",
"untrusted 0.9.0",
"untrusted",
"windows-sys 0.52.0",
]
@@ -14413,9 +14351,9 @@ dependencies = [
[[package]]
name = "rsa"
version = "0.9.9"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88"
checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
dependencies = [
"const-oid",
"digest",
@@ -14465,26 +14403,25 @@ dependencies = [
[[package]]
name = "runtimelib"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "481b48894073a0096f28cbe9860af01fc1b861e55b3bc96afafc645ee3de62dc"
version = "0.25.0"
source = "git+https://github.com/ConradIrwin/runtimed?rev=7130c804216b6914355d15d0b91ea91f6babd734#7130c804216b6914355d15d0b91ea91f6babd734"
dependencies = [
"anyhow",
"async-dispatcher",
"async-std",
"aws-lc-rs",
"base64 0.22.1",
"bytes 1.10.1",
"chrono",
"data-encoding",
"dirs 6.0.0",
"dirs 5.0.1",
"futures 0.3.31",
"glob",
"jupyter-protocol",
"ring",
"serde",
"serde_json",
"shellexpand 3.1.1",
"smol",
"thiserror 2.0.17",
"uuid",
"zeromq",
]
@@ -14752,7 +14689,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [
"ring",
"untrusted 0.9.0",
"untrusted",
]
[[package]]
@@ -14764,7 +14701,7 @@ dependencies = [
"aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted 0.9.0",
"untrusted",
]
[[package]]
@@ -14994,7 +14931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
"ring",
"untrusted 0.9.0",
"untrusted",
]
[[package]]
@@ -15897,15 +15834,6 @@ dependencies = [
"lock_api",
]
[[package]]
name = "spin"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
dependencies = [
"lock_api",
]
[[package]]
name = "spirv"
version = "0.3.0+sdk-1.3.268.0"
@@ -16277,6 +16205,7 @@ dependencies = [
"log",
"menu",
"picker",
"project",
"reqwest_client",
"rust-embed",
"settings",
@@ -16286,6 +16215,7 @@ dependencies = [
"theme",
"title_bar",
"ui",
"workspace",
]
[[package]]
@@ -16591,33 +16521,6 @@ dependencies = [
"zeno",
]
[[package]]
name = "sweep_ai"
version = "0.1.0"
dependencies = [
"anyhow",
"arrayvec",
"brotli",
"client",
"collections",
"edit_prediction",
"feature_flags",
"futures 0.3.31",
"gpui",
"http_client",
"indoc",
"language",
"project",
"release_channel",
"reqwest_client",
"serde",
"serde_json",
"tree-sitter-rust",
"util",
"workspace",
"zlog",
]
[[package]]
name = "symphonia"
version = "0.5.5"
@@ -18139,7 +18042,7 @@ dependencies = [
[[package]]
name = "tree-sitter-gomod"
version = "1.1.1"
source = "git+https://github.com/camdencheek/tree-sitter-go-mod?rev=2e886870578eeba1927a2dc4bd2e2b3f598c5f9a#2e886870578eeba1927a2dc4bd2e2b3f598c5f9a"
source = "git+https://github.com/camdencheek/tree-sitter-go-mod?rev=6efb59652d30e0e9cd5f3b3a669afd6f1a926d3c#6efb59652d30e0e9cd5f3b3a669afd6f1a926d3c"
dependencies = [
"cc",
"tree-sitter-language",
@@ -18614,12 +18517,6 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "untrusted"
version = "0.9.0"
@@ -18714,7 +18611,6 @@ dependencies = [
"itertools 0.14.0",
"libc",
"log",
"mach2 0.5.0",
"nix 0.29.0",
"pretty_assertions",
"rand 0.9.2",
@@ -18904,6 +18800,7 @@ dependencies = [
name = "vim_mode_setting"
version = "0.1.0"
dependencies = [
"gpui",
"settings",
]
@@ -21047,7 +20944,6 @@ dependencies = [
"gh-workflow",
"indexmap 2.11.4",
"indoc",
"serde",
"toml 0.8.23",
"toml_edit 0.22.27",
]
@@ -21232,7 +21128,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.214.0"
version = "0.212.0"
dependencies = [
"acp_tools",
"activity_indicator",
@@ -21245,11 +21141,11 @@ dependencies = [
"audio",
"auto_update",
"auto_update_ui",
"backtrace",
"bincode 1.3.3",
"breadcrumbs",
"call",
"channel",
"chrono",
"clap",
"cli",
"client",
@@ -21307,8 +21203,8 @@ dependencies = [
"menu",
"migrator",
"mimalloc",
"miniprofiler_ui",
"nc",
"nix 0.29.0",
"node_runtime",
"notifications",
"onboarding",
@@ -21324,7 +21220,6 @@ dependencies = [
"project_symbols",
"prompt_store",
"proto",
"rayon",
"recent_projects",
"release_channel",
"remote",
@@ -21344,13 +21239,13 @@ dependencies = [
"snippets_ui",
"supermaven",
"svg_preview",
"sweep_ai",
"sysinfo 0.37.2",
"system_specs",
"tab_switcher",
"task",
"tasks_ui",
"telemetry",
"telemetry_events",
"terminal_view",
"theme",
"theme_extension",
@@ -21771,21 +21666,18 @@ dependencies = [
"language_model",
"log",
"lsp",
"open_ai",
"pretty_assertions",
"project",
"release_channel",
"schemars 1.0.4",
"serde",
"serde_json",
"settings",
"smol",
"strsim",
"thiserror 2.0.17",
"util",
"uuid",
"workspace",
"worktree",
"zlog",
]
[[package]]
@@ -21797,7 +21689,6 @@ dependencies = [
"clap",
"client",
"cloud_llm_client",
"cloud_zeta2_prompt",
"collections",
"edit_prediction_context",
"editor",
@@ -21811,6 +21702,7 @@ dependencies = [
"ordered-float 2.10.1",
"pretty_assertions",
"project",
"regex-syntax",
"serde",
"serde_json",
"settings",
@@ -21842,7 +21734,6 @@ dependencies = [
"futures 0.3.31",
"gpui",
"gpui_tokio",
"indoc",
"language",
"language_extension",
"language_model",
@@ -21853,7 +21744,6 @@ dependencies = [
"ordered-float 2.10.1",
"paths",
"polars",
"pretty_assertions",
"project",
"prompt_store",
"pulldown-cmark 0.12.2",

View File

@@ -110,7 +110,6 @@ members = [
"crates/menu",
"crates/migrator",
"crates/mistral",
"crates/miniprofiler_ui",
"crates/multi_buffer",
"crates/nc",
"crates/net",
@@ -127,7 +126,6 @@ members = [
"crates/picker",
"crates/prettier",
"crates/project",
"crates/project_benchmarks",
"crates/project_panel",
"crates/project_symbols",
"crates/prompt_store",
@@ -165,7 +163,6 @@ members = [
"crates/sum_tree",
"crates/supermaven",
"crates/supermaven_api",
"crates/sweep_ai",
"crates/codestral",
"crates/svg_preview",
"crates/system_specs",
@@ -344,7 +341,6 @@ menu = { path = "crates/menu" }
migrator = { path = "crates/migrator" }
mistral = { path = "crates/mistral" }
multi_buffer = { path = "crates/multi_buffer" }
miniprofiler_ui = { path = "crates/miniprofiler_ui" }
nc = { path = "crates/nc" }
net = { path = "crates/net" }
node_runtime = { path = "crates/node_runtime" }
@@ -399,7 +395,6 @@ streaming_diff = { path = "crates/streaming_diff" }
sum_tree = { path = "crates/sum_tree" }
supermaven = { path = "crates/supermaven" }
supermaven_api = { path = "crates/supermaven_api" }
sweep_ai = { path = "crates/sweep_ai" }
codestral = { path = "crates/codestral" }
system_specs = { path = "crates/system_specs" }
tab_switcher = { path = "crates/tab_switcher" }
@@ -480,7 +475,6 @@ bitflags = "2.6.0"
blade-graphics = { version = "0.7.0" }
blade-macros = { version = "0.3.0" }
blade-util = { version = "0.3.0" }
brotli = "8.0.2"
bytes = "1.0"
cargo_metadata = "0.19"
cargo_toml = "0.21"
@@ -488,7 +482,7 @@ cfg-if = "1.0.3"
chrono = { version = "0.4", features = ["serde"] }
ciborium = "0.2"
circular-buffer = "1.0"
clap = { version = "4.4", features = ["derive", "wrap_help"] }
clap = { version = "4.4", features = ["derive"] }
cocoa = "=0.26.0"
cocoa-foundation = "=0.2.0"
convert_case = "0.8.0"
@@ -510,11 +504,11 @@ emojis = "0.6.1"
env_logger = "0.11"
exec = "0.3.1"
fancy-regex = "0.14.0"
fork = "0.4.0"
fork = "0.2.0"
futures = "0.3"
futures-batch = "0.6.1"
futures-lite = "1.13"
gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "3eaa84abca0778eb54272f45a312cb24f9a0b435" }
gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "0090c6b6ef82fff02bc8616645953e778d1acc08" }
git2 = { version = "0.20.1", default-features = false }
globset = "0.4"
handlebars = "4.3"
@@ -537,8 +531,8 @@ itertools = "0.14.0"
json_dotpath = "1.1"
jsonschema = "0.30.0"
jsonwebtoken = "9.3"
jupyter-protocol = "0.10.0"
jupyter-websocket-client = "0.15.0"
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed" ,rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
@@ -551,7 +545,7 @@ minidumper = "0.8"
moka = { version = "0.12.10", features = ["sync"] }
naga = { version = "25.0", features = ["wgsl-in"] }
nanoid = "0.4"
nbformat = "0.15.0"
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
nix = "0.29"
num-format = "0.4.4"
num-traits = "0.2"
@@ -622,8 +616,8 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "c15662
"stream",
], package = "zed-reqwest", version = "0.12.15-zed" }
rsa = "0.9.6"
runtimelib = { version = "0.30.0", default-features = false, features = [
"async-dispatcher-runtime", "aws-lc-rs"
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
"async-dispatcher-runtime",
] }
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustc-hash = "2.1.0"
@@ -634,7 +628,6 @@ scap = { git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197
schemars = { version = "1.0", features = ["indexmap2"] }
semver = "1.0"
serde = { version = "1.0.221", features = ["derive", "rc"] }
serde_derive = "1.0.221"
serde_json = { version = "1.0.144", features = ["preserve_order", "raw_value"] }
serde_json_lenient = { version = "0.2", features = [
"preserve_order",
@@ -670,7 +663,6 @@ time = { version = "0.3", features = [
"serde",
"serde-well-known",
"formatting",
"local-offset",
] }
tiny_http = "0.8"
tokio = { version = "1" }
@@ -688,7 +680,7 @@ tree-sitter-elixir = "0.3"
tree-sitter-embedded-template = "0.23.0"
tree-sitter-gitcommit = { git = "https://github.com/zed-industries/tree-sitter-git-commit", rev = "88309716a69dd13ab83443721ba6e0b491d37ee9" }
tree-sitter-go = "0.23"
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "2e886870578eeba1927a2dc4bd2e2b3f598c5f9a", package = "tree-sitter-gomod" }
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "6efb59652d30e0e9cd5f3b3a669afd6f1a926d3c", package = "tree-sitter-gomod" }
tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
tree-sitter-heex = { git = "https://github.com/zed-industries/tree-sitter-heex", rev = "1dd45142fbb05562e35b2040c6129c9bca346592" }
tree-sitter-html = "0.23"
@@ -728,7 +720,6 @@ yawc = "0.2.5"
zeroize = "1.8"
zstd = "0.11"
[workspace.dependencies.windows]
version = "0.61"
features = [
@@ -781,8 +772,8 @@ features = [
]
[patch.crates-io]
notify = { git = "https://github.com/zed-industries/notify.git", rev = "b4588b2e5aee68f4c0e100f140e808cbce7b1419" }
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "b4588b2e5aee68f4c0e100f140e808cbce7b1419" }
notify = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
windows-capture = { git = "https://github.com/zed-industries/windows-capture.git", rev = "f0d6c1b6691db75461b732f6d5ff56eed002eeb9" }
[profile.dev]
@@ -797,19 +788,6 @@ codegen-units = 16
codegen-units = 16
[profile.dev.package]
# proc-macros start
gpui_macros = { opt-level = 3 }
derive_refineable = { opt-level = 3 }
settings_macros = { opt-level = 3 }
sqlez_macros = { opt-level = 3, codegen-units = 1 }
ui_macros = { opt-level = 3 }
util_macros = { opt-level = 3 }
serde_derive = { opt-level = 3 }
quote = { opt-level = 3 }
syn = { opt-level = 3 }
proc-macro2 = { opt-level = 3 }
# proc-macros end
taffy = { opt-level = 3 }
cranelift-codegen = { opt-level = 3 }
cranelift-codegen-meta = { opt-level = 3 }
@@ -851,6 +829,7 @@ semantic_version = { codegen-units = 1 }
session = { codegen-units = 1 }
snippet = { codegen-units = 1 }
snippets_ui = { codegen-units = 1 }
sqlez_macros = { codegen-units = 1 }
story = { codegen-units = 1 }
supermaven_api = { codegen-units = 1 }
telemetry_events = { codegen-units = 1 }

View File

@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.2
FROM rust:1.91.1-bookworm as builder
FROM rust:1.90-bookworm as builder
WORKDIR app
COPY . .

View File

@@ -1,7 +1,7 @@
# Zed
[![Zed](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/zed-industries/zed/main/assets/badge/v0.json)](https://zed.dev)
[![CI](https://github.com/zed-industries/zed/actions/workflows/run_tests.yml/badge.svg)](https://github.com/zed-industries/zed/actions/workflows/run_tests.yml)
[![CI](https://github.com/zed-industries/zed/actions/workflows/ci.yml/badge.svg)](https://github.com/zed-industries/zed/actions/workflows/ci.yml)
Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter).

View File

@@ -8,114 +8,106 @@
; to other areas too.
<all>
= @cole-miller
= @ConradIrwin
= @danilo-leal
= @dinocosta
= @HactarCE
= @kubkon
= @maxdeviant
= @p1n3appl3
= @probably-neb
= @smitbarmase
= @SomeoneToIgnore
= @Veykril
ai
= @benbrandt
= @bennetbo
= @danilo-leal
= @rtfeldman
audio
= @dvdsk
crashes
= @p1n3appl3
= @Veykril
debugger
= @Anthony-Eid
= @kubkon
= @osiewicz
design
= @danilo-leal
docs
= @probably-neb
= @miguelraz
extension
= @danilo-leal
= @Veykril
= @kubkon
= @p1n3appl3
= @dinocosta
= @smitbarmase
= @cole-miller
vim
= @ConradIrwin
= @probably-neb
= @p1n3appl3
= @dinocosta
gpui
= @mikayla-maki
git
= @cole-miller
= @danilo-leal
gpui
= @Anthony-Eid
= @cameron1024
= @mikayla-maki
linux
= @dvdsk
= @smitbarmase
= @p1n3appl3
= @cole-miller
= @probably-neb
windows
= @reflectronic
= @localcc
pickers
= @p1n3appl3
= @dvdsk
= @SomeoneToIgnore
audio
= @dvdsk
helix
= @kubkon
languages
= @osiewicz
= @probably-neb
= @smitbarmase
= @SomeoneToIgnore
= @Veykril
linux
= @cole-miller
= @dvdsk
= @p1n3appl3
= @probably-neb
= @smitbarmase
lsp
= @osiewicz
= @smitbarmase
= @SomeoneToIgnore
= @Veykril
multi_buffer
= @Veykril
= @SomeoneToIgnore
pickers
= @dvdsk
= @p1n3appl3
= @SomeoneToIgnore
project_panel
= @smitbarmase
settings_ui
= @Anthony-Eid
= @danilo-leal
= @probably-neb
support
= @miguelraz
tasks
= @SomeoneToIgnore
= @Veykril
terminal
= @kubkon
= @Veykril
vim
= @ConradIrwin
= @dinocosta
debugger
= @kubkon
= @osiewicz
= @Anthony-Eid
extension
= @kubkon
settings_ui
= @probably-neb
= @danilo-leal
= @Anthony-Eid
crashes
= @p1n3appl3
= @Veykril
ai
= @rtfeldman
= @danilo-leal
= @benbrandt
= @bennetbo
design
= @danilo-leal
multi_buffer
= @Veykril
= @SomeoneToIgnore
lsp
= @osiewicz
= @Veykril
= @smitbarmase
= @SomeoneToIgnore
languages
= @osiewicz
= @Veykril
= @smitbarmase
= @SomeoneToIgnore
= @probably-neb
windows
= @localcc
= @reflectronic
project_panel
= @smitbarmase
tasks
= @SomeoneToIgnore
= @Veykril
docs
= @probably-neb

View File

@@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00156 10.3996C9.32705 10.3996 10.4016 9.32509 10.4016 7.99961C10.4016 6.67413 9.32705 5.59961 8.00156 5.59961C6.67608 5.59961 5.60156 6.67413 5.60156 7.99961C5.60156 9.32509 6.67608 10.3996 8.00156 10.3996Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.4 5.6V8.6C10.4 9.07739 10.5896 9.53523 10.9272 9.8728C11.2648 10.2104 11.7226 10.4 12.2 10.4C12.6774 10.4 13.1352 10.2104 13.4728 9.8728C13.8104 9.53523 14 9.07739 14 8.6V8C14 6.64839 13.5436 5.33636 12.7048 4.27651C11.8661 3.21665 10.694 2.47105 9.37852 2.16051C8.06306 1.84997 6.68129 1.99269 5.45707 2.56554C4.23285 3.13838 3.23791 4.1078 2.63344 5.31672C2.02898 6.52565 1.85041 7.90325 2.12667 9.22633C2.40292 10.5494 3.11782 11.7405 4.15552 12.6065C5.19323 13.4726 6.49295 13.9629 7.84411 13.998C9.19527 14.0331 10.5187 13.611 11.6 12.8" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.3335 13.3333L8.00017 10L4.66685 13.3333" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.3335 2.66669L8.00017 6.00002L4.66685 2.66669" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 382 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -43,8 +43,7 @@
"f11": "zed::ToggleFullScreen",
"ctrl-alt-z": "edit_prediction::RateCompletions",
"ctrl-alt-shift-i": "edit_prediction::ToggleMenu",
"ctrl-alt-l": "lsp_tool::ToggleMenu",
"ctrl-alt-.": "project_panel::ToggleHideHidden"
"ctrl-alt-l": "lsp_tool::ToggleMenu"
}
},
{
@@ -236,13 +235,12 @@
"ctrl-alt-n": "agent::NewTextThread",
"ctrl-shift-h": "agent::OpenHistory",
"ctrl-alt-c": "agent::OpenSettings",
"ctrl-alt-p": "agent::ManageProfiles",
"ctrl-alt-l": "agent::OpenRulesLibrary",
"ctrl-alt-p": "agent::OpenRulesLibrary",
"ctrl-i": "agent::ToggleProfileSelector",
"ctrl-alt-/": "agent::ToggleModelSelector",
"ctrl-shift-a": "agent::ToggleContextPicker",
"ctrl-shift-j": "agent::ToggleNavigationMenu",
"ctrl-alt-i": "agent::ToggleOptionsMenu",
"ctrl-shift-i": "agent::ToggleOptionsMenu",
"ctrl-alt-shift-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl->": "agent::AddSelectionToThread",
@@ -409,7 +407,6 @@
"bindings": {
"escape": "project_search::ToggleFocus",
"shift-find": "search::FocusSearch",
"shift-enter": "project_search::ToggleAllSearchResults",
"ctrl-shift-f": "search::FocusSearch",
"ctrl-shift-h": "search::ToggleReplace",
"alt-ctrl-g": "search::ToggleRegex",
@@ -482,7 +479,6 @@
"alt-w": "search::ToggleWholeWord",
"alt-find": "project_search::ToggleFilters",
"alt-ctrl-f": "project_search::ToggleFilters",
"shift-enter": "project_search::ToggleAllSearchResults",
"ctrl-alt-shift-r": "search::ToggleRegex",
"ctrl-alt-shift-x": "search::ToggleRegex",
"alt-r": "search::ToggleRegex",
@@ -736,17 +732,11 @@
}
},
{
"context": "Editor && in_snippet && has_next_tabstop && !showing_completions",
"context": "Editor && in_snippet",
"use_key_equivalents": true,
"bindings": {
"tab": "editor::NextSnippetTabstop"
}
},
{
"context": "Editor && in_snippet && has_previous_tabstop && !showing_completions",
"use_key_equivalents": true,
"bindings": {
"shift-tab": "editor::PreviousSnippetTabstop"
"alt-right": "editor::NextSnippetTabstop",
"alt-left": "editor::PreviousSnippetTabstop"
}
},
// Bindings for accepting edit predictions
@@ -951,7 +941,6 @@
"ctrl-g ctrl-g": "git::Fetch",
"ctrl-g up": "git::Push",
"ctrl-g down": "git::Pull",
"ctrl-g shift-down": "git::PullRebase",
"ctrl-g shift-up": "git::ForcePush",
"ctrl-g d": "git::Diff",
"ctrl-g backspace": "git::RestoreTrackedFiles",
@@ -1263,14 +1252,6 @@
"ctrl-shift-enter": "workspace::OpenWithSystem"
}
},
{
"context": "GitWorktreeSelector || (GitWorktreeSelector > Picker > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-space": "git::WorktreeFromDefaultOnWindow",
"ctrl-space": "git::WorktreeFromDefault"
}
},
{
"context": "SettingsWindow",
"use_key_equivalents": true,

View File

@@ -49,8 +49,7 @@
"ctrl-cmd-f": "zed::ToggleFullScreen",
"ctrl-cmd-z": "edit_prediction::RateCompletions",
"ctrl-cmd-i": "edit_prediction::ToggleMenu",
"ctrl-cmd-l": "lsp_tool::ToggleMenu",
"cmd-alt-.": "project_panel::ToggleHideHidden"
"ctrl-cmd-l": "lsp_tool::ToggleMenu"
}
},
{
@@ -275,13 +274,12 @@
"cmd-alt-n": "agent::NewTextThread",
"cmd-shift-h": "agent::OpenHistory",
"cmd-alt-c": "agent::OpenSettings",
"cmd-alt-l": "agent::OpenRulesLibrary",
"cmd-alt-p": "agent::ManageProfiles",
"cmd-alt-p": "agent::OpenRulesLibrary",
"cmd-i": "agent::ToggleProfileSelector",
"cmd-alt-/": "agent::ToggleModelSelector",
"cmd-shift-a": "agent::ToggleContextPicker",
"cmd-shift-j": "agent::ToggleNavigationMenu",
"cmd-alt-m": "agent::ToggleOptionsMenu",
"cmd-shift-i": "agent::ToggleOptionsMenu",
"cmd-alt-shift-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"cmd->": "agent::AddSelectionToThread",
@@ -313,7 +311,7 @@
"use_key_equivalents": true,
"bindings": {
"cmd-n": "agent::NewTextThread",
"cmd-alt-n": "agent::NewExternalAgentThread"
"cmd-alt-t": "agent::NewThread"
}
},
{
@@ -470,7 +468,6 @@
"bindings": {
"escape": "project_search::ToggleFocus",
"cmd-shift-j": "project_search::ToggleFilters",
"shift-enter": "project_search::ToggleAllSearchResults",
"cmd-shift-f": "search::FocusSearch",
"cmd-shift-h": "search::ToggleReplace",
"alt-cmd-g": "search::ToggleRegex",
@@ -499,7 +496,6 @@
"bindings": {
"escape": "project_search::ToggleFocus",
"cmd-shift-j": "project_search::ToggleFilters",
"shift-enter": "project_search::ToggleAllSearchResults",
"cmd-shift-h": "search::ToggleReplace",
"alt-cmd-g": "search::ToggleRegex",
"alt-cmd-x": "search::ToggleRegex"
@@ -806,17 +802,11 @@
}
},
{
"context": "Editor && in_snippet && has_next_tabstop && !showing_completions",
"context": "Editor && in_snippet",
"use_key_equivalents": true,
"bindings": {
"tab": "editor::NextSnippetTabstop"
}
},
{
"context": "Editor && in_snippet && has_previous_tabstop && !showing_completions",
"use_key_equivalents": true,
"bindings": {
"shift-tab": "editor::PreviousSnippetTabstop"
"alt-right": "editor::NextSnippetTabstop",
"alt-left": "editor::PreviousSnippetTabstop"
}
},
{
@@ -1044,7 +1034,6 @@
"ctrl-g ctrl-g": "git::Fetch",
"ctrl-g up": "git::Push",
"ctrl-g down": "git::Pull",
"ctrl-g shift-down": "git::PullRebase",
"ctrl-g shift-up": "git::ForcePush",
"ctrl-g d": "git::Diff",
"ctrl-g backspace": "git::RestoreTrackedFiles",
@@ -1368,14 +1357,6 @@
"ctrl-shift-enter": "workspace::OpenWithSystem"
}
},
{
"context": "GitWorktreeSelector || (GitWorktreeSelector > Picker > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-space": "git::WorktreeFromDefaultOnWindow",
"ctrl-space": "git::WorktreeFromDefault"
}
},
{
"context": "SettingsWindow",
"use_key_equivalents": true,

View File

@@ -41,8 +41,7 @@
"shift-f11": "debugger::StepOut",
"f11": "zed::ToggleFullScreen",
"ctrl-shift-i": "edit_prediction::ToggleMenu",
"shift-alt-l": "lsp_tool::ToggleMenu",
"ctrl-alt-.": "project_panel::ToggleHideHidden"
"shift-alt-l": "lsp_tool::ToggleMenu"
}
},
{
@@ -237,13 +236,12 @@
"shift-alt-n": "agent::NewTextThread",
"ctrl-shift-h": "agent::OpenHistory",
"shift-alt-c": "agent::OpenSettings",
"shift-alt-l": "agent::OpenRulesLibrary",
"shift-alt-p": "agent::ManageProfiles",
"shift-alt-p": "agent::OpenRulesLibrary",
"ctrl-i": "agent::ToggleProfileSelector",
"shift-alt-/": "agent::ToggleModelSelector",
"ctrl-shift-a": "agent::ToggleContextPicker",
"ctrl-shift-j": "agent::ToggleNavigationMenu",
"ctrl-alt-i": "agent::ToggleOptionsMenu",
"ctrl-shift-i": "agent::ToggleOptionsMenu",
// "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl-shift-.": "agent::AddSelectionToThread",
@@ -490,7 +488,6 @@
"alt-c": "search::ToggleCaseSensitive",
"alt-w": "search::ToggleWholeWord",
"alt-f": "project_search::ToggleFilters",
"shift-enter": "project_search::ToggleAllSearchResults",
"alt-r": "search::ToggleRegex",
// "ctrl-shift-alt-x": "search::ToggleRegex",
"ctrl-k shift-enter": "pane::TogglePinTab"
@@ -740,17 +737,11 @@
}
},
{
"context": "Editor && in_snippet && has_next_tabstop && !showing_completions",
"context": "Editor && in_snippet",
"use_key_equivalents": true,
"bindings": {
"tab": "editor::NextSnippetTabstop"
}
},
{
"context": "Editor && in_snippet && has_previous_tabstop && !showing_completions",
"use_key_equivalents": true,
"bindings": {
"shift-tab": "editor::PreviousSnippetTabstop"
"alt-right": "editor::NextSnippetTabstop",
"alt-left": "editor::PreviousSnippetTabstop"
}
},
// Bindings for accepting edit predictions
@@ -960,7 +951,6 @@
"ctrl-g ctrl-g": "git::Fetch",
"ctrl-g up": "git::Push",
"ctrl-g down": "git::Pull",
"ctrl-g shift-down": "git::PullRebase",
"ctrl-g shift-up": "git::ForcePush",
"ctrl-g d": "git::Diff",
"ctrl-g backspace": "git::RestoreTrackedFiles",
@@ -1290,14 +1280,6 @@
"shift-alt-a": "onboarding::OpenAccount"
}
},
{
"context": "GitWorktreeSelector || (GitWorktreeSelector > Picker > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-space": "git::WorktreeFromDefaultOnWindow",
"ctrl-space": "git::WorktreeFromDefault"
}
},
{
"context": "SettingsWindow",
"use_key_equivalents": true,

View File

@@ -449,7 +449,6 @@
"<": "vim::Outdent",
"=": "vim::AutoIndent",
"d": "vim::HelixDelete",
"alt-d": "editor::Delete", // Delete selection, without yanking
"c": "vim::HelixSubstitute",
"alt-c": "vim::HelixSubstituteNoYank",

View File

@@ -255,19 +255,6 @@
// Whether to display inline and alongside documentation for items in the
// completions menu
"show_completion_documentation": true,
// When to show the scrollbar in the completion menu.
// This setting can take four values:
//
// 1. Show the scrollbar if there's important information or
// follow the system's configured behavior
// "auto"
// 2. Match the system's configured behavior:
// "system"
// 3. Always show the scrollbar:
// "always"
// 4. Never show the scrollbar:
// "never" (default)
"completion_menu_scrollbar": "never",
// Show method signatures in the editor, when inside parentheses.
"auto_signature_help": false,
// Whether to show the signature help after completion or a bracket pair inserted.
@@ -605,27 +592,17 @@
// to both the horizontal and vertical delta values while scrolling. Fast scrolling
// happens when a user holds the alt or option key while scrolling.
"fast_scroll_sensitivity": 4.0,
"sticky_scroll": {
// Whether to stick scopes to the top of the editor.
"enabled": false
},
"relative_line_numbers": "disabled",
"relative_line_numbers": false,
// If 'search_wrap' is disabled, search result do not wrap around the end of the file.
"search_wrap": true,
// Search options to enable by default when opening new project and buffer searches.
"search": {
// Whether to show the project search button in the status bar.
"button": true,
// Whether to only match on whole words.
"whole_word": false,
// Whether to match case sensitively.
"case_sensitive": false,
// Whether to include gitignored files in search results.
"include_ignored": false,
// Whether to interpret the search query as a regular expression.
"regex": false,
// Whether to center the cursor on each search match when navigating.
"center_on_match": false
"regex": false
},
// When to populate a new search's query based on the text under the cursor.
// This setting can take the following three values:
@@ -742,31 +719,14 @@
// "never"
"show": "always"
},
// Sort order for entries in the project panel.
// This setting can take three values:
//
// 1. Show directories first, then files:
// "directories_first"
// 2. Mix directories and files together:
// "mixed"
// 3. Show files first, then directories:
// "files_first"
"sort_mode": "directories_first",
// Whether to enable drag-and-drop operations in the project panel.
"drag_and_drop": true,
// Whether to hide the root entry when only one folder is open in the window.
"hide_root": false,
// Whether to hide the hidden entries in the project panel.
"hide_hidden": false,
// Settings for automatically opening files.
"auto_open": {
// Whether to automatically open newly created files in the editor.
"on_create": true,
// Whether to automatically open files after pasting or duplicating them.
"on_paste": true,
// Whether to automatically open files dropped from external sources.
"on_drop": true
}
// Whether to automatically open files when pasting them in the project panel.
"open_file_on_paste": true
},
"outline_panel": {
// Whether to show the outline panel button in the status bar
@@ -1272,9 +1232,6 @@
// that are overly broad can slow down Zed's file scanning. `file_scan_exclusions` takes
// precedence over these inclusions.
"file_scan_inclusions": [".env*"],
// Globs to match files that will be considered "hidden". These files can be hidden from the
// project panel by toggling the "hide_hidden" setting.
"hidden_files": ["**/.*"],
// Git gutter behavior configuration.
"git": {
// Control whether the git gutter is shown. May take 2 values:
@@ -1512,11 +1469,7 @@
// in your project's settings, rather than globally.
"directories": [".env", "env", ".venv", "venv"],
// Can also be `csh`, `fish`, `nushell` and `power_shell`
"activate_script": "default",
// Preferred Conda manager to use when activating Conda environments.
// Values: "auto", "conda", "mamba", "micromamba"
// Default: "auto"
"conda_manager": "auto"
"activate_script": "default"
}
},
"toolbar": {
@@ -1560,8 +1513,6 @@
// Default: 10_000, maximum: 100_000 (all bigger values set will be treated as 100_000), 0 disables the scrolling.
// Existing terminals will not pick up this change until they are recreated.
"max_scroll_history_lines": 10000,
// The multiplier for scrolling speed in the terminal.
"scroll_multiplier": 1.0,
// The minimum APCA perceptual contrast between foreground and background colors.
// APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x,
// especially for dark mode. Values range from 0 to 106.
@@ -1768,9 +1719,6 @@
"allowed": true
}
},
"HTML+ERB": {
"language_servers": ["herb", "!ruby-lsp", "..."]
},
"Java": {
"prettier": {
"allowed": true,
@@ -1793,9 +1741,6 @@
"allowed": true
}
},
"JS+ERB": {
"language_servers": ["!ruby-lsp", "..."]
},
"Kotlin": {
"language_servers": ["!kotlin-language-server", "kotlin-lsp", "..."]
},
@@ -1810,7 +1755,6 @@
"Markdown": {
"format_on_save": "off",
"use_on_type_format": false,
"remove_trailing_whitespace_on_save": false,
"allow_rewrap": "anywhere",
"soft_wrap": "editor_width",
"prettier": {
@@ -1901,9 +1845,6 @@
"allowed": true
}
},
"YAML+ERB": {
"language_servers": ["!ruby-lsp", "..."]
},
"Zig": {
"language_servers": ["zls", "..."]
}

View File

@@ -39,7 +39,6 @@ serde_json.workspace = true
settings.workspace = true
smol.workspace = true
task.workspace = true
telemetry.workspace = true
terminal.workspace = true
ui.workspace = true
url.workspace = true
@@ -57,4 +56,3 @@ rand.workspace = true
tempfile.workspace = true
util.workspace = true
settings.workspace = true
zlog.workspace = true

View File

@@ -3,6 +3,7 @@ mod diff;
mod mention;
mod terminal;
use ::terminal::terminal_settings::TerminalSettings;
use agent_settings::AgentSettings;
use collections::HashSet;
pub use connection::*;
@@ -11,11 +12,11 @@ use language::language_settings::FormatOnSave;
pub use mention::*;
use project::lsp_store::{FormatTrigger, LspFormatTarget};
use serde::{Deserialize, Serialize};
use settings::Settings as _;
use settings::{Settings as _, SettingsLocation};
use task::{Shell, ShellBuilder};
pub use terminal::*;
use action_log::{ActionLog, ActionLogTelemetry};
use action_log::ActionLog;
use agent_client_protocol::{self as acp};
use anyhow::{Context as _, Result, anyhow};
use editor::Bias;
@@ -820,15 +821,6 @@ pub struct AcpThread {
pending_terminal_exit: HashMap<acp::TerminalId, acp::TerminalExitStatus>,
}
impl From<&AcpThread> for ActionLogTelemetry {
fn from(value: &AcpThread) -> Self {
Self {
agent_telemetry_id: value.connection().telemetry_id(),
session_id: value.session_id.0.clone(),
}
}
}
#[derive(Debug)]
pub enum AcpThreadEvent {
NewEntry,
@@ -1355,17 +1347,6 @@ impl AcpThread {
let path_style = self.project.read(cx).path_style(cx);
let id = update.id.clone();
let agent = self.connection().telemetry_id();
let session = self.session_id();
if let ToolCallStatus::Completed | ToolCallStatus::Failed = status {
let status = if matches!(status, ToolCallStatus::Completed) {
"completed"
} else {
"failed"
};
telemetry::event!("Agent Tool Call Completed", agent, session, status);
}
if let Some(ix) = self.index_for_tool_call(&id) {
let AgentThreadEntry::ToolCall(call) = &mut self.entries[ix] else {
unreachable!()
@@ -1866,14 +1847,10 @@ impl AcpThread {
.checkpoint
.as_ref()
.map(|c| c.git_checkpoint.clone());
// Cancel any in-progress generation before restoring
let cancel_task = self.cancel(cx);
let rewind = self.rewind(id.clone(), cx);
let git_store = self.project.read(cx).git_store().clone();
cx.spawn(async move |_, cx| {
cancel_task.await;
rewind.await?;
if let Some(checkpoint) = checkpoint {
git_store
@@ -1893,34 +1870,16 @@ impl AcpThread {
return Task::ready(Err(anyhow!("not supported")));
};
let telemetry = ActionLogTelemetry::from(&*self);
cx.spawn(async move |this, cx| {
cx.update(|cx| truncate.run(id.clone(), cx))?.await?;
this.update(cx, |this, cx| {
if let Some((ix, _)) = this.user_message_mut(&id) {
// Collect all terminals from entries that will be removed
let terminals_to_remove: Vec<acp::TerminalId> = this.entries[ix..]
.iter()
.flat_map(|entry| entry.terminals())
.filter_map(|terminal| terminal.read(cx).id().clone().into())
.collect();
let range = ix..this.entries.len();
this.entries.truncate(ix);
cx.emit(AcpThreadEvent::EntriesRemoved(range));
// Kill and remove the terminals
for terminal_id in terminals_to_remove {
if let Some(terminal) = this.terminals.remove(&terminal_id) {
terminal.update(cx, |terminal, cx| {
terminal.kill(cx);
});
}
}
}
this.action_log().update(cx, |action_log, cx| {
action_log.reject_all_edits(Some(telemetry), cx)
})
this.action_log()
.update(cx, |action_log, cx| action_log.reject_all_edits(cx))
})?
.await;
Ok(())
@@ -2182,9 +2141,17 @@ impl AcpThread {
) -> Task<Result<Entity<Terminal>>> {
let env = match &cwd {
Some(dir) => self.project.update(cx, |project, cx| {
project.environment().update(cx, |env, cx| {
env.directory_environment(dir.as_path().into(), cx)
})
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
project.directory_environment(&shell, dir.as_path().into(), cx)
}),
None => Task::ready(None).shared(),
};
@@ -2397,6 +2364,8 @@ mod tests {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
Project::init_settings(cx);
language::init(cx);
});
}
@@ -3654,10 +3623,6 @@ mod tests {
}
impl AgentConnection for FakeAgentConnection {
fn telemetry_id(&self) -> &'static str {
"fake"
}
fn auth_methods(&self) -> &[acp::AuthMethod] {
&self.auth_methods
}
@@ -3823,314 +3788,4 @@ mod tests {
}
});
}
/// Tests that restoring a checkpoint properly cleans up terminals that were
/// created after that checkpoint, and cancels any in-progress generation.
///
/// Reproduces issue #35142: When a checkpoint is restored, any terminal processes
/// that were started after that checkpoint should be terminated, and any in-progress
/// AI generation should be canceled.
#[gpui::test]
async fn test_restore_checkpoint_kills_terminal(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, [], cx).await;
let connection = Rc::new(FakeAgentConnection::new());
let thread = cx
.update(|cx| connection.new_thread(project, Path::new(path!("/test")), cx))
.await
.unwrap();
// Send first user message to create a checkpoint
cx.update(|cx| {
thread.update(cx, |thread, cx| {
thread.send(vec!["first message".into()], cx)
})
})
.await
.unwrap();
// Send second message (creates another checkpoint) - we'll restore to this one
cx.update(|cx| {
thread.update(cx, |thread, cx| {
thread.send(vec!["second message".into()], cx)
})
})
.await
.unwrap();
// Create 2 terminals BEFORE the checkpoint that have completed running
let terminal_id_1 = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
let mock_terminal_1 = cx.new(|cx| {
let builder = ::terminal::TerminalBuilder::new_display_only(
::terminal::terminal_settings::CursorShape::default(),
::terminal::terminal_settings::AlternateScroll::On,
None,
0,
)
.unwrap();
builder.subscribe(cx)
});
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Created {
terminal_id: terminal_id_1.clone(),
label: "echo 'first'".to_string(),
cwd: Some(PathBuf::from("/test")),
output_byte_limit: None,
terminal: mock_terminal_1.clone(),
},
cx,
);
});
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Output {
terminal_id: terminal_id_1.clone(),
data: b"first\n".to_vec(),
},
cx,
);
});
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Exit {
terminal_id: terminal_id_1.clone(),
status: acp::TerminalExitStatus {
exit_code: Some(0),
signal: None,
meta: None,
},
},
cx,
);
});
let terminal_id_2 = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
let mock_terminal_2 = cx.new(|cx| {
let builder = ::terminal::TerminalBuilder::new_display_only(
::terminal::terminal_settings::CursorShape::default(),
::terminal::terminal_settings::AlternateScroll::On,
None,
0,
)
.unwrap();
builder.subscribe(cx)
});
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Created {
terminal_id: terminal_id_2.clone(),
label: "echo 'second'".to_string(),
cwd: Some(PathBuf::from("/test")),
output_byte_limit: None,
terminal: mock_terminal_2.clone(),
},
cx,
);
});
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Output {
terminal_id: terminal_id_2.clone(),
data: b"second\n".to_vec(),
},
cx,
);
});
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Exit {
terminal_id: terminal_id_2.clone(),
status: acp::TerminalExitStatus {
exit_code: Some(0),
signal: None,
meta: None,
},
},
cx,
);
});
// Get the second message ID to restore to
let second_message_id = thread.read_with(cx, |thread, _| {
// At this point we have:
// - Index 0: First user message (with checkpoint)
// - Index 1: Second user message (with checkpoint)
// No assistant responses because FakeAgentConnection just returns EndTurn
let AgentThreadEntry::UserMessage(message) = &thread.entries[1] else {
panic!("expected user message at index 1");
};
message.id.clone().unwrap()
});
// Create a terminal AFTER the checkpoint we'll restore to.
// This simulates the AI agent starting a long-running terminal command.
let terminal_id = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
let mock_terminal = cx.new(|cx| {
let builder = ::terminal::TerminalBuilder::new_display_only(
::terminal::terminal_settings::CursorShape::default(),
::terminal::terminal_settings::AlternateScroll::On,
None,
0,
)
.unwrap();
builder.subscribe(cx)
});
// Register the terminal as created
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Created {
terminal_id: terminal_id.clone(),
label: "sleep 1000".to_string(),
cwd: Some(PathBuf::from("/test")),
output_byte_limit: None,
terminal: mock_terminal.clone(),
},
cx,
);
});
// Simulate the terminal producing output (still running)
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Output {
terminal_id: terminal_id.clone(),
data: b"terminal is running...\n".to_vec(),
},
cx,
);
});
// Create a tool call entry that references this terminal
// This represents the agent requesting a terminal command
thread.update(cx, |thread, cx| {
thread
.handle_session_update(
acp::SessionUpdate::ToolCall(acp::ToolCall {
id: acp::ToolCallId("terminal-tool-1".into()),
title: "Running command".into(),
kind: acp::ToolKind::Execute,
status: acp::ToolCallStatus::InProgress,
content: vec![acp::ToolCallContent::Terminal {
terminal_id: terminal_id.clone(),
}],
locations: vec![],
raw_input: Some(
serde_json::json!({"command": "sleep 1000", "cd": "/test"}),
),
raw_output: None,
meta: None,
}),
cx,
)
.unwrap();
});
// Verify terminal exists and is in the thread
let terminal_exists_before =
thread.read_with(cx, |thread, _| thread.terminals.contains_key(&terminal_id));
assert!(
terminal_exists_before,
"Terminal should exist before checkpoint restore"
);
// Verify the terminal's underlying task is still running (not completed)
let terminal_running_before = thread.read_with(cx, |thread, _cx| {
let terminal_entity = thread.terminals.get(&terminal_id).unwrap();
terminal_entity.read_with(cx, |term, _cx| {
term.output().is_none() // output is None means it's still running
})
});
assert!(
terminal_running_before,
"Terminal should be running before checkpoint restore"
);
// Verify we have the expected entries before restore
let entry_count_before = thread.read_with(cx, |thread, _| thread.entries.len());
assert!(
entry_count_before > 1,
"Should have multiple entries before restore"
);
// Restore the checkpoint to the second message.
// This should:
// 1. Cancel any in-progress generation (via the cancel() call)
// 2. Remove the terminal that was created after that point
thread
.update(cx, |thread, cx| {
thread.restore_checkpoint(second_message_id, cx)
})
.await
.unwrap();
// Verify that no send_task is in progress after restore
// (cancel() clears the send_task)
let has_send_task_after = thread.read_with(cx, |thread, _| thread.send_task.is_some());
assert!(
!has_send_task_after,
"Should not have a send_task after restore (cancel should have cleared it)"
);
// Verify the entries were truncated (restoring to index 1 truncates at 1, keeping only index 0)
let entry_count = thread.read_with(cx, |thread, _| thread.entries.len());
assert_eq!(
entry_count, 1,
"Should have 1 entry after restore (only the first user message)"
);
// Verify the 2 completed terminals from before the checkpoint still exist
let terminal_1_exists = thread.read_with(cx, |thread, _| {
thread.terminals.contains_key(&terminal_id_1)
});
assert!(
terminal_1_exists,
"Terminal 1 (from before checkpoint) should still exist"
);
let terminal_2_exists = thread.read_with(cx, |thread, _| {
thread.terminals.contains_key(&terminal_id_2)
});
assert!(
terminal_2_exists,
"Terminal 2 (from before checkpoint) should still exist"
);
// Verify they're still in completed state
let terminal_1_completed = thread.read_with(cx, |thread, _cx| {
let terminal_entity = thread.terminals.get(&terminal_id_1).unwrap();
terminal_entity.read_with(cx, |term, _cx| term.output().is_some())
});
assert!(terminal_1_completed, "Terminal 1 should still be completed");
let terminal_2_completed = thread.read_with(cx, |thread, _cx| {
let terminal_entity = thread.terminals.get(&terminal_id_2).unwrap();
terminal_entity.read_with(cx, |term, _cx| term.output().is_some())
});
assert!(terminal_2_completed, "Terminal 2 should still be completed");
// Verify the running terminal (created after checkpoint) was removed
let terminal_3_exists =
thread.read_with(cx, |thread, _| thread.terminals.contains_key(&terminal_id));
assert!(
!terminal_3_exists,
"Terminal 3 (created after checkpoint) should have been removed"
);
// Verify total count is 2 (the two from before the checkpoint)
let terminal_count = thread.read_with(cx, |thread, _| thread.terminals.len());
assert_eq!(
terminal_count, 2,
"Should have exactly 2 terminals (the completed ones from before checkpoint)"
);
}
}

View File

@@ -20,8 +20,6 @@ impl UserMessageId {
}
pub trait AgentConnection {
fn telemetry_id(&self) -> &'static str;
fn new_thread(
self: Rc<Self>,
project: Entity<Project>,
@@ -108,6 +106,9 @@ pub trait AgentSessionSetTitle {
}
pub trait AgentTelemetry {
/// The name of the agent used for telemetry.
fn agent_name(&self) -> String;
/// A representation of the current thread state that can be serialized for
/// storage with telemetry events.
fn thread_data(
@@ -317,10 +318,6 @@ mod test_support {
}
impl AgentConnection for StubAgentConnection {
fn telemetry_id(&self) -> &'static str {
"stub"
}
fn auth_methods(&self) -> &[acp::AuthMethod] {
&[]
}

View File

@@ -5,8 +5,10 @@ use gpui::{App, AppContext, AsyncApp, Context, Entity, Task};
use language::LanguageRegistry;
use markdown::Markdown;
use project::Project;
use settings::{Settings as _, SettingsLocation};
use std::{path::PathBuf, process::ExitStatus, sync::Arc, time::Instant};
use task::Shell;
use terminal::terminal_settings::TerminalSettings;
use util::get_default_system_shell_preferring_bash;
pub struct Terminal {
@@ -185,9 +187,17 @@ pub async fn create_terminal_entity(
let mut env = if let Some(dir) = &cwd {
project
.update(cx, |project, cx| {
project.environment().update(cx, |env, cx| {
env.directory_environment(dir.clone().into(), cx)
})
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
project.directory_environment(&shell, dir.clone().into(), cx)
})?
.await
.unwrap_or_default()

View File

@@ -19,7 +19,7 @@ use markdown::{CodeBlockRenderer, Markdown, MarkdownElement, MarkdownStyle};
use project::Project;
use settings::Settings;
use theme::ThemeSettings;
use ui::{Tooltip, WithScrollbar, prelude::*};
use ui::{Tooltip, prelude::*};
use util::ResultExt as _;
use workspace::{
Item, ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
@@ -291,19 +291,17 @@ impl AcpTools {
let expanded = self.expanded.contains(&index);
v_flex()
.id(index)
.group("message")
.cursor_pointer()
.font_buffer(cx)
.w_full()
.px_4()
.py_3()
.pl_4()
.pr_5()
.gap_2()
.items_start()
.text_size(base_size)
.border_color(colors.border)
.border_b_1()
.gap_2()
.items_start()
.font_buffer(cx)
.text_size(base_size)
.id(index)
.group("message")
.hover(|this| this.bg(colors.element_background.opacity(0.5)))
.on_click(cx.listener(move |this, _, _, cx| {
if this.expanded.contains(&index) {
@@ -325,14 +323,15 @@ impl AcpTools {
h_flex()
.w_full()
.gap_2()
.items_center()
.flex_shrink_0()
.child(match message.direction {
acp::StreamMessageDirection::Incoming => Icon::new(IconName::ArrowDown)
.color(Color::Error)
.size(IconSize::Small),
acp::StreamMessageDirection::Outgoing => Icon::new(IconName::ArrowUp)
.color(Color::Success)
.size(IconSize::Small),
acp::StreamMessageDirection::Incoming => {
ui::Icon::new(ui::IconName::ArrowDown).color(Color::Error)
}
acp::StreamMessageDirection::Outgoing => {
ui::Icon::new(ui::IconName::ArrowUp).color(Color::Success)
}
})
.child(
Label::new(message.name.clone())
@@ -502,7 +501,7 @@ impl Focusable for AcpTools {
}
impl Render for AcpTools {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.track_focus(&self.focus_handle)
.size_full()
@@ -517,19 +516,13 @@ impl Render for AcpTools {
.child("No messages recorded yet")
.into_any()
} else {
div()
.size_full()
.flex_grow()
.child(
list(
connection.list_state.clone(),
cx.processor(Self::render_message),
)
.with_sizing_behavior(gpui::ListSizingBehavior::Auto)
.size_full(),
)
.vertical_scrollbar_for(connection.list_state.clone(), window, cx)
.into_any()
list(
connection.list_state.clone(),
cx.processor(Self::render_message),
)
.with_sizing_behavior(gpui::ListSizingBehavior::Auto)
.flex_grow()
.into_any()
}
}
None => h_flex()

View File

@@ -20,7 +20,6 @@ futures.workspace = true
gpui.workspace = true
language.workspace = true
project.workspace = true
telemetry.workspace = true
text.workspace = true
util.workspace = true
watch.workspace = true

View File

@@ -3,9 +3,7 @@ use buffer_diff::BufferDiff;
use clock;
use collections::BTreeMap;
use futures::{FutureExt, StreamExt, channel::mpsc};
use gpui::{
App, AppContext, AsyncApp, Context, Entity, SharedString, Subscription, Task, WeakEntity,
};
use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task, WeakEntity};
use language::{Anchor, Buffer, BufferEvent, DiskState, Point, ToPoint};
use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle};
use std::{cmp, ops::Range, sync::Arc};
@@ -33,6 +31,71 @@ impl ActionLog {
&self.project
}
pub fn latest_snapshot(&self, buffer: &Entity<Buffer>) -> Option<text::BufferSnapshot> {
Some(self.tracked_buffers.get(buffer)?.snapshot.clone())
}
/// Return a unified diff patch with user edits made since last read or notification
pub fn unnotified_user_edits(&self, cx: &Context<Self>) -> Option<String> {
let diffs = self
.tracked_buffers
.values()
.filter_map(|tracked| {
if !tracked.may_have_unnotified_user_edits {
return None;
}
let text_with_latest_user_edits = tracked.diff_base.to_string();
let text_with_last_seen_user_edits = tracked.last_seen_base.to_string();
if text_with_latest_user_edits == text_with_last_seen_user_edits {
return None;
}
let patch = language::unified_diff(
&text_with_last_seen_user_edits,
&text_with_latest_user_edits,
);
let buffer = tracked.buffer.clone();
let file_path = buffer
.read(cx)
.file()
.map(|file| {
let mut path = file.full_path(cx).to_string_lossy().into_owned();
if file.path_style(cx).is_windows() {
path = path.replace('\\', "/");
}
path
})
.unwrap_or_else(|| format!("buffer_{}", buffer.entity_id()));
let mut result = String::new();
result.push_str(&format!("--- a/{}\n", file_path));
result.push_str(&format!("+++ b/{}\n", file_path));
result.push_str(&patch);
Some(result)
})
.collect::<Vec<_>>();
if diffs.is_empty() {
return None;
}
let unified_diff = diffs.join("\n\n");
Some(unified_diff)
}
/// Return a unified diff patch with user edits made since last read/notification
/// and mark them as notified
pub fn flush_unnotified_user_edits(&mut self, cx: &Context<Self>) -> Option<String> {
let patch = self.unnotified_user_edits(cx);
self.tracked_buffers.values_mut().for_each(|tracked| {
tracked.may_have_unnotified_user_edits = false;
tracked.last_seen_base = tracked.diff_base.clone();
});
patch
}
fn track_buffer_internal(
&mut self,
buffer: Entity<Buffer>,
@@ -82,26 +145,31 @@ impl ActionLog {
let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
let (diff_update_tx, diff_update_rx) = mpsc::unbounded();
let diff_base;
let last_seen_base;
let unreviewed_edits;
if is_created {
diff_base = Rope::default();
last_seen_base = Rope::default();
unreviewed_edits = Patch::new(vec![Edit {
old: 0..1,
new: 0..text_snapshot.max_point().row + 1,
}])
} else {
diff_base = buffer.read(cx).as_rope().clone();
last_seen_base = diff_base.clone();
unreviewed_edits = Patch::default();
}
TrackedBuffer {
buffer: buffer.clone(),
diff_base,
last_seen_base,
unreviewed_edits,
snapshot: text_snapshot,
status,
version: buffer.read(cx).version(),
diff,
diff_update: diff_update_tx,
may_have_unnotified_user_edits: false,
_open_lsp_handle: open_lsp_handle,
_maintain_diff: cx.spawn({
let buffer = buffer.clone();
@@ -252,9 +320,10 @@ impl ActionLog {
let new_snapshot = buffer_snapshot.clone();
let unreviewed_edits = tracked_buffer.unreviewed_edits.clone();
let edits = diff_snapshots(&old_snapshot, &new_snapshot);
let mut has_user_changes = false;
async move {
if let ChangeAuthor::User = author {
apply_non_conflicting_edits(
has_user_changes = apply_non_conflicting_edits(
&unreviewed_edits,
edits,
&mut base_text,
@@ -262,13 +331,22 @@ impl ActionLog {
);
}
(Arc::new(base_text.to_string()), base_text)
(Arc::new(base_text.to_string()), base_text, has_user_changes)
}
});
anyhow::Ok(rebase)
})??;
let (new_base_text, new_diff_base) = rebase.await;
let (new_base_text, new_diff_base, has_user_changes) = rebase.await;
this.update(cx, |this, _| {
let tracked_buffer = this
.tracked_buffers
.get_mut(buffer)
.context("buffer not tracked")
.unwrap();
tracked_buffer.may_have_unnotified_user_edits |= has_user_changes;
})?;
Self::update_diff(
this,
@@ -487,17 +565,14 @@ impl ActionLog {
&mut self,
buffer: Entity<Buffer>,
buffer_range: Range<impl language::ToPoint>,
telemetry: Option<ActionLogTelemetry>,
cx: &mut Context<Self>,
) {
let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else {
return;
};
let mut metrics = ActionLogMetrics::for_buffer(buffer.read(cx));
match tracked_buffer.status {
TrackedBufferStatus::Deleted => {
metrics.add_edits(tracked_buffer.unreviewed_edits.edits());
self.tracked_buffers.remove(&buffer);
cx.notify();
}
@@ -506,6 +581,7 @@ impl ActionLog {
let buffer_range =
buffer_range.start.to_point(buffer)..buffer_range.end.to_point(buffer);
let mut delta = 0i32;
tracked_buffer.unreviewed_edits.retain_mut(|edit| {
edit.old.start = (edit.old.start as i32 + delta) as u32;
edit.old.end = (edit.old.end as i32 + delta) as u32;
@@ -537,7 +613,6 @@ impl ActionLog {
.collect::<String>(),
);
delta += edit.new_len() as i32 - edit.old_len() as i32;
metrics.add_edit(edit);
false
}
});
@@ -549,24 +624,19 @@ impl ActionLog {
tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx);
}
}
if let Some(telemetry) = telemetry {
telemetry_report_accepted_edits(&telemetry, metrics);
}
}
pub fn reject_edits_in_ranges(
&mut self,
buffer: Entity<Buffer>,
buffer_ranges: Vec<Range<impl language::ToPoint>>,
telemetry: Option<ActionLogTelemetry>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else {
return Task::ready(Ok(()));
};
let mut metrics = ActionLogMetrics::for_buffer(buffer.read(cx));
let task = match &tracked_buffer.status {
match &tracked_buffer.status {
TrackedBufferStatus::Created {
existing_file_content,
} => {
@@ -616,7 +686,6 @@ impl ActionLog {
}
};
metrics.add_edits(tracked_buffer.unreviewed_edits.edits());
self.tracked_buffers.remove(&buffer);
cx.notify();
task
@@ -630,7 +699,6 @@ impl ActionLog {
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx));
// Clear all tracked edits for this buffer and start over as if we just read it.
metrics.add_edits(tracked_buffer.unreviewed_edits.edits());
self.tracked_buffers.remove(&buffer);
self.buffer_read(buffer.clone(), cx);
cx.notify();
@@ -670,7 +738,6 @@ impl ActionLog {
}
if revert {
metrics.add_edit(edit);
let old_range = tracked_buffer
.diff_base
.point_to_offset(Point::new(edit.old.start, 0))
@@ -691,25 +758,12 @@ impl ActionLog {
self.project
.update(cx, |project, cx| project.save_buffer(buffer, cx))
}
};
if let Some(telemetry) = telemetry {
telemetry_report_rejected_edits(&telemetry, metrics);
}
task
}
pub fn keep_all_edits(
&mut self,
telemetry: Option<ActionLogTelemetry>,
cx: &mut Context<Self>,
) {
self.tracked_buffers.retain(|buffer, tracked_buffer| {
let mut metrics = ActionLogMetrics::for_buffer(buffer.read(cx));
metrics.add_edits(tracked_buffer.unreviewed_edits.edits());
if let Some(telemetry) = telemetry.as_ref() {
telemetry_report_accepted_edits(telemetry, metrics);
}
match tracked_buffer.status {
pub fn keep_all_edits(&mut self, cx: &mut Context<Self>) {
self.tracked_buffers
.retain(|_buffer, tracked_buffer| match tracked_buffer.status {
TrackedBufferStatus::Deleted => false,
_ => {
if let TrackedBufferStatus::Created { .. } = &mut tracked_buffer.status {
@@ -720,24 +774,13 @@ impl ActionLog {
tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx);
true
}
}
});
});
cx.notify();
}
pub fn reject_all_edits(
&mut self,
telemetry: Option<ActionLogTelemetry>,
cx: &mut Context<Self>,
) -> Task<()> {
pub fn reject_all_edits(&mut self, cx: &mut Context<Self>) -> Task<()> {
let futures = self.changed_buffers(cx).into_keys().map(|buffer| {
let reject = self.reject_edits_in_ranges(
buffer,
vec![Anchor::MIN..Anchor::MAX],
telemetry.clone(),
cx,
);
let reject = self.reject_edits_in_ranges(buffer, vec![Anchor::MIN..Anchor::MAX], cx);
async move {
reject.await.log_err();
@@ -745,7 +788,8 @@ impl ActionLog {
});
let task = futures::future::join_all(futures);
cx.background_spawn(async move {
cx.spawn(async move |_, _| {
task.await;
})
}
@@ -775,61 +819,6 @@ impl ActionLog {
}
}
#[derive(Clone)]
pub struct ActionLogTelemetry {
pub agent_telemetry_id: &'static str,
pub session_id: Arc<str>,
}
struct ActionLogMetrics {
lines_removed: u32,
lines_added: u32,
language: Option<SharedString>,
}
impl ActionLogMetrics {
fn for_buffer(buffer: &Buffer) -> Self {
Self {
language: buffer.language().map(|l| l.name().0),
lines_removed: 0,
lines_added: 0,
}
}
fn add_edits(&mut self, edits: &[Edit<u32>]) {
for edit in edits {
self.add_edit(edit);
}
}
fn add_edit(&mut self, edit: &Edit<u32>) {
self.lines_added += edit.new_len();
self.lines_removed += edit.old_len();
}
}
fn telemetry_report_accepted_edits(telemetry: &ActionLogTelemetry, metrics: ActionLogMetrics) {
telemetry::event!(
"Agent Edits Accepted",
agent = telemetry.agent_telemetry_id,
session = telemetry.session_id,
language = metrics.language,
lines_added = metrics.lines_added,
lines_removed = metrics.lines_removed
);
}
fn telemetry_report_rejected_edits(telemetry: &ActionLogTelemetry, metrics: ActionLogMetrics) {
telemetry::event!(
"Agent Edits Rejected",
agent = telemetry.agent_telemetry_id,
session = telemetry.session_id,
language = metrics.language,
lines_added = metrics.lines_added,
lines_removed = metrics.lines_removed
);
}
fn apply_non_conflicting_edits(
patch: &Patch<u32>,
edits: Vec<Edit<u32>>,
@@ -960,12 +949,14 @@ enum TrackedBufferStatus {
struct TrackedBuffer {
buffer: Entity<Buffer>,
diff_base: Rope,
last_seen_base: Rope,
unreviewed_edits: Patch<u32>,
status: TrackedBufferStatus,
version: clock::Global,
diff: Entity<BufferDiff>,
snapshot: text::BufferSnapshot,
diff_update: mpsc::UnboundedSender<(ChangeAuthor, text::BufferSnapshot)>,
may_have_unnotified_user_edits: bool,
_open_lsp_handle: OpenLspBufferHandle,
_maintain_diff: Task<()>,
_subscription: Subscription,
@@ -996,6 +987,7 @@ mod tests {
use super::*;
use buffer_diff::DiffHunkStatusKind;
use gpui::TestAppContext;
use indoc::indoc;
use language::Point;
use project::{FakeFs, Fs, Project, RemoveOptions};
use rand::prelude::*;
@@ -1013,6 +1005,8 @@ mod tests {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
});
}
@@ -1072,7 +1066,7 @@ mod tests {
);
action_log.update(cx, |log, cx| {
log.keep_edits_in_range(buffer.clone(), Point::new(3, 0)..Point::new(4, 3), None, cx)
log.keep_edits_in_range(buffer.clone(), Point::new(3, 0)..Point::new(4, 3), cx)
});
cx.run_until_parked();
assert_eq!(
@@ -1088,7 +1082,7 @@ mod tests {
);
action_log.update(cx, |log, cx| {
log.keep_edits_in_range(buffer.clone(), Point::new(0, 0)..Point::new(4, 3), None, cx)
log.keep_edits_in_range(buffer.clone(), Point::new(0, 0)..Point::new(4, 3), cx)
});
cx.run_until_parked();
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
@@ -1173,7 +1167,7 @@ mod tests {
);
action_log.update(cx, |log, cx| {
log.keep_edits_in_range(buffer.clone(), Point::new(1, 0)..Point::new(1, 0), None, cx)
log.keep_edits_in_range(buffer.clone(), Point::new(1, 0)..Point::new(1, 0), cx)
});
cx.run_until_parked();
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
@@ -1270,7 +1264,111 @@ mod tests {
);
action_log.update(cx, |log, cx| {
log.keep_edits_in_range(buffer.clone(), Point::new(0, 0)..Point::new(1, 0), None, cx)
log.keep_edits_in_range(buffer.clone(), Point::new(0, 0)..Point::new(1, 0), cx)
});
cx.run_until_parked();
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
}
#[gpui::test(iterations = 10)]
async fn test_user_edits_notifications(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/dir"),
json!({"file": indoc! {"
abc
def
ghi
jkl
mno"}}),
)
.await;
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let file_path = project
.read_with(cx, |project, cx| project.find_project_path("dir/file", cx))
.unwrap();
let buffer = project
.update(cx, |project, cx| project.open_buffer(file_path, cx))
.await
.unwrap();
// Agent edits
cx.update(|cx| {
action_log.update(cx, |log, cx| log.buffer_read(buffer.clone(), cx));
buffer.update(cx, |buffer, cx| {
buffer
.edit([(Point::new(1, 2)..Point::new(2, 3), "F\nGHI")], None, cx)
.unwrap()
});
action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
});
cx.run_until_parked();
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.text()),
indoc! {"
abc
deF
GHI
jkl
mno"}
);
assert_eq!(
unreviewed_hunks(&action_log, cx),
vec![(
buffer.clone(),
vec![HunkStatus {
range: Point::new(1, 0)..Point::new(3, 0),
diff_status: DiffHunkStatusKind::Modified,
old_text: "def\nghi\n".into(),
}],
)]
);
// User edits
buffer.update(cx, |buffer, cx| {
buffer.edit(
[
(Point::new(0, 2)..Point::new(0, 2), "X"),
(Point::new(3, 0)..Point::new(3, 0), "Y"),
],
None,
cx,
)
});
cx.run_until_parked();
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.text()),
indoc! {"
abXc
deF
GHI
Yjkl
mno"}
);
// User edits should be stored separately from agent's
let user_edits = action_log.update(cx, |log, cx| log.unnotified_user_edits(cx));
assert_eq!(
user_edits.expect("should have some user edits"),
indoc! {"
--- a/dir/file
+++ b/dir/file
@@ -1,5 +1,5 @@
-abc
+abXc
def
ghi
-jkl
+Yjkl
mno
"}
);
action_log.update(cx, |log, cx| {
log.keep_edits_in_range(buffer.clone(), Point::new(0, 0)..Point::new(1, 0), cx)
});
cx.run_until_parked();
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
@@ -1329,7 +1427,7 @@ mod tests {
);
action_log.update(cx, |log, cx| {
log.keep_edits_in_range(buffer.clone(), 0..5, None, cx)
log.keep_edits_in_range(buffer.clone(), 0..5, cx)
});
cx.run_until_parked();
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
@@ -1381,7 +1479,7 @@ mod tests {
action_log
.update(cx, |log, cx| {
log.reject_edits_in_ranges(buffer.clone(), vec![2..5], None, cx)
log.reject_edits_in_ranges(buffer.clone(), vec![2..5], cx)
})
.await
.unwrap();
@@ -1461,7 +1559,7 @@ mod tests {
action_log
.update(cx, |log, cx| {
log.reject_edits_in_ranges(buffer.clone(), vec![2..5], None, cx)
log.reject_edits_in_ranges(buffer.clone(), vec![2..5], cx)
})
.await
.unwrap();
@@ -1644,7 +1742,6 @@ mod tests {
log.reject_edits_in_ranges(
buffer.clone(),
vec![Point::new(4, 0)..Point::new(4, 0)],
None,
cx,
)
})
@@ -1679,7 +1776,6 @@ mod tests {
log.reject_edits_in_ranges(
buffer.clone(),
vec![Point::new(0, 0)..Point::new(1, 0)],
None,
cx,
)
})
@@ -1707,7 +1803,6 @@ mod tests {
log.reject_edits_in_ranges(
buffer.clone(),
vec![Point::new(4, 0)..Point::new(4, 0)],
None,
cx,
)
})
@@ -1782,7 +1877,7 @@ mod tests {
let range_2 = buffer.read(cx).anchor_before(Point::new(5, 0))
..buffer.read(cx).anchor_before(Point::new(5, 3));
log.reject_edits_in_ranges(buffer.clone(), vec![range_1, range_2], None, cx)
log.reject_edits_in_ranges(buffer.clone(), vec![range_1, range_2], cx)
.detach();
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.text()),
@@ -1843,7 +1938,6 @@ mod tests {
log.reject_edits_in_ranges(
buffer.clone(),
vec![Point::new(0, 0)..Point::new(0, 0)],
None,
cx,
)
})
@@ -1899,7 +1993,6 @@ mod tests {
log.reject_edits_in_ranges(
buffer.clone(),
vec![Point::new(0, 0)..Point::new(0, 11)],
None,
cx,
)
})
@@ -1962,7 +2055,6 @@ mod tests {
log.reject_edits_in_ranges(
buffer.clone(),
vec![Point::new(0, 0)..Point::new(100, 0)],
None,
cx,
)
})
@@ -2010,7 +2102,7 @@ mod tests {
// User accepts the single hunk
action_log.update(cx, |log, cx| {
log.keep_edits_in_range(buffer.clone(), Anchor::MIN..Anchor::MAX, None, cx)
log.keep_edits_in_range(buffer.clone(), Anchor::MIN..Anchor::MAX, cx)
});
cx.run_until_parked();
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
@@ -2031,7 +2123,7 @@ mod tests {
// User rejects the hunk
action_log
.update(cx, |log, cx| {
log.reject_edits_in_ranges(buffer.clone(), vec![Anchor::MIN..Anchor::MAX], None, cx)
log.reject_edits_in_ranges(buffer.clone(), vec![Anchor::MIN..Anchor::MAX], cx)
})
.await
.unwrap();
@@ -2075,7 +2167,7 @@ mod tests {
cx.run_until_parked();
// User clicks "Accept All"
action_log.update(cx, |log, cx| log.keep_all_edits(None, cx));
action_log.update(cx, |log, cx| log.keep_all_edits(cx));
cx.run_until_parked();
assert!(fs.is_file(path!("/dir/new_file").as_ref()).await);
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]); // Hunks are cleared
@@ -2094,7 +2186,7 @@ mod tests {
// User clicks "Reject All"
action_log
.update(cx, |log, cx| log.reject_all_edits(None, cx))
.update(cx, |log, cx| log.reject_all_edits(cx))
.await;
cx.run_until_parked();
assert!(fs.is_file(path!("/dir/new_file").as_ref()).await);
@@ -2134,7 +2226,7 @@ mod tests {
action_log.update(cx, |log, cx| {
let range = buffer.read(cx).random_byte_range(0, &mut rng);
log::info!("keeping edits in range {:?}", range);
log.keep_edits_in_range(buffer.clone(), range, None, cx)
log.keep_edits_in_range(buffer.clone(), range, cx)
});
}
25..50 => {
@@ -2142,7 +2234,7 @@ mod tests {
.update(cx, |log, cx| {
let range = buffer.read(cx).random_byte_range(0, &mut rng);
log::info!("rejecting edits in range {:?}", range);
log.reject_edits_in_ranges(buffer.clone(), vec![range], None, cx)
log.reject_edits_in_ranges(buffer.clone(), vec![range], cx)
})
.await
.unwrap();
@@ -2396,4 +2488,61 @@ mod tests {
.collect()
})
}
#[gpui::test]
async fn test_format_patch(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/dir"),
json!({"test.txt": "line 1\nline 2\nline 3\n"}),
)
.await;
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let file_path = project
.read_with(cx, |project, cx| {
project.find_project_path("dir/test.txt", cx)
})
.unwrap();
let buffer = project
.update(cx, |project, cx| project.open_buffer(file_path, cx))
.await
.unwrap();
cx.update(|cx| {
// Track the buffer and mark it as read first
action_log.update(cx, |log, cx| {
log.buffer_read(buffer.clone(), cx);
});
// Make some edits to create a patch
buffer.update(cx, |buffer, cx| {
buffer
.edit([(Point::new(1, 0)..Point::new(1, 6), "CHANGED")], None, cx)
.unwrap(); // Replace "line2" with "CHANGED"
});
});
cx.run_until_parked();
// Get the patch
let patch = action_log.update(cx, |log, cx| log.unnotified_user_edits(cx));
// Verify the patch format contains expected unified diff elements
assert_eq!(
patch.unwrap(),
indoc! {"
--- a/dir/test.txt
+++ b/dir/test.txt
@@ -1,3 +1,3 @@
line 1
-line 2
+CHANGED
line 3
"}
);
}
}

View File

@@ -17,7 +17,6 @@ anyhow.workspace = true
auto_update.workspace = true
editor.workspace = true
extension_host.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true

View File

@@ -51,7 +51,6 @@ pub struct ActivityIndicator {
project: Entity<Project>,
auto_updater: Option<Entity<AutoUpdater>>,
context_menu_handle: PopoverMenuHandle<ContextMenu>,
fs_jobs: Vec<fs::JobInfo>,
}
#[derive(Debug)]
@@ -100,27 +99,6 @@ impl ActivityIndicator {
})
.detach();
let fs = project.read(cx).fs().clone();
let mut job_events = fs.subscribe_to_jobs();
cx.spawn(async move |this, cx| {
while let Some(job_event) = job_events.next().await {
this.update(cx, |this: &mut ActivityIndicator, cx| {
match job_event {
fs::JobEvent::Started { info } => {
this.fs_jobs.retain(|j| j.id != info.id);
this.fs_jobs.push(info);
}
fs::JobEvent::Completed { id } => {
this.fs_jobs.retain(|j| j.id != id);
}
}
cx.notify();
})?;
}
anyhow::Ok(())
})
.detach();
cx.subscribe(
&project.read(cx).lsp_store(),
|activity_indicator, _, event, cx| {
@@ -223,8 +201,7 @@ impl ActivityIndicator {
statuses: Vec::new(),
project: project.clone(),
auto_updater,
context_menu_handle: PopoverMenuHandle::default(),
fs_jobs: Vec::new(),
context_menu_handle: Default::default(),
}
});
@@ -455,23 +432,6 @@ impl ActivityIndicator {
});
}
// Show any long-running fs command
for fs_job in &self.fs_jobs {
if Instant::now().duration_since(fs_job.start) >= GIT_OPERATION_DELAY {
return Some(Content {
icon: Some(
Icon::new(IconName::ArrowCircle)
.size(IconSize::Small)
.with_rotate_animation(2)
.into_any_element(),
),
message: fs_job.message.clone().into(),
on_click: None,
tooltip_message: None,
});
}
}
// Show any language server installation info.
let mut downloading = SmallVec::<[_; 3]>::new();
let mut checking_for_update = SmallVec::<[_; 3]>::new();

View File

@@ -63,6 +63,7 @@ streaming_diff.workspace = true
strsim.workspace = true
task.workspace = true
telemetry.workspace = true
terminal.workspace = true
text.workspace = true
thiserror.workspace = true
ui.workspace = true

View File

@@ -6,6 +6,7 @@ mod native_agent_server;
pub mod outline;
mod templates;
mod thread;
mod tool_schema;
mod tools;
#[cfg(test)]
@@ -133,7 +134,9 @@ impl LanguageModels {
for model in provider.provided_models(cx) {
let model_info = Self::map_language_model_to_info(&model, &provider);
let model_id = model_info.id.clone();
provider_models.push(model_info);
if !recommended_models.contains(&(model.provider_id(), model.id())) {
provider_models.push(model_info);
}
models.insert(model_id, model);
}
if !provider_models.is_empty() {
@@ -215,7 +218,7 @@ impl LanguageModels {
}
_ => {
log::error!(
"Failed to authenticate provider: {}: {err:#}",
"Failed to authenticate provider: {}: {err}",
provider_name.0
);
}
@@ -964,10 +967,6 @@ impl acp_thread::AgentModelSelector for NativeAgentModelSelector {
}
impl acp_thread::AgentConnection for NativeAgentConnection {
fn telemetry_id(&self) -> &'static str {
"zed"
}
fn new_thread(
self: Rc<Self>,
project: Entity<Project>,
@@ -1108,6 +1107,10 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
}
impl acp_thread::AgentTelemetry for NativeAgentConnection {
fn agent_name(&self) -> String {
"Zed".into()
}
fn thread_data(
&self,
session_id: &acp::SessionId,
@@ -1624,7 +1627,9 @@ mod internal_tests {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
Project::init_settings(cx);
agent_settings::init(cx);
language::init(cx);
LanguageModelRegistry::test(cx);
});
}

View File

@@ -1394,7 +1394,7 @@ mod tests {
async fn init_test(cx: &mut TestAppContext) -> EditAgent {
cx.update(settings::init);
cx.update(Project::init_settings);
let project = Project::test(FakeFs::new(cx.executor()), [], cx).await;
let model = Arc::new(FakeLanguageModel::default());
let action_log = cx.new(|_| ActionLog::new(project.clone()));

View File

@@ -13,17 +13,7 @@ const EDITS_END_TAG: &str = "</edits>";
const SEARCH_MARKER: &str = "<<<<<<< SEARCH";
const SEPARATOR_MARKER: &str = "=======";
const REPLACE_MARKER: &str = ">>>>>>> REPLACE";
const SONNET_PARAMETER_INVOKE_1: &str = "</parameter>\n</invoke>";
const SONNET_PARAMETER_INVOKE_2: &str = "</parameter></invoke>";
const SONNET_PARAMETER_INVOKE_3: &str = "</parameter>";
const END_TAGS: [&str; 6] = [
OLD_TEXT_END_TAG,
NEW_TEXT_END_TAG,
EDITS_END_TAG,
SONNET_PARAMETER_INVOKE_1, // Remove these after switching to streaming tool call
SONNET_PARAMETER_INVOKE_2,
SONNET_PARAMETER_INVOKE_3,
];
const END_TAGS: [&str; 3] = [OLD_TEXT_END_TAG, NEW_TEXT_END_TAG, EDITS_END_TAG];
#[derive(Debug)]
pub enum EditParserEvent {
@@ -557,45 +547,6 @@ mod tests {
);
}
#[gpui::test(iterations = 1000)]
fn test_xml_edits_with_closing_parameter_invoke(mut rng: StdRng) {
// This case is a regression with Claude Sonnet 4.5.
// Sometimes Sonnet thinks that it's doing a tool call
// and closes its response with '</parameter></invoke>'
// instead of properly closing </new_text>
let mut parser = EditParser::new(EditFormat::XmlTags);
assert_eq!(
parse_random_chunks(
indoc! {"
<old_text>some text</old_text><new_text>updated text</parameter></invoke>
<old_text>more text</old_text><new_text>upd</parameter></new_text>
"},
&mut parser,
&mut rng
),
vec![
Edit {
old_text: "some text".to_string(),
new_text: "updated text".to_string(),
line_hint: None,
},
Edit {
old_text: "more text".to_string(),
new_text: "upd".to_string(),
line_hint: None,
},
]
);
assert_eq!(
parser.finish(),
EditParserMetrics {
tags: 4,
mismatched_tags: 2
}
);
}
#[gpui::test(iterations = 1000)]
fn test_xml_nested_tags(mut rng: StdRng) {
let mut parser = EditParser::new(EditFormat::XmlTags);
@@ -1084,11 +1035,6 @@ mod tests {
last_ix = chunk_ix;
}
if new_text.is_some() {
pending_edit.new_text = new_text.take().unwrap();
edits.push(pending_edit);
}
edits
}
}

View File

@@ -1468,9 +1468,14 @@ impl EditAgentTest {
gpui_tokio::init(cx);
let http_client = Arc::new(ReqwestClient::user_agent("agent tests").unwrap());
cx.set_http_client(http_client);
client::init_settings(cx);
let client = Client::production(cx);
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
settings::init(cx);
Project::init_settings(cx);
language::init(cx);
language_model::init(client.clone(), cx);
language_models::init(user_store, client.clone(), cx);
});
@@ -1576,7 +1581,6 @@ impl EditAgentTest {
let template = crate::SystemPromptTemplate {
project: &project_context,
available_tools: tool_names,
model_name: None,
};
let templates = Templates::new();
template.render(&templates).unwrap()

View File

@@ -88,6 +88,8 @@ mod tests {
async |fs, project, cx| {
let auth = cx.update(|cx| {
prompt_store::init(cx);
terminal::init(cx);
let registry = language_model::LanguageModelRegistry::read_global(cx);
let auth = registry
.provider(&language_model::ANTHROPIC_PROVIDER_ID)

View File

@@ -1,6 +1,6 @@
use anyhow::Result;
use gpui::{AsyncApp, Entity};
use language::{Buffer, OutlineItem};
use language::{Buffer, OutlineItem, ParseStatus};
use regex::Regex;
use std::fmt::Write;
use text::Point;
@@ -30,9 +30,10 @@ pub async fn get_buffer_content_or_outline(
if file_size > AUTO_OUTLINE_SIZE {
// For large files, use outline instead of full content
// Wait until the buffer has been fully parsed, so we can read its outline
buffer
.read_with(cx, |buffer, _| buffer.parsing_idle())?
.await;
let mut parse_status = buffer.read_with(cx, |buffer, _| buffer.parse_status())?;
while *parse_status.borrow() != ParseStatus::Idle {
parse_status.changed().await?;
}
let outline_items = buffer.read_with(cx, |buffer, _| {
let snapshot = buffer.snapshot();
@@ -44,25 +45,6 @@ pub async fn get_buffer_content_or_outline(
.collect::<Vec<_>>()
})?;
// If no outline exists, fall back to first 1KB so the agent has some context
if outline_items.is_empty() {
let text = buffer.read_with(cx, |buffer, _| {
let snapshot = buffer.snapshot();
let len = snapshot.len().min(1024);
let content = snapshot.text_for_range(0..len).collect::<String>();
if let Some(path) = path {
format!("# First 1KB of {path} (file too large to show full content, and no outline available)\n\n{content}")
} else {
format!("# First 1KB of file (file too large to show full content, and no outline available)\n\n{content}")
}
})?;
return Ok(BufferContent {
text,
is_outline: false,
});
}
let outline_text = render_outline(outline_items, None, 0, usize::MAX).await?;
let text = if let Some(path) = path {
@@ -159,62 +141,3 @@ fn render_entries(
entries_rendered
}
#[cfg(test)]
mod tests {
use super::*;
use fs::FakeFs;
use gpui::TestAppContext;
use project::Project;
use settings::SettingsStore;
#[gpui::test]
async fn test_large_file_fallback_to_subset(cx: &mut TestAppContext) {
cx.update(|cx| {
let settings = SettingsStore::test(cx);
cx.set_global(settings);
});
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, [], cx).await;
let content = "A".repeat(100 * 1024); // 100KB
let content_len = content.len();
let buffer = project
.update(cx, |project, cx| project.create_buffer(true, cx))
.await
.expect("failed to create buffer");
buffer.update(cx, |buffer, cx| buffer.set_text(content, cx));
let result = cx
.spawn(|cx| async move { get_buffer_content_or_outline(buffer, None, &cx).await })
.await
.unwrap();
// Should contain some of the actual file content
assert!(
result.text.contains("AAAAAAAAAA"),
"Result did not contain content subset"
);
// Should be marked as not an outline (it's truncated content)
assert!(
!result.is_outline,
"Large file without outline should not be marked as outline"
);
// Should be reasonably sized (much smaller than original)
assert!(
result.text.len() < 50 * 1024,
"Result size {} should be smaller than 50KB",
result.text.len()
);
// Should be significantly smaller than the original content
assert!(
result.text.len() < content_len / 10,
"Result should be much smaller than original content"
);
}
}

View File

@@ -38,7 +38,6 @@ pub struct SystemPromptTemplate<'a> {
#[serde(flatten)]
pub project: &'a prompt_store::ProjectContext,
pub available_tools: Vec<SharedString>,
pub model_name: Option<String>,
}
impl Template for SystemPromptTemplate<'_> {
@@ -80,11 +79,9 @@ mod tests {
let template = SystemPromptTemplate {
project: &project,
available_tools: vec!["echo".into()],
model_name: Some("test-model".to_string()),
};
let templates = Templates::new();
let rendered = template.render(&templates).unwrap();
assert!(rendered.contains("## Fixing Diagnostics"));
assert!(rendered.contains("test-model"));
}
}

View File

@@ -150,12 +150,6 @@ Otherwise, follow debugging best practices:
Operating System: {{os}}
Default Shell: {{shell}}
{{#if model_name}}
## Model Information
You are powered by the model named {{model_name}}.
{{/if}}
{{#if (or has_rules has_user_rules)}}
## User's Custom Instructions

View File

@@ -933,7 +933,7 @@ async fn test_profiles(cx: &mut TestAppContext) {
// Test that test-1 profile (default) has echo and delay tools
thread
.update(cx, |thread, cx| {
thread.set_profile(AgentProfileId("test-1".into()), cx);
thread.set_profile(AgentProfileId("test-1".into()));
thread.send(UserMessageId::new(), ["test"], cx)
})
.unwrap();
@@ -953,7 +953,7 @@ async fn test_profiles(cx: &mut TestAppContext) {
// Switch to test-2 profile, and verify that it has only the infinite tool.
thread
.update(cx, |thread, cx| {
thread.set_profile(AgentProfileId("test-2".into()), cx);
thread.set_profile(AgentProfileId("test-2".into()));
thread.send(UserMessageId::new(), ["test2"], cx)
})
.unwrap();
@@ -1002,8 +1002,8 @@ async fn test_mcp_tools(cx: &mut TestAppContext) {
)
.await;
cx.run_until_parked();
thread.update(cx, |thread, cx| {
thread.set_profile(AgentProfileId("test".into()), cx)
thread.update(cx, |thread, _| {
thread.set_profile(AgentProfileId("test".into()))
});
let mut mcp_tool_calls = setup_context_server(
@@ -1169,8 +1169,8 @@ async fn test_mcp_tool_truncation(cx: &mut TestAppContext) {
.await;
cx.run_until_parked();
thread.update(cx, |thread, cx| {
thread.set_profile(AgentProfileId("test".into()), cx);
thread.update(cx, |thread, _| {
thread.set_profile(AgentProfileId("test".into()));
thread.add_tool(EchoTool);
thread.add_tool(DelayTool);
thread.add_tool(WordListTool);
@@ -1851,6 +1851,7 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
// Initialize language model system with test provider
cx.update(|cx| {
gpui_tokio::init(cx);
client::init_settings(cx);
let http_client = FakeHttpClient::with_404_response();
let clock = Arc::new(clock::FakeSystemClock::new());
@@ -1858,7 +1859,9 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
language_model::init(client.clone(), cx);
language_models::init(user_store, client.clone(), cx);
Project::init_settings(cx);
LanguageModelRegistry::test(cx);
agent_settings::init(cx);
});
cx.executor().forbid_parking();
@@ -2392,6 +2395,8 @@ async fn setup(cx: &mut TestAppContext, model: TestModel) -> ThreadTest {
cx.update(|cx| {
settings::init(cx);
Project::init_settings(cx);
agent_settings::init(cx);
match model {
TestModel::Fake => {}
@@ -2399,6 +2404,7 @@ async fn setup(cx: &mut TestAppContext, model: TestModel) -> ThreadTest {
gpui_tokio::init(cx);
let http_client = ReqwestClient::user_agent("agent tests").unwrap();
cx.set_http_client(Arc::new(http_client));
client::init_settings(cx);
let client = Client::production(cx);
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
language_model::init(client.clone(), cx);

View File

@@ -30,17 +30,16 @@ use gpui::{
};
use language_model::{
LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelExt,
LanguageModelId, LanguageModelImage, LanguageModelProviderId, LanguageModelRegistry,
LanguageModelRequest, LanguageModelRequestMessage, LanguageModelRequestTool,
LanguageModelToolResult, LanguageModelToolResultContent, LanguageModelToolSchemaFormat,
LanguageModelToolUse, LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage,
ZED_CLOUD_PROVIDER_ID,
LanguageModelImage, LanguageModelProviderId, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult,
LanguageModelToolResultContent, LanguageModelToolSchemaFormat, LanguageModelToolUse,
LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage, ZED_CLOUD_PROVIDER_ID,
};
use project::Project;
use prompt_store::ProjectContext;
use schemars::{JsonSchema, Schema};
use serde::{Deserialize, Serialize};
use settings::{LanguageModelSelection, Settings, update_settings_file};
use settings::{Settings, update_settings_file};
use smol::stream::StreamExt;
use std::{
collections::BTreeMap,
@@ -607,8 +606,6 @@ pub struct Thread {
pub(crate) prompt_capabilities_rx: watch::Receiver<acp::PromptCapabilities>,
pub(crate) project: Entity<Project>,
pub(crate) action_log: Entity<ActionLog>,
/// Tracks the last time files were read by the agent, to detect external modifications
pub(crate) file_read_times: HashMap<PathBuf, fs::MTime>,
}
impl Thread {
@@ -667,7 +664,6 @@ impl Thread {
prompt_capabilities_rx,
project,
action_log,
file_read_times: HashMap::default(),
}
}
@@ -802,8 +798,7 @@ impl Thread {
let profile_id = db_thread
.profile
.unwrap_or_else(|| AgentSettings::get_global(cx).default_profile.clone());
let mut model = LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
let model = LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
db_thread
.model
.and_then(|model| {
@@ -816,16 +811,6 @@ impl Thread {
.or_else(|| registry.default_model())
.map(|model| model.model)
});
if model.is_none() {
model = Self::resolve_profile_model(&profile_id, cx);
}
if model.is_none() {
model = LanguageModelRegistry::global(cx).update(cx, |registry, _cx| {
registry.default_model().map(|model| model.model)
});
}
let (prompt_capabilities_tx, prompt_capabilities_rx) =
watch::channel(Self::prompt_capabilities(model.as_deref()));
@@ -863,7 +848,6 @@ impl Thread {
updated_at: db_thread.updated_at,
prompt_capabilities_tx,
prompt_capabilities_rx,
file_read_times: HashMap::default(),
}
}
@@ -1003,7 +987,6 @@ impl Thread {
self.add_tool(NowTool);
self.add_tool(OpenTool::new(self.project.clone()));
self.add_tool(ReadFileTool::new(
cx.weak_entity(),
self.project.clone(),
self.action_log.clone(),
));
@@ -1024,17 +1007,8 @@ impl Thread {
&self.profile_id
}
pub fn set_profile(&mut self, profile_id: AgentProfileId, cx: &mut Context<Self>) {
if self.profile_id == profile_id {
return;
}
pub fn set_profile(&mut self, profile_id: AgentProfileId) {
self.profile_id = profile_id;
// Swap to the profile's preferred model when available.
if let Some(model) = Self::resolve_profile_model(&self.profile_id, cx) {
self.set_model(model, cx);
}
}
pub fn cancel(&mut self, cx: &mut Context<Self>) {
@@ -1091,35 +1065,6 @@ impl Thread {
})
}
/// Look up the active profile and resolve its preferred model if one is configured.
fn resolve_profile_model(
profile_id: &AgentProfileId,
cx: &mut Context<Self>,
) -> Option<Arc<dyn LanguageModel>> {
let selection = AgentSettings::get_global(cx)
.profiles
.get(profile_id)?
.default_model
.clone()?;
Self::resolve_model_from_selection(&selection, cx)
}
/// Translate a stored model selection into the configured model from the registry.
fn resolve_model_from_selection(
selection: &LanguageModelSelection,
cx: &mut Context<Self>,
) -> Option<Arc<dyn LanguageModel>> {
let selected = SelectedModel {
provider: LanguageModelProviderId::from(selection.provider.0.clone()),
model: LanguageModelId::from(selection.model.clone()),
};
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry
.select_model(&selected, cx)
.map(|configured| configured.model)
})
}
pub fn resume(
&mut self,
cx: &mut Context<Self>,
@@ -1983,7 +1928,6 @@ impl Thread {
let system_prompt = SystemPromptTemplate {
project: self.project_context.read(cx),
available_tools,
model_name: self.model.as_ref().map(|m| m.name().0.to_string()),
}
.render(&self.templates)
.context("failed to build system prompt")
@@ -2194,7 +2138,7 @@ where
/// Returns the JSON schema that describes the tool's input.
fn input_schema(format: LanguageModelToolSchemaFormat) -> Schema {
language_model::tool_schema::root_schema_for::<Self::Input>(format)
crate::tool_schema::root_schema_for::<Self::Input>(format)
}
/// Some tools rely on a provider for the underlying billing or other reasons.
@@ -2281,7 +2225,7 @@ where
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
let mut json = serde_json::to_value(T::input_schema(format))?;
language_model::tool_schema::adapt_schema_to_format(&mut json, format)?;
crate::tool_schema::adapt_schema_to_format(&mut json, format)?;
Ok(json)
}

View File

@@ -1,4 +1,5 @@
use anyhow::Result;
use language_model::LanguageModelToolSchemaFormat;
use schemars::{
JsonSchema, Schema,
generate::SchemaSettings,
@@ -6,16 +7,7 @@ use schemars::{
};
use serde_json::Value;
/// Indicates the format used to define the input schema for a language model tool.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum LanguageModelToolSchemaFormat {
/// A JSON schema, see https://json-schema.org
JsonSchema,
/// A subset of an OpenAPI 3.0 schema object supported by Google AI, see https://ai.google.dev/api/caching#Schema
JsonSchemaSubset,
}
pub fn root_schema_for<T: JsonSchema>(format: LanguageModelToolSchemaFormat) -> Schema {
pub(crate) fn root_schema_for<T: JsonSchema>(format: LanguageModelToolSchemaFormat) -> Schema {
let mut generator = match format {
LanguageModelToolSchemaFormat::JsonSchema => SchemaSettings::draft07().into_generator(),
LanguageModelToolSchemaFormat::JsonSchemaSubset => SchemaSettings::openapi3()

View File

@@ -165,7 +165,7 @@ impl AnyAgentTool for ContextServerTool {
format: language_model::LanguageModelToolSchemaFormat,
) -> Result<serde_json::Value> {
let mut schema = self.tool.input_schema.clone();
language_model::tool_schema::adapt_schema_to_format(&mut schema, format)?;
crate::tool_schema::adapt_schema_to_format(&mut schema, format)?;
Ok(match schema {
serde_json::Value::Null => {
serde_json::json!({ "type": "object", "properties": [] })

View File

@@ -309,40 +309,6 @@ impl AgentTool for EditFileTool {
})?
.await?;
// Check if the file has been modified since the agent last read it
if let Some(abs_path) = abs_path.as_ref() {
let (last_read_mtime, current_mtime, is_dirty) = self.thread.update(cx, |thread, cx| {
let last_read = thread.file_read_times.get(abs_path).copied();
let current = buffer.read(cx).file().and_then(|file| file.disk_state().mtime());
let dirty = buffer.read(cx).is_dirty();
(last_read, current, dirty)
})?;
// Check for unsaved changes first - these indicate modifications we don't know about
if is_dirty {
anyhow::bail!(
"This file cannot be written to because it has unsaved changes. \
Please end the current conversation immediately by telling the user you want to write to this file (mention its path explicitly) but you can't write to it because it has unsaved changes. \
Ask the user to save that buffer's changes and to inform you when it's ok to proceed."
);
}
// Check if the file was modified on disk since we last read it
if let (Some(last_read), Some(current)) = (last_read_mtime, current_mtime) {
// MTime can be unreliable for comparisons, so our newtype intentionally
// doesn't support comparing them. If the mtime at all different
// (which could be because of a modification or because e.g. system clock changed),
// we pessimistically assume it was modified.
if current != last_read {
anyhow::bail!(
"The file {} has been modified since you last read it. \
Please read the file again to get the current state before editing it.",
input.path.display()
);
}
}
}
let diff = cx.new(|cx| Diff::new(buffer.clone(), cx))?;
event_stream.update_diff(diff.clone());
let _finalize_diff = util::defer({
@@ -455,17 +421,6 @@ impl AgentTool for EditFileTool {
log.buffer_edited(buffer.clone(), cx);
})?;
// Update the recorded read time after a successful edit so consecutive edits work
if let Some(abs_path) = abs_path.as_ref() {
if let Some(new_mtime) = buffer.read_with(cx, |buffer, _| {
buffer.file().and_then(|file| file.disk_state().mtime())
})? {
self.thread.update(cx, |thread, _| {
thread.file_read_times.insert(abs_path.to_path_buf(), new_mtime);
})?;
}
}
let new_snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
let (new_text, unified_diff) = cx
.background_spawn({
@@ -607,6 +562,7 @@ fn resolve_path(
mod tests {
use super::*;
use crate::{ContextServerRegistry, Templates};
use client::TelemetrySettings;
use fs::Fs;
use gpui::{TestAppContext, UpdateGlobal};
use language_model::fake_provider::FakeLanguageModel;
@@ -1793,426 +1749,14 @@ mod tests {
}
}
#[gpui::test]
async fn test_file_read_times_tracking(cx: &mut TestAppContext) {
init_test(cx);
let fs = project::FakeFs::new(cx.executor());
fs.insert_tree(
"/root",
json!({
"test.txt": "original content"
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
let context_server_registry =
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
Some(model.clone()),
cx,
)
});
let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
// Initially, file_read_times should be empty
let is_empty = thread.read_with(cx, |thread, _| thread.file_read_times.is_empty());
assert!(is_empty, "file_read_times should start empty");
// Create read tool
let read_tool = Arc::new(crate::ReadFileTool::new(
thread.downgrade(),
project.clone(),
action_log,
));
// Read the file to record the read time
cx.update(|cx| {
read_tool.clone().run(
crate::ReadFileToolInput {
path: "root/test.txt".to_string(),
start_line: None,
end_line: None,
},
ToolCallEventStream::test().0,
cx,
)
})
.await
.unwrap();
// Verify that file_read_times now contains an entry for the file
let has_entry = thread.read_with(cx, |thread, _| {
thread.file_read_times.len() == 1
&& thread
.file_read_times
.keys()
.any(|path| path.ends_with("test.txt"))
});
assert!(
has_entry,
"file_read_times should contain an entry after reading the file"
);
// Read the file again - should update the entry
cx.update(|cx| {
read_tool.clone().run(
crate::ReadFileToolInput {
path: "root/test.txt".to_string(),
start_line: None,
end_line: None,
},
ToolCallEventStream::test().0,
cx,
)
})
.await
.unwrap();
// Should still have exactly one entry
let has_one_entry = thread.read_with(cx, |thread, _| thread.file_read_times.len() == 1);
assert!(
has_one_entry,
"file_read_times should still have one entry after re-reading"
);
}
fn init_test(cx: &mut TestAppContext) {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
TelemetrySettings::register(cx);
agent_settings::AgentSettings::register(cx);
Project::init_settings(cx);
});
}
#[gpui::test]
async fn test_consecutive_edits_work(cx: &mut TestAppContext) {
init_test(cx);
let fs = project::FakeFs::new(cx.executor());
fs.insert_tree(
"/root",
json!({
"test.txt": "original content"
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
let context_server_registry =
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
Some(model.clone()),
cx,
)
});
let languages = project.read_with(cx, |project, _| project.languages().clone());
let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
let read_tool = Arc::new(crate::ReadFileTool::new(
thread.downgrade(),
project.clone(),
action_log,
));
let edit_tool = Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
languages,
Templates::new(),
));
// Read the file first
cx.update(|cx| {
read_tool.clone().run(
crate::ReadFileToolInput {
path: "root/test.txt".to_string(),
start_line: None,
end_line: None,
},
ToolCallEventStream::test().0,
cx,
)
})
.await
.unwrap();
// First edit should work
let edit_result = {
let edit_task = cx.update(|cx| {
edit_tool.clone().run(
EditFileToolInput {
display_description: "First edit".into(),
path: "root/test.txt".into(),
mode: EditFileMode::Edit,
},
ToolCallEventStream::test().0,
cx,
)
});
cx.executor().run_until_parked();
model.send_last_completion_stream_text_chunk(
"<old_text>original content</old_text><new_text>modified content</new_text>"
.to_string(),
);
model.end_last_completion_stream();
edit_task.await
};
assert!(
edit_result.is_ok(),
"First edit should succeed, got error: {:?}",
edit_result.as_ref().err()
);
// Second edit should also work because the edit updated the recorded read time
let edit_result = {
let edit_task = cx.update(|cx| {
edit_tool.clone().run(
EditFileToolInput {
display_description: "Second edit".into(),
path: "root/test.txt".into(),
mode: EditFileMode::Edit,
},
ToolCallEventStream::test().0,
cx,
)
});
cx.executor().run_until_parked();
model.send_last_completion_stream_text_chunk(
"<old_text>modified content</old_text><new_text>further modified content</new_text>".to_string(),
);
model.end_last_completion_stream();
edit_task.await
};
assert!(
edit_result.is_ok(),
"Second consecutive edit should succeed, got error: {:?}",
edit_result.as_ref().err()
);
}
#[gpui::test]
async fn test_external_modification_detected(cx: &mut TestAppContext) {
init_test(cx);
let fs = project::FakeFs::new(cx.executor());
fs.insert_tree(
"/root",
json!({
"test.txt": "original content"
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
let context_server_registry =
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
Some(model.clone()),
cx,
)
});
let languages = project.read_with(cx, |project, _| project.languages().clone());
let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
let read_tool = Arc::new(crate::ReadFileTool::new(
thread.downgrade(),
project.clone(),
action_log,
));
let edit_tool = Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
languages,
Templates::new(),
));
// Read the file first
cx.update(|cx| {
read_tool.clone().run(
crate::ReadFileToolInput {
path: "root/test.txt".to_string(),
start_line: None,
end_line: None,
},
ToolCallEventStream::test().0,
cx,
)
})
.await
.unwrap();
// Simulate external modification - advance time and save file
cx.background_executor
.advance_clock(std::time::Duration::from_secs(2));
fs.save(
path!("/root/test.txt").as_ref(),
&"externally modified content".into(),
language::LineEnding::Unix,
)
.await
.unwrap();
// Reload the buffer to pick up the new mtime
let project_path = project
.read_with(cx, |project, cx| {
project.find_project_path("root/test.txt", cx)
})
.expect("Should find project path");
let buffer = project
.update(cx, |project, cx| project.open_buffer(project_path, cx))
.await
.unwrap();
buffer
.update(cx, |buffer, cx| buffer.reload(cx))
.await
.unwrap();
cx.executor().run_until_parked();
// Try to edit - should fail because file was modified externally
let result = cx
.update(|cx| {
edit_tool.clone().run(
EditFileToolInput {
display_description: "Edit after external change".into(),
path: "root/test.txt".into(),
mode: EditFileMode::Edit,
},
ToolCallEventStream::test().0,
cx,
)
})
.await;
assert!(
result.is_err(),
"Edit should fail after external modification"
);
let error_msg = result.unwrap_err().to_string();
assert!(
error_msg.contains("has been modified since you last read it"),
"Error should mention file modification, got: {}",
error_msg
);
}
#[gpui::test]
async fn test_dirty_buffer_detected(cx: &mut TestAppContext) {
init_test(cx);
let fs = project::FakeFs::new(cx.executor());
fs.insert_tree(
"/root",
json!({
"test.txt": "original content"
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
let context_server_registry =
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
Some(model.clone()),
cx,
)
});
let languages = project.read_with(cx, |project, _| project.languages().clone());
let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
let read_tool = Arc::new(crate::ReadFileTool::new(
thread.downgrade(),
project.clone(),
action_log,
));
let edit_tool = Arc::new(EditFileTool::new(
project.clone(),
thread.downgrade(),
languages,
Templates::new(),
));
// Read the file first
cx.update(|cx| {
read_tool.clone().run(
crate::ReadFileToolInput {
path: "root/test.txt".to_string(),
start_line: None,
end_line: None,
},
ToolCallEventStream::test().0,
cx,
)
})
.await
.unwrap();
// Open the buffer and make it dirty by editing without saving
let project_path = project
.read_with(cx, |project, cx| {
project.find_project_path("root/test.txt", cx)
})
.expect("Should find project path");
let buffer = project
.update(cx, |project, cx| project.open_buffer(project_path, cx))
.await
.unwrap();
// Make an in-memory edit to the buffer (making it dirty)
buffer.update(cx, |buffer, cx| {
let end_point = buffer.max_point();
buffer.edit([(end_point..end_point, " added text")], None, cx);
});
// Verify buffer is dirty
let is_dirty = buffer.read_with(cx, |buffer, _| buffer.is_dirty());
assert!(is_dirty, "Buffer should be dirty after in-memory edit");
// Try to edit - should fail because buffer has unsaved changes
let result = cx
.update(|cx| {
edit_tool.clone().run(
EditFileToolInput {
display_description: "Edit with dirty buffer".into(),
path: "root/test.txt".into(),
mode: EditFileMode::Edit,
},
ToolCallEventStream::test().0,
cx,
)
})
.await;
assert!(result.is_err(), "Edit should fail when buffer is dirty");
let error_msg = result.unwrap_err().to_string();
assert!(
error_msg.contains("cannot be written to because it has unsaved changes"),
"Error should mention unsaved changes, got: {}",
error_msg
);
}
}

View File

@@ -246,6 +246,8 @@ mod test {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
});
}
}

View File

@@ -778,6 +778,8 @@ mod tests {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
});
}

View File

@@ -223,6 +223,8 @@ mod tests {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
});
}

View File

@@ -163,6 +163,8 @@ mod tests {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
});
}
}

View File

@@ -1,7 +1,7 @@
use action_log::ActionLog;
use agent_client_protocol::{self as acp, ToolCallUpdateFields};
use anyhow::{Context as _, Result, anyhow};
use gpui::{App, Entity, SharedString, Task, WeakEntity};
use gpui::{App, Entity, SharedString, Task};
use indoc::formatdoc;
use language::Point;
use language_model::{LanguageModelImage, LanguageModelToolResultContent};
@@ -12,7 +12,7 @@ use settings::Settings;
use std::sync::Arc;
use util::markdown::MarkdownCodeBlock;
use crate::{AgentTool, Thread, ToolCallEventStream, outline};
use crate::{AgentTool, ToolCallEventStream, outline};
/// Reads the content of the given file in the project.
///
@@ -42,19 +42,13 @@ pub struct ReadFileToolInput {
}
pub struct ReadFileTool {
thread: WeakEntity<Thread>,
project: Entity<Project>,
action_log: Entity<ActionLog>,
}
impl ReadFileTool {
pub fn new(
thread: WeakEntity<Thread>,
project: Entity<Project>,
action_log: Entity<ActionLog>,
) -> Self {
pub fn new(project: Entity<Project>, action_log: Entity<ActionLog>) -> Self {
Self {
thread,
project,
action_log,
}
@@ -201,17 +195,6 @@ impl AgentTool for ReadFileTool {
anyhow::bail!("{file_path} not found");
}
// Record the file read time and mtime
if let Some(mtime) = buffer.read_with(cx, |buffer, _| {
buffer.file().and_then(|file| file.disk_state().mtime())
})? {
self.thread
.update(cx, |thread, _| {
thread.file_read_times.insert(abs_path.to_path_buf(), mtime);
})
.ok();
}
let mut anchor = None;
// Check if specific line ranges are provided
@@ -302,15 +285,11 @@ impl AgentTool for ReadFileTool {
#[cfg(test)]
mod test {
use super::*;
use crate::{ContextServerRegistry, Templates, Thread};
use gpui::{AppContext, TestAppContext, UpdateGlobal as _};
use language::{Language, LanguageConfig, LanguageMatcher, tree_sitter_rust};
use language_model::fake_provider::FakeLanguageModel;
use project::{FakeFs, Project};
use prompt_store::ProjectContext;
use serde_json::json;
use settings::SettingsStore;
use std::sync::Arc;
use util::path;
#[gpui::test]
@@ -321,20 +300,7 @@ mod test {
fs.insert_tree(path!("/root"), json!({})).await;
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let context_server_registry =
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
Some(model),
cx,
)
});
let tool = Arc::new(ReadFileTool::new(thread.downgrade(), project, action_log));
let tool = Arc::new(ReadFileTool::new(project, action_log));
let (event_stream, _) = ToolCallEventStream::test();
let result = cx
@@ -367,20 +333,7 @@ mod test {
.await;
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let context_server_registry =
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
Some(model),
cx,
)
});
let tool = Arc::new(ReadFileTool::new(thread.downgrade(), project, action_log));
let tool = Arc::new(ReadFileTool::new(project, action_log));
let result = cx
.update(|cx| {
let input = ReadFileToolInput {
@@ -410,20 +363,7 @@ mod test {
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(Arc::new(rust_lang()));
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let context_server_registry =
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
Some(model),
cx,
)
});
let tool = Arc::new(ReadFileTool::new(thread.downgrade(), project, action_log));
let tool = Arc::new(ReadFileTool::new(project, action_log));
let result = cx
.update(|cx| {
let input = ReadFileToolInput {
@@ -495,20 +435,7 @@ mod test {
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let context_server_registry =
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
Some(model),
cx,
)
});
let tool = Arc::new(ReadFileTool::new(thread.downgrade(), project, action_log));
let tool = Arc::new(ReadFileTool::new(project, action_log));
let result = cx
.update(|cx| {
let input = ReadFileToolInput {
@@ -536,20 +463,7 @@ mod test {
.await;
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let context_server_registry =
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
Some(model),
cx,
)
});
let tool = Arc::new(ReadFileTool::new(thread.downgrade(), project, action_log));
let tool = Arc::new(ReadFileTool::new(project, action_log));
// start_line of 0 should be treated as 1
let result = cx
@@ -595,6 +509,8 @@ mod test {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
});
}
@@ -693,20 +609,7 @@ mod test {
let project = Project::test(fs.clone(), [path!("/project_root").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let context_server_registry =
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
Some(model),
cx,
)
});
let tool = Arc::new(ReadFileTool::new(thread.downgrade(), project, action_log));
let tool = Arc::new(ReadFileTool::new(project, action_log));
// Reading a file outside the project worktree should fail
let result = cx
@@ -920,24 +823,7 @@ mod test {
.await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let context_server_registry =
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let model = Arc::new(FakeLanguageModel::default());
let thread = cx.new(|cx| {
Thread::new(
project.clone(),
cx.new(|_cx| ProjectContext::default()),
context_server_registry,
Templates::new(),
Some(model),
cx,
)
});
let tool = Arc::new(ReadFileTool::new(
thread.downgrade(),
project.clone(),
action_log.clone(),
));
let tool = Arc::new(ReadFileTool::new(project.clone(), action_log.clone()));
// Test reading allowed files in worktree1
let result = cx

View File

@@ -21,6 +21,7 @@ acp_tools.workspace = true
acp_thread.workspace = true
action_log.workspace = true
agent-client-protocol.workspace = true
agent_settings.workspace = true
anyhow.workspace = true
async-trait.workspace = true
client.workspace = true
@@ -32,6 +33,7 @@ gpui.workspace = true
gpui_tokio = { workspace = true, optional = true }
http_client.workspace = true
indoc.workspace = true
language.workspace = true
language_model.workspace = true
language_models.workspace = true
log.workspace = true

View File

@@ -29,7 +29,6 @@ pub struct UnsupportedVersion;
pub struct AcpConnection {
server_name: SharedString,
telemetry_id: &'static str,
connection: Rc<acp::ClientSideConnection>,
sessions: Rc<RefCell<HashMap<acp::SessionId, AcpSession>>>,
auth_methods: Vec<acp::AuthMethod>,
@@ -53,7 +52,6 @@ pub struct AcpSession {
pub async fn connect(
server_name: SharedString,
telemetry_id: &'static str,
command: AgentServerCommand,
root_dir: &Path,
default_mode: Option<acp::SessionModeId>,
@@ -62,7 +60,6 @@ pub async fn connect(
) -> Result<Rc<dyn AgentConnection>> {
let conn = AcpConnection::stdio(
server_name,
telemetry_id,
command.clone(),
root_dir,
default_mode,
@@ -78,7 +75,6 @@ const MINIMUM_SUPPORTED_VERSION: acp::ProtocolVersion = acp::V1;
impl AcpConnection {
pub async fn stdio(
server_name: SharedString,
telemetry_id: &'static str,
command: AgentServerCommand,
root_dir: &Path,
default_mode: Option<acp::SessionModeId>,
@@ -136,7 +132,7 @@ impl AcpConnection {
while let Ok(n) = stderr.read_line(&mut line).await
&& n > 0
{
log::warn!("agent stderr: {}", line.trim());
log::warn!("agent stderr: {}", &line);
line.clear();
}
Ok(())
@@ -182,7 +178,6 @@ impl AcpConnection {
meta: Some(serde_json::json!({
// Experimental: Allow for rendering terminal output from the agents
"terminal_output": true,
"terminal-auth": true,
})),
},
client_info: Some(acp::Implementation {
@@ -203,7 +198,6 @@ impl AcpConnection {
root_dir: root_dir.to_owned(),
connection,
server_name,
telemetry_id,
sessions,
agent_capabilities: response.agent_capabilities,
default_mode,
@@ -231,10 +225,6 @@ impl Drop for AcpConnection {
}
impl AgentConnection for AcpConnection {
fn telemetry_id(&self) -> &'static str {
self.telemetry_id
}
fn new_thread(
self: Rc<Self>,
project: Entity<Project>,

View File

@@ -62,7 +62,6 @@ impl AgentServer for ClaudeCode {
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
let name = self.name();
let telemetry_id = self.telemetry_id();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
let store = delegate.store.downgrade();
@@ -86,7 +85,6 @@ impl AgentServer for ClaudeCode {
.await?;
let connection = crate::acp::connect(
name,
telemetry_id,
command,
root_dir.as_ref(),
default_mode,

View File

@@ -63,7 +63,6 @@ impl AgentServer for Codex {
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
let name = self.name();
let telemetry_id = self.telemetry_id();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
let store = delegate.store.downgrade();
@@ -88,7 +87,6 @@ impl AgentServer for Codex {
let connection = crate::acp::connect(
name,
telemetry_id,
command,
root_dir.as_ref(),
default_mode,

View File

@@ -50,14 +50,13 @@ impl crate::AgentServer for CustomAgentServer {
fn set_default_mode(&self, mode_id: Option<acp::SessionModeId>, fs: Arc<dyn Fs>, cx: &mut App) {
let name = self.name();
update_settings_file(fs, cx, move |settings, _| {
if let Some(settings) = settings
settings
.agent_servers
.get_or_insert_default()
.custom
.get_mut(&name)
{
settings.default_mode = mode_id.map(|m| m.to_string())
}
.unwrap()
.default_mode = mode_id.map(|m| m.to_string())
});
}
@@ -68,7 +67,6 @@ impl crate::AgentServer for CustomAgentServer {
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
let name = self.name();
let telemetry_id = self.telemetry_id();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
let default_mode = self.default_mode(cx);
@@ -94,7 +92,6 @@ impl crate::AgentServer for CustomAgentServer {
.await?;
let connection = crate::acp::connect(
name,
telemetry_id,
command,
root_dir.as_ref(),
default_mode,

View File

@@ -6,9 +6,7 @@ use gpui::{AppContext, Entity, TestAppContext};
use indoc::indoc;
#[cfg(test)]
use project::agent_server_store::BuiltinAgentServerSettings;
use project::{FakeFs, Project};
#[cfg(test)]
use settings::Settings;
use project::{FakeFs, Project, agent_server_store::AllAgentServersSettings};
use std::{
path::{Path, PathBuf},
sync::Arc,
@@ -454,22 +452,29 @@ pub use common_e2e_tests;
// Helpers
pub async fn init_test(cx: &mut TestAppContext) -> Arc<FakeFs> {
use settings::Settings;
env_logger::try_init().ok();
cx.update(|cx| {
let settings_store = settings::SettingsStore::test(cx);
cx.set_global(settings_store);
Project::init_settings(cx);
language::init(cx);
gpui_tokio::init(cx);
let http_client = reqwest_client::ReqwestClient::user_agent("agent tests").unwrap();
cx.set_http_client(Arc::new(http_client));
client::init_settings(cx);
let client = client::Client::production(cx);
let user_store = cx.new(|cx| client::UserStore::new(client.clone(), cx));
language_model::init(client.clone(), cx);
language_models::init(user_store, client, cx);
agent_settings::init(cx);
AllAgentServersSettings::register(cx);
#[cfg(test)]
project::agent_server_store::AllAgentServersSettings::override_global(
project::agent_server_store::AllAgentServersSettings {
AllAgentServersSettings::override_global(
AllAgentServersSettings {
claude: Some(BuiltinAgentServerSettings {
path: Some("claude-code-acp".into()),
args: None,

View File

@@ -31,7 +31,6 @@ impl AgentServer for Gemini {
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
let name = self.name();
let telemetry_id = self.telemetry_id();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
let store = delegate.store.downgrade();
@@ -65,7 +64,6 @@ impl AgentServer for Gemini {
let connection = crate::acp::connect(
name,
telemetry_id,
command,
root_dir.as_ref(),
default_mode,

View File

@@ -6,8 +6,8 @@ use convert_case::{Case, Casing as _};
use fs::Fs;
use gpui::{App, SharedString};
use settings::{
AgentProfileContent, ContextServerPresetContent, LanguageModelSelection, Settings as _,
SettingsContent, update_settings_file,
AgentProfileContent, ContextServerPresetContent, Settings as _, SettingsContent,
update_settings_file,
};
use util::ResultExt as _;
@@ -53,30 +53,19 @@ impl AgentProfile {
let base_profile =
base_profile_id.and_then(|id| AgentSettings::get_global(cx).profiles.get(&id).cloned());
// Copy toggles from the base profile so the new profile starts with familiar defaults.
let tools = base_profile
.as_ref()
.map(|profile| profile.tools.clone())
.unwrap_or_default();
let enable_all_context_servers = base_profile
.as_ref()
.map(|profile| profile.enable_all_context_servers)
.unwrap_or_default();
let context_servers = base_profile
.as_ref()
.map(|profile| profile.context_servers.clone())
.unwrap_or_default();
// Preserve the base profile's model preference when cloning into a new profile.
let default_model = base_profile
.as_ref()
.and_then(|profile| profile.default_model.clone());
let profile_settings = AgentProfileSettings {
name: name.into(),
tools,
enable_all_context_servers,
context_servers,
default_model,
tools: base_profile
.as_ref()
.map(|profile| profile.tools.clone())
.unwrap_or_default(),
enable_all_context_servers: base_profile
.as_ref()
.map(|profile| profile.enable_all_context_servers)
.unwrap_or_default(),
context_servers: base_profile
.map(|profile| profile.context_servers)
.unwrap_or_default(),
};
update_settings_file(fs, cx, {
@@ -107,8 +96,6 @@ pub struct AgentProfileSettings {
pub tools: IndexMap<Arc<str>, bool>,
pub enable_all_context_servers: bool,
pub context_servers: IndexMap<Arc<str>, ContextServerPreset>,
/// Default language model to apply when this profile becomes active.
pub default_model: Option<LanguageModelSelection>,
}
impl AgentProfileSettings {
@@ -157,7 +144,6 @@ impl AgentProfileSettings {
)
})
.collect(),
default_model: self.default_model.clone(),
},
);
@@ -167,23 +153,15 @@ impl AgentProfileSettings {
impl From<AgentProfileContent> for AgentProfileSettings {
fn from(content: AgentProfileContent) -> Self {
let AgentProfileContent {
name,
tools,
enable_all_context_servers,
context_servers,
default_model,
} = content;
Self {
name: name.into(),
tools,
enable_all_context_servers: enable_all_context_servers.unwrap_or_default(),
context_servers: context_servers
name: content.name.into(),
tools: content.tools,
enable_all_context_servers: content.enable_all_context_servers.unwrap_or_default(),
context_servers: content
.context_servers
.into_iter()
.map(|(server_id, preset)| (server_id, preset.into()))
.collect(),
default_model,
}
}
}

View File

@@ -10,7 +10,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{
DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection,
NotifyWhenAgentWaiting, RegisterSetting, Settings,
NotifyWhenAgentWaiting, Settings,
};
pub use crate::agent_profile::*;
@@ -19,7 +19,11 @@ pub const SUMMARIZE_THREAD_PROMPT: &str = include_str!("prompts/summarize_thread
pub const SUMMARIZE_THREAD_DETAILED_PROMPT: &str =
include_str!("prompts/summarize_thread_detailed_prompt.txt");
#[derive(Clone, Debug, RegisterSetting)]
pub fn init(cx: &mut App) {
AgentSettings::register(cx);
}
#[derive(Clone, Debug)]
pub struct AgentSettings {
pub enabled: bool,
pub button: bool,

View File

@@ -98,8 +98,6 @@ util.workspace = true
watch.workspace = true
workspace.workspace = true
zed_actions.workspace = true
image.workspace = true
async-fs.workspace = true
[dev-dependencies]
acp_thread = { workspace = true, features = ["test-support"] }

View File

@@ -109,8 +109,6 @@ impl ContextPickerCompletionProvider {
icon_path: Some(mode.icon().path().into()),
documentation: None,
source: project::CompletionSource::Custom,
match_start: None,
snippet_deduplication_key: None,
insert_text_mode: None,
// This ensures that when a user accepts this completion, the
// completion menu will still be shown after "@category " is
@@ -148,8 +146,6 @@ impl ContextPickerCompletionProvider {
documentation: None,
insert_text_mode: None,
source: project::CompletionSource::Custom,
match_start: None,
snippet_deduplication_key: None,
icon_path: Some(icon_for_completion),
confirm: Some(confirm_completion_callback(
thread_entry.title().clone(),
@@ -181,8 +177,6 @@ impl ContextPickerCompletionProvider {
documentation: None,
insert_text_mode: None,
source: project::CompletionSource::Custom,
match_start: None,
snippet_deduplication_key: None,
icon_path: Some(icon_path),
confirm: Some(confirm_completion_callback(
rule.title,
@@ -239,8 +233,6 @@ impl ContextPickerCompletionProvider {
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(completion_icon_path),
match_start: None,
snippet_deduplication_key: None,
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
file_name,
@@ -292,8 +284,6 @@ impl ContextPickerCompletionProvider {
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(icon_path),
match_start: None,
snippet_deduplication_key: None,
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
symbol.name.into(),
@@ -326,8 +316,6 @@ impl ContextPickerCompletionProvider {
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(icon_path),
match_start: None,
snippet_deduplication_key: None,
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
url_to_fetch.to_string().into(),
@@ -396,8 +384,6 @@ impl ContextPickerCompletionProvider {
icon_path: Some(action.icon().path().into()),
documentation: None,
source: project::CompletionSource::Custom,
match_start: None,
snippet_deduplication_key: None,
insert_text_mode: None,
// This ensures that when a user accepts this completion, the
// completion menu will still be shown after "@category " is
@@ -660,14 +646,16 @@ impl ContextPickerCompletionProvider {
cx: &mut App,
) -> Vec<ContextPickerEntry> {
let embedded_context = self.prompt_capabilities.borrow().embedded_context;
let mut entries = vec![
ContextPickerEntry::Mode(ContextPickerMode::File),
ContextPickerEntry::Mode(ContextPickerMode::Symbol),
];
if embedded_context {
entries.push(ContextPickerEntry::Mode(ContextPickerMode::Thread));
}
let mut entries = if embedded_context {
vec![
ContextPickerEntry::Mode(ContextPickerMode::File),
ContextPickerEntry::Mode(ContextPickerMode::Symbol),
ContextPickerEntry::Mode(ContextPickerMode::Thread),
]
} else {
// File is always available, but we don't need a mode entry
vec![]
};
let has_selection = workspace
.read(cx)
@@ -708,18 +696,14 @@ fn build_symbol_label(symbol_name: &str, file_name: &str, line: u32, cx: &App) -
}
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
let path = cx
.theme()
.syntax()
.highlight_id("variable")
.map(HighlightId);
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
let mut label = CodeLabelBuilder::default();
label.push_str(file_name, None);
label.push_str(" ", None);
if let Some(directory) = directory {
label.push_str(directory, path);
label.push_str(directory, comment_id);
}
label.build()
@@ -788,8 +772,6 @@ impl CompletionProvider for ContextPickerCompletionProvider {
)),
source: project::CompletionSource::Custom,
icon_path: None,
match_start: None,
snippet_deduplication_key: None,
insert_text_mode: None,
confirm: Some(Arc::new({
let editor = editor.clone();

View File

@@ -4,7 +4,7 @@ use acp_thread::{AcpThread, AgentThreadEntry};
use agent::HistoryStore;
use agent_client_protocol::{self as acp, ToolCallId};
use collections::HashMap;
use editor::{Editor, EditorMode, MinimapVisibility, SizingBehavior};
use editor::{Editor, EditorMode, MinimapVisibility};
use gpui::{
AnyEntity, App, AppContext as _, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
ScrollHandle, SharedString, TextStyleRefinement, WeakEntity, Window,
@@ -357,7 +357,7 @@ fn create_editor_diff(
EditorMode::Full {
scale_ui_elements_with_buffer_font_size: false,
show_active_line_background: false,
sizing_behavior: SizingBehavior::SizeByContent,
sized_by_content: true,
},
diff.read(cx).multibuffer().clone(),
None,
@@ -401,9 +401,10 @@ mod tests {
use acp_thread::{AgentConnection, StubAgentConnection};
use agent::HistoryStore;
use agent_client_protocol as acp;
use agent_settings::AgentSettings;
use assistant_text_thread::TextThreadStore;
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
use editor::RowInfo;
use editor::{EditorSettings, RowInfo};
use fs::FakeFs;
use gpui::{AppContext as _, SemanticVersion, TestAppContext};
@@ -412,7 +413,7 @@ mod tests {
use pretty_assertions::assert_matches;
use project::Project;
use serde_json::json;
use settings::SettingsStore;
use settings::{Settings as _, SettingsStore};
use util::path;
use workspace::Workspace;
@@ -538,8 +539,13 @@ mod tests {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
AgentSettings::register(cx);
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
release_channel::init(SemanticVersion::default(), cx);
EditorSettings::register(cx);
});
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,8 @@
use acp_thread::AgentSessionModes;
use agent_client_protocol as acp;
use agent_servers::AgentServer;
use agent_settings::AgentSettings;
use fs::Fs;
use gpui::{Context, Entity, FocusHandle, WeakEntity, Window, prelude::*};
use settings::Settings as _;
use std::{rc::Rc, sync::Arc};
use ui::{
Button, ContextMenu, ContextMenuEntry, DocumentationEdge, DocumentationSide, KeyBinding,
@@ -86,14 +84,6 @@ impl ModeSelector {
let current_mode = self.connection.current_mode();
let default_mode = self.agent_server.default_mode(cx);
let settings = AgentSettings::get_global(cx);
let side = match settings.dock {
settings::DockPosition::Left => DocumentationSide::Right,
settings::DockPosition::Bottom | settings::DockPosition::Right => {
DocumentationSide::Left
}
};
for mode in all_modes {
let is_selected = &mode.id == &current_mode;
let is_default = Some(&mode.id) == default_mode.as_ref();
@@ -101,7 +91,7 @@ impl ModeSelector {
.toggleable(IconPosition::End, is_selected);
let entry = if let Some(description) = &mode.description {
entry.documentation_aside(side, DocumentationEdge::Bottom, {
entry.documentation_aside(DocumentationSide::Left, DocumentationEdge::Bottom, {
let description = description.clone();
move |cx| {

View File

@@ -251,17 +251,17 @@ impl PickerDelegate for AcpModelPickerDelegate {
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.start_slot::<Icon>(model_info.icon.map(|icon| {
Icon::new(icon)
.color(model_icon_color)
.size(IconSize::Small)
}))
.child(
h_flex()
.w_full()
.pl_0p5()
.gap_1p5()
.when_some(model_info.icon, |this, icon| {
this.child(
Icon::new(icon)
.color(model_icon_color)
.size(IconSize::Small)
)
})
.w(px(240.))
.child(Label::new(model_info.name.clone()).truncate()),
)
.end_slot(div().pr_3().when(is_selected, |this| {

View File

@@ -1,6 +1,6 @@
use std::rc::Rc;
use acp_thread::{AgentModelInfo, AgentModelSelector};
use acp_thread::AgentModelSelector;
use gpui::{Entity, FocusHandle};
use picker::popover_menu::PickerPopoverMenu;
use ui::{
@@ -36,8 +36,12 @@ impl AcpModelSelectorPopover {
self.menu_handle.toggle(window, cx);
}
pub fn active_model<'a>(&self, cx: &'a App) -> Option<&'a AgentModelInfo> {
self.selector.read(cx).delegate.active_model()
pub fn active_model_name(&self, cx: &App) -> Option<SharedString> {
self.selector
.read(cx)
.delegate
.active_model()
.map(|model| model.name.clone())
}
}

View File

@@ -457,23 +457,25 @@ impl Render for AcpThreadHistory {
.on_action(cx.listener(Self::select_last))
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::remove_selected_thread))
.child(
h_flex()
.h(px(41.)) // Match the toolbar perfectly
.w_full()
.py_1()
.px_2()
.gap_2()
.justify_between()
.border_b_1()
.border_color(cx.theme().colors().border)
.child(
Icon::new(IconName::MagnifyingGlass)
.color(Color::Muted)
.size(IconSize::Small),
)
.child(self.search_editor.clone()),
)
.when(!self.history_store.read(cx).is_empty(cx), |parent| {
parent.child(
h_flex()
.h(px(41.)) // Match the toolbar perfectly
.w_full()
.py_1()
.px_2()
.gap_2()
.justify_between()
.border_b_1()
.border_color(cx.theme().colors().border)
.child(
Icon::new(IconName::MagnifyingGlass)
.color(Color::Muted)
.size(IconSize::Small),
)
.child(self.search_editor.clone()),
)
})
.child({
let view = v_flex()
.id("list-container")
@@ -482,15 +484,19 @@ impl Render for AcpThreadHistory {
.flex_grow();
if self.history_store.read(cx).is_empty(cx) {
view.justify_center().items_center().child(
Label::new("You don't have any past threads yet.")
.size(LabelSize::Small)
.color(Color::Muted),
)
} else if self.search_produced_no_matches() {
view.justify_center()
.items_center()
.child(Label::new("No threads match your search.").size(LabelSize::Small))
.child(
h_flex().w_full().justify_center().child(
Label::new("You don't have any past threads yet.")
.size(LabelSize::Small),
),
)
} else if self.search_produced_no_matches() {
view.justify_center().child(
h_flex().w_full().justify_center().child(
Label::new("No threads match your search.").size(LabelSize::Small),
),
)
} else {
view.child(
uniform_list(
@@ -667,7 +673,7 @@ impl EntryTimeFormat {
timezone,
time_format::TimestampFormat::EnhancedAbsolute,
),
EntryTimeFormat::TimeOnly => time_format::format_time(timestamp.to_offset(timezone)),
EntryTimeFormat::TimeOnly => time_format::format_time(timestamp),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,6 @@ use std::{ops::Range, sync::Arc};
use agent::ContextServerRegistry;
use anyhow::Result;
use client::zed_urls;
use cloud_llm_client::{Plan, PlanV1, PlanV2};
use collections::HashMap;
use context_server::ContextServerId;
@@ -24,23 +23,19 @@ use language::LanguageRegistry;
use language_model::{
LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,
};
use language_models::AllLanguageModelSettings;
use notifications::status_toast::{StatusToast, ToastIcon};
use project::{
agent_server_store::{
AgentServerStore, CLAUDE_CODE_NAME, CODEX_NAME, ExternalAgentServerName, GEMINI_NAME,
},
agent_server_store::{AgentServerStore, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME},
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
};
use settings::{Settings, SettingsStore, update_settings_file};
use settings::{SettingsStore, update_settings_file};
use ui::{
Button, ButtonStyle, Chip, CommonAnimationExt, ContextMenu, ContextMenuEntry, Disclosure,
Divider, DividerColor, ElevationIndex, IconName, IconPosition, IconSize, Indicator, LabelSize,
PopoverMenu, Switch, SwitchColor, Tooltip, WithScrollbar, prelude::*,
Chip, CommonAnimationExt, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex,
Indicator, PopoverMenu, Switch, SwitchColor, Tooltip, WithScrollbar, prelude::*,
};
use util::ResultExt as _;
use workspace::{Workspace, create_and_open_local_file};
use zed_actions::{ExtensionCategoryFilter, OpenBrowser};
use zed_actions::ExtensionCategoryFilter;
pub(crate) use configure_context_server_modal::ConfigureContextServerModal;
pub(crate) use configure_context_server_tools_modal::ConfigureContextServerToolsModal;
@@ -157,42 +152,7 @@ pub enum AssistantConfigurationEvent {
impl EventEmitter<AssistantConfigurationEvent> for AgentConfiguration {}
enum AgentIcon {
Name(IconName),
Path(SharedString),
}
impl AgentConfiguration {
fn render_section_title(
&mut self,
title: impl Into<SharedString>,
description: impl Into<SharedString>,
menu: AnyElement,
) -> impl IntoElement {
h_flex()
.p_4()
.pb_0()
.mb_2p5()
.items_start()
.justify_between()
.child(
v_flex()
.w_full()
.gap_0p5()
.child(
h_flex()
.pr_1()
.w_full()
.gap_2()
.justify_between()
.flex_wrap()
.child(Headline::new(title.into()))
.child(menu),
)
.child(Label::new(description.into()).color(Color::Muted)),
)
}
fn render_provider_configuration_block(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
@@ -327,7 +287,7 @@ impl AgentConfiguration {
"Start New Thread",
)
.full_width()
.style(ButtonStyle::Outlined)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.icon_position(IconPosition::Start)
.icon(IconName::Thread)
@@ -343,127 +303,89 @@ impl AgentConfiguration {
}
})),
)
})
.when(
is_expanded && is_removable_provider(&provider.id(), cx),
|this| {
this.child(
Button::new(
SharedString::from(format!("delete-provider-{provider_id}")),
"Remove Provider",
)
.full_width()
.style(ButtonStyle::Outlined)
.icon_position(IconPosition::Start)
.icon(IconName::Trash)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.label_size(LabelSize::Small)
.on_click(cx.listener({
let provider = provider.clone();
move |this, _event, window, cx| {
this.delete_provider(provider.clone(), window, cx);
}
})),
)
},
),
}),
)
}
fn delete_provider(
&mut self,
provider: Arc<dyn LanguageModelProvider>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let fs = self.fs.clone();
let provider_id = provider.id();
cx.spawn_in(window, async move |_, cx| {
cx.update(|_window, cx| {
update_settings_file(fs.clone(), cx, {
let provider_id = provider_id.clone();
move |settings, _| {
if let Some(ref mut openai_compatible) = settings
.language_models
.as_mut()
.and_then(|lm| lm.openai_compatible.as_mut())
{
let key_to_remove: Arc<str> = Arc::from(provider_id.0.as_ref());
openai_compatible.remove(&key_to_remove);
}
}
});
})
.log_err();
cx.update(|_window, cx| {
LanguageModelRegistry::global(cx).update(cx, {
let provider_id = provider_id.clone();
move |registry, cx| {
registry.unregister_provider(provider_id, cx);
}
})
})
.log_err();
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
fn render_provider_configuration_section(
&mut self,
cx: &mut Context<Self>,
) -> impl IntoElement {
let providers = LanguageModelRegistry::read_global(cx).providers();
let popover_menu = PopoverMenu::new("add-provider-popover")
.trigger(
Button::new("add-provider", "Add Provider")
.style(ButtonStyle::Outlined)
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.label_size(LabelSize::Small),
)
.menu({
let workspace = self.workspace.clone();
move |window, cx| {
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
menu.header("Compatible APIs").entry("OpenAI", None, {
let workspace = workspace.clone();
move |window, cx| {
workspace
.update(cx, |workspace, cx| {
AddLlmProviderModal::toggle(
LlmCompatibleProvider::OpenAi,
workspace,
window,
cx,
);
})
.log_err();
}
})
}))
}
})
.anchor(gpui::Corner::TopRight)
.offset(gpui::Point {
x: px(0.0),
y: px(2.0),
});
v_flex()
.w_full()
.child(self.render_section_title(
"LLM Providers",
"Add at least one provider to use AI-powered features with Zed's native agent.",
popover_menu.into_any_element(),
))
.child(
h_flex()
.p(DynamicSpacing::Base16.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.pb_0()
.mb_2p5()
.items_start()
.justify_between()
.child(
v_flex()
.w_full()
.gap_0p5()
.child(
h_flex()
.pr_1()
.w_full()
.gap_2()
.justify_between()
.child(Headline::new("LLM Providers"))
.child(
PopoverMenu::new("add-provider-popover")
.trigger(
Button::new("add-provider", "Add Provider")
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.label_size(LabelSize::Small),
)
.anchor(gpui::Corner::TopRight)
.menu({
let workspace = self.workspace.clone();
move |window, cx| {
Some(ContextMenu::build(
window,
cx,
|menu, _window, _cx| {
menu.header("Compatible APIs").entry(
"OpenAI",
None,
{
let workspace =
workspace.clone();
move |window, cx| {
workspace
.update(cx, |workspace, cx| {
AddLlmProviderModal::toggle(
LlmCompatibleProvider::OpenAi,
workspace,
window,
cx,
);
})
.log_err();
}
},
)
},
))
}
}),
),
)
.child(
Label::new("Add at least one provider to use AI-powered features with Zed's native agent.")
.color(Color::Muted),
),
),
)
.child(
div()
.w_full()
@@ -542,13 +464,15 @@ impl AgentConfiguration {
let add_server_popover = PopoverMenu::new("add-server-popover")
.trigger(
Button::new("add-server", "Add Server")
.style(ButtonStyle::Outlined)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.label_size(LabelSize::Small),
)
.anchor(gpui::Corner::TopRight)
.menu({
move |window, cx| {
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
@@ -571,65 +495,64 @@ impl AgentConfiguration {
})
}))
}
})
.anchor(gpui::Corner::TopRight)
.offset(gpui::Point {
x: px(0.0),
y: px(2.0),
});
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.gap_2()
.border_b_1()
.border_color(cx.theme().colors().border)
.child(self.render_section_title(
"Model Context Protocol (MCP) Servers",
"All MCP servers connected directly or via a Zed extension.",
add_server_popover.into_any_element(),
))
.child(
v_flex()
.pl_4()
.pb_4()
.pr_5()
h_flex()
.w_full()
.items_start()
.justify_between()
.gap_1()
.map(|mut parent| {
if context_server_ids.is_empty() {
parent.child(
h_flex()
.p_4()
.justify_center()
.border_1()
.border_dashed()
.border_color(cx.theme().colors().border.opacity(0.6))
.rounded_sm()
.child(
Label::new("No MCP servers added yet.")
.color(Color::Muted)
.size(LabelSize::Small),
),
)
} else {
for (index, context_server_id) in
context_server_ids.into_iter().enumerate()
{
if index > 0 {
parent = parent.child(
Divider::horizontal()
.color(DividerColor::BorderFaded)
.into_any_element(),
);
}
parent = parent.child(self.render_context_server(
context_server_id,
window,
cx,
));
}
parent
}
}),
.child(
v_flex()
.gap_0p5()
.child(Headline::new("Model Context Protocol (MCP) Servers"))
.child(
Label::new(
"All MCP servers connected directly or via a Zed extension.",
)
.color(Color::Muted),
),
)
.child(add_server_popover),
)
.child(v_flex().w_full().gap_1().map(|mut parent| {
if context_server_ids.is_empty() {
parent.child(
h_flex()
.p_4()
.justify_center()
.border_1()
.border_dashed()
.border_color(cx.theme().colors().border.opacity(0.6))
.rounded_sm()
.child(
Label::new("No MCP servers added yet.")
.color(Color::Muted)
.size(LabelSize::Small),
),
)
} else {
for (index, context_server_id) in context_server_ids.into_iter().enumerate() {
if index > 0 {
parent = parent.child(
Divider::horizontal()
.color(DividerColor::BorderFaded)
.into_any_element(),
);
}
parent =
parent.child(self.render_context_server(context_server_id, window, cx));
}
parent
}
}))
}
fn render_context_server(
@@ -650,13 +573,15 @@ impl AgentConfiguration {
let is_running = matches!(server_status, ContextServerStatus::Running);
let item_id = SharedString::from(context_server_id.0.clone());
// Servers without a configuration can only be provided by extensions.
let provided_by_extension = server_configuration.is_none_or(|config| {
matches!(
config.as_ref(),
ContextServerConfiguration::Extension { .. }
)
});
let is_from_extension = server_configuration
.as_ref()
.map(|config| {
matches!(
config.as_ref(),
ContextServerConfiguration::Extension { .. }
)
})
.unwrap_or(false);
let error = if let ContextServerStatus::Error(error) = server_status.clone() {
Some(error)
@@ -670,14 +595,14 @@ impl AgentConfiguration {
.tools_for_server(&context_server_id)
.count();
let (source_icon, source_tooltip) = if provided_by_extension {
let (source_icon, source_tooltip) = if is_from_extension {
(
IconName::ZedSrcExtension,
IconName::ZedMcpExtension,
"This MCP server was installed from an extension.",
)
} else {
(
IconName::ZedSrcCustom,
IconName::ZedMcpCustom,
"This custom MCP server was installed directly.",
)
};
@@ -720,6 +645,7 @@ impl AgentConfiguration {
let fs = self.fs.clone();
let context_server_id = context_server_id.clone();
let language_registry = self.language_registry.clone();
let context_server_store = self.context_server_store.clone();
let workspace = self.workspace.clone();
let context_server_registry = self.context_server_registry.clone();
@@ -761,10 +687,23 @@ impl AgentConfiguration {
.entry("Uninstall", None, {
let fs = fs.clone();
let context_server_id = context_server_id.clone();
let context_server_store = context_server_store.clone();
let workspace = workspace.clone();
move |_, cx| {
let is_provided_by_extension = context_server_store
.read(cx)
.configuration_for_server(&context_server_id)
.as_ref()
.map(|config| {
matches!(
config.as_ref(),
ContextServerConfiguration::Extension { .. }
)
})
.unwrap_or(false);
let uninstall_extension_task = match (
provided_by_extension,
is_provided_by_extension,
resolve_extension_for_context_server(&context_server_id, cx),
) {
(true, Some((id, manifest))) => {
@@ -945,9 +884,9 @@ impl AgentConfiguration {
}
fn render_agent_servers_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let agent_server_store = self.agent_server_store.read(cx);
let user_defined_agents = agent_server_store
let user_defined_agents = self
.agent_server_store
.read(cx)
.external_agents()
.filter(|name| {
name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME && name.0 != CODEX_NAME
@@ -955,219 +894,105 @@ impl AgentConfiguration {
.cloned()
.collect::<Vec<_>>();
let user_defined_agents: Vec<_> = user_defined_agents
let user_defined_agents = user_defined_agents
.into_iter()
.map(|name| {
let icon = if let Some(icon_path) = agent_server_store.agent_icon(&name) {
AgentIcon::Path(icon_path)
} else {
AgentIcon::Name(IconName::Ai)
};
(name, icon)
self.render_agent_server(IconName::Ai, name)
.into_any_element()
})
.collect();
let add_agent_popover = PopoverMenu::new("add-agent-server-popover")
.trigger(
Button::new("add-agent", "Add Agent")
.style(ButtonStyle::Outlined)
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.label_size(LabelSize::Small),
)
.menu({
move |window, cx| {
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
menu.entry("Install from Extensions", None, {
|window, cx| {
window.dispatch_action(
zed_actions::Extensions {
category_filter: Some(
ExtensionCategoryFilter::AgentServers,
),
id: None,
}
.boxed_clone(),
cx,
)
}
})
.entry("Add Custom Agent", None, {
move |window, cx| {
if let Some(workspace) = window.root().flatten() {
let workspace = workspace.downgrade();
window
.spawn(cx, async |cx| {
open_new_agent_servers_entry_in_settings_editor(
workspace, cx,
)
.await
})
.detach_and_log_err(cx);
}
}
})
.separator()
.header("Learn More")
.item(
ContextMenuEntry::new("Agent Servers Docs")
.icon(IconName::ArrowUpRight)
.icon_color(Color::Muted)
.icon_position(IconPosition::End)
.handler({
move |window, cx| {
window.dispatch_action(
Box::new(OpenBrowser {
url: zed_urls::agent_server_docs(cx),
}),
cx,
);
}
}),
)
.item(
ContextMenuEntry::new("ACP Docs")
.icon(IconName::ArrowUpRight)
.icon_color(Color::Muted)
.icon_position(IconPosition::End)
.handler({
move |window, cx| {
window.dispatch_action(
Box::new(OpenBrowser {
url: "https://agentclientprotocol.com/".into(),
}),
cx,
);
}
}),
)
}))
}
})
.anchor(gpui::Corner::TopRight)
.offset(gpui::Point {
x: px(0.0),
y: px(2.0),
});
.collect::<Vec<_>>();
v_flex()
.border_b_1()
.border_color(cx.theme().colors().border)
.child(
v_flex()
.child(self.render_section_title(
"External Agents",
"All agents connected through the Agent Client Protocol.",
add_agent_popover.into_any_element(),
))
.p(DynamicSpacing::Base16.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.gap_2()
.child(
v_flex()
.p_4()
.pt_0()
.gap_2()
.child(self.render_agent_server(
AgentIcon::Name(IconName::AiClaude),
"Claude Code",
false,
cx,
))
.child(Divider::horizontal().color(DividerColor::BorderFaded))
.child(self.render_agent_server(
AgentIcon::Name(IconName::AiOpenAi),
"Codex CLI",
false,
cx,
))
.child(Divider::horizontal().color(DividerColor::BorderFaded))
.child(self.render_agent_server(
AgentIcon::Name(IconName::AiGemini),
"Gemini CLI",
false,
cx,
))
.map(|mut parent| {
for (name, icon) in user_defined_agents {
parent = parent
.child(
Divider::horizontal().color(DividerColor::BorderFaded),
)
.child(self.render_agent_server(icon, name, true, cx));
}
parent
}),
),
.gap_0p5()
.child(
h_flex()
.pr_1()
.w_full()
.gap_2()
.justify_between()
.child(Headline::new("External Agents"))
.child(
Button::new("add-agent", "Add Agent")
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.label_size(LabelSize::Small)
.on_click(
move |_, window, cx| {
if let Some(workspace) = window.root().flatten() {
let workspace = workspace.downgrade();
window
.spawn(cx, async |cx| {
open_new_agent_servers_entry_in_settings_editor(
workspace,
cx,
).await
})
.detach_and_log_err(cx);
}
}
),
)
)
.child(
Label::new(
"All agents connected through the Agent Client Protocol.",
)
.color(Color::Muted),
),
)
.child(self.render_agent_server(
IconName::AiClaude,
"Claude Code",
))
.child(Divider::horizontal().color(DividerColor::BorderFaded))
.child(self.render_agent_server(
IconName::AiOpenAi,
"Codex",
))
.child(Divider::horizontal().color(DividerColor::BorderFaded))
.child(self.render_agent_server(
IconName::AiGemini,
"Gemini CLI",
))
.map(|mut parent| {
for agent in user_defined_agents {
parent = parent.child(Divider::horizontal().color(DividerColor::BorderFaded))
.child(agent);
}
parent
})
)
}
fn render_agent_server(
&self,
icon: AgentIcon,
icon: IconName,
name: impl Into<SharedString>,
external: bool,
cx: &mut Context<Self>,
) -> impl IntoElement {
let name = name.into();
let icon = match icon {
AgentIcon::Name(icon_name) => Icon::new(icon_name)
.size(IconSize::Small)
.color(Color::Muted),
AgentIcon::Path(icon_path) => Icon::from_external_svg(icon_path)
.size(IconSize::Small)
.color(Color::Muted),
};
let tooltip_id = SharedString::new(format!("agent-source-{}", name));
let tooltip_message = format!("The {} agent was installed from an extension.", name);
let agent_server_name = ExternalAgentServerName(name.clone());
let uninstall_btn_id = SharedString::from(format!("uninstall-{}", name));
let uninstall_button = IconButton::new(uninstall_btn_id, IconName::Trash)
.icon_color(Color::Muted)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Uninstall Agent Extension"))
.on_click(cx.listener(move |this, _, _window, cx| {
let agent_name = agent_server_name.clone();
if let Some(ext_id) = this.agent_server_store.update(cx, |store, _cx| {
store.get_extension_id_for_agent(&agent_name)
}) {
ExtensionStore::global(cx)
.update(cx, |store, cx| store.uninstall_extension(ext_id, cx))
.detach_and_log_err(cx);
}
}));
h_flex()
.gap_1()
.justify_between()
.child(
h_flex()
.gap_1p5()
.child(icon)
.child(Label::new(name))
.when(external, |this| {
this.child(
div()
.id(tooltip_id)
.flex_none()
.tooltip(Tooltip::text(tooltip_message))
.child(
Icon::new(IconName::ZedSrcExtension)
.size(IconSize::Small)
.color(Color::Muted),
),
)
})
.child(
Icon::new(IconName::Check)
.color(Color::Success)
.size(IconSize::Small),
),
)
.when(external, |this| this.child(uninstall_button))
h_flex().gap_1p5().justify_between().child(
h_flex()
.gap_1p5()
.child(Icon::new(icon).size(IconSize::Small).color(Color::Muted))
.child(Label::new(name.into()))
.child(
Icon::new(IconName::Check)
.size(IconSize::Small)
.color(Color::Success),
),
)
}
}
@@ -1396,14 +1221,3 @@ fn find_text_in_buffer(
None
}
}
// OpenAI-compatible providers are user-configured and can be removed,
// whereas built-in providers (like Anthropic, OpenAI, Google, etc.) can't.
//
// If in the future we have more "API-compatible-type" of providers,
// they should be included here as removable providers.
fn is_removable_provider(provider_id: &LanguageModelProviderId, cx: &App) -> bool {
AllLanguageModelSettings::get_global(cx)
.openai_compatible
.contains_key(provider_id.0.as_ref())
}

View File

@@ -515,14 +515,16 @@ impl Render for AddLlmProviderModal {
#[cfg(test)]
mod tests {
use super::*;
use editor::EditorSettings;
use fs::FakeFs;
use gpui::{TestAppContext, VisualTestContext};
use language::language_settings;
use language_model::{
LanguageModelProviderId, LanguageModelProviderName,
fake_provider::FakeLanguageModelProvider,
};
use project::Project;
use settings::SettingsStore;
use settings::{Settings as _, SettingsStore};
use util::path;
#[gpui::test]
@@ -728,9 +730,13 @@ mod tests {
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
workspace::init_settings(cx);
Project::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
language_settings::init(cx);
EditorSettings::register(cx);
language_model::init_settings(cx);
language_models::init_settings(cx);
});
let fs = FakeFs::new(cx.executor());

View File

@@ -7,8 +7,8 @@ use anyhow::{Context as _, Result};
use context_server::{ContextServerCommand, ContextServerId};
use editor::{Editor, EditorElement, EditorStyle};
use gpui::{
AsyncWindowContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle,
Task, TextStyle, TextStyleRefinement, UnderlineStyle, WeakEntity, prelude::*,
AsyncWindowContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task,
TextStyle, TextStyleRefinement, UnderlineStyle, WeakEntity, prelude::*,
};
use language::{Language, LanguageRegistry};
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
@@ -23,8 +23,7 @@ use project::{
use settings::{Settings as _, update_settings_file};
use theme::ThemeSettings;
use ui::{
CommonAnimationExt, KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip,
WithScrollbar, prelude::*,
CommonAnimationExt, KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*,
};
use util::ResultExt as _;
use workspace::{ModalView, Workspace};
@@ -253,7 +252,6 @@ pub struct ConfigureContextServerModal {
source: ConfigurationSource,
state: State,
original_server_id: Option<ContextServerId>,
scroll_handle: ScrollHandle,
}
impl ConfigureContextServerModal {
@@ -363,7 +361,6 @@ impl ConfigureContextServerModal {
window,
cx,
),
scroll_handle: ScrollHandle::new(),
})
})
})
@@ -683,7 +680,6 @@ impl ConfigureContextServerModal {
impl Render for ConfigureContextServerModal {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let scroll_handle = self.scroll_handle.clone();
div()
.elevation_3(cx)
.w(rems(34.))
@@ -703,29 +699,14 @@ impl Render for ConfigureContextServerModal {
Modal::new("configure-context-server", None)
.header(self.render_modal_header())
.section(
Section::new().child(
div()
.size_full()
.child(
div()
.id("modal-content")
.max_h(vh(0.7, window))
.overflow_y_scroll()
.track_scroll(&scroll_handle)
.child(self.render_modal_description(window, cx))
.child(self.render_modal_content(cx))
.child(match &self.state {
State::Idle => div(),
State::Waiting => {
Self::render_waiting_for_context_server()
}
State::Error(error) => {
Self::render_modal_error(error.clone())
}
}),
)
.vertical_scrollbar_for(scroll_handle, window, cx),
),
Section::new()
.child(self.render_modal_description(window, cx))
.child(self.render_modal_content(cx))
.child(match &self.state {
State::Idle => div(),
State::Waiting => Self::render_waiting_for_context_server(),
State::Error(error) => Self::render_modal_error(error.clone()),
}),
)
.footer(self.render_modal_footer(cx)),
)

View File

@@ -7,10 +7,8 @@ use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profil
use editor::Editor;
use fs::Fs;
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, prelude::*};
use language_model::{LanguageModel, LanguageModelRegistry};
use settings::{
LanguageModelProviderSetting, LanguageModelSelection, Settings as _, update_settings_file,
};
use language_model::LanguageModel;
use settings::Settings as _;
use ui::{
KeyBinding, ListItem, ListItemSpacing, ListSeparator, Navigable, NavigableEntry, prelude::*,
};
@@ -18,7 +16,6 @@ use workspace::{ModalView, Workspace};
use crate::agent_configuration::manage_profiles_modal::profile_modal_header::ProfileModalHeader;
use crate::agent_configuration::tool_picker::{ToolPicker, ToolPickerDelegate};
use crate::language_model_selector::{LanguageModelSelector, language_model_selector};
use crate::{AgentPanel, ManageProfiles};
enum Mode {
@@ -35,11 +32,6 @@ enum Mode {
tool_picker: Entity<ToolPicker>,
_subscription: Subscription,
},
ConfigureDefaultModel {
profile_id: AgentProfileId,
model_picker: Entity<LanguageModelSelector>,
_subscription: Subscription,
},
}
impl Mode {
@@ -91,7 +83,6 @@ pub struct ChooseProfileMode {
pub struct ViewProfileMode {
profile_id: AgentProfileId,
fork_profile: NavigableEntry,
configure_default_model: NavigableEntry,
configure_tools: NavigableEntry,
configure_mcps: NavigableEntry,
cancel_item: NavigableEntry,
@@ -189,7 +180,6 @@ impl ManageProfilesModal {
self.mode = Mode::ViewProfile(ViewProfileMode {
profile_id,
fork_profile: NavigableEntry::focusable(cx),
configure_default_model: NavigableEntry::focusable(cx),
configure_tools: NavigableEntry::focusable(cx),
configure_mcps: NavigableEntry::focusable(cx),
cancel_item: NavigableEntry::focusable(cx),
@@ -197,83 +187,6 @@ impl ManageProfilesModal {
self.focus_handle(cx).focus(window);
}
fn configure_default_model(
&mut self,
profile_id: AgentProfileId,
window: &mut Window,
cx: &mut Context<Self>,
) {
let fs = self.fs.clone();
let profile_id_for_closure = profile_id.clone();
let model_picker = cx.new(|cx| {
let fs = fs.clone();
let profile_id = profile_id_for_closure.clone();
language_model_selector(
{
let profile_id = profile_id.clone();
move |cx| {
let settings = AgentSettings::get_global(cx);
settings
.profiles
.get(&profile_id)
.and_then(|profile| profile.default_model.as_ref())
.and_then(|selection| {
let registry = LanguageModelRegistry::read_global(cx);
let provider_id = language_model::LanguageModelProviderId(
gpui::SharedString::from(selection.provider.0.clone()),
);
let provider = registry.provider(&provider_id)?;
let model = provider
.provided_models(cx)
.iter()
.find(|m| m.id().0 == selection.model.as_str())?
.clone();
Some(language_model::ConfiguredModel { provider, model })
})
}
},
move |model, cx| {
let provider = model.provider_id().0.to_string();
let model_id = model.id().0.to_string();
let profile_id = profile_id.clone();
update_settings_file(fs.clone(), cx, move |settings, _cx| {
let agent_settings = settings.agent.get_or_insert_default();
if let Some(profiles) = agent_settings.profiles.as_mut() {
if let Some(profile) = profiles.get_mut(profile_id.0.as_ref()) {
profile.default_model = Some(LanguageModelSelection {
provider: LanguageModelProviderSetting(provider.clone()),
model: model_id.clone(),
});
}
}
});
},
false, // Do not use popover styles for the model picker
window,
cx,
)
.modal(false)
});
let dismiss_subscription = cx.subscribe_in(&model_picker, window, {
let profile_id = profile_id.clone();
move |this, _picker, _: &DismissEvent, window, cx| {
this.view_profile(profile_id.clone(), window, cx);
}
});
self.mode = Mode::ConfigureDefaultModel {
profile_id,
model_picker,
_subscription: dismiss_subscription,
};
self.focus_handle(cx).focus(window);
}
fn configure_mcp_tools(
&mut self,
profile_id: AgentProfileId,
@@ -364,7 +277,6 @@ impl ManageProfilesModal {
Mode::ViewProfile(_) => {}
Mode::ConfigureTools { .. } => {}
Mode::ConfigureMcps { .. } => {}
Mode::ConfigureDefaultModel { .. } => {}
}
}
@@ -387,9 +299,6 @@ impl ManageProfilesModal {
Mode::ConfigureMcps { profile_id, .. } => {
self.view_profile(profile_id.clone(), window, cx)
}
Mode::ConfigureDefaultModel { profile_id, .. } => {
self.view_profile(profile_id.clone(), window, cx)
}
}
}
}
@@ -404,7 +313,6 @@ impl Focusable for ManageProfilesModal {
Mode::ViewProfile(_) => self.focus_handle.clone(),
Mode::ConfigureTools { tool_picker, .. } => tool_picker.focus_handle(cx),
Mode::ConfigureMcps { tool_picker, .. } => tool_picker.focus_handle(cx),
Mode::ConfigureDefaultModel { model_picker, .. } => model_picker.focus_handle(cx),
}
}
}
@@ -636,47 +544,6 @@ impl ManageProfilesModal {
}),
),
)
.child(
div()
.id("configure-default-model")
.track_focus(&mode.configure_default_model.focus_handle)
.on_action({
let profile_id = mode.profile_id.clone();
cx.listener(move |this, _: &menu::Confirm, window, cx| {
this.configure_default_model(
profile_id.clone(),
window,
cx,
);
})
})
.child(
ListItem::new("model-item")
.toggle_state(
mode.configure_default_model
.focus_handle
.contains_focused(window, cx),
)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.start_slot(
Icon::new(IconName::ZedAssistant)
.size(IconSize::Small)
.color(Color::Muted),
)
.child(Label::new("Configure Default Model"))
.on_click({
let profile_id = mode.profile_id.clone();
cx.listener(move |this, _, window, cx| {
this.configure_default_model(
profile_id.clone(),
window,
cx,
);
})
}),
),
)
.child(
div()
.id("configure-builtin-tools")
@@ -801,7 +668,6 @@ impl ManageProfilesModal {
.into_any_element(),
)
.entry(mode.fork_profile)
.entry(mode.configure_default_model)
.entry(mode.configure_tools)
.entry(mode.configure_mcps)
.entry(mode.cancel_item)
@@ -887,29 +753,6 @@ impl Render for ManageProfilesModal {
.child(go_back_item)
.into_any_element()
}
Mode::ConfigureDefaultModel {
profile_id,
model_picker,
..
} => {
let profile_name = settings
.profiles
.get(profile_id)
.map(|profile| profile.name.clone())
.unwrap_or_else(|| "Unknown".into());
v_flex()
.pb_1()
.child(ProfileModalHeader::new(
format!("{profile_name} — Configure Default Model"),
Some(IconName::Ai),
))
.child(ListSeparator)
.child(v_flex().w(rems(34.)).child(model_picker.clone()))
.child(ListSeparator)
.child(go_back_item)
.into_any_element()
}
Mode::ConfigureMcps {
profile_id,
tool_picker,

View File

@@ -314,7 +314,6 @@ impl PickerDelegate for ToolPickerDelegate {
)
})
.collect(),
default_model: default_profile.default_model.clone(),
});
if let Some(server_id) = server_id {

View File

@@ -1,6 +1,6 @@
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll};
use acp_thread::{AcpThread, AcpThreadEvent};
use action_log::ActionLogTelemetry;
use action_log::ActionLog;
use agent_settings::AgentSettings;
use anyhow::Result;
use buffer_diff::DiffHunkStatus;
@@ -40,16 +40,87 @@ use zed_actions::assistant::ToggleFocus;
pub struct AgentDiffPane {
multibuffer: Entity<MultiBuffer>,
editor: Entity<Editor>,
thread: Entity<AcpThread>,
thread: AgentDiffThread,
focus_handle: FocusHandle,
workspace: WeakEntity<Workspace>,
title: SharedString,
_subscriptions: Vec<Subscription>,
}
#[derive(PartialEq, Eq, Clone)]
pub enum AgentDiffThread {
AcpThread(Entity<AcpThread>),
}
impl AgentDiffThread {
fn project(&self, cx: &App) -> Entity<Project> {
match self {
AgentDiffThread::AcpThread(thread) => thread.read(cx).project().clone(),
}
}
fn action_log(&self, cx: &App) -> Entity<ActionLog> {
match self {
AgentDiffThread::AcpThread(thread) => thread.read(cx).action_log().clone(),
}
}
fn title(&self, cx: &App) -> SharedString {
match self {
AgentDiffThread::AcpThread(thread) => thread.read(cx).title(),
}
}
fn is_generating(&self, cx: &App) -> bool {
match self {
AgentDiffThread::AcpThread(thread) => {
thread.read(cx).status() == acp_thread::ThreadStatus::Generating
}
}
}
fn has_pending_edit_tool_uses(&self, cx: &App) -> bool {
match self {
AgentDiffThread::AcpThread(thread) => thread.read(cx).has_pending_edit_tool_calls(),
}
}
fn downgrade(&self) -> WeakAgentDiffThread {
match self {
AgentDiffThread::AcpThread(thread) => {
WeakAgentDiffThread::AcpThread(thread.downgrade())
}
}
}
}
impl From<Entity<AcpThread>> for AgentDiffThread {
fn from(entity: Entity<AcpThread>) -> Self {
AgentDiffThread::AcpThread(entity)
}
}
#[derive(PartialEq, Eq, Clone)]
pub enum WeakAgentDiffThread {
AcpThread(WeakEntity<AcpThread>),
}
impl WeakAgentDiffThread {
pub fn upgrade(&self) -> Option<AgentDiffThread> {
match self {
WeakAgentDiffThread::AcpThread(weak) => weak.upgrade().map(AgentDiffThread::AcpThread),
}
}
}
impl From<WeakEntity<AcpThread>> for WeakAgentDiffThread {
fn from(entity: WeakEntity<AcpThread>) -> Self {
WeakAgentDiffThread::AcpThread(entity)
}
}
impl AgentDiffPane {
pub fn deploy(
thread: Entity<AcpThread>,
thread: impl Into<AgentDiffThread>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut App,
@@ -60,11 +131,12 @@ impl AgentDiffPane {
}
pub fn deploy_in_workspace(
thread: Entity<AcpThread>,
thread: impl Into<AgentDiffThread>,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Entity<Self> {
let thread = thread.into();
let existing_diff = workspace
.items_of_type::<AgentDiffPane>(cx)
.find(|diff| diff.read(cx).thread == thread);
@@ -81,7 +153,7 @@ impl AgentDiffPane {
}
pub fn new(
thread: Entity<AcpThread>,
thread: AgentDiffThread,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -89,7 +161,7 @@ impl AgentDiffPane {
let focus_handle = cx.focus_handle();
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
let project = thread.read(cx).project().clone();
let project = thread.project(cx);
let editor = cx.new(|cx| {
let mut editor =
Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx);
@@ -100,16 +172,19 @@ impl AgentDiffPane {
editor
});
let action_log = thread.read(cx).action_log().clone();
let action_log = thread.action_log(cx);
let mut this = Self {
_subscriptions: vec![
cx.observe_in(&action_log, window, |this, _action_log, window, cx| {
this.update_excerpts(window, cx)
}),
cx.subscribe(&thread, |this, _thread, event, cx| {
this.handle_acp_thread_event(event, cx)
}),
match &thread {
AgentDiffThread::AcpThread(thread) => cx
.subscribe(thread, |this, _thread, event, cx| {
this.handle_acp_thread_event(event, cx)
}),
},
],
title: SharedString::default(),
multibuffer,
@@ -124,12 +199,7 @@ impl AgentDiffPane {
}
fn update_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let changed_buffers = self
.thread
.read(cx)
.action_log()
.read(cx)
.changed_buffers(cx);
let changed_buffers = self.thread.action_log(cx).read(cx).changed_buffers(cx);
let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::<HashSet<_>>();
for (buffer, diff_handle) in changed_buffers {
@@ -216,7 +286,7 @@ impl AgentDiffPane {
}
fn update_title(&mut self, cx: &mut Context<Self>) {
let new_title = self.thread.read(cx).title();
let new_title = self.thread.title(cx);
if new_title != self.title {
self.title = new_title;
cx.emit(EditorEvent::TitleChanged);
@@ -278,18 +348,16 @@ impl AgentDiffPane {
}
fn keep_all(&mut self, _: &KeepAll, _window: &mut Window, cx: &mut Context<Self>) {
let telemetry = ActionLogTelemetry::from(self.thread.read(cx));
let action_log = self.thread.read(cx).action_log().clone();
action_log.update(cx, |action_log, cx| {
action_log.keep_all_edits(Some(telemetry), cx)
});
self.thread
.action_log(cx)
.update(cx, |action_log, cx| action_log.keep_all_edits(cx))
}
}
fn keep_edits_in_selection(
editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot,
thread: &Entity<AcpThread>,
thread: &AgentDiffThread,
window: &mut Window,
cx: &mut Context<Editor>,
) {
@@ -304,7 +372,7 @@ fn keep_edits_in_selection(
fn reject_edits_in_selection(
editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot,
thread: &Entity<AcpThread>,
thread: &AgentDiffThread,
window: &mut Window,
cx: &mut Context<Editor>,
) {
@@ -318,7 +386,7 @@ fn reject_edits_in_selection(
fn keep_edits_in_ranges(
editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot,
thread: &Entity<AcpThread>,
thread: &AgentDiffThread,
ranges: Vec<Range<editor::Anchor>>,
window: &mut Window,
cx: &mut Context<Editor>,
@@ -333,15 +401,8 @@ fn keep_edits_in_ranges(
for hunk in &diff_hunks_in_ranges {
let buffer = multibuffer.read(cx).buffer(hunk.buffer_id);
if let Some(buffer) = buffer {
let action_log = thread.read(cx).action_log().clone();
let telemetry = ActionLogTelemetry::from(thread.read(cx));
action_log.update(cx, |action_log, cx| {
action_log.keep_edits_in_range(
buffer,
hunk.buffer_range.clone(),
Some(telemetry),
cx,
)
thread.action_log(cx).update(cx, |action_log, cx| {
action_log.keep_edits_in_range(buffer, hunk.buffer_range.clone(), cx)
});
}
}
@@ -350,7 +411,7 @@ fn keep_edits_in_ranges(
fn reject_edits_in_ranges(
editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot,
thread: &Entity<AcpThread>,
thread: &AgentDiffThread,
ranges: Vec<Range<editor::Anchor>>,
window: &mut Window,
cx: &mut Context<Editor>,
@@ -374,12 +435,11 @@ fn reject_edits_in_ranges(
}
}
let action_log = thread.read(cx).action_log().clone();
let telemetry = ActionLogTelemetry::from(thread.read(cx));
for (buffer, ranges) in ranges_by_buffer {
action_log
thread
.action_log(cx)
.update(cx, |action_log, cx| {
action_log.reject_edits_in_ranges(buffer, ranges, Some(telemetry.clone()), cx)
action_log.reject_edits_in_ranges(buffer, ranges, cx)
})
.detach_and_log_err(cx);
}
@@ -479,7 +539,7 @@ impl Item for AgentDiffPane {
}
fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement {
let title = self.thread.read(cx).title();
let title = self.thread.title(cx);
Label::new(format!("Review: {}", title))
.color(if params.selected {
Color::Default
@@ -660,7 +720,7 @@ impl Render for AgentDiffPane {
}
}
fn diff_hunk_controls(thread: &Entity<AcpThread>) -> editor::RenderDiffHunkControlsFn {
fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControlsFn {
let thread = thread.clone();
Arc::new(
@@ -687,7 +747,7 @@ fn render_diff_hunk_controls(
hunk_range: Range<editor::Anchor>,
is_created_file: bool,
line_height: Pixels,
thread: &Entity<AcpThread>,
thread: &AgentDiffThread,
editor: &Entity<Editor>,
cx: &mut App,
) -> AnyElement {
@@ -910,7 +970,9 @@ impl AgentDiffToolbar {
None => ToolbarItemLocation::Hidden,
Some(AgentDiffToolbarItem::Pane(_)) => ToolbarItemLocation::PrimaryRight,
Some(AgentDiffToolbarItem::Editor { state, .. }) => match state {
EditorState::Reviewing => ToolbarItemLocation::PrimaryRight,
EditorState::Generating | EditorState::Reviewing => {
ToolbarItemLocation::PrimaryRight
}
EditorState::Idle => ToolbarItemLocation::Hidden,
},
}
@@ -988,6 +1050,7 @@ impl Render for AgentDiffToolbar {
let content = match state {
EditorState::Idle => return Empty.into_any(),
EditorState::Generating => vec![spinner_icon],
EditorState::Reviewing => vec![
h_flex()
.child(
@@ -1101,11 +1164,8 @@ impl Render for AgentDiffToolbar {
return Empty.into_any();
};
let has_pending_edit_tool_use = agent_diff
.read(cx)
.thread
.read(cx)
.has_pending_edit_tool_calls();
let has_pending_edit_tool_use =
agent_diff.read(cx).thread.has_pending_edit_tool_uses(cx);
if has_pending_edit_tool_use {
return div().px_2().child(spinner_icon).into_any();
@@ -1162,10 +1222,11 @@ pub struct AgentDiff {
pub enum EditorState {
Idle,
Reviewing,
Generating,
}
struct WorkspaceThread {
thread: WeakEntity<AcpThread>,
thread: WeakAgentDiffThread,
_thread_subscriptions: (Subscription, Subscription),
singleton_editors: HashMap<WeakEntity<Buffer>, HashMap<WeakEntity<Editor>, Subscription>>,
_settings_subscription: Subscription,
@@ -1190,23 +1251,23 @@ impl AgentDiff {
pub fn set_active_thread(
workspace: &WeakEntity<Workspace>,
thread: Entity<AcpThread>,
thread: impl Into<AgentDiffThread>,
window: &mut Window,
cx: &mut App,
) {
Self::global(cx).update(cx, |this, cx| {
this.register_active_thread_impl(workspace, thread, window, cx);
this.register_active_thread_impl(workspace, thread.into(), window, cx);
});
}
fn register_active_thread_impl(
&mut self,
workspace: &WeakEntity<Workspace>,
thread: Entity<AcpThread>,
thread: AgentDiffThread,
window: &mut Window,
cx: &mut Context<Self>,
) {
let action_log = thread.read(cx).action_log().clone();
let action_log = thread.action_log(cx);
let action_log_subscription = cx.observe_in(&action_log, window, {
let workspace = workspace.clone();
@@ -1215,12 +1276,14 @@ impl AgentDiff {
}
});
let thread_subscription = cx.subscribe_in(&thread, window, {
let workspace = workspace.clone();
move |this, thread, event, window, cx| {
this.handle_acp_thread_event(&workspace, thread, event, window, cx)
}
});
let thread_subscription = match &thread {
AgentDiffThread::AcpThread(thread) => cx.subscribe_in(thread, window, {
let workspace = workspace.clone();
move |this, thread, event, window, cx| {
this.handle_acp_thread_event(&workspace, thread, event, window, cx)
}
}),
};
if let Some(workspace_thread) = self.workspace_threads.get_mut(workspace) {
// replace thread and action log subscription, but keep editors
@@ -1297,7 +1360,7 @@ impl AgentDiff {
fn register_review_action<T: Action>(
workspace: &mut Workspace,
review: impl Fn(&Entity<Editor>, &Entity<AcpThread>, &mut Window, &mut App) -> PostReviewState
review: impl Fn(&Entity<Editor>, &AgentDiffThread, &mut Window, &mut App) -> PostReviewState
+ 'static,
this: &Entity<AgentDiff>,
) {
@@ -1457,7 +1520,7 @@ impl AgentDiff {
return;
};
let action_log = thread.read(cx).action_log();
let action_log = thread.action_log(cx);
let changed_buffers = action_log.read(cx).changed_buffers(cx);
let mut unaffected = self.reviewing_editors.clone();
@@ -1482,11 +1545,15 @@ impl AgentDiff {
multibuffer.add_diff(diff_handle.clone(), cx);
});
let reviewing_state = EditorState::Reviewing;
let new_state = if thread.is_generating(cx) {
EditorState::Generating
} else {
EditorState::Reviewing
};
let previous_state = self
.reviewing_editors
.insert(weak_editor.clone(), reviewing_state.clone());
.insert(weak_editor.clone(), new_state.clone());
if previous_state.is_none() {
editor.update(cx, |editor, cx| {
@@ -1499,9 +1566,7 @@ impl AgentDiff {
unaffected.remove(weak_editor);
}
if reviewing_state == EditorState::Reviewing
&& previous_state != Some(reviewing_state)
{
if new_state == EditorState::Reviewing && previous_state != Some(new_state) {
// Jump to first hunk when we enter review mode
editor.update(cx, |editor, cx| {
let snapshot = multibuffer.read(cx).snapshot(cx);
@@ -1576,7 +1641,7 @@ impl AgentDiff {
fn keep_all(
editor: &Entity<Editor>,
thread: &Entity<AcpThread>,
thread: &AgentDiffThread,
window: &mut Window,
cx: &mut App,
) -> PostReviewState {
@@ -1596,7 +1661,7 @@ impl AgentDiff {
fn reject_all(
editor: &Entity<Editor>,
thread: &Entity<AcpThread>,
thread: &AgentDiffThread,
window: &mut Window,
cx: &mut App,
) -> PostReviewState {
@@ -1616,7 +1681,7 @@ impl AgentDiff {
fn keep(
editor: &Entity<Editor>,
thread: &Entity<AcpThread>,
thread: &AgentDiffThread,
window: &mut Window,
cx: &mut App,
) -> PostReviewState {
@@ -1629,7 +1694,7 @@ impl AgentDiff {
fn reject(
editor: &Entity<Editor>,
thread: &Entity<AcpThread>,
thread: &AgentDiffThread,
window: &mut Window,
cx: &mut App,
) -> PostReviewState {
@@ -1652,7 +1717,7 @@ impl AgentDiff {
fn review_in_active_editor(
&mut self,
workspace: &mut Workspace,
review: impl Fn(&Entity<Editor>, &Entity<AcpThread>, &mut Window, &mut App) -> PostReviewState,
review: impl Fn(&Entity<Editor>, &AgentDiffThread, &mut Window, &mut App) -> PostReviewState,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Task<Result<()>>> {
@@ -1674,7 +1739,7 @@ impl AgentDiff {
if let PostReviewState::AllReviewed = review(&editor, &thread, window, cx)
&& let Some(curr_buffer) = editor.read(cx).buffer().read(cx).as_singleton()
{
let changed_buffers = thread.read(cx).action_log().read(cx).changed_buffers(cx);
let changed_buffers = thread.action_log(cx).read(cx).changed_buffers(cx);
let mut keys = changed_buffers.keys().cycle();
keys.find(|k| *k == &curr_buffer);
@@ -1717,11 +1782,12 @@ mod tests {
use super::*;
use crate::Keep;
use acp_thread::AgentConnection as _;
use agent_settings::AgentSettings;
use editor::EditorSettings;
use gpui::{TestAppContext, UpdateGlobal, VisualTestContext};
use project::{FakeFs, Project};
use serde_json::json;
use settings::SettingsStore;
use settings::{Settings, SettingsStore};
use std::{path::Path, rc::Rc};
use util::path;
@@ -1730,8 +1796,13 @@ mod tests {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
AgentSettings::register(cx);
prompt_store::init(cx);
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
EditorSettings::register(cx);
language_model::init_settings(cx);
});
@@ -1758,7 +1829,8 @@ mod tests {
.await
.unwrap();
let action_log = cx.read(|cx| thread.read(cx).action_log().clone());
let thread = AgentDiffThread::AcpThread(thread);
let action_log = cx.read(|cx| thread.action_log(cx));
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
@@ -1884,8 +1956,13 @@ mod tests {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
AgentSettings::register(cx);
prompt_store::init(cx);
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
EditorSettings::register(cx);
language_model::init_settings(cx);
workspace::register_project_item::<Editor>(cx);
});
@@ -1941,6 +2018,7 @@ mod tests {
let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
// Set the active thread
let thread = AgentDiffThread::AcpThread(thread);
cx.update(|window, cx| {
AgentDiff::set_active_thread(&workspace.downgrade(), thread.clone(), window, cx)
});

View File

@@ -47,7 +47,6 @@ impl AgentModelSelector {
}
}
},
true, // Use popover styles for picker
window,
cx,
)

View File

@@ -16,9 +16,11 @@ use serde::{Deserialize, Serialize};
use settings::{
DefaultAgentView as DefaultView, LanguageModelProviderSetting, LanguageModelSelection,
};
use zed_actions::OpenBrowser;
use zed_actions::agent::{OpenClaudeCodeOnboardingModal, ReauthenticateAgent};
use crate::acp::{AcpThreadHistory, ThreadHistoryEvent};
use crate::context_store::ContextStore;
use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
use crate::{
AddContextServer, AgentDiffPane, DeleteRecentlyOpenThread, Follow, InlineAssistant,
@@ -31,14 +33,9 @@ use crate::{
text_thread_editor::{AgentPanelDelegate, TextThreadEditor, make_lsp_adapter_delegate},
ui::{AgentOnboardingModal, EndTrialUpsell},
};
use crate::{
ExpandMessageEditor,
acp::{AcpThreadHistory, ThreadHistoryEvent},
};
use crate::{
ExternalAgent, NewExternalAgentThread, NewNativeAgentThreadFromSummary, placeholder_command,
};
use crate::{ManageProfiles, context_store::ContextStore};
use agent_settings::AgentSettings;
use ai_onboarding::AgentPanelOnboarding;
use anyhow::{Result, anyhow};
@@ -109,12 +106,6 @@ pub fn init(cx: &mut App) {
}
},
)
.register_action(|workspace, _: &ExpandMessageEditor, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
workspace.focus_panel::<AgentPanel>(window, cx);
panel.update(cx, |panel, cx| panel.expand_message_editor(window, cx));
}
})
.register_action(|workspace, _: &OpenHistory, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
workspace.focus_panel::<AgentPanel>(window, cx);
@@ -738,25 +729,6 @@ impl AgentPanel {
&self.context_server_registry
}
pub fn is_hidden(workspace: &Entity<Workspace>, cx: &App) -> bool {
let workspace_read = workspace.read(cx);
workspace_read
.panel::<AgentPanel>(cx)
.map(|panel| {
let panel_id = Entity::entity_id(&panel);
let is_visible = workspace_read.all_docks().iter().any(|dock| {
dock.read(cx)
.visible_panel()
.is_some_and(|visible_panel| visible_panel.panel_id() == panel_id)
});
!is_visible
})
.unwrap_or(true)
}
fn active_thread_view(&self) -> Option<&Entity<AcpThreadView>> {
match &self.active_view {
ActiveView::ExternalAgentThread { thread_view, .. } => Some(thread_view),
@@ -953,15 +925,6 @@ impl AgentPanel {
.detach_and_log_err(cx);
}
fn expand_message_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if let Some(thread_view) = self.active_thread_view() {
thread_view.update(cx, |view, cx| {
view.expand_message_editor(&ExpandMessageEditor, window, cx);
view.focus_handle(cx).focus(window);
});
}
}
fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if matches!(self.active_view, ActiveView::History) {
if let Some(previous_view) = self.previous_view.take() {
@@ -1780,9 +1743,10 @@ impl AgentPanel {
}),
)
.action("Add Custom Server…", Box::new(AddContextServer))
.separator()
.separator();
menu = menu
.action("Rules", Box::new(OpenRulesLibrary::default()))
.action("Profiles", Box::new(ManageProfiles::default()))
.action("Settings", Box::new(OpenSettings))
.separator()
.action(full_screen_label, Box::new(ToggleZoom));
@@ -1880,21 +1844,13 @@ impl AgentPanel {
{
let focus_handle = focus_handle.clone();
move |_window, cx| {
Tooltip::for_action_in(
"New Thread…",
&ToggleNewThreadMenu,
&focus_handle,
cx,
)
Tooltip::for_action_in("New…", &ToggleNewThreadMenu, &focus_handle, cx)
}
},
)
.anchor(Corner::TopRight)
.with_handle(self.new_thread_menu_handle.clone())
.menu({
let selected_agent = self.selected_agent.clone();
let is_agent_selected = move |agent_type: AgentType| selected_agent == agent_type;
let workspace = self.workspace.clone();
let is_via_collab = workspace
.update(cx, |workspace, cx| {
@@ -1908,6 +1864,7 @@ impl AgentPanel {
let active_thread = active_thread.clone();
Some(ContextMenu::build(window, cx, |menu, _window, cx| {
menu.context(focus_handle.clone())
.header("Zed Agent")
.when_some(active_thread, |this, active_thread| {
let thread = active_thread.read(cx);
@@ -1931,11 +1888,9 @@ impl AgentPanel {
}
})
.item(
ContextMenuEntry::new("Zed Agent")
.when(is_agent_selected(AgentType::NativeAgent) | is_agent_selected(AgentType::TextThread) , |this| {
this.action(Box::new(NewExternalAgentThread { agent: None }))
})
.icon(IconName::ZedAgent)
ContextMenuEntry::new("New Thread")
.action(NewThread.boxed_clone())
.icon(IconName::Thread)
.icon_color(Color::Muted)
.handler({
let workspace = workspace.clone();
@@ -1959,10 +1914,10 @@ impl AgentPanel {
}),
)
.item(
ContextMenuEntry::new("Text Thread")
.action(NewTextThread.boxed_clone())
ContextMenuEntry::new("New Text Thread")
.icon(IconName::TextThread)
.icon_color(Color::Muted)
.action(NewTextThread.boxed_clone())
.handler({
let workspace = workspace.clone();
move |window, cx| {
@@ -1987,10 +1942,7 @@ impl AgentPanel {
.separator()
.header("External Agents")
.item(
ContextMenuEntry::new("Claude Code")
.when(is_agent_selected(AgentType::ClaudeCode), |this| {
this.action(Box::new(NewExternalAgentThread { agent: None }))
})
ContextMenuEntry::new("New Claude Code Thread")
.icon(IconName::AiClaude)
.disabled(is_via_collab)
.icon_color(Color::Muted)
@@ -2016,10 +1968,7 @@ impl AgentPanel {
}),
)
.item(
ContextMenuEntry::new("Codex CLI")
.when(is_agent_selected(AgentType::Codex), |this| {
this.action(Box::new(NewExternalAgentThread { agent: None }))
})
ContextMenuEntry::new("New Codex Thread")
.icon(IconName::AiOpenAi)
.disabled(is_via_collab)
.icon_color(Color::Muted)
@@ -2045,10 +1994,7 @@ impl AgentPanel {
}),
)
.item(
ContextMenuEntry::new("Gemini CLI")
.when(is_agent_selected(AgentType::Gemini), |this| {
this.action(Box::new(NewExternalAgentThread { agent: None }))
})
ContextMenuEntry::new("New Gemini CLI Thread")
.icon(IconName::AiGemini)
.icon_color(Color::Muted)
.disabled(is_via_collab)
@@ -2074,8 +2020,8 @@ impl AgentPanel {
}),
)
.map(|mut menu| {
let agent_server_store = agent_server_store.read(cx);
let agent_names = agent_server_store
let agent_server_store_read = agent_server_store.read(cx);
let agent_names = agent_server_store_read
.external_agents()
.filter(|name| {
name.0 != GEMINI_NAME
@@ -2084,38 +2030,21 @@ impl AgentPanel {
})
.cloned()
.collect::<Vec<_>>();
let custom_settings = cx
.global::<SettingsStore>()
.get::<AllAgentServersSettings>(None)
.custom
.clone();
for agent_name in agent_names {
let icon_path = agent_server_store.agent_icon(&agent_name);
let mut entry = ContextMenuEntry::new(agent_name.clone());
let command = custom_settings
.get(&agent_name.0)
.map(|settings| settings.command.clone())
.unwrap_or(placeholder_command());
let icon_path = agent_server_store_read.agent_icon(&agent_name);
let mut entry =
ContextMenuEntry::new(format!("New {} Thread", agent_name));
if let Some(icon_path) = icon_path {
entry = entry.custom_icon_svg(icon_path);
entry = entry.custom_icon_path(icon_path);
} else {
entry = entry.icon(IconName::Terminal);
}
entry = entry
.when(
is_agent_selected(AgentType::Custom {
name: agent_name.0.clone(),
command: command.clone(),
}),
|this| {
this.action(Box::new(NewExternalAgentThread { agent: None }))
},
)
.icon_color(Color::Muted)
.disabled(is_via_collab)
.handler({
@@ -2155,27 +2084,18 @@ impl AgentPanel {
}
}
});
menu = menu.item(entry);
}
menu
})
.separator()
.item(
ContextMenuEntry::new("Add More Agents")
.icon(IconName::Plus)
.icon_color(Color::Muted)
.handler({
move |window, cx| {
window.dispatch_action(Box::new(zed_actions::Extensions {
category_filter: Some(
zed_actions::ExtensionCategoryFilter::AgentServers,
),
id: None,
}), cx)
}
}),
.link(
"Add Other Agents",
OpenBrowser {
url: zed_urls::external_agents_docs(cx),
}
.boxed_clone(),
)
}))
}
@@ -2188,8 +2108,8 @@ impl AgentPanel {
.id("selected_agent_icon")
.when_some(selected_agent_custom_icon, |this, icon_path| {
let label = selected_agent_label.clone();
this.px_1()
.child(Icon::from_external_svg(icon_path).color(Color::Muted))
this.px(DynamicSpacing::Base02.rems(cx))
.child(Icon::from_path(icon_path).color(Color::Muted))
.tooltip(move |_window, cx| {
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
})
@@ -2197,7 +2117,7 @@ impl AgentPanel {
.when(!has_custom_icon, |this| {
this.when_some(self.selected_agent.icon(), |this, icon| {
let label = selected_agent_label.clone();
this.px_1()
this.px(DynamicSpacing::Base02.rems(cx))
.child(Icon::new(icon).color(Color::Muted))
.tooltip(move |_window, cx| {
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)

View File

@@ -12,6 +12,7 @@ mod context_strip;
mod inline_assistant;
mod inline_prompt_editor;
mod language_model_selector;
mod message_editor;
mod profile_selector;
mod slash_command;
mod slash_command_picker;
@@ -30,10 +31,7 @@ use command_palette_hooks::CommandPaletteFilter;
use feature_flags::FeatureFlagAppExt as _;
use fs::Fs;
use gpui::{Action, App, Entity, SharedString, actions};
use language::{
LanguageRegistry,
language_settings::{AllLanguageSettings, EditPredictionProvider},
};
use language::LanguageRegistry;
use language_model::{
ConfiguredModel, LanguageModel, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry,
};
@@ -250,6 +248,8 @@ pub fn init(
is_eval: bool,
cx: &mut App,
) {
AgentSettings::register(cx);
assistant_text_thread::init(client.clone(), cx);
rules_library::init(cx);
if !is_eval {
@@ -289,25 +289,7 @@ pub fn init(
fn update_command_palette_filter(cx: &mut App) {
let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
let agent_enabled = AgentSettings::get_global(cx).enabled;
let edit_prediction_provider = AllLanguageSettings::get_global(cx)
.edit_predictions
.provider;
CommandPaletteFilter::update_global(cx, |filter, _| {
use editor::actions::{
AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
};
let edit_prediction_actions = [
TypeId::of::<AcceptEditPrediction>(),
TypeId::of::<AcceptPartialEditPrediction>(),
TypeId::of::<ShowEditPrediction>(),
TypeId::of::<NextEditPrediction>(),
TypeId::of::<PreviousEditPrediction>(),
TypeId::of::<ToggleEditPrediction>(),
];
if disable_ai {
filter.hide_namespace("agent");
filter.hide_namespace("assistant");
@@ -316,47 +298,42 @@ fn update_command_palette_filter(cx: &mut App) {
filter.hide_namespace("zed_predict_onboarding");
filter.hide_namespace("edit_prediction");
use editor::actions::{
AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
};
let edit_prediction_actions = [
TypeId::of::<AcceptEditPrediction>(),
TypeId::of::<AcceptPartialEditPrediction>(),
TypeId::of::<ShowEditPrediction>(),
TypeId::of::<NextEditPrediction>(),
TypeId::of::<PreviousEditPrediction>(),
TypeId::of::<ToggleEditPrediction>(),
];
filter.hide_action_types(&edit_prediction_actions);
filter.hide_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
} else {
if agent_enabled {
filter.show_namespace("agent");
} else {
filter.hide_namespace("agent");
}
filter.show_namespace("agent");
filter.show_namespace("assistant");
match edit_prediction_provider {
EditPredictionProvider::None => {
filter.hide_namespace("edit_prediction");
filter.hide_namespace("copilot");
filter.hide_namespace("supermaven");
filter.hide_action_types(&edit_prediction_actions);
}
EditPredictionProvider::Copilot => {
filter.show_namespace("edit_prediction");
filter.show_namespace("copilot");
filter.hide_namespace("supermaven");
filter.show_action_types(edit_prediction_actions.iter());
}
EditPredictionProvider::Supermaven => {
filter.show_namespace("edit_prediction");
filter.hide_namespace("copilot");
filter.show_namespace("supermaven");
filter.show_action_types(edit_prediction_actions.iter());
}
EditPredictionProvider::Zed
| EditPredictionProvider::Codestral
| EditPredictionProvider::Experimental(_) => {
filter.show_namespace("edit_prediction");
filter.hide_namespace("copilot");
filter.hide_namespace("supermaven");
filter.show_action_types(edit_prediction_actions.iter());
}
}
filter.show_namespace("copilot");
filter.show_namespace("zed_predict_onboarding");
filter.show_namespace("edit_prediction");
use editor::actions::{
AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
};
let edit_prediction_actions = [
TypeId::of::<AcceptEditPrediction>(),
TypeId::of::<AcceptPartialEditPrediction>(),
TypeId::of::<ShowEditPrediction>(),
TypeId::of::<NextEditPrediction>(),
TypeId::of::<PreviousEditPrediction>(),
TypeId::of::<ToggleEditPrediction>(),
];
filter.show_action_types(edit_prediction_actions.iter());
filter.show_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
}
});
@@ -446,137 +423,3 @@ fn register_slash_commands(cx: &mut App) {
})
.detach();
}
#[cfg(test)]
mod tests {
use super::*;
use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
use command_palette_hooks::CommandPaletteFilter;
use editor::actions::AcceptEditPrediction;
use gpui::{BorrowAppContext, TestAppContext, px};
use project::DisableAiSettings;
use settings::{
DefaultAgentView, DockPosition, NotifyWhenAgentWaiting, Settings, SettingsStore,
};
#[gpui::test]
fn test_agent_command_palette_visibility(cx: &mut TestAppContext) {
// Init settings
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
command_palette_hooks::init(cx);
AgentSettings::register(cx);
DisableAiSettings::register(cx);
AllLanguageSettings::register(cx);
});
let agent_settings = AgentSettings {
enabled: true,
button: true,
dock: DockPosition::Right,
default_width: px(300.),
default_height: px(600.),
default_model: None,
inline_assistant_model: None,
commit_message_model: None,
thread_summary_model: None,
inline_alternatives: vec![],
default_profile: AgentProfileId::default(),
default_view: DefaultAgentView::Thread,
profiles: Default::default(),
always_allow_tool_actions: false,
notify_when_agent_waiting: NotifyWhenAgentWaiting::default(),
play_sound_when_agent_done: false,
single_file_review: false,
model_parameters: vec![],
preferred_completion_mode: CompletionMode::Normal,
enable_feedback: false,
expand_edit_card: true,
expand_terminal_card: true,
use_modifier_to_send: true,
message_editor_min_lines: 1,
};
cx.update(|cx| {
AgentSettings::override_global(agent_settings.clone(), cx);
DisableAiSettings::override_global(DisableAiSettings { disable_ai: false }, cx);
// Initial update
update_command_palette_filter(cx);
});
// Assert visible
cx.update(|cx| {
let filter = CommandPaletteFilter::try_global(cx).unwrap();
assert!(
!filter.is_hidden(&NewThread),
"NewThread should be visible by default"
);
});
// Disable agent
cx.update(|cx| {
let mut new_settings = agent_settings.clone();
new_settings.enabled = false;
AgentSettings::override_global(new_settings, cx);
// Trigger update
update_command_palette_filter(cx);
});
// Assert hidden
cx.update(|cx| {
let filter = CommandPaletteFilter::try_global(cx).unwrap();
assert!(
filter.is_hidden(&NewThread),
"NewThread should be hidden when agent is disabled"
);
});
// Test EditPredictionProvider
// Enable EditPredictionProvider::Copilot
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |s| {
s.project
.all_languages
.features
.get_or_insert(Default::default())
.edit_prediction_provider = Some(EditPredictionProvider::Copilot);
});
});
update_command_palette_filter(cx);
});
cx.update(|cx| {
let filter = CommandPaletteFilter::try_global(cx).unwrap();
assert!(
!filter.is_hidden(&AcceptEditPrediction),
"EditPrediction should be visible when provider is Copilot"
);
});
// Disable EditPredictionProvider (None)
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |s| {
s.project
.all_languages
.features
.get_or_insert(Default::default())
.edit_prediction_provider = Some(EditPredictionProvider::None);
});
});
update_command_palette_filter(cx);
});
cx.update(|cx| {
let filter = CommandPaletteFilter::try_global(cx).unwrap();
assert!(
filter.is_hidden(&AcceptEditPrediction),
"EditPrediction should be hidden when provider is None"
);
});
}
}

View File

@@ -1082,7 +1082,10 @@ mod tests {
};
use gpui::TestAppContext;
use indoc::indoc;
use language::{Buffer, Language, LanguageConfig, LanguageMatcher, Point, tree_sitter_rust};
use language::{
Buffer, Language, LanguageConfig, LanguageMatcher, Point, language_settings,
tree_sitter_rust,
};
use language_model::{LanguageModelRegistry, TokenUsage};
use rand::prelude::*;
use settings::SettingsStore;
@@ -1462,6 +1465,8 @@ mod tests {
fn init_test(cx: &mut TestAppContext) {
cx.update(LanguageModelRegistry::test);
cx.set_global(cx.update(SettingsStore::test));
cx.update(Project::init_settings);
cx.update(language_settings::init);
}
fn simulate_response_stream(

View File

@@ -620,18 +620,8 @@ impl TextThreadContextHandle {
impl Display for TextThreadContext {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "<text_thread title=\"")?;
for c in self.title.chars() {
match c {
'&' => write!(f, "&amp;")?,
'<' => write!(f, "&lt;")?,
'>' => write!(f, "&gt;")?,
'"' => write!(f, "&quot;")?,
'\'' => write!(f, "&apos;")?,
_ => write!(f, "{}", c)?,
}
}
writeln!(f, "\">")?;
// TODO: escape title?
writeln!(f, "<text_thread title=\"{}\">", self.title)?;
write!(f, "{}", self.text.trim())?;
write!(f, "\n</text_thread>")
}
@@ -1075,6 +1065,8 @@ mod tests {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
});
}
@@ -1089,7 +1081,7 @@ mod tests {
}
#[gpui::test]
async fn test_large_file_uses_fallback(cx: &mut TestAppContext) {
async fn test_large_file_uses_outline(cx: &mut TestAppContext) {
init_test_settings(cx);
// Create a large file that exceeds AUTO_OUTLINE_SIZE
@@ -1101,16 +1093,16 @@ mod tests {
let file_context = load_context_for("file.txt", large_content, cx).await;
// Should contain some of the actual file content
assert!(
file_context.text.contains(LINE),
"Should contain some of the file content"
file_context
.text
.contains(&format!("# File outline for {}", path!("test/file.txt"))),
"Large files should not get an outline"
);
// Should be much smaller than original
assert!(
file_context.text.len() < content_len / 10,
"Should be significantly smaller than original content"
file_context.text.len() < content_len,
"Outline should be smaller than original content"
);
}

View File

@@ -42,7 +42,7 @@ use super::{
ContextPickerAction, ContextPickerEntry, ContextPickerMode, MentionLink, RecentEntry,
available_context_picker_entries, recent_context_picker_entries_with_store, selection_ranges,
};
use crate::inline_prompt_editor::ContextCreasesAddon;
use crate::message_editor::ContextCreasesAddon;
pub(crate) enum Match {
File(FileMatch),
@@ -278,8 +278,6 @@ impl ContextPickerCompletionProvider {
icon_path: Some(mode.icon().path().into()),
documentation: None,
source: project::CompletionSource::Custom,
match_start: None,
snippet_deduplication_key: None,
insert_text_mode: None,
// This ensures that when a user accepts this completion, the
// completion menu will still be shown after "@category " is
@@ -388,8 +386,6 @@ impl ContextPickerCompletionProvider {
icon_path: Some(action.icon().path().into()),
documentation: None,
source: project::CompletionSource::Custom,
match_start: None,
snippet_deduplication_key: None,
insert_text_mode: None,
// This ensures that when a user accepts this completion, the
// completion menu will still be shown after "@category " is
@@ -421,8 +417,6 @@ impl ContextPickerCompletionProvider {
replace_range: source_range.clone(),
new_text,
label: CodeLabel::plain(thread_entry.title().to_string(), None),
match_start: None,
snippet_deduplication_key: None,
documentation: None,
insert_text_mode: None,
source: project::CompletionSource::Custom,
@@ -490,8 +484,6 @@ impl ContextPickerCompletionProvider {
replace_range: source_range.clone(),
new_text,
label: CodeLabel::plain(rules.title.to_string(), None),
match_start: None,
snippet_deduplication_key: None,
documentation: None,
insert_text_mode: None,
source: project::CompletionSource::Custom,
@@ -532,8 +524,6 @@ impl ContextPickerCompletionProvider {
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(IconName::ToolWeb.path().into()),
match_start: None,
snippet_deduplication_key: None,
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
IconName::ToolWeb.path().into(),
@@ -622,8 +612,6 @@ impl ContextPickerCompletionProvider {
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(completion_icon_path),
match_start: None,
snippet_deduplication_key: None,
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
crease_icon_path,
@@ -701,8 +689,6 @@ impl ContextPickerCompletionProvider {
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(IconName::Code.path().into()),
match_start: None,
snippet_deduplication_key: None,
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
IconName::Code.path().into(),
@@ -1196,8 +1182,10 @@ mod tests {
let app_state = cx.update(AppState::test);
cx.update(|cx| {
language::init(cx);
editor::init(cx);
workspace::init(app_state.clone(), cx);
Project::init_settings(cx);
});
app_state
@@ -1498,8 +1486,10 @@ mod tests {
let app_state = cx.update(AppState::test);
cx.update(|cx| {
language::init(cx);
editor::init(cx);
workspace::init(app_state.clone(), cx);
Project::init_settings(cx);
});
app_state
@@ -1696,6 +1686,11 @@ mod tests {
let store = SettingsStore::test(cx);
cx.set_global(store);
theme::init(theme::LoadThemes::JustBase, cx);
client::init_settings(cx);
language::init(cx);
Project::init_settings(cx);
workspace::init_settings(cx);
editor::init_settings(cx);
});
}
}

Some files were not shown because too many files have changed in this diff Show More