diff --git a/.github/CODEOWNERS.hold b/.github/CODEOWNERS.hold
index 3d315b36401b2e27e29a2377aeabab8c09c75d39..3b7cbc644768f82646591619e49c4b6a0d6de200 100644
--- a/.github/CODEOWNERS.hold
+++ b/.github/CODEOWNERS.hold
@@ -48,7 +48,6 @@
/crates/edit_prediction_context/ @zed-industries/ai-team
/crates/edit_prediction_types/ @zed-industries/ai-team
/crates/edit_prediction_ui/ @zed-industries/ai-team
-/crates/eval/ @zed-industries/ai-team
/crates/eval_utils/ @zed-industries/ai-team
/crates/google_ai/ @zed-industries/ai-team
/crates/language_model/ @zed-industries/ai-team
diff --git a/.github/actions/run_tests/action.yml b/.github/actions/run_tests/action.yml
index a071aba3a87dcf8e8f48f740115cfddf48b9f805..610c334a65c3a3817ab0ee2bb7356a923643092b 100644
--- a/.github/actions/run_tests/action.yml
+++ b/.github/actions/run_tests/action.yml
@@ -5,7 +5,7 @@ runs:
using: "composite"
steps:
- name: Install nextest
- uses: taiki-e/install-action@nextest
+ uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c # nextest
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
diff --git a/.github/actions/run_tests_windows/action.yml b/.github/actions/run_tests_windows/action.yml
index 307b73f363b7d5fd7a3c9e5082c4f17d622ec165..3752cbb50d538459ea58d2219e591d1abbda6247 100644
--- a/.github/actions/run_tests_windows/action.yml
+++ b/.github/actions/run_tests_windows/action.yml
@@ -12,7 +12,7 @@ runs:
steps:
- name: Install test runner
working-directory: ${{ inputs.working-directory }}
- uses: taiki-e/install-action@nextest
+ uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c # nextest
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index b8b7939813f9cc72da88e75653b6f2933403a239..a56793ad6222e5788621f6c8a430205e9ad848d7 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,28 +1,13 @@
-## Context
+Self-Review Checklist:
-
-
-## How to Review
-
-
-
-## Self-Review Checklist
-
-
- [ ] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable
+Closes #ISSUE
+
Release Notes:
- N/A or Added/Fixed/Improved ...
diff --git a/.github/workflows/add_commented_closed_issue_to_project.yml b/.github/workflows/add_commented_closed_issue_to_project.yml
index bd84eaa9446e57c5482ab818df3dbcfe587e040e..27315e7160200dc323899b58d5c307aae656d5c6 100644
--- a/.github/workflows/add_commented_closed_issue_to_project.yml
+++ b/.github/workflows/add_commented_closed_issue_to_project.yml
@@ -35,7 +35,7 @@ jobs:
- if: steps.is-post-close-comment.outputs.result == 'true'
id: get-app-token
- uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1 # v2.1.4
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
with:
app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }}
private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }}
diff --git a/.github/workflows/after_release.yml b/.github/workflows/after_release.yml
index 95229f9f46bbd34ffe02832114b2b39da1b7e090..ab2220764861b17317f1fa3971ecf2aa9b645c8d 100644
--- a/.github/workflows/after_release.yml
+++ b/.github/workflows/after_release.yml
@@ -27,7 +27,7 @@ jobs:
- 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 || inputs.tag_name }}
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: after_release::rebuild_releases_page::redeploy_zed_dev
@@ -110,7 +110,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: release::create_sentry_release
diff --git a/.github/workflows/assign-reviewers.yml b/.github/workflows/assign-reviewers.yml
index 1a21879b639736232f965863a31b9a8d3a2c2b35..2a12a69defdd4f8933f1c549f0624d9bdcc9fd40 100644
--- a/.github/workflows/assign-reviewers.yml
+++ b/.github/workflows/assign-reviewers.yml
@@ -51,7 +51,7 @@ jobs:
steps:
- name: Generate app token
id: app-token
- uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
with:
app-id: ${{ vars.COORDINATOR_APP_ID }}
private-key: ${{ secrets.COORDINATOR_APP_PRIVATE_KEY }}
@@ -60,7 +60,7 @@ jobs:
# SECURITY: checks out the coordinator repo at ref: main, NOT the PR branch.
# persist-credentials: false prevents the token from leaking into .git/config.
- name: Checkout coordinator repo
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
repository: zed-industries/codeowner-coordinator
ref: main
@@ -69,7 +69,7 @@ jobs:
persist-credentials: false
- name: Setup Python
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.11"
@@ -83,6 +83,8 @@ jobs:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
PR_URL: ${{ github.event.pull_request.html_url }}
TARGET_REPO: ${{ github.repository }}
+ ASSIGN_INTERNAL: ${{ vars.ASSIGN_INTERNAL || 'false' }}
+ ASSIGN_EXTERNAL: ${{ vars.ASSIGN_EXTERNAL || 'true' }}
run: |
cd codeowner-coordinator
python .github/scripts/assign-reviewers.py \
@@ -95,7 +97,7 @@ jobs:
- name: Upload output
if: always()
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: assign-reviewers-output
path: /tmp/assign-reviewers-output.txt
diff --git a/.github/workflows/autofix_pr.yml b/.github/workflows/autofix_pr.yml
index 1f9e6320700d14cab69662e317c30fa7206eb655..f055c078cf4f814e342697e311ad5660f68f4624 100644
--- a/.github/workflows/autofix_pr.yml
+++ b/.github/workflows/autofix_pr.yml
@@ -18,7 +18,7 @@ jobs:
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: autofix_pr::run_autofix::checkout_pr
@@ -31,7 +31,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -91,22 +91,22 @@ jobs:
if: needs.run_autofix.outputs.has_changes == 'true'
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- - id: get-app-token
+ - id: generate-token
name: steps::authenticate_as_zippy
- uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
with:
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- token: ${{ steps.get-app-token.outputs.token }}
+ token: ${{ steps.generate-token.outputs.token }}
- name: autofix_pr::commit_changes::checkout_pr
run: gh pr checkout "$PR_NUMBER"
env:
PR_NUMBER: ${{ inputs.pr_number }}
- GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
+ GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
- name: autofix_pr::download_patch_artifact
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
@@ -122,7 +122,7 @@ jobs:
GIT_COMMITTER_EMAIL: 234243425+zed-zippy[bot]@users.noreply.github.com
GIT_AUTHOR_NAME: Zed Zippy
GIT_AUTHOR_EMAIL: 234243425+zed-zippy[bot]@users.noreply.github.com
- GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
+ GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
concurrency:
group: ${{ github.workflow }}-${{ inputs.pr_number }}
cancel-in-progress: true
diff --git a/.github/workflows/background_agent_mvp.yml b/.github/workflows/background_agent_mvp.yml
index 528600138243cb8aca2e0fe0645eda198fc4f2b2..2f048d572df6fb45368c6d7aece574e83c9e7949 100644
--- a/.github/workflows/background_agent_mvp.yml
+++ b/.github/workflows/background_agent_mvp.yml
@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
fetch-depth: 0
@@ -50,7 +50,7 @@ jobs:
"${HOME}/.local/bin/droid" --version
- name: Setup Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
diff --git a/.github/workflows/bump_collab_staging.yml b/.github/workflows/bump_collab_staging.yml
index d400905b4da3304a8b916d3a38ae9d8a2855dbf5..4f9724439f37b276de625e5810c777c12f20e4b9 100644
--- a/.github/workflows/bump_collab_staging.yml
+++ b/.github/workflows/bump_collab_staging.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: Checkout repository
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
fetch-depth: 0
diff --git a/.github/workflows/bump_patch_version.yml b/.github/workflows/bump_patch_version.yml
index 62540321ed755f2fd3879a7ddfc3a37237d8e7de..6b2fa66147b656efd9c8e28cd43cd2e010930dd1 100644
--- a/.github/workflows/bump_patch_version.yml
+++ b/.github/workflows/bump_patch_version.yml
@@ -13,18 +13,18 @@ jobs:
if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- - id: get-app-token
+ - id: generate-token
name: steps::authenticate_as_zippy
- uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
with:
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
ref: ${{ inputs.branch }}
- token: ${{ steps.get-app-token.outputs.token }}
+ token: ${{ steps.generate-token.outputs.token }}
- name: bump_patch_version::run_bump_patch_version::bump_patch_version
run: |
channel="$(cat crates/zed/RELEASE_CHANNEL)"
@@ -51,7 +51,7 @@ jobs:
GIT_COMMITTER_EMAIL: 234243425+zed-zippy[bot]@users.noreply.github.com
GIT_AUTHOR_NAME: Zed Zippy
GIT_AUTHOR_EMAIL: 234243425+zed-zippy[bot]@users.noreply.github.com
- GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
+ GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
concurrency:
group: ${{ github.workflow }}-${{ inputs.branch }}
cancel-in-progress: true
diff --git a/.github/workflows/catch_blank_issues.yml b/.github/workflows/catch_blank_issues.yml
index c6f595ef2e0890ce107829f3e91490332567368a..dbceac5a196f2dc9c0963e491bd346dc8c0eff51 100644
--- a/.github/workflows/catch_blank_issues.yml
+++ b/.github/workflows/catch_blank_issues.yml
@@ -16,7 +16,7 @@ jobs:
timeout-minutes: 5
steps:
- id: get-app-token
- uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1 # v2.1.4
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
with:
app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }}
private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }}
diff --git a/.github/workflows/cherry_pick.yml b/.github/workflows/cherry_pick.yml
index ee0c1d35d0f9825d7c39b81fba0fe35901de2611..4a3bd0e643e027e7feaeac4760797e2a1fb16e11 100644
--- a/.github/workflows/cherry_pick.yml
+++ b/.github/workflows/cherry_pick.yml
@@ -26,12 +26,12 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- - id: get-app-token
+ - id: generate-token
name: steps::authenticate_as_zippy
- uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
with:
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
@@ -43,7 +43,7 @@ jobs:
CHANNEL: ${{ inputs.channel }}
GIT_COMMITTER_NAME: Zed Zippy
GIT_COMMITTER_EMAIL: hi@zed.dev
- GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
+ GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
defaults:
run:
shell: bash -euxo pipefail {0}
diff --git a/.github/workflows/comment_on_potential_duplicate_issues.yml b/.github/workflows/comment_on_potential_duplicate_issues.yml
index de51cb1105c98901237ec88d47c34c69ea5c8080..0d7ce3aad3ce9deacfedfe1d237c41127a639da0 100644
--- a/.github/workflows/comment_on_potential_duplicate_issues.yml
+++ b/.github/workflows/comment_on_potential_duplicate_issues.yml
@@ -27,14 +27,14 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
sparse-checkout: script/github-check-new-issue-for-duplicates.py
sparse-checkout-cone-mode: false
- name: Get github app token
id: get-app-token
- uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1 # v1.11.7
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
with:
app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }}
private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }}
diff --git a/.github/workflows/community_champion_auto_labeler.yml b/.github/workflows/community_champion_auto_labeler.yml
index fa44afc16dcaee4c1e1176b9344aed476ac6d8e5..82a9e274d64725b0e55c6ced46ca64ac3890e35e 100644
--- a/.github/workflows/community_champion_auto_labeler.yml
+++ b/.github/workflows/community_champion_auto_labeler.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: Check if author is a community champion and apply label
- uses: actions/github-script@v7
+ uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
env:
COMMUNITY_CHAMPIONS: |
0x2CA
diff --git a/.github/workflows/community_update_all_top_ranking_issues.yml b/.github/workflows/community_update_all_top_ranking_issues.yml
index ef3b4fc39ddb5f0db9b09c5e861547ae8cd7eb08..b8003a69b243c3cafbf40857c653fb03f515eeec 100644
--- a/.github/workflows/community_update_all_top_ranking_issues.yml
+++ b/.github/workflows/community_update_all_top_ranking_issues.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
if: github.repository == 'zed-industries/zed'
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+ - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: Set up uv
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
with:
diff --git a/.github/workflows/community_update_weekly_top_ranking_issues.yml b/.github/workflows/community_update_weekly_top_ranking_issues.yml
index 53b548f2bb4286e5de86d3823e67d75c0413a1cb..90d1934ffcb6d5d711896d3902b70599e4b06872 100644
--- a/.github/workflows/community_update_weekly_top_ranking_issues.yml
+++ b/.github/workflows/community_update_weekly_top_ranking_issues.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
if: github.repository == 'zed-industries/zed'
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+ - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: Set up uv
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
with:
diff --git a/.github/workflows/compare_perf.yml b/.github/workflows/compare_perf.yml
index 03113f2aa0be4dc794f8f5edec18df22fb0daa31..2b2154ce9bd14c85d0f0d10e95c4065a458006a1 100644
--- a/.github/workflows/compare_perf.yml
+++ b/.github/workflows/compare_perf.yml
@@ -21,7 +21,7 @@ jobs:
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -33,7 +33,7 @@ jobs:
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
- name: compare_perf::run_perf::install_hyperfine
- uses: taiki-e/install-action@hyperfine
+ uses: taiki-e/install-action@b4f2d5cb8597b15997c8ede873eb6185efc5f0ad
- name: steps::git_checkout
run: git fetch origin "$REF_NAME" && git checkout "$REF_NAME"
env:
diff --git a/.github/workflows/congrats.yml b/.github/workflows/congrats.yml
index 6a4111a1c5b5143ee9be067911207d5b4ca1448c..4866b3c33bc6bab9f9d20ac1701b7d6535b356ee 100644
--- a/.github/workflows/congrats.yml
+++ b/.github/workflows/congrats.yml
@@ -13,7 +13,7 @@ jobs:
steps:
- name: Get PR info and check if author is external
id: check
- uses: actions/github-script@v7
+ uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
github-token: ${{ secrets.CONGRATSBOT_GITHUB_TOKEN }}
script: |
@@ -29,6 +29,13 @@ jobs:
}
const mergedPR = prs.find(pr => pr.merged_at !== null) || prs[0];
+
+ if (mergedPR.user.type === "Bot") {
+ // They are a good bot, but not good enough to be congratulated
+ core.setOutput('should_congratulate', 'false');
+ return;
+ }
+
const prAuthor = mergedPR.user.login;
try {
@@ -50,7 +57,7 @@ jobs:
congrats:
needs: check-author
if: needs.check-author.outputs.should_congratulate == 'true'
- uses: withastro/automation/.github/workflows/congratsbot.yml@main
+ uses: withastro/automation/.github/workflows/congratsbot.yml@a5bd0c5748c4d56e687cdd558064f9ee8adfb1f2 # main
with:
EMOJIS: 🎉,🎊,🧑🚀,🥳,🙌,🚀,🦀,🔥,🚢
secrets:
diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml
index 62f799baae1fb64a31807030c5700019a3d2c1b7..62739b21675fec2b4289b646fec794846c5fe783 100644
--- a/.github/workflows/danger.yml
+++ b/.github/workflows/danger.yml
@@ -16,7 +16,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_pnpm
diff --git a/.github/workflows/deploy_cloudflare.yml b/.github/workflows/deploy_cloudflare.yml
index 37f23b20d2825e9f3d26c456903962a10c2d0081..4e029c63ccd8a022ac9d6107748f964585058735 100644
--- a/.github/workflows/deploy_cloudflare.yml
+++ b/.github/workflows/deploy_cloudflare.yml
@@ -13,7 +13,7 @@ jobs:
steps:
- name: Checkout repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
clean: false
diff --git a/.github/workflows/deploy_collab.yml b/.github/workflows/deploy_collab.yml
index 7fe06460f752599513c79b71bb01636d69d20e6c..5a3eff186814128ebb3973642040d9228f0e87fd 100644
--- a/.github/workflows/deploy_collab.yml
+++ b/.github/workflows/deploy_collab.yml
@@ -17,7 +17,7 @@ jobs:
CXX: clang++
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
fetch-depth: 0
@@ -26,7 +26,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -48,7 +48,7 @@ jobs:
CXX: clang++
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
fetch-depth: 0
@@ -57,7 +57,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -66,7 +66,7 @@ jobs:
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
- name: steps::cargo_install_nextest
- uses: taiki-e/install-action@nextest
+ uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 250
- name: deploy_collab::tests::run_collab_tests
@@ -93,7 +93,7 @@ jobs:
- name: deploy_collab::publish::sign_into_registry
run: doctl registry login
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: deploy_collab::publish::build_docker_image
@@ -113,7 +113,7 @@ jobs:
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: deploy_collab::deploy::install_doctl
diff --git a/.github/workflows/docs_suggestions.yml b/.github/workflows/docs_suggestions.yml
index c2dc8b4d5197bcbf38dbfb92dac8c23386726d53..c3d04d5780b290c81470dea16d11f473ee7361b1 100644
--- a/.github/workflows/docs_suggestions.yml
+++ b/.github/workflows/docs_suggestions.yml
@@ -64,7 +64,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -296,7 +296,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
fetch-depth: 0
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.ref || '' }}
diff --git a/.github/workflows/extension_auto_bump.yml b/.github/workflows/extension_auto_bump.yml
index 9388a0a442bf249505aaf51e9b6826d3bb228fb7..d4480194edbcacd24d0dff9bfd807abeb513d8ae 100644
--- a/.github/workflows/extension_auto_bump.yml
+++ b/.github/workflows/extension_auto_bump.yml
@@ -17,7 +17,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
fetch-depth: 2
diff --git a/.github/workflows/extension_bump.yml b/.github/workflows/extension_bump.yml
index cbe38ee9e5b958eeee80eb5576c93896cc6763e1..b4cbac4ec8c0ab37ebad73eb96c2ee074ca969a6 100644
--- a/.github/workflows/extension_bump.yml
+++ b/.github/workflows/extension_bump.yml
@@ -5,7 +5,7 @@ env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: '1'
CARGO_INCREMENTAL: '0'
- ZED_EXTENSION_CLI_SHA: 03d8e9aee95ea6117d75a48bcac2e19241f6e667
+ ZED_EXTENSION_CLI_SHA: 1fa7f1a3ec28ea1eae6db2e937d7a538fb10c0c7
on:
workflow_call:
inputs:
@@ -34,7 +34,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
fetch-depth: 0
@@ -74,17 +74,17 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- id: generate-token
- name: extension_bump::generate_token
- uses: actions/create-github-app-token@v2
+ name: steps::generate_token
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
with:
app-id: ${{ secrets.app-id }}
private-key: ${{ secrets.app-secret }}
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -138,7 +138,7 @@ jobs:
BUMP_TYPE: ${{ inputs.bump-type }}
WORKING_DIR: ${{ inputs.working-directory }}
- name: extension_bump::create_pull_request
- uses: peter-evans/create-pull-request@v7
+ uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
with:
title: ${{ steps.bump-version.outputs.title }}
body: ${{ steps.bump-version.outputs.body }}
@@ -162,13 +162,13 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- id: generate-token
- name: extension_bump::generate_token
- uses: actions/create-github-app-token@v2
+ name: steps::generate_token
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
with:
app-id: ${{ secrets.app-id }}
private-key: ${{ secrets.app-secret }}
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- id: determine-tag
@@ -187,7 +187,7 @@ jobs:
CURRENT_VERSION: ${{ needs.check_version_changed.outputs.current_version }}
WORKING_DIR: ${{ inputs.working-directory }}
- name: extension_bump::create_version_tag
- uses: actions/github-script@v7
+ uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b
with:
script: |-
github.rest.git.createRef({
@@ -212,15 +212,15 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- id: generate-token
- name: extension_bump::generate_token
- uses: actions/create-github-app-token@v2
+ name: steps::generate_token
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
with:
app-id: ${{ secrets.app-id }}
private-key: ${{ secrets.app-secret }}
owner: zed-industries
repositories: extensions
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- id: get-extension-id
@@ -239,7 +239,7 @@ jobs:
env:
COMMITTER_TOKEN: ${{ steps.generate-token.outputs.token }}
- name: extension_bump::enable_automerge_if_staff
- uses: actions/github-script@v7
+ uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b
with:
github-token: ${{ steps.generate-token.outputs.token }}
script: |
diff --git a/.github/workflows/extension_tests.yml b/.github/workflows/extension_tests.yml
index 89668c028a6d1fa4baddd417687226dd55a52426..622f4c8f1034b4ec0c7625a361ecdb6fb84d9429 100644
--- a/.github/workflows/extension_tests.yml
+++ b/.github/workflows/extension_tests.yml
@@ -5,7 +5,7 @@ env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: '1'
CARGO_INCREMENTAL: '0'
- ZED_EXTENSION_CLI_SHA: 03d8e9aee95ea6117d75a48bcac2e19241f6e667
+ ZED_EXTENSION_CLI_SHA: 1fa7f1a3ec28ea1eae6db2e937d7a538fb10c0c7
RUSTUP_TOOLCHAIN: stable
CARGO_BUILD_TARGET: wasm32-wasip2
on:
@@ -21,7 +21,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
fetch-depth: ${{ github.ref == 'refs/heads/main' && 2 || 350 }}
@@ -73,11 +73,11 @@ jobs:
runs-on: namespace-profile-8x32-ubuntu-2404
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -97,7 +97,7 @@ jobs:
env:
PACKAGE_NAME: ${{ steps.get-package-name.outputs.package_name }}
- name: steps::cargo_install_nextest
- uses: taiki-e/install-action@nextest
+ uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
- name: extension_tests::run_nextest
run: 'cargo nextest run -p "$PACKAGE_NAME" --no-fail-fast --no-tests=warn --target "$(rustc -vV | sed -n ''s|host: ||p'')"'
env:
@@ -115,7 +115,7 @@ jobs:
runs-on: namespace-profile-8x32-ubuntu-2404
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
fetch-depth: 0
@@ -131,7 +131,7 @@ jobs:
wget --quiet "https://zed-extension-cli.nyc3.digitaloceanspaces.com/$ZED_EXTENSION_CLI_SHA/x86_64-unknown-linux-gnu/zed-extension" -O "$GITHUB_WORKSPACE/zed-extension"
chmod +x "$GITHUB_WORKSPACE/zed-extension"
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
diff --git a/.github/workflows/extension_workflow_rollout.yml b/.github/workflows/extension_workflow_rollout.yml
index f695b43ecac47a221bbc795d03e6ddd6259d7014..5bb315a730d8f25f6e1eccbbe5e1734e1cda6d99 100644
--- a/.github/workflows/extension_workflow_rollout.yml
+++ b/.github/workflows/extension_workflow_rollout.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: checkout_zed_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
fetch-depth: 0
@@ -57,7 +57,7 @@ jobs:
PREV_COMMIT: ${{ steps.prev-tag.outputs.prev_commit }}
- id: list-repos
name: extension_workflow_rollout::fetch_extension_repos::get_repositories
- uses: actions/github-script@v7
+ uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b
with:
script: |
const repos = await github.paginate(github.rest.repos.listForOrg, {
@@ -81,7 +81,7 @@ jobs:
return filteredRepos;
result-encoding: json
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -114,8 +114,8 @@ jobs:
max-parallel: 10
steps:
- id: generate-token
- name: extension_bump::generate_token
- uses: actions/create-github-app-token@v2
+ name: steps::generate_token
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
with:
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
@@ -125,7 +125,7 @@ jobs:
permission-contents: write
permission-workflows: write
- name: checkout_extension_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
path: extension
@@ -173,7 +173,7 @@ jobs:
echo "sha_short=$(echo "$GITHUB_SHA" | cut -c1-7)" >> "$GITHUB_OUTPUT"
- id: create-pr
name: extension_workflow_rollout::rollout_workflows_to_extension::create_pull_request
- uses: peter-evans/create-pull-request@v7
+ uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
with:
path: extension
title: Update CI workflows to `${{ steps.short-sha.outputs.sha_short }}`
@@ -207,14 +207,14 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- id: generate-token
- name: extension_bump::generate_token
- uses: actions/create-github-app-token@v2
+ name: steps::generate_token
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
with:
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
permission-contents: write
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
fetch-depth: 0
diff --git a/.github/workflows/good_first_issue_notifier.yml b/.github/workflows/good_first_issue_notifier.yml
index f366c671726348f605325576d65e13c6faa5616e..fc1b49424dce248d107d35cd6f228dd297478cad 100644
--- a/.github/workflows/good_first_issue_notifier.yml
+++ b/.github/workflows/good_first_issue_notifier.yml
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: Prepare Discord message
id: prepare-message
diff --git a/.github/workflows/pr-size-check.yml b/.github/workflows/pr-size-check.yml
deleted file mode 100644
index 6cbed314e012c66da16fd016dd9b3cdcf9788149..0000000000000000000000000000000000000000
--- a/.github/workflows/pr-size-check.yml
+++ /dev/null
@@ -1,109 +0,0 @@
-# PR Size Check — Compute
-#
-# Calculates PR size and saves the result as an artifact. A companion
-# workflow (pr-size-label.yml) picks up the artifact via workflow_run
-# and applies labels + comments with write permissions.
-#
-# This two-workflow split is required because fork PRs receive a
-# read-only GITHUB_TOKEN. The compute step needs no write access;
-# the label/comment step runs via workflow_run on the base repo with
-# full write permissions.
-#
-# Security note: This workflow only reads PR file data via the JS API
-# and writes a JSON artifact. No untrusted input is interpolated into
-# shell commands.
-
-name: PR Size Check
-
-on:
- pull_request:
- types: [opened, synchronize]
-
-permissions:
- contents: read
- pull-requests: read
-
-jobs:
- compute-size:
- if: github.repository_owner == 'zed-industries'
- runs-on: ubuntu-latest
- timeout-minutes: 5
- steps:
- - name: Calculate PR size
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
- with:
- script: |
- const fs = require('fs');
-
- const { data: files } = await github.rest.pulls.listFiles({
- owner: context.repo.owner,
- repo: context.repo.repo,
- pull_number: context.issue.number,
- per_page: 300,
- });
-
- // Sum additions + deletions, excluding generated/lock files
- const IGNORED_PATTERNS = [
- /\.lock$/,
- /^Cargo\.lock$/,
- /pnpm-lock\.yaml$/,
- /\.generated\./,
- /\/fixtures\//,
- /\/snapshots\//,
- ];
-
- let totalChanges = 0;
- for (const file of files) {
- const ignored = IGNORED_PATTERNS.some(p => p.test(file.filename));
- if (!ignored) {
- totalChanges += file.additions + file.deletions;
- }
- }
-
- // Assign size bracket
- const SIZE_BRACKETS = [
- ['Size S', 0, 100, '0e8a16'],
- ['Size M', 100, 400, 'fbca04'],
- ['Size L', 400, 800, 'e99695'],
- ['Size XL', 800, Infinity, 'b60205'],
- ];
-
- let sizeLabel = 'Size S';
- let labelColor = '0e8a16';
- for (const [label, min, max, color] of SIZE_BRACKETS) {
- if (totalChanges >= min && totalChanges < max) {
- sizeLabel = label;
- labelColor = color;
- break;
- }
- }
-
- // Check if the author wrote content in the "How to Review" section.
- const rawBody = context.payload.pull_request.body || '';
- const howToReview = rawBody.match(/## How to Review\s*\n([\s\S]*?)(?=\n## |$)/i);
- const hasReviewGuidance = howToReview
- ? howToReview[1].replace(//g, '').trim().length > 0
- : false;
-
- const result = {
- pr_number: context.issue.number,
- total_changes: totalChanges,
- size_label: sizeLabel,
- label_color: labelColor,
- has_review_guidance: hasReviewGuidance,
- };
-
- console.log(`PR #${result.pr_number}: ${totalChanges} LOC, ${sizeLabel}`);
-
- fs.mkdirSync('pr-size', { recursive: true });
- fs.writeFileSync('pr-size/result.json', JSON.stringify(result));
-
- - name: Upload size result
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
- with:
- name: pr-size-result
- path: pr-size/
- retention-days: 1
-defaults:
- run:
- shell: bash -euxo pipefail {0}
diff --git a/.github/workflows/pr-size-label.yml b/.github/workflows/pr-size-label.yml
deleted file mode 100644
index 599daf122aac728c469acd45da865e1079c07fb6..0000000000000000000000000000000000000000
--- a/.github/workflows/pr-size-label.yml
+++ /dev/null
@@ -1,195 +0,0 @@
-# PR Size Check — Label & Comment
-#
-# Triggered by workflow_run after pr-size-check.yml completes.
-# Downloads the size result artifact and applies labels + comments.
-#
-# This runs on the base repo with full GITHUB_TOKEN write access,
-# so it works for both same-repo and fork PRs.
-#
-# Security note: The artifact is treated as untrusted data — only
-# structured JSON fields (PR number, size label, color, boolean) are
-# read. No artifact content is executed or interpolated into shell.
-
-name: PR Size Label
-
-on:
- workflow_run:
- workflows: ["PR Size Check"]
- types: [completed]
-
-jobs:
- apply-labels:
- if: >
- github.repository_owner == 'zed-industries' &&
- github.event.workflow_run.conclusion == 'success'
- permissions:
- contents: read
- pull-requests: write
- issues: write
- runs-on: ubuntu-latest
- timeout-minutes: 5
- steps:
- - name: Download size result artifact
- id: download
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
- with:
- script: |
- const fs = require('fs');
- const path = require('path');
-
- const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: context.payload.workflow_run.id,
- });
-
- const match = allArtifacts.data.artifacts.find(a => a.name === 'pr-size-result');
- if (!match) {
- console.log('No pr-size-result artifact found, skipping');
- core.setOutput('found', 'false');
- return;
- }
-
- const download = await github.rest.actions.downloadArtifact({
- owner: context.repo.owner,
- repo: context.repo.repo,
- artifact_id: match.id,
- archive_format: 'zip',
- });
-
- const temp = path.join(process.env.RUNNER_TEMP, 'pr-size');
- fs.mkdirSync(temp, { recursive: true });
- fs.writeFileSync(path.join(temp, 'result.zip'), Buffer.from(download.data));
- core.setOutput('found', 'true');
-
- - name: Unzip artifact
- if: steps.download.outputs.found == 'true'
- env:
- ARTIFACT_DIR: ${{ runner.temp }}/pr-size
- run: unzip "$ARTIFACT_DIR/result.zip" -d "$ARTIFACT_DIR"
-
- - name: Apply labels and comment
- if: steps.download.outputs.found == 'true'
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
- with:
- script: |
- const fs = require('fs');
- const path = require('path');
-
- const temp = path.join(process.env.RUNNER_TEMP, 'pr-size');
- const resultPath = path.join(temp, 'result.json');
- if (!fs.existsSync(resultPath)) {
- console.log('No result.json found, skipping');
- return;
- }
-
- const result = JSON.parse(fs.readFileSync(resultPath, 'utf8'));
-
- // Validate artifact data (treat as untrusted)
- const prNumber = Number(result.pr_number);
- const totalChanges = Number(result.total_changes);
- const sizeLabel = String(result.size_label);
- const labelColor = String(result.label_color);
- const hasReviewGuidance = Boolean(result.has_review_guidance);
-
- if (!prNumber || !sizeLabel.startsWith('Size ')) {
- core.setFailed(`Invalid artifact data: pr=${prNumber}, label=${sizeLabel}`);
- return;
- }
-
- console.log(`PR #${prNumber}: ${totalChanges} LOC, ${sizeLabel}`);
-
- // --- Size label (idempotent) ---
- const existingLabels = (await github.rest.issues.listLabelsOnIssue({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: prNumber,
- })).data.map(l => l.name);
-
- const existingSizeLabels = existingLabels.filter(l => l.startsWith('Size '));
- const alreadyCorrect = existingSizeLabels.length === 1 && existingSizeLabels[0] === sizeLabel;
-
- if (!alreadyCorrect) {
- for (const label of existingSizeLabels) {
- await github.rest.issues.removeLabel({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: prNumber,
- name: label,
- });
- }
-
- try {
- await github.rest.issues.createLabel({
- owner: context.repo.owner,
- repo: context.repo.repo,
- name: sizeLabel,
- color: labelColor,
- });
- } catch (e) {
- if (e.status !== 422) throw e;
- }
-
- await github.rest.issues.addLabels({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: prNumber,
- labels: [sizeLabel],
- });
- }
-
- // --- Large PR handling (400+ LOC) ---
- if (totalChanges >= 400) {
- if (!existingLabels.includes('large-pr')) {
- try {
- await github.rest.issues.createLabel({
- owner: context.repo.owner,
- repo: context.repo.repo,
- name: 'large-pr',
- color: 'e99695',
- });
- } catch (e) {
- if (e.status !== 422) throw e;
- }
-
- await github.rest.issues.addLabels({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: prNumber,
- labels: ['large-pr'],
- });
- }
-
- // Comment once with guidance
- const MARKER = '';
- const { data: comments } = await github.rest.issues.listComments({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: prNumber,
- });
-
- const alreadyCommented = comments.some(c => c.body.includes(MARKER));
- if (!alreadyCommented) {
- let body = `${MARKER}\n`;
- body += `### :straight_ruler: PR Size: **${totalChanges} lines changed** (${sizeLabel})\n\n`;
- body += `Please note: this PR exceeds the 400 LOC soft limit.\n`;
- body += `- Consider **splitting** into separate PRs if the changes are separable\n`;
- body += `- Ensure the PR description includes a **guided tour** in the "How to Review" section so reviewers know where to start\n`;
-
- if (hasReviewGuidance) {
- body += `\n:white_check_mark: "How to Review" section appears to include guidance — thank you!\n`;
- }
-
- await github.rest.issues.createComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: prNumber,
- body: body,
- });
- }
- }
-
- console.log(`PR #${prNumber}: labeled ${sizeLabel}, done`);
-defaults:
- run:
- shell: bash -euxo pipefail {0}
diff --git a/.github/workflows/pr_labeler.yml b/.github/workflows/pr_labeler.yml
index 4a1f9c474c6d00bec137bbfb58ba78acb15440d1..2f09ad681698d008845565c989b26f51c489d500 100644
--- a/.github/workflows/pr_labeler.yml
+++ b/.github/workflows/pr_labeler.yml
@@ -17,7 +17,7 @@ jobs:
timeout-minutes: 5
steps:
- id: get-app-token
- uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1 # v2.1.4
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
with:
app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }}
private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }}
diff --git a/.github/workflows/publish_extension_cli.yml b/.github/workflows/publish_extension_cli.yml
index 75f1b16b007e33d0c4f346a33a1403648f1cd6c6..17248cea11307d4604b05d5160212a4f38e2874a 100644
--- a/.github/workflows/publish_extension_cli.yml
+++ b/.github/workflows/publish_extension_cli.yml
@@ -11,14 +11,14 @@ on:
jobs:
publish_job:
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
- runs-on: namespace-profile-2x4-ubuntu-2404
+ runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -38,17 +38,17 @@ jobs:
runs-on: namespace-profile-8x16-ubuntu-2204
steps:
- id: generate-token
- name: extension_bump::generate_token
- uses: actions/create-github-app-token@v2
+ name: steps::generate_token
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
with:
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -63,7 +63,7 @@ jobs:
- name: publish_extension_cli::update_sha_in_zed::regenerate_workflows
run: cargo xtask workflows
- name: publish_extension_cli::create_pull_request_zed
- uses: peter-evans/create-pull-request@v7
+ uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
with:
title: 'extension_ci: Bump extension CLI version to `${{ steps.short-sha.outputs.sha_short }}`'
body: |
@@ -87,8 +87,8 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- id: generate-token
- name: extension_bump::generate_token
- uses: actions/create-github-app-token@v2
+ name: steps::generate_token
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
with:
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
@@ -108,7 +108,7 @@ jobs:
sed -i "s/ZED_EXTENSION_CLI_SHA: [a-f0-9]*/ZED_EXTENSION_CLI_SHA: $GITHUB_SHA/" \
.github/workflows/ci.yml
- name: publish_extension_cli::create_pull_request_extensions
- uses: peter-evans/create-pull-request@v7
+ uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
with:
title: Bump extension CLI version to `${{ steps.short-sha.outputs.sha_short }}`
body: |
diff --git a/.github/workflows/randomized_tests.yml b/.github/workflows/randomized_tests.yml
index de96c3df78bdb67edd584696f02316478e4446dd..9655a81235d79e1e24ae5185ebce8c8051437392 100644
--- a/.github/workflows/randomized_tests.yml
+++ b/.github/workflows/randomized_tests.yml
@@ -28,7 +28,7 @@ jobs:
node-version: "18"
- name: Checkout repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
clean: false
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 07a0a6d672a0a66c9c1609e82a22af9034dc936e..b651e7046bc7d603a7a829ce1b59fcf0468bdd3b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: namespace-profile-mac-large
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -22,7 +22,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -31,7 +31,7 @@ jobs:
with:
node-version: '20'
- name: steps::cargo_install_nextest
- uses: taiki-e/install-action@nextest
+ uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
- name: steps::setup_sccache
@@ -58,7 +58,7 @@ jobs:
CXX: clang++
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -66,7 +66,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -79,7 +79,7 @@ jobs:
with:
node-version: '20'
- name: steps::cargo_install_nextest
- uses: taiki-e/install-action@nextest
+ uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 250
- name: steps::setup_sccache
@@ -111,7 +111,7 @@ jobs:
runs-on: self-32vcpu-windows-2022
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -151,7 +151,7 @@ jobs:
runs-on: namespace-profile-mac-large
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -159,7 +159,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -183,7 +183,7 @@ jobs:
CXX: clang++
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -191,7 +191,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -216,7 +216,7 @@ jobs:
runs-on: self-32vcpu-windows-2022
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -244,7 +244,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: run_tests::check_scripts::run_shellcheck
@@ -257,7 +257,7 @@ jobs:
env:
ACTIONLINT_BIN: ${{ steps.get_actionlint.outputs.executable }}
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -275,7 +275,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
fetch-depth: 25
@@ -305,7 +305,7 @@ jobs:
CXX: clang++-18
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_sentry
@@ -345,7 +345,7 @@ jobs:
CXX: clang++-18
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_sentry
@@ -388,7 +388,7 @@ jobs:
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_node
@@ -433,7 +433,7 @@ jobs:
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_node
@@ -482,7 +482,7 @@ jobs:
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_sentry
@@ -527,7 +527,7 @@ jobs:
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_sentry
@@ -617,16 +617,16 @@ jobs:
if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- - id: get-app-token
+ - id: generate-token
name: steps::authenticate_as_zippy
- uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859
with:
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
- 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
env:
- GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
+ GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
push_release_update_notification:
needs:
- create_draft_release
diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml
index 093a17e8760e52fc4278d56dd6144b6a0432f3c5..30d0e1fbf9c7955d1216e2e3d7ac51a9a51f4416 100644
--- a/.github/workflows/release_nightly.yml
+++ b/.github/workflows/release_nightly.yml
@@ -16,7 +16,7 @@ jobs:
runs-on: namespace-profile-mac-large
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
fetch-depth: 0
@@ -30,7 +30,7 @@ jobs:
runs-on: self-32vcpu-windows-2022
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -70,7 +70,7 @@ jobs:
runs-on: self-32vcpu-windows-2022
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -107,7 +107,7 @@ jobs:
CXX: clang++-18
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
@@ -153,7 +153,7 @@ jobs:
CXX: clang++-18
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
@@ -202,7 +202,7 @@ jobs:
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
@@ -253,7 +253,7 @@ jobs:
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
@@ -308,7 +308,7 @@ jobs:
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
@@ -361,7 +361,7 @@ jobs:
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: run_bundling::set_release_channel_to_nightly
@@ -406,11 +406,11 @@ jobs:
GIT_LFS_SKIP_SMUDGE: '1'
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::cache_nix_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: nix
- name: nix_build::build_nix::install_nix
@@ -440,11 +440,11 @@ jobs:
GIT_LFS_SKIP_SMUDGE: '1'
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::cache_nix_store_macos
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
path: ~/nix-cache
- name: nix_build::build_nix::install_nix
@@ -488,7 +488,7 @@ jobs:
runs-on: namespace-profile-4x8-ubuntu-2204
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
fetch-depth: 0
diff --git a/.github/workflows/run_agent_evals.yml b/.github/workflows/run_agent_evals.yml
deleted file mode 100644
index 56cbd17a197200a6764ed1e28c87e90740cd7deb..0000000000000000000000000000000000000000
--- a/.github/workflows/run_agent_evals.yml
+++ /dev/null
@@ -1,71 +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
- path: ~/.rustup
- - name: steps::setup_linux
- run: ./script/linux
- - name: steps::download_wasi_sdk
- run: ./script/download-wasi-sdk
- - name: steps::setup_cargo_config
- run: |
- mkdir -p ./../.cargo
- cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- - name: steps::setup_sccache
- run: ./script/setup-sccache
- env:
- R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
- R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
- R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
- SCCACHE_BUCKET: sccache-zed
- - name: cargo build --package=eval
- run: cargo build --package=eval
- - name: run_agent_evals::agent_evals::run_eval
- run: cargo run --package=eval -- --repetitions=8 --concurrency=1 --model "${MODEL_NAME}"
- 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::show_sccache_stats
- run: sccache --show-stats || true
- - name: steps::cleanup_cargo_config
- if: always()
- run: |
- rm -rf ./../.cargo
- timeout-minutes: 600
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
- cancel-in-progress: true
-defaults:
- run:
- shell: bash -euxo pipefail {0}
diff --git a/.github/workflows/run_bundling.yml b/.github/workflows/run_bundling.yml
index 5a93cf074e2a2d7f2f3cf8418ed508c5ad359d9e..71b2e4d5fa0b386334bb8acab8e732f1c7d0ad93 100644
--- a/.github/workflows/run_bundling.yml
+++ b/.github/workflows/run_bundling.yml
@@ -23,7 +23,7 @@ jobs:
CXX: clang++-18
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_sentry
@@ -62,7 +62,7 @@ jobs:
CXX: clang++-18
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_sentry
@@ -104,7 +104,7 @@ jobs:
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_node
@@ -148,7 +148,7 @@ jobs:
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_node
@@ -196,7 +196,7 @@ jobs:
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_sentry
@@ -240,7 +240,7 @@ jobs:
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_sentry
@@ -274,11 +274,11 @@ jobs:
GIT_LFS_SKIP_SMUDGE: '1'
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::cache_nix_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: nix
- name: nix_build::build_nix::install_nix
@@ -306,11 +306,11 @@ jobs:
GIT_LFS_SKIP_SMUDGE: '1'
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::cache_nix_store_macos
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
path: ~/nix-cache
- name: nix_build::build_nix::install_nix
diff --git a/.github/workflows/run_cron_unit_evals.yml b/.github/workflows/run_cron_unit_evals.yml
index 6af46e678d3d629cc2f7973b8b31ee99477dfefc..7bb7f79473eb4dae170eb18edd454b7ae35d13e8 100644
--- a/.github/workflows/run_cron_unit_evals.yml
+++ b/.github/workflows/run_cron_unit_evals.yml
@@ -21,7 +21,7 @@ jobs:
fail-fast: false
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -29,7 +29,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -38,7 +38,7 @@ jobs:
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
- name: steps::cargo_install_nextest
- uses: taiki-e/install-action@nextest
+ uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 250
- name: steps::setup_sccache
diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml
index 1906acf9fab7bbaab81b0549328c2e85d732756d..9f335a76beab036d97fe5555cd049ea46b4f87f0 100644
--- a/.github/workflows/run_tests.yml
+++ b/.github/workflows/run_tests.yml
@@ -19,7 +19,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
fetch-depth: ${{ github.ref == 'refs/heads/main' && 2 || 350 }}
@@ -124,11 +124,11 @@ jobs:
runs-on: namespace-profile-4x8-ubuntu-2204
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -171,7 +171,7 @@ jobs:
runs-on: self-32vcpu-windows-2022
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -204,7 +204,7 @@ jobs:
CXX: clang++
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -212,7 +212,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -239,7 +239,7 @@ jobs:
runs-on: namespace-profile-mac-large
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -247,7 +247,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -270,7 +270,7 @@ jobs:
runs-on: namespace-profile-mac-large
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -278,7 +278,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -303,7 +303,7 @@ jobs:
runs-on: self-32vcpu-windows-2022
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -348,7 +348,7 @@ jobs:
CXX: clang++
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -356,7 +356,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -369,7 +369,7 @@ jobs:
with:
node-version: '20'
- name: steps::cargo_install_nextest
- uses: taiki-e/install-action@nextest
+ uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 250
- name: steps::setup_sccache
@@ -403,7 +403,7 @@ jobs:
runs-on: namespace-profile-mac-large
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -411,7 +411,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -420,7 +420,7 @@ jobs:
with:
node-version: '20'
- name: steps::cargo_install_nextest
- uses: taiki-e/install-action@nextest
+ uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 300
- name: steps::setup_sccache
@@ -449,11 +449,11 @@ jobs:
CXX: clang++
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -493,7 +493,7 @@ jobs:
CXX: clang++
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -501,7 +501,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -534,7 +534,7 @@ jobs:
runs-on: namespace-profile-8x16-ubuntu-2204
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -542,7 +542,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -576,11 +576,11 @@ jobs:
CXX: clang++
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -611,7 +611,7 @@ jobs:
CXX: clang++
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -619,7 +619,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -657,11 +657,11 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -676,7 +676,7 @@ jobs:
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: run_tests::check_scripts::run_shellcheck
@@ -689,7 +689,7 @@ jobs:
env:
ACTIONLINT_BIN: ${{ steps.get_actionlint.outputs.executable }}
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -714,7 +714,7 @@ jobs:
GIT_COMMITTER_EMAIL: ci@zed.dev
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
fetch-depth: 0
diff --git a/.github/workflows/run_unit_evals.yml b/.github/workflows/run_unit_evals.yml
index 44f12a1886bdac2fa1da8c870d223dd358285658..1bf75188832668f40a24c4d3452940bf05fcd3fd 100644
--- a/.github/workflows/run_unit_evals.yml
+++ b/.github/workflows/run_unit_evals.yml
@@ -24,7 +24,7 @@ jobs:
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: steps::checkout_repo
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
with:
clean: false
- name: steps::setup_cargo_config
@@ -32,7 +32,7 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: steps::cache_rust_dependencies_namespace
- uses: namespacelabs/nscloud-cache-action@v1
+ uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9
with:
cache: rust
path: ~/.rustup
@@ -41,7 +41,7 @@ jobs:
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
- name: steps::cargo_install_nextest
- uses: taiki-e/install-action@nextest
+ uses: taiki-e/install-action@921e2c9f7148d7ba14cd819f417db338f63e733c
- name: steps::clear_target_dir_if_large
run: ./script/clear-target-dir-if-larger-than 250
- name: steps::setup_sccache
diff --git a/.github/workflows/track_duplicate_bot_effectiveness.yml b/.github/workflows/track_duplicate_bot_effectiveness.yml
index fa1c80616cb6133a7a4cad8841bbaad03115ff58..0d41a6070610ce9e9cc3faa06af78145bc9caec1 100644
--- a/.github/workflows/track_duplicate_bot_effectiveness.yml
+++ b/.github/workflows/track_duplicate_bot_effectiveness.yml
@@ -22,14 +22,14 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout repository
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
sparse-checkout: script/github-track-duplicate-bot-effectiveness.py
sparse-checkout-cone-mode: false
- name: Get github app token
id: get-app-token
- uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1 # v1.11.7
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
with:
app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }}
private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }}
@@ -61,14 +61,14 @@ jobs:
timeout-minutes: 10
steps:
- name: Checkout repository
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
sparse-checkout: script/github-track-duplicate-bot-effectiveness.py
sparse-checkout-cone-mode: false
- name: Get github app token
id: get-app-token
- uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1 # v1.11.7
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
with:
app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }}
private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }}
diff --git a/.github/workflows/update_duplicate_magnets.yml b/.github/workflows/update_duplicate_magnets.yml
index c3832b7bdbec13f74a8136cb1120a682f6e53920..d14f4aa92451aab9c36df49d3be128fd4797a4da 100644
--- a/.github/workflows/update_duplicate_magnets.yml
+++ b/.github/workflows/update_duplicate_magnets.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
if: github.repository == 'zed-industries/zed'
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+ - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
diff --git a/.zed/settings.json b/.zed/settings.json
index e9bbe9aa4ffd142ad1733d4c18a4e54230a8b541..2ecbd5623d26bd32d40443f8553bf4062248ec45 100644
--- a/.zed/settings.json
+++ b/.zed/settings.json
@@ -58,8 +58,7 @@
"ensure_final_newline_on_save": true,
"file_scan_exclusions": [
"crates/agent/src/edit_agent/evals/fixtures",
- "crates/eval/worktrees/",
- "crates/eval/repos/",
+ "crates/agent/src/tools/evals/fixtures",
"**/.git",
"**/.svn",
"**/.hg",
diff --git a/Cargo.lock b/Cargo.lock
index a3ddf3f4960224f4ebf46c4850f7214d3fc493d1..1f208e459a7eb0323aa378a168c3648052135200 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -59,7 +59,7 @@ dependencies = [
"serde",
"serde_json",
"settings",
- "theme",
+ "theme_settings",
"ui",
"util",
"workspace",
@@ -312,6 +312,7 @@ dependencies = [
"gpui",
"language_model",
"log",
+ "paths",
"project",
"regex",
"schemars",
@@ -337,14 +338,12 @@ dependencies = [
"assistant_slash_command",
"assistant_slash_commands",
"assistant_text_thread",
- "async-fs",
"audio",
"base64 0.22.1",
"buffer_diff",
"chrono",
"client",
"cloud_api_types",
- "cloud_llm_client",
"collections",
"command_palette_hooks",
"component",
@@ -408,6 +407,7 @@ dependencies = [
"terminal_view",
"text",
"theme",
+ "theme_settings",
"time",
"time_format",
"tree-sitter-md",
@@ -513,21 +513,6 @@ dependencies = [
"equator",
]
-[[package]]
-name = "alloc-no-stdlib"
-version = "2.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
-
-[[package]]
-name = "alloc-stdlib"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
-dependencies = [
- "alloc-no-stdlib",
-]
-
[[package]]
name = "allocator-api2"
version = "0.2.21"
@@ -865,7 +850,6 @@ dependencies = [
"chrono",
"client",
"clock",
- "cloud_llm_client",
"collections",
"context_server",
"fs",
@@ -1278,7 +1262,6 @@ name = "audio"
version = "0.1.0"
dependencies = [
"anyhow",
- "async-tar",
"collections",
"cpal",
"crossbeam",
@@ -1290,7 +1273,6 @@ dependencies = [
"rodio",
"serde",
"settings",
- "smol",
"thiserror 2.0.17",
"util",
]
@@ -2252,27 +2234,6 @@ dependencies = [
"workspace",
]
-[[package]]
-name = "brotli"
-version = "8.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560"
-dependencies = [
- "alloc-no-stdlib",
- "alloc-stdlib",
- "brotli-decompressor",
-]
-
-[[package]]
-name = "brotli-decompressor"
-version = "5.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03"
-dependencies = [
- "alloc-no-stdlib",
- "alloc-stdlib",
-]
-
[[package]]
name = "brush-parser"
version = "0.3.0"
@@ -2440,6 +2401,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "bzip2"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c"
+dependencies = [
+ "libbz2-rs-sys",
+]
+
[[package]]
name = "bzip2-sys"
version = "0.1.13+1.0.8"
@@ -3004,6 +2974,7 @@ dependencies = [
"cloud_llm_client",
"collections",
"credentials_provider",
+ "db",
"derive_more",
"feature_flags",
"fs",
@@ -3289,6 +3260,7 @@ dependencies = [
"telemetry_events",
"text",
"theme",
+ "theme_settings",
"time",
"tokio",
"toml 0.8.23",
@@ -3334,6 +3306,7 @@ dependencies = [
"smallvec",
"telemetry",
"theme",
+ "theme_settings",
"time",
"time_format",
"title_bar",
@@ -3406,6 +3379,7 @@ dependencies = [
"settings",
"telemetry",
"theme",
+ "theme_settings",
"time",
"ui",
"util",
@@ -3458,6 +3432,7 @@ dependencies = [
"session",
"settings",
"theme",
+ "theme_settings",
"ui",
"ui_input",
"uuid",
@@ -3470,6 +3445,7 @@ version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23"
dependencies = [
+ "bzip2 0.6.1",
"compression-core",
"deflate64",
"flate2",
@@ -3654,6 +3630,7 @@ dependencies = [
"settings",
"sum_tree",
"theme",
+ "theme_settings",
"util",
"workspace",
"zlog",
@@ -4724,6 +4701,7 @@ dependencies = [
"terminal_view",
"text",
"theme",
+ "theme_settings",
"tree-sitter",
"tree-sitter-go",
"tree-sitter-json",
@@ -4911,6 +4889,7 @@ dependencies = [
"settings",
"text",
"theme",
+ "theme_settings",
"ui",
"unindent",
"util",
@@ -5239,7 +5218,6 @@ version = "0.1.0"
dependencies = [
"ai_onboarding",
"anyhow",
- "brotli",
"buffer_diff",
"client",
"clock",
@@ -5428,6 +5406,7 @@ dependencies = [
"telemetry",
"text",
"theme",
+ "theme_settings",
"time",
"ui",
"util",
@@ -5496,6 +5475,7 @@ dependencies = [
"telemetry",
"text",
"theme",
+ "theme_settings",
"time",
"tracing",
"tree-sitter-bash",
@@ -5846,62 +5826,6 @@ dependencies = [
"num-traits",
]
-[[package]]
-name = "eval"
-version = "0.1.0"
-dependencies = [
- "acp_thread",
- "agent",
- "agent-client-protocol",
- "agent_settings",
- "agent_ui",
- "anyhow",
- "async-trait",
- "buffer_diff",
- "chrono",
- "clap",
- "client",
- "collections",
- "debug_adapter_extension",
- "dirs 4.0.0",
- "dotenvy",
- "env_logger 0.11.8",
- "extension",
- "fs",
- "futures 0.3.31",
- "gpui",
- "gpui_platform",
- "gpui_tokio",
- "handlebars 4.5.0",
- "language",
- "language_extension",
- "language_model",
- "language_models",
- "languages",
- "markdown",
- "node_runtime",
- "pathdiff",
- "paths",
- "pretty_assertions",
- "project",
- "prompt_store",
- "rand 0.9.2",
- "regex",
- "release_channel",
- "reqwest_client",
- "serde",
- "serde_json",
- "settings",
- "shellexpand 2.1.2",
- "telemetry",
- "terminal_view",
- "toml 0.8.23",
- "unindent",
- "util",
- "uuid",
- "watch",
-]
-
[[package]]
name = "eval_cli"
version = "0.1.0"
@@ -5914,6 +5838,7 @@ dependencies = [
"clap",
"client",
"ctrlc",
+ "db",
"debug_adapter_extension",
"env_logger 0.11.8",
"extension",
@@ -6074,7 +5999,7 @@ dependencies = [
"settings_content",
"snippet_provider",
"task",
- "theme",
+ "theme_settings",
"tokio",
"toml 0.8.23",
"tree-sitter",
@@ -6123,6 +6048,7 @@ dependencies = [
"tempfile",
"theme",
"theme_extension",
+ "theme_settings",
"toml 0.8.23",
"tracing",
"url",
@@ -6161,7 +6087,7 @@ dependencies = [
"smallvec",
"strum 0.27.2",
"telemetry",
- "theme",
+ "theme_settings",
"ui",
"util",
"vim_mode_setting",
@@ -6318,6 +6244,7 @@ dependencies = [
"serde_json",
"settings",
"theme",
+ "theme_settings",
"ui",
"util",
"workspace",
@@ -7320,6 +7247,7 @@ dependencies = [
"anyhow",
"collections",
"db",
+ "editor",
"feature_flags",
"fs",
"git",
@@ -7329,10 +7257,13 @@ dependencies = [
"menu",
"project",
"rand 0.9.2",
+ "search",
"serde_json",
"settings",
"smallvec",
+ "smol",
"theme",
+ "theme_settings",
"time",
"ui",
"workspace",
@@ -7369,7 +7300,6 @@ dependencies = [
"askpass",
"buffer_diff",
"call",
- "cloud_llm_client",
"collections",
"component",
"ctor",
@@ -7409,6 +7339,7 @@ dependencies = [
"strum 0.27.2",
"telemetry",
"theme",
+ "theme_settings",
"time",
"time_format",
"tracing",
@@ -7637,7 +7568,6 @@ dependencies = [
"block",
"cbindgen",
"chrono",
- "circular-buffer",
"cocoa 0.26.0",
"cocoa-foundation 0.2.0",
"collections",
@@ -7684,6 +7614,7 @@ dependencies = [
"rand 0.9.2",
"raw-window-handle",
"refineable",
+ "regex",
"reqwest_client",
"resvg",
"scheduler",
@@ -7699,6 +7630,7 @@ dependencies = [
"sum_tree",
"taffy",
"thiserror 2.0.17",
+ "ttf-parser 0.25.1",
"unicode-segmentation",
"url",
"usvg",
@@ -7916,6 +7848,35 @@ dependencies = [
"zed-scap",
]
+[[package]]
+name = "grammars"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "language_core",
+ "rust-embed",
+ "toml 0.8.23",
+ "tree-sitter",
+ "tree-sitter-bash",
+ "tree-sitter-c",
+ "tree-sitter-cpp",
+ "tree-sitter-css",
+ "tree-sitter-diff",
+ "tree-sitter-gitcommit",
+ "tree-sitter-go",
+ "tree-sitter-gomod",
+ "tree-sitter-gowork",
+ "tree-sitter-jsdoc",
+ "tree-sitter-json",
+ "tree-sitter-md",
+ "tree-sitter-python",
+ "tree-sitter-regex",
+ "tree-sitter-rust",
+ "tree-sitter-typescript",
+ "tree-sitter-yaml",
+ "util",
+]
+
[[package]]
name = "grid"
version = "0.18.0"
@@ -8730,7 +8691,7 @@ dependencies = [
"project",
"serde",
"settings",
- "theme",
+ "theme_settings",
"ui",
"util",
"workspace",
@@ -8846,7 +8807,7 @@ dependencies = [
"project",
"serde_json",
"serde_json_lenient",
- "theme",
+ "theme_settings",
"ui",
"util",
"util_macros",
@@ -9289,6 +9250,7 @@ dependencies = [
"telemetry",
"tempfile",
"theme",
+ "theme_settings",
"tree-sitter-json",
"tree-sitter-rust",
"ui",
@@ -9363,6 +9325,7 @@ dependencies = [
"async-trait",
"clock",
"collections",
+ "criterion",
"ctor",
"diffy",
"ec4rs",
@@ -9376,6 +9339,7 @@ dependencies = [
"imara-diff",
"indoc",
"itertools 0.14.0",
+ "language_core",
"log",
"lsp",
"parking_lot",
@@ -9384,7 +9348,6 @@ dependencies = [
"rand 0.9.2",
"regex",
"rpc",
- "schemars",
"semver",
"serde",
"serde_json",
@@ -9398,6 +9361,7 @@ dependencies = [
"task",
"text",
"theme",
+ "theme_settings",
"toml 0.8.23",
"tracing",
"tree-sitter",
@@ -9419,6 +9383,25 @@ dependencies = [
"ztracing",
]
+[[package]]
+name = "language_core"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "collections",
+ "gpui",
+ "log",
+ "lsp",
+ "parking_lot",
+ "regex",
+ "schemars",
+ "serde",
+ "serde_json",
+ "toml 0.8.23",
+ "tree-sitter",
+ "util",
+]
+
[[package]]
name = "language_extension"
version = "0.1.0"
@@ -9592,6 +9575,7 @@ dependencies = [
"sysinfo 0.37.2",
"telemetry",
"theme",
+ "theme_settings",
"tree-sitter",
"ui",
"util",
@@ -9611,9 +9595,11 @@ dependencies = [
"async-trait",
"chrono",
"collections",
+ "fs",
"futures 0.3.31",
"globset",
"gpui",
+ "grammars",
"http_client",
"itertools 0.14.0",
"json_schema_store",
@@ -9633,7 +9619,6 @@ dependencies = [
"project",
"regex",
"rope",
- "rust-embed",
"semver",
"serde",
"serde_json",
@@ -9645,25 +9630,16 @@ dependencies = [
"task",
"terminal",
"theme",
- "toml 0.8.23",
"tree-sitter",
"tree-sitter-bash",
"tree-sitter-c",
"tree-sitter-cpp",
"tree-sitter-css",
- "tree-sitter-diff",
"tree-sitter-gitcommit",
"tree-sitter-go",
- "tree-sitter-gomod",
- "tree-sitter-gowork",
- "tree-sitter-jsdoc",
- "tree-sitter-json",
- "tree-sitter-md",
"tree-sitter-python",
- "tree-sitter-regex",
"tree-sitter-rust",
"tree-sitter-typescript",
- "tree-sitter-yaml",
"unindent",
"url",
"util",
@@ -9711,6 +9687,12 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
+[[package]]
+name = "libbz2-rs-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
+
[[package]]
name = "libc"
version = "0.2.182"
@@ -9800,7 +9782,7 @@ dependencies = [
[[package]]
name = "libwebrtc"
version = "0.3.26"
-source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=c1209aa155cbf4543383774f884a46ae7e53ee2e#c1209aa155cbf4543383774f884a46ae7e53ee2e"
+source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1#147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1"
dependencies = [
"cxx",
"glib",
@@ -9898,7 +9880,7 @@ checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "livekit"
version = "0.7.32"
-source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=c1209aa155cbf4543383774f884a46ae7e53ee2e#c1209aa155cbf4543383774f884a46ae7e53ee2e"
+source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1#147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1"
dependencies = [
"base64 0.22.1",
"bmrng",
@@ -9924,7 +9906,7 @@ dependencies = [
[[package]]
name = "livekit-api"
version = "0.4.14"
-source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=c1209aa155cbf4543383774f884a46ae7e53ee2e#c1209aa155cbf4543383774f884a46ae7e53ee2e"
+source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1#147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1"
dependencies = [
"base64 0.21.7",
"futures-util",
@@ -9951,7 +9933,7 @@ dependencies = [
[[package]]
name = "livekit-protocol"
version = "0.7.1"
-source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=c1209aa155cbf4543383774f884a46ae7e53ee2e#c1209aa155cbf4543383774f884a46ae7e53ee2e"
+source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1#147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1"
dependencies = [
"futures-util",
"livekit-runtime",
@@ -9967,7 +9949,7 @@ dependencies = [
[[package]]
name = "livekit-runtime"
version = "0.4.0"
-source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=c1209aa155cbf4543383774f884a46ae7e53ee2e#c1209aa155cbf4543383774f884a46ae7e53ee2e"
+source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1#147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1"
dependencies = [
"tokio",
"tokio-stream",
@@ -10250,6 +10232,7 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
name = "markdown"
version = "0.1.0"
dependencies = [
+ "anyhow",
"assets",
"base64 0.22.1",
"collections",
@@ -10258,15 +10241,20 @@ dependencies = [
"futures 0.3.31",
"gpui",
"gpui_platform",
+ "html5ever 0.27.0",
"language",
"languages",
"linkify",
"log",
+ "markup5ever_rcdom",
+ "mermaid-rs-renderer",
"node_runtime",
"pulldown-cmark 0.13.0",
"settings",
+ "stacksafe",
"sum_tree",
"theme",
+ "theme_settings",
"ui",
"util",
]
@@ -10276,22 +10264,14 @@ name = "markdown_preview"
version = "0.1.0"
dependencies = [
"anyhow",
- "async-recursion",
- "collections",
"editor",
"gpui",
- "html5ever 0.27.0",
"language",
- "linkify",
"log",
"markdown",
- "markup5ever_rcdom",
- "mermaid-rs-renderer",
- "pretty_assertions",
- "pulldown-cmark 0.13.0",
"settings",
- "stacksafe",
- "theme",
+ "tempfile",
+ "theme_settings",
"ui",
"urlencoding",
"util",
@@ -10651,7 +10631,7 @@ dependencies = [
"rpc",
"serde_json",
"smol",
- "theme",
+ "theme_settings",
"util",
"workspace",
"zed_actions",
@@ -10777,6 +10757,7 @@ dependencies = [
"theme",
"tracing",
"tree-sitter",
+ "unicode-segmentation",
"util",
"zlog",
"ztracing",
@@ -11541,6 +11522,7 @@ dependencies = [
"settings",
"telemetry",
"theme",
+ "theme_settings",
"ui",
"util",
"vim_mode_setting",
@@ -11646,6 +11628,7 @@ dependencies = [
"serde_json",
"settings",
"theme",
+ "theme_settings",
"ui",
"util",
"workspace",
@@ -11824,6 +11807,7 @@ dependencies = [
"settings",
"smol",
"theme",
+ "theme_settings",
"ui",
"util",
"workspace",
@@ -11856,6 +11840,7 @@ dependencies = [
"smallvec",
"smol",
"theme",
+ "theme_settings",
"ui",
"util",
"workspace",
@@ -12721,6 +12706,7 @@ dependencies = [
"serde",
"settings",
"theme",
+ "theme_settings",
"ui",
"ui_input",
"workspace",
@@ -12829,6 +12815,7 @@ dependencies = [
"settings",
"smallvec",
"theme",
+ "theme_settings",
"ui",
"windows 0.61.3",
"workspace",
@@ -13232,6 +13219,7 @@ dependencies = [
"node_runtime",
"parking_lot",
"paths",
+ "percent-encoding",
"postage",
"prettier",
"pretty_assertions",
@@ -13303,7 +13291,6 @@ dependencies = [
"collections",
"command_palette_hooks",
"criterion",
- "db",
"editor",
"feature_flags",
"file_icons",
@@ -13327,6 +13314,7 @@ dependencies = [
"telemetry",
"tempfile",
"theme",
+ "theme_settings",
"ui",
"util",
"workspace",
@@ -13353,6 +13341,7 @@ dependencies = [
"serde_json",
"settings",
"theme",
+ "theme_settings",
"util",
"workspace",
]
@@ -14345,7 +14334,7 @@ dependencies = [
"remote",
"semver",
"settings",
- "theme",
+ "theme_settings",
"ui",
"ui_input",
"workspace",
@@ -14413,6 +14402,7 @@ dependencies = [
"sysinfo 0.37.2",
"task",
"theme",
+ "theme_settings",
"thiserror 2.0.17",
"toml 0.8.23",
"unindent",
@@ -14483,6 +14473,7 @@ dependencies = [
"terminal",
"terminal_view",
"theme",
+ "theme_settings",
"tree-sitter-md",
"tree-sitter-python",
"tree-sitter-typescript",
@@ -14596,12 +14587,15 @@ version = "0.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43"
dependencies = [
+ "gif",
+ "image-webp",
"log",
"pico-args",
"rgb",
"svgtypes",
"tiny-skia",
"usvg",
+ "zune-jpeg",
]
[[package]]
@@ -14819,7 +14813,7 @@ dependencies = [
"rope",
"serde",
"settings",
- "theme",
+ "theme_settings",
"ui",
"ui_input",
"util",
@@ -14855,9 +14849,9 @@ dependencies = [
[[package]]
name = "rust-embed"
-version = "8.7.2"
+version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a"
+checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
@@ -14866,9 +14860,9 @@ dependencies = [
[[package]]
name = "rust-embed-impl"
-version = "8.7.2"
+version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c"
+checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
dependencies = [
"proc-macro2",
"quote",
@@ -14879,9 +14873,9 @@ dependencies = [
[[package]]
name = "rust-embed-utils"
-version = "8.7.2"
+version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594"
+checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
dependencies = [
"globset",
"sha2",
@@ -15233,6 +15227,7 @@ dependencies = [
"serde_json",
"settings",
"theme",
+ "theme_settings",
]
[[package]]
@@ -15456,6 +15451,7 @@ dependencies = [
"settings",
"smol",
"theme",
+ "theme_settings",
"tracing",
"ui",
"unindent",
@@ -15800,6 +15796,7 @@ dependencies = [
"serde_json",
"settings",
"theme",
+ "theme_settings",
"ui",
"workspace",
"zed_actions",
@@ -15848,6 +15845,7 @@ dependencies = [
"strum 0.27.2",
"telemetry",
"theme",
+ "theme_settings",
"title_bar",
"ui",
"util",
@@ -15964,10 +15962,12 @@ dependencies = [
"action_log",
"agent",
"agent-client-protocol",
+ "agent_settings",
"agent_ui",
"anyhow",
"assistant_text_thread",
"chrono",
+ "collections",
"editor",
"feature_flags",
"fs",
@@ -15975,13 +15975,16 @@ dependencies = [
"gpui",
"language_model",
"menu",
+ "platform_title_bar",
"pretty_assertions",
"project",
"prompt_store",
"recent_projects",
+ "remote",
"serde_json",
"settings",
"theme",
+ "theme_settings",
"ui",
"util",
"vim_mode_setting",
@@ -16637,6 +16640,7 @@ dependencies = [
"story",
"strum 0.27.2",
"theme",
+ "theme_settings",
"title_bar",
"ui",
]
@@ -17287,6 +17291,7 @@ dependencies = [
"settings",
"smol",
"theme",
+ "theme_settings",
"ui",
"util",
"workspace",
@@ -17471,6 +17476,7 @@ dependencies = [
"sysinfo 0.37.2",
"task",
"theme",
+ "theme_settings",
"thiserror 2.0.17",
"url",
"urlencoding",
@@ -17518,6 +17524,7 @@ dependencies = [
"task",
"terminal",
"theme",
+ "theme_settings",
"ui",
"util",
"workspace",
@@ -17552,10 +17559,7 @@ dependencies = [
"anyhow",
"collections",
"derive_more",
- "fs",
- "futures 0.3.31",
"gpui",
- "log",
"palette",
"parking_lot",
"refineable",
@@ -17563,10 +17567,8 @@ dependencies = [
"serde",
"serde_json",
"serde_json_lenient",
- "settings",
"strum 0.27.2",
"thiserror 2.0.17",
- "util",
"uuid",
]
@@ -17579,6 +17581,7 @@ dependencies = [
"fs",
"gpui",
"theme",
+ "theme_settings",
]
[[package]]
@@ -17598,6 +17601,7 @@ dependencies = [
"simplelog",
"strum 0.27.2",
"theme",
+ "theme_settings",
"vscode_theme",
]
@@ -17614,12 +17618,33 @@ dependencies = [
"settings",
"telemetry",
"theme",
+ "theme_settings",
"ui",
"util",
"workspace",
"zed_actions",
]
+[[package]]
+name = "theme_settings"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "collections",
+ "gpui",
+ "gpui_util",
+ "log",
+ "palette",
+ "refineable",
+ "schemars",
+ "serde",
+ "serde_json",
+ "serde_json_lenient",
+ "settings",
+ "theme",
+ "uuid",
+]
+
[[package]]
name = "thiserror"
version = "1.0.69"
@@ -18551,9 +18576,9 @@ dependencies = [
[[package]]
name = "tree-sitter-rust"
-version = "0.24.0"
+version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b9b18034c684a2420722be8b2a91c9c44f2546b631c039edf575ccba8c61be1"
+checksum = "439e577dbe07423ec2582ac62c7531120dbfccfa6e5f92406f93dd271a120e45"
dependencies = [
"cc",
"tree-sitter-language",
@@ -18752,18 +18777,17 @@ dependencies = [
"documented",
"gpui",
"gpui_macros",
+ "gpui_util",
"icons",
"itertools 0.14.0",
"menu",
"schemars",
"serde",
- "settings",
"smallvec",
"story",
"strum 0.27.2",
"theme",
"ui_macros",
- "util",
"windows 0.61.3",
]
@@ -18794,7 +18818,7 @@ dependencies = [
"markdown",
"menu",
"settings",
- "theme",
+ "theme_settings",
"ui",
"workspace",
]
@@ -19193,6 +19217,7 @@ dependencies = [
"task",
"text",
"theme",
+ "theme_settings",
"tokio",
"ui",
"util",
@@ -20098,7 +20123,7 @@ dependencies = [
[[package]]
name = "webrtc-sys"
version = "0.3.23"
-source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=c1209aa155cbf4543383774f884a46ae7e53ee2e#c1209aa155cbf4543383774f884a46ae7e53ee2e"
+source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1#147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1"
dependencies = [
"cc",
"cxx",
@@ -20112,7 +20137,7 @@ dependencies = [
[[package]]
name = "webrtc-sys-build"
version = "0.3.13"
-source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=c1209aa155cbf4543383774f884a46ae7e53ee2e#c1209aa155cbf4543383774f884a46ae7e53ee2e"
+source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1#147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1"
dependencies = [
"anyhow",
"fs2",
@@ -20320,7 +20345,7 @@ dependencies = [
"gpui",
"serde",
"settings",
- "theme",
+ "theme_settings",
"ui",
"util",
"workspace",
@@ -21500,6 +21525,7 @@ dependencies = [
name = "workspace"
version = "0.1.0"
dependencies = [
+ "agent_settings",
"any_vec",
"anyhow",
"async-recursion",
@@ -21538,6 +21564,7 @@ dependencies = [
"telemetry",
"tempfile",
"theme",
+ "theme_settings",
"ui",
"util",
"uuid",
@@ -21958,7 +21985,7 @@ dependencies = [
[[package]]
name = "zed"
-version = "0.230.0"
+version = "0.231.0"
dependencies = [
"acp_thread",
"acp_tools",
@@ -22099,6 +22126,7 @@ dependencies = [
"theme",
"theme_extension",
"theme_selector",
+ "theme_settings",
"time",
"time_format",
"title_bar",
@@ -22453,7 +22481,7 @@ checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"aes",
"byteorder",
- "bzip2",
+ "bzip2 0.4.4",
"constant_time_eq",
"crc32fast",
"crossbeam-utils",
diff --git a/Cargo.toml b/Cargo.toml
index dd426748606407aad3fdce359bc4ba0abe64727d..7c6fdb14defc7c060ee162a78f4319b2dff4deef 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -65,7 +65,6 @@ members = [
"crates/editor",
"crates/encoding_selector",
"crates/etw_tracing",
- "crates/eval",
"crates/eval_cli",
"crates/eval_utils",
"crates/explorer_command_injector",
@@ -87,6 +86,7 @@ members = [
"crates/git_ui",
"crates/go_to_line",
"crates/google_ai",
+ "crates/grammars",
"crates/gpui",
"crates/gpui_linux",
"crates/gpui_macos",
@@ -108,6 +108,7 @@ members = [
"crates/json_schema_store",
"crates/keymap_editor",
"crates/language",
+ "crates/language_core",
"crates/language_extension",
"crates/language_model",
"crates/language_models",
@@ -196,6 +197,7 @@ members = [
"crates/text",
"crates/theme",
"crates/theme_extension",
+ "crates/theme_settings",
"crates/theme_importer",
"crates/theme_selector",
"crates/time_format",
@@ -330,6 +332,7 @@ git_hosting_providers = { path = "crates/git_hosting_providers" }
git_ui = { path = "crates/git_ui" }
go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" }
+grammars = { path = "crates/grammars" }
gpui = { path = "crates/gpui", default-features = false }
gpui_linux = { path = "crates/gpui_linux", default-features = false }
gpui_macos = { path = "crates/gpui_macos", default-features = false }
@@ -354,6 +357,7 @@ journal = { path = "crates/journal" }
json_schema_store = { path = "crates/json_schema_store" }
keymap_editor = { path = "crates/keymap_editor" }
language = { path = "crates/language" }
+language_core = { path = "crates/language_core" }
language_extension = { path = "crates/language_extension" }
language_model = { path = "crates/language_model" }
language_models = { path = "crates/language_models" }
@@ -441,6 +445,7 @@ terminal_view = { path = "crates/terminal_view" }
text = { path = "crates/text" }
theme = { path = "crates/theme" }
theme_extension = { path = "crates/theme_extension" }
+theme_settings = { path = "crates/theme_settings" }
theme_selector = { path = "crates/theme_selector" }
time_format = { path = "crates/time_format" }
platform_title_bar = { path = "crates/platform_title_bar" }
@@ -492,7 +497,7 @@ ashpd = { version = "0.13", default-features = false, features = [
] }
async-channel = "2.5.0"
async-compat = "0.2.1"
-async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
+async-compression = { version = "0.4", features = ["bzip2", "gzip", "futures-io"] }
async-dispatcher = "0.1"
async-fs = "2.1"
async-lock = "2.1"
@@ -676,7 +681,7 @@ rsa = "0.9.6"
runtimelib = { version = "1.4.0", default-features = false, features = [
"async-dispatcher-runtime", "aws-lc-rs"
] }
-rust-embed = { version = "8.4", features = ["include-exclude"] }
+rust-embed = { version = "8.11", features = ["include-exclude"] }
rustc-hash = "2.1.0"
rustls = { version = "0.23.26" }
rustls-platform-verifier = "0.5.0"
@@ -752,7 +757,7 @@ tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-ma
tree-sitter-python = "0.25"
tree-sitter-regex = "0.24"
tree-sitter-ruby = "0.23"
-tree-sitter-rust = "0.24"
+tree-sitter-rust = "0.24.2"
tree-sitter-typescript = { git = "https://github.com/zed-industries/tree-sitter-typescript", rev = "e2c53597d6a5d9cf7bbe8dccde576fe1e46c5899" } # https://github.com/tree-sitter/tree-sitter-typescript/pull/347
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
tracing = "0.1.40"
@@ -806,6 +811,7 @@ features = [
"Win32_Graphics_Direct3D_Fxc",
"Win32_Graphics_DirectComposition",
"Win32_Graphics_DirectWrite",
+ "Win32_Graphics_DirectManipulation",
"Win32_Graphics_Dwm",
"Win32_Graphics_Dxgi",
"Win32_Graphics_Dxgi_Common",
@@ -837,6 +843,7 @@ features = [
"Win32_UI_HiDpi",
"Win32_UI_Input_Ime",
"Win32_UI_Input_KeyboardAndMouse",
+ "Win32_UI_Input_Pointer",
"Win32_UI_Shell",
"Win32_UI_Shell_Common",
"Win32_UI_Shell_PropertiesSystem",
@@ -850,9 +857,9 @@ notify = { git = "https://github.com/zed-industries/notify.git", rev = "ce58c24c
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "ce58c24cad542c28e04ced02e20325a4ec28a31d" }
windows-capture = { git = "https://github.com/zed-industries/windows-capture.git", rev = "f0d6c1b6691db75461b732f6d5ff56eed002eeb9" }
calloop = { git = "https://github.com/zed-industries/calloop" }
-livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "c1209aa155cbf4543383774f884a46ae7e53ee2e" }
-libwebrtc = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "c1209aa155cbf4543383774f884a46ae7e53ee2e" }
-webrtc-sys = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "c1209aa155cbf4543383774f884a46ae7e53ee2e" }
+livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1" }
+libwebrtc = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1" }
+webrtc-sys = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "147fbca3d4b592d96d33f5e6a84b59fc0b5d9bf1" }
[profile.dev]
split-debuginfo = "unpacked"
diff --git a/Dockerfile-collab b/Dockerfile-collab
index 50af874200a6ef3bc3c882b7d08257ec41f944de..fbbcb0df0484c26a65823171cc976de8cb838b8c 100644
--- a/Dockerfile-collab
+++ b/Dockerfile-collab
@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.2
-FROM rust:1.93-bookworm as builder
+FROM rust:1.94-bookworm as builder
WORKDIR app
COPY . .
diff --git a/assets/icons/ai.svg b/assets/icons/ai.svg
deleted file mode 100644
index 4236d50337bef92cb550cdbf71d83843ab35e2f3..0000000000000000000000000000000000000000
--- a/assets/icons/ai.svg
+++ /dev/null
@@ -1,27 +0,0 @@
-
diff --git a/assets/icons/box_open.svg b/assets/icons/box_open.svg
new file mode 100644
index 0000000000000000000000000000000000000000..5e30fc40c3446485412e2a2607b0d07dc2f68b4b
--- /dev/null
+++ b/assets/icons/box_open.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/cog.svg b/assets/icons/cog.svg
deleted file mode 100644
index 7dd3a8befff59b5aaa0506df9b2cd7140725ab81..0000000000000000000000000000000000000000
--- a/assets/icons/cog.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/assets/icons/ellipsis_vertical.svg b/assets/icons/ellipsis_vertical.svg
deleted file mode 100644
index c38437667ebbe095aaa4be27244997a9138bf659..0000000000000000000000000000000000000000
--- a/assets/icons/ellipsis_vertical.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/assets/icons/file_icons/editorconfig.svg b/assets/icons/file_icons/editorconfig.svg
new file mode 100644
index 0000000000000000000000000000000000000000..81355bec4603e678c3b1d1097d00ef03da5edf7f
--- /dev/null
+++ b/assets/icons/file_icons/editorconfig.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/focus.svg b/assets/icons/focus.svg
new file mode 100644
index 0000000000000000000000000000000000000000..9003e437cee1afa43e87fa273c9510284bb5ae0b
--- /dev/null
+++ b/assets/icons/focus.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/icons/menu_alt.svg b/assets/icons/menu_alt.svg
deleted file mode 100644
index b9cc19e22febe045ca9ccf4a7e86d69b258f875c..0000000000000000000000000000000000000000
--- a/assets/icons/menu_alt.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/assets/icons/shield_check.svg b/assets/icons/shield_check.svg
deleted file mode 100644
index 43b52f43a8d70beb6e69c2271235090db4dc2c00..0000000000000000000000000000000000000000
--- a/assets/icons/shield_check.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/assets/icons/stop.svg b/assets/icons/stop.svg
index cc2bbe9207acf5acd44ff13e93140099d222250b..5ca9cd29edf17981500482b81e47aa53a16e2713 100644
--- a/assets/icons/stop.svg
+++ b/assets/icons/stop.svg
@@ -1,3 +1,3 @@
diff --git a/assets/icons/swatch_book.svg b/assets/icons/swatch_book.svg
deleted file mode 100644
index b37d5df8c1a5f0f6b9fa9cb46b3004a2ba55da4f..0000000000000000000000000000000000000000
--- a/assets/icons/swatch_book.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/assets/icons/sweep_ai.svg b/assets/icons/sweep_ai.svg
deleted file mode 100644
index 9c63c810dd9e164c14c1ad1a1bca9c6ec68fc95e..0000000000000000000000000000000000000000
--- a/assets/icons/sweep_ai.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/assets/icons/sweep_ai_disabled.svg b/assets/icons/sweep_ai_disabled.svg
deleted file mode 100644
index b15a8d8526f36f312482effefd3d7538ce5f7a04..0000000000000000000000000000000000000000
--- a/assets/icons/sweep_ai_disabled.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/assets/icons/sweep_ai_down.svg b/assets/icons/sweep_ai_down.svg
deleted file mode 100644
index f08dcb171811c761cd13c4efd0ef0acdc78f9951..0000000000000000000000000000000000000000
--- a/assets/icons/sweep_ai_down.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/assets/icons/sweep_ai_error.svg b/assets/icons/sweep_ai_error.svg
deleted file mode 100644
index 95285a1273e72ec4f02cb23e3c2fb39460f42761..0000000000000000000000000000000000000000
--- a/assets/icons/sweep_ai_error.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/assets/icons/sweep_ai_up.svg b/assets/icons/sweep_ai_up.svg
deleted file mode 100644
index 7c28282a6a14c47561a50ab456c0bec2e05b07cc..0000000000000000000000000000000000000000
--- a/assets/icons/sweep_ai_up.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/assets/icons/terminal_ghost.svg b/assets/icons/terminal_ghost.svg
deleted file mode 100644
index 7d0d0e068e8a6f01837e860e8223690a95541769..0000000000000000000000000000000000000000
--- a/assets/icons/terminal_ghost.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/assets/icons/tool_read.svg b/assets/icons/tool_read.svg
deleted file mode 100644
index d22e9d8c7da9ba04fe194339d787e40637cf5257..0000000000000000000000000000000000000000
--- a/assets/icons/tool_read.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/assets/icons/tool_regex.svg b/assets/icons/tool_regex.svg
deleted file mode 100644
index 818c2ba360bc5aca3d4a7bf8ab65a03a2efe235e..0000000000000000000000000000000000000000
--- a/assets/icons/tool_regex.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json
index e4183965fa0b798d526ad6d59d0ce936269cab51..d5643e092268470e61b54001ab57d83ec7cd9467 100644
--- a/assets/keymaps/default-linux.json
+++ b/assets/keymaps/default-linux.json
@@ -31,7 +31,6 @@
"ctrl-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
- "ctrl-,": "zed::OpenSettings",
"ctrl-alt-,": "zed::OpenSettingsFile",
"ctrl-q": "zed::Quit",
"f4": "debugger::Start",
@@ -255,17 +254,20 @@
"alt-tab": "agent::CycleFavoriteModels",
// `alt-l` is provided as an alternative to `alt-tab` as the latter breaks on Linux under the `AgentPanel` context
"alt-l": "agent::CycleFavoriteModels",
- "ctrl-shift-j": "agent::ToggleNavigationMenu",
- "ctrl-alt-i": "agent::ToggleOptionsMenu",
+ "shift-alt-j": "agent::ToggleNavigationMenu",
+ "shift-alt-i": "agent::ToggleOptionsMenu",
"ctrl-alt-shift-n": "agent::ToggleNewThreadMenu",
"ctrl-shift-t": "agent::CycleStartThreadIn",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl->": "agent::AddSelectionToThread",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
- "ctrl-y": "agent::AllowOnce",
+ "shift-alt-q": "agent::AllowAlways",
+ "shift-alt-a": "agent::AllowOnce",
"ctrl-alt-a": "agent::OpenPermissionDropdown",
- "ctrl-alt-z": "agent::RejectOnce",
+ "shift-alt-x": "agent::RejectOnce",
+ "ctrl-tab": "agents_sidebar::ToggleThreadSwitcher",
+ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }],
},
},
{
@@ -339,6 +341,13 @@
"ctrl-alt-.": "agent::ToggleFastMode",
},
},
+ {
+ "context": "AcpThread > Editor && mode == full",
+ "use_key_equivalents": true,
+ "bindings": {
+ "alt-enter": "editor::OpenExcerpts",
+ },
+ },
{
"context": "AcpThread > Editor && !use_modifier_to_send",
"use_key_equivalents": true,
@@ -692,12 +701,27 @@
"left": "menu::SelectParent",
"right": "menu::SelectChild",
"enter": "menu::Confirm",
- "space": "menu::Confirm",
"ctrl-f": "agents_sidebar::FocusSidebarFilter",
"ctrl-g": "agents_sidebar::ToggleArchive",
"shift-backspace": "agent::RemoveSelectedThread",
+ "ctrl-tab": "agents_sidebar::ToggleThreadSwitcher",
+ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }],
},
},
+ {
+ "context": "ThreadsSidebar && not_searching",
+ "use_key_equivalents": true,
+ "bindings": {
+ "space": "menu::Confirm",
+ },
+ },
+ {
+ "context": "ThreadSwitcher",
+ "bindings": {
+ "ctrl-tab": "agents_sidebar::ToggleThreadSwitcher",
+ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }]
+ }
+ },
{
"context": "Workspace && debugger_running",
"bindings": {
@@ -790,7 +814,7 @@
},
},
{
- "context": "Editor && edit_prediction && edit_prediction_mode == eager",
+ "context": "Editor && edit_prediction && edit_prediction_mode == eager && !showing_completions",
"bindings": {
"tab": "editor::AcceptEditPrediction",
},
@@ -1065,6 +1089,7 @@
"alt-up": "collab_panel::MoveChannelUp",
"alt-down": "collab_panel::MoveChannelDown",
"alt-enter": "collab_panel::OpenSelectedChannelNotes",
+ "shift-enter": "collab_panel::ToggleSelectedChannelFavorite",
},
},
{
@@ -1339,6 +1364,15 @@
"ctrl-shift-backspace": "git::DeleteWorktree",
},
},
+ {
+ // Handled under a more specific context to avoid conflicts with the
+ // `OpenCurrentFile` keybind from the settings UI
+ "context": "!SettingsWindow",
+ "use_key_equivalents": true,
+ "bindings": {
+ "ctrl-,": "zed::OpenSettings",
+ },
+ },
{
"context": "SettingsWindow",
"use_key_equivalents": true,
diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json
index 27901157e75813109e2b13fb44d6ffe71a04a0f5..e2073c170b375baea22f5018c2c7dba632dd9b05 100644
--- a/assets/keymaps/default-macos.json
+++ b/assets/keymaps/default-macos.json
@@ -39,7 +39,6 @@
"cmd-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
"cmd--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"cmd-0": ["zed::ResetBufferFontSize", { "persist": false }],
- "cmd-,": "zed::OpenSettings",
"cmd-alt-,": "zed::OpenSettingsFile",
"cmd-q": "zed::Quit",
"cmd-h": "zed::Hide",
@@ -305,6 +304,8 @@
"cmd-y": "agent::AllowOnce",
"cmd-alt-a": "agent::OpenPermissionDropdown",
"cmd-alt-z": "agent::RejectOnce",
+ "ctrl-tab": "agents_sidebar::ToggleThreadSwitcher",
+ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }],
},
},
{
@@ -383,6 +384,13 @@
"cmd-alt-.": "agent::ToggleFastMode",
},
},
+ {
+ "context": "AcpThread > Editor && mode == full",
+ "use_key_equivalents": true,
+ "bindings": {
+ "alt-enter": "editor::OpenExcerpts",
+ },
+ },
{
"context": "AcpThread > Editor && !use_modifier_to_send",
"use_key_equivalents": true,
@@ -758,12 +766,27 @@
"left": "menu::SelectParent",
"right": "menu::SelectChild",
"enter": "menu::Confirm",
- "space": "menu::Confirm",
"cmd-f": "agents_sidebar::FocusSidebarFilter",
"cmd-g": "agents_sidebar::ToggleArchive",
"shift-backspace": "agent::RemoveSelectedThread",
+ "ctrl-tab": "agents_sidebar::ToggleThreadSwitcher",
+ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }],
},
},
+ {
+ "context": "ThreadsSidebar && not_searching",
+ "use_key_equivalents": true,
+ "bindings": {
+ "space": "menu::Confirm",
+ },
+ },
+ {
+ "context": "ThreadSwitcher",
+ "bindings": {
+ "ctrl-tab": "agents_sidebar::ToggleThreadSwitcher",
+ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }]
+ }
+ },
{
"context": "Workspace && debugger_running",
"use_key_equivalents": true,
@@ -852,7 +875,7 @@
},
},
{
- "context": "Editor && edit_prediction && edit_prediction_mode == eager",
+ "context": "Editor && edit_prediction && edit_prediction_mode == eager && !showing_completions",
"bindings": {
"tab": "editor::AcceptEditPrediction",
},
@@ -1126,6 +1149,7 @@
"alt-up": "collab_panel::MoveChannelUp",
"alt-down": "collab_panel::MoveChannelDown",
"alt-enter": "collab_panel::OpenSelectedChannelNotes",
+ "shift-enter": "collab_panel::ToggleSelectedChannelFavorite",
},
},
{
@@ -1441,6 +1465,15 @@
"cmd-shift-backspace": "git::DeleteWorktree",
},
},
+ {
+ // Handled under a more specific context to avoid conflicts with the
+ // `OpenCurrentFile` keybind from the settings UI
+ "context": "!SettingsWindow",
+ "use_key_equivalents": true,
+ "bindings": {
+ "cmd-,": "zed::OpenSettings",
+ },
+ },
{
"context": "SettingsWindow",
"use_key_equivalents": true,
diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json
index 8a071c9043a88868d4b91bdde3791bdd118e7a84..32f827259cbcf8bab39a8bbe45a9010d7239e2a7 100644
--- a/assets/keymaps/default-windows.json
+++ b/assets/keymaps/default-windows.json
@@ -30,7 +30,6 @@
"ctrl-shift-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
- "ctrl-,": "zed::OpenSettings",
"ctrl-alt-,": "zed::OpenSettingsFile",
"ctrl-q": "zed::Quit",
"f4": "debugger::Start",
@@ -264,9 +263,12 @@
"ctrl-shift-.": "agent::AddSelectionToThread",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
+ "shift-alt-q": "agent::AllowAlways",
"shift-alt-a": "agent::AllowOnce",
"ctrl-alt-a": "agent::OpenPermissionDropdown",
- "shift-alt-z": "agent::RejectOnce",
+ "shift-alt-x": "agent::RejectOnce",
+ "ctrl-tab": "agents_sidebar::ToggleThreadSwitcher",
+ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }],
},
},
{
@@ -341,6 +343,13 @@
"ctrl-alt-.": "agent::ToggleFastMode",
},
},
+ {
+ "context": "AcpThread > Editor && mode == full",
+ "use_key_equivalents": true,
+ "bindings": {
+ "alt-enter": "editor::OpenExcerpts",
+ },
+ },
{
"context": "AcpThread > Editor && !use_modifier_to_send",
"use_key_equivalents": true,
@@ -694,12 +703,27 @@
"left": "menu::SelectParent",
"right": "menu::SelectChild",
"enter": "menu::Confirm",
- "space": "menu::Confirm",
"ctrl-f": "agents_sidebar::FocusSidebarFilter",
"ctrl-g": "agents_sidebar::ToggleArchive",
"shift-backspace": "agent::RemoveSelectedThread",
+ "ctrl-tab": "agents_sidebar::ToggleThreadSwitcher",
+ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }],
},
},
+ {
+ "context": "ThreadsSidebar && not_searching",
+ "use_key_equivalents": true,
+ "bindings": {
+ "space": "menu::Confirm",
+ },
+ },
+ {
+ "context": "ThreadSwitcher",
+ "bindings": {
+ "ctrl-tab": "agents_sidebar::ToggleThreadSwitcher",
+ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }]
+ }
+ },
{
"context": "ApplicationMenu",
"use_key_equivalents": true,
@@ -784,7 +808,7 @@
},
},
{
- "context": "Editor && edit_prediction && edit_prediction_mode == eager",
+ "context": "Editor && edit_prediction && edit_prediction_mode == eager && !showing_completions",
"use_key_equivalents": true,
"bindings": {
"tab": "editor::AcceptEditPrediction",
@@ -1070,6 +1094,7 @@
"alt-up": "collab_panel::MoveChannelUp",
"alt-down": "collab_panel::MoveChannelDown",
"alt-enter": "collab_panel::OpenSelectedChannelNotes",
+ "shift-enter": "collab_panel::ToggleSelectedChannelFavorite",
},
},
{
@@ -1357,6 +1382,15 @@
"ctrl-shift-backspace": "git::DeleteWorktree",
},
},
+ {
+ // Handled under a more specific context to avoid conflicts with the
+ // `OpenCurrentFile` keybind from the settings UI
+ "context": "!SettingsWindow",
+ "use_key_equivalents": true,
+ "bindings": {
+ "ctrl-,": "zed::OpenSettings",
+ },
+ },
{
"context": "SettingsWindow",
"use_key_equivalents": true,
diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json
index 6d1a0cf278d5eb7598ed92e91b7d4ffad90d9c05..ae0a0dd0f1ef3ba99814b39db6ec3932d0ef3730 100644
--- a/assets/keymaps/vim.json
+++ b/assets/keymaps/vim.json
@@ -337,6 +337,8 @@
"shift-j": "vim::JoinLines",
"i": "vim::InsertBefore",
"a": "vim::InsertAfter",
+ "o": "vim::InsertLineBelow",
+ "shift-o": "vim::InsertLineAbove",
"p": "vim::Paste",
"u": "vim::Undo",
"r": "vim::PushReplace",
@@ -1060,7 +1062,7 @@
},
},
{
- "context": "Editor && edit_prediction && edit_prediction_mode == eager",
+ "context": "Editor && edit_prediction && edit_prediction_mode == eager && !showing_completions",
"bindings": {
// This is identical to the binding in the base keymap, but the vim bindings above to
// "vim::Tab" shadow it, so it needs to be bound again.
diff --git a/assets/settings/default.json b/assets/settings/default.json
index 97c74af5ad6b158a8658a944bdc0e5e16982e91f..d3defb428c68120e89c6bc6cc82488f03a06b320 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -299,6 +299,13 @@
//
// Default: split
"diff_view_style": "split",
+ // The minimum width (in em-widths) at which the split diff view is used.
+ // When the editor is narrower than this, the diff view automatically
+ // switches to unified mode and switches back when the editor is wide
+ // enough. Set to 0 to disable automatic switching.
+ //
+ // Default: 100
+ "minimum_split_diff_width": 100,
// 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.
@@ -460,12 +467,10 @@
"show_sign_in": true,
// Whether to show the menus in the titlebar.
"show_menus": false,
+ // The layout of window control buttons in the title bar (Linux only).
+ "button_layout": "platform_default",
},
"audio": {
- // Opt into the new audio system.
- "experimental.rodio_audio": false,
- // Requires 'rodio_audio: true'
- //
// Automatically increase or decrease you microphone's volume. This affects how
// loud you sound to others.
//
@@ -474,33 +479,10 @@
// audio and has auto speaker volume on this will make you very loud
// compared to other speakers.
"experimental.auto_microphone_volume": false,
- // Requires 'rodio_audio: true'
- //
- // Automatically increate or decrease the volume of other call members.
- // This only affects how things sound for you.
- "experimental.auto_speaker_volume": true,
- // Requires 'rodio_audio: true'
- //
- // Remove background noises. Works great for typing, cars, dogs, AC. Does
- // not work well on music.
- "experimental.denoise": true,
- // Requires 'rodio_audio: true'
- //
- // Use audio parameters compatible with the previous versions of
- // experimental audio and non-experimental audio. When this is false you
- // will sound strange to anyone not on the latest experimental audio. In
- // the future we will migrate by setting this to false
- //
- // You need to rejoin a call for this setting to apply
- "experimental.legacy_audio_compatible": true,
- // Requires 'rodio_audio: true'
- //
// Select specific output audio device.
// `null` means use system default.
// Any unrecognized output device will fall back to system default.
"experimental.output_audio_device": null,
- // Requires 'rodio_audio: true'
- //
// Select specific input audio device.
// `null` means use system default.
// Any unrecognized input device will fall back to system default.
@@ -807,6 +789,8 @@
"sort_mode": "directories_first",
// Whether to show error and warning count badges next to file names in the project panel.
"diagnostic_badges": false,
+ // Whether to show the git status indicator next to file names in the project panel.
+ "git_status_indicator": false,
// 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;
@@ -966,6 +950,12 @@
"button": true,
// Where to dock the agent panel. Can be 'left', 'right' or 'bottom'.
"dock": "right",
+ // Whether the agent panel should use flexible (proportional) sizing.
+ //
+ // Default: true
+ "flexible": true,
+ // Where to position the sidebar. Can be 'left' or 'right'.
+ "sidebar_side": "left",
// Default width when the agent panel is docked to the left or right.
"default_width": 640,
// Default height when the agent panel is docked to the bottom.
@@ -1127,6 +1117,10 @@
//
// Default: true
"expand_terminal_card": true,
+ // How thinking blocks should be displayed by default in the agent panel.
+ //
+ // Default: automatic
+ "thinking_display": "automatic",
// Whether clicking the stop button on a running terminal tool should also cancel the agent's generation.
// Note that this only applies to the stop button, not to ctrl+c inside the terminal.
//
@@ -1371,6 +1365,12 @@
"hard_tabs": false,
// How many columns a tab should occupy.
"tab_size": 4,
+ // Number of lines to search for modelines at the beginning and end of files.
+ // Modelines contain editor directives (e.g., vim/emacs settings) that configure
+ // the editor behavior for specific files.
+ //
+ // A value of 0 disables modelines support.
+ "modeline_lines": 5,
// What debuggers are preferred by default for all languages.
"debuggers": [],
// Whether to enable word diff highlighting in the editor.
@@ -1602,13 +1602,6 @@
"model": "codestral-latest",
"max_tokens": 150,
},
- "sweep": {
- // When enabled, Sweep will not store edit prediction inputs or outputs.
- // When disabled, Sweep may collect data including buffer contents,
- // diagnostics, file paths, repository names, and generated predictions
- // to improve the service.
- "privacy_mode": false,
- },
"ollama": {
"api_url": "http://localhost:11434",
"model": "qwen2.5-coder:7b-base",
@@ -1639,6 +1632,8 @@
"status_bar": {
// Whether to show the status bar.
"experimental.show": true,
+ // Whether to show the name of the active file in the status bar.
+ "show_active_file": false,
// Whether to show the active language button in the status bar.
"active_language_button": true,
// Whether to show the cursor position button in the status bar.
@@ -1667,6 +1662,10 @@
"shell": "system",
// Where to dock terminals panel. Can be `left`, `right`, `bottom`.
"dock": "bottom",
+ // Whether the terminal panel should use flexible (proportional) sizing.
+ //
+ // Default: true
+ "flexible": true,
// Default width when the terminal is docked to the left or right.
"default_width": 640,
// Default height when the terminal is docked to the bottom.
@@ -2046,9 +2045,12 @@
"remove_trailing_whitespace_on_save": false,
"ensure_final_newline_on_save": false,
},
- "Elixir": {
+ "EEx": {
"language_servers": ["elixir-ls", "!expert", "!next-ls", "!lexical", "..."],
},
+ "Elixir": {
+ "language_servers": ["elixir-ls", "!expert", "!next-ls", "!lexical", "!emmet-language-server", "..."],
+ },
"Elm": {
"tab_size": 4,
},
@@ -2072,7 +2074,7 @@
"allowed": true,
},
},
- "HEEX": {
+ "HEEx": {
"language_servers": ["elixir-ls", "!expert", "!next-ls", "!lexical", "..."],
},
"HTML": {
diff --git a/assets/settings/default_semantic_token_rules.json b/assets/settings/default_semantic_token_rules.json
index 65b20a7423aef3c3221f9f80e345fd503627d98d..c070a253d3065feff6647123b5f687e94f5e85d6 100644
--- a/assets/settings/default_semantic_token_rules.json
+++ b/assets/settings/default_semantic_token_rules.json
@@ -119,6 +119,16 @@
"style": ["type"],
},
// References
+ {
+ "token_type": "parameter",
+ "token_modifiers": ["declaration"],
+ "style": ["variable.parameter"]
+ },
+ {
+ "token_type": "parameter",
+ "token_modifiers": ["definition"],
+ "style": ["variable.parameter"]
+ },
{
"token_type": "parameter",
"token_modifiers": [],
@@ -201,6 +211,11 @@
"token_modifiers": [],
"style": ["comment"],
},
+ {
+ "token_type": "string",
+ "token_modifiers": ["documentation"],
+ "style": ["string.doc"],
+ },
{
"token_type": "string",
"token_modifiers": [],
diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs
index 5fb27bcd5289f60bcdfdb6adc9007c86c60fbad7..54565997e15f5f79e4f242680403d2f1f75ca6eb 100644
--- a/crates/acp_thread/src/acp_thread.rs
+++ b/crates/acp_thread/src/acp_thread.rs
@@ -160,6 +160,7 @@ pub enum AgentThreadEntry {
UserMessage(UserMessage),
AssistantMessage(AssistantMessage),
ToolCall(ToolCall),
+ CompletedPlan(Vec),
}
impl AgentThreadEntry {
@@ -168,6 +169,7 @@ impl AgentThreadEntry {
Self::UserMessage(message) => message.indented,
Self::AssistantMessage(message) => message.indented,
Self::ToolCall(_) => false,
+ Self::CompletedPlan(_) => false,
}
}
@@ -176,6 +178,14 @@ impl AgentThreadEntry {
Self::UserMessage(message) => message.to_markdown(cx),
Self::AssistantMessage(message) => message.to_markdown(cx),
Self::ToolCall(tool_call) => tool_call.to_markdown(cx),
+ Self::CompletedPlan(entries) => {
+ let mut md = String::from("## Plan\n\n");
+ for entry in entries {
+ let source = entry.content.read(cx).source().to_string();
+ md.push_str(&format!("- [x] {}\n", source));
+ }
+ md
+ }
}
}
@@ -502,13 +512,15 @@ pub enum SelectedPermissionParams {
#[derive(Debug)]
pub struct SelectedPermissionOutcome {
pub option_id: acp::PermissionOptionId,
+ pub option_kind: acp::PermissionOptionKind,
pub params: Option,
}
impl SelectedPermissionOutcome {
- pub fn new(option_id: acp::PermissionOptionId) -> Self {
+ pub fn new(option_id: acp::PermissionOptionId, option_kind: acp::PermissionOptionKind) -> Self {
Self {
option_id,
+ option_kind,
params: None,
}
}
@@ -519,12 +531,6 @@ impl SelectedPermissionOutcome {
}
}
-impl From for SelectedPermissionOutcome {
- fn from(option_id: acp::PermissionOptionId) -> Self {
- Self::new(option_id)
- }
-}
-
impl From for acp::SelectedPermissionOutcome {
fn from(value: SelectedPermissionOutcome) -> Self {
Self::new(value.option_id)
@@ -1282,6 +1288,10 @@ impl AcpThread {
self.work_dirs.as_ref()
}
+ pub fn set_work_dirs(&mut self, work_dirs: PathList) {
+ self.work_dirs = Some(work_dirs);
+ }
+
pub fn status(&self) -> ThreadStatus {
if self.running_turn.is_some() {
ThreadStatus::Generating
@@ -1302,7 +1312,9 @@ impl AcpThread {
status: ToolCallStatus::WaitingForConfirmation { .. },
..
}) => return true,
- AgentThreadEntry::ToolCall(_) | AgentThreadEntry::AssistantMessage(_) => {}
+ AgentThreadEntry::ToolCall(_)
+ | AgentThreadEntry::AssistantMessage(_)
+ | AgentThreadEntry::CompletedPlan(_) => {}
}
}
false
@@ -1324,7 +1336,9 @@ impl AcpThread {
) if call.diffs().next().is_some() => {
return true;
}
- AgentThreadEntry::ToolCall(_) | AgentThreadEntry::AssistantMessage(_) => {}
+ AgentThreadEntry::ToolCall(_)
+ | AgentThreadEntry::AssistantMessage(_)
+ | AgentThreadEntry::CompletedPlan(_) => {}
}
}
@@ -1341,7 +1355,9 @@ impl AcpThread {
}) => {
return true;
}
- AgentThreadEntry::ToolCall(_) | AgentThreadEntry::AssistantMessage(_) => {}
+ AgentThreadEntry::ToolCall(_)
+ | AgentThreadEntry::AssistantMessage(_)
+ | AgentThreadEntry::CompletedPlan(_) => {}
}
}
@@ -1352,7 +1368,9 @@ impl AcpThread {
for entry in self.entries.iter().rev() {
match entry {
AgentThreadEntry::UserMessage(..) => return false,
- AgentThreadEntry::AssistantMessage(..) => continue,
+ AgentThreadEntry::AssistantMessage(..) | AgentThreadEntry::CompletedPlan(..) => {
+ continue;
+ }
AgentThreadEntry::ToolCall(..) => return true,
}
}
@@ -2013,14 +2031,13 @@ impl AcpThread {
&mut self,
id: acp::ToolCallId,
outcome: SelectedPermissionOutcome,
- option_kind: acp::PermissionOptionKind,
cx: &mut Context,
) {
let Some((ix, call)) = self.tool_call_mut(&id) else {
return;
};
- let new_status = match option_kind {
+ let new_status = match outcome.option_kind {
acp::PermissionOptionKind::RejectOnce | acp::PermissionOptionKind::RejectAlways => {
ToolCallStatus::Rejected
}
@@ -2070,6 +2087,13 @@ impl AcpThread {
cx.notify();
}
+ pub fn snapshot_completed_plan(&mut self, cx: &mut Context) {
+ if !self.plan.is_empty() && self.plan.stats().pending == 0 {
+ let completed_entries = std::mem::take(&mut self.plan.entries);
+ self.push_entry(AgentThreadEntry::CompletedPlan(completed_entries), cx);
+ }
+ }
+
fn clear_completed_plan_entries(&mut self, cx: &mut Context) {
self.plan
.entries
@@ -2077,6 +2101,11 @@ impl AcpThread {
cx.notify();
}
+ pub fn clear_plan(&mut self, cx: &mut Context) {
+ self.plan.entries.clear();
+ cx.notify();
+ }
+
#[cfg(any(test, feature = "test-support"))]
pub fn send_raw(
&mut self,
@@ -2215,7 +2244,24 @@ impl AcpThread {
this.had_error = true;
cx.emit(AcpThreadEvent::Error);
log::error!("Max tokens reached. Usage: {:?}", this.token_usage);
- return Err(anyhow!("Max tokens reached"));
+
+ let exceeded_max_output_tokens =
+ this.token_usage.as_ref().is_some_and(|u| {
+ u.max_output_tokens
+ .is_some_and(|max| u.output_tokens >= max)
+ });
+
+ let message = if exceeded_max_output_tokens {
+ log::error!(
+ "Max output tokens reached. Usage: {:?}",
+ this.token_usage
+ );
+ "Maximum output tokens reached"
+ } else {
+ log::error!("Max tokens reached. Usage: {:?}", this.token_usage);
+ "Maximum tokens reached"
+ };
+ return Err(anyhow!(message));
}
let canceled = matches!(r.stop_reason, acp::StopReason::Cancelled);
@@ -2223,6 +2269,10 @@ impl AcpThread {
this.mark_pending_tools_as_canceled();
}
+ if !canceled {
+ this.snapshot_completed_plan(cx);
+ }
+
// Handle refusal - distinguish between user prompt and tool call refusals
if let acp::StopReason::Refusal = r.stop_reason {
this.had_error = true;
@@ -2593,11 +2643,8 @@ impl AcpThread {
let format_on_save = buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
- let settings = language::language_settings::language_settings(
- buffer.language().map(|l| l.name()),
- buffer.file(),
- cx,
- );
+ let settings =
+ language::language_settings::LanguageSettings::for_buffer(buffer, cx);
settings.format_on_save != FormatOnSave::Off
});
@@ -3185,9 +3232,27 @@ mod tests {
);
});
- // Wait for the printf command to execute and produce output
- // Use real time since parking is enabled
- cx.executor().timer(Duration::from_millis(500)).await;
+ // Poll until the printf command produces output, rather than using a
+ // fixed sleep which is flaky on loaded machines.
+ let deadline = std::time::Instant::now() + Duration::from_secs(10);
+ loop {
+ let has_output = thread.read_with(cx, |thread, cx| {
+ let term = thread
+ .terminals
+ .get(&terminal_id)
+ .expect("terminal not found");
+ let content = term.read(cx).inner().read(cx).get_content();
+ content.contains("output_before_kill")
+ });
+ if has_output {
+ break;
+ }
+ assert!(
+ std::time::Instant::now() < deadline,
+ "Timed out waiting for printf output to appear in terminal",
+ );
+ cx.executor().timer(Duration::from_millis(50)).await;
+ }
// Get the acp_thread Terminal and kill it
let wait_for_exit = thread.update(cx, |thread, cx| {
diff --git a/crates/acp_thread/src/connection.rs b/crates/acp_thread/src/connection.rs
index 1c6f2a49f18b4b6c664f49d1a732de9a53c75aab..58a8aa33830f12ffb713490c87c47133cc2ad96f 100644
--- a/crates/acp_thread/src/connection.rs
+++ b/crates/acp_thread/src/connection.rs
@@ -477,6 +477,24 @@ impl PermissionOptionChoice {
pub fn label(&self) -> SharedString {
self.allow.name.clone().into()
}
+
+ /// Build a `SelectedPermissionOutcome` for this choice.
+ ///
+ /// If the choice carries `sub_patterns`, they are attached as
+ /// `SelectedPermissionParams::Terminal`.
+ pub fn build_outcome(&self, is_allow: bool) -> crate::SelectedPermissionOutcome {
+ let option = if is_allow { &self.allow } else { &self.deny };
+
+ let params = if !self.sub_patterns.is_empty() {
+ Some(crate::SelectedPermissionParams::Terminal {
+ patterns: self.sub_patterns.clone(),
+ })
+ } else {
+ None
+ };
+
+ crate::SelectedPermissionOutcome::new(option.option_id.clone(), option.kind).params(params)
+ }
}
/// Pairs a tool's permission pattern with its display name
@@ -548,6 +566,57 @@ impl PermissionOptions {
self.first_option_of_kind(acp::PermissionOptionKind::RejectOnce)
.map(|option| option.option_id.clone())
}
+
+ /// Build a `SelectedPermissionOutcome` for the `DropdownWithPatterns`
+ /// variant when the user has checked specific pattern indices.
+ ///
+ /// Returns `Some` with the always-allow/deny outcome when at least one
+ /// pattern is checked. Returns `None` when zero patterns are checked,
+ /// signaling that the caller should degrade to allow-once / deny-once.
+ ///
+ /// Panics (debug) or returns `None` (release) if called on a non-
+ /// `DropdownWithPatterns` variant.
+ pub fn build_outcome_for_checked_patterns(
+ &self,
+ checked_indices: &[usize],
+ is_allow: bool,
+ ) -> Option {
+ let PermissionOptions::DropdownWithPatterns {
+ choices, patterns, ..
+ } = self
+ else {
+ debug_assert!(
+ false,
+ "build_outcome_for_checked_patterns called on non-DropdownWithPatterns"
+ );
+ return None;
+ };
+
+ let checked_patterns: Vec = patterns
+ .iter()
+ .enumerate()
+ .filter(|(index, _)| checked_indices.contains(index))
+ .map(|(_, cp)| cp.pattern.clone())
+ .collect();
+
+ if checked_patterns.is_empty() {
+ return None;
+ }
+
+ // Use the first choice (the "Always" choice) as the base for the outcome.
+ let always_choice = choices.first()?;
+ let option = if is_allow {
+ &always_choice.allow
+ } else {
+ &always_choice.deny
+ };
+
+ let outcome = crate::SelectedPermissionOutcome::new(option.option_id.clone(), option.kind)
+ .params(Some(crate::SelectedPermissionParams::Terminal {
+ patterns: checked_patterns,
+ }));
+ Some(outcome)
+ }
}
#[cfg(feature = "test-support")]
diff --git a/crates/acp_tools/Cargo.toml b/crates/acp_tools/Cargo.toml
index 0720c4b6685ecf7fa20d8cacd2b61baa765c961c..8f14b1f93b32c6df521ea13ebf3f0f73e7ed755c 100644
--- a/crates/acp_tools/Cargo.toml
+++ b/crates/acp_tools/Cargo.toml
@@ -23,7 +23,7 @@ project.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
-theme.workspace = true
+theme_settings.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
diff --git a/crates/acp_tools/src/acp_tools.rs b/crates/acp_tools/src/acp_tools.rs
index 30d13effcb53395972879ef109a253be0c134ec1..52a9d03f893d0b82bf6395b4c96bc9ebe14d3afe 100644
--- a/crates/acp_tools/src/acp_tools.rs
+++ b/crates/acp_tools/src/acp_tools.rs
@@ -16,7 +16,7 @@ use language::LanguageRegistry;
use markdown::{CodeBlockRenderer, Markdown, MarkdownElement, MarkdownStyle};
use project::{AgentId, Project};
use settings::Settings;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
use ui::{CopyButton, Tooltip, WithScrollbar, prelude::*};
use util::ResultExt as _;
use workspace::{
@@ -291,7 +291,6 @@ impl AcpTools {
v_flex()
.id(index)
.group("message")
- .cursor_pointer()
.font_buffer(cx)
.w_full()
.py_3()
@@ -303,27 +302,29 @@ impl AcpTools {
.border_color(colors.border)
.border_b_1()
.hover(|this| this.bg(colors.element_background.opacity(0.5)))
- .on_click(cx.listener(move |this, _, _, cx| {
- if this.expanded.contains(&index) {
- this.expanded.remove(&index);
- } else {
- this.expanded.insert(index);
- let Some(connection) = &mut this.watched_connection else {
- return;
- };
- let Some(message) = connection.messages.get_mut(index) else {
- return;
- };
- message.expanded(this.project.read(cx).languages().clone(), cx);
- connection.list_state.scroll_to_reveal_item(index);
- }
- cx.notify()
- }))
.child(
h_flex()
+ .id(("acp-log-message-header", index))
.w_full()
.gap_2()
.flex_shrink_0()
+ .cursor_pointer()
+ .on_click(cx.listener(move |this, _, _, cx| {
+ if this.expanded.contains(&index) {
+ this.expanded.remove(&index);
+ } else {
+ this.expanded.insert(index);
+ let Some(connection) = &mut this.watched_connection else {
+ return;
+ };
+ let Some(message) = connection.messages.get_mut(index) else {
+ return;
+ };
+ message.expanded(this.project.read(cx).languages().clone(), cx);
+ connection.list_state.scroll_to_reveal_item(index);
+ }
+ cx.notify()
+ }))
.child(match message.direction {
acp::StreamMessageDirection::Incoming => Icon::new(IconName::ArrowDown)
.color(Color::Error)
diff --git a/crates/agent/Cargo.toml b/crates/agent/Cargo.toml
index fe2089d94dc2e3fc812f6cbe39c16c5cadc1a1f5..a5a4c2742a444bf2e8b0a12b0bb233c6e51684f2 100644
--- a/crates/agent/Cargo.toml
+++ b/crates/agent/Cargo.toml
@@ -10,7 +10,6 @@ path = "src/agent.rs"
[features]
test-support = ["db/test-support"]
-eval = []
unit-eval = []
e2e = []
diff --git a/crates/agent/src/agent.rs b/crates/agent/src/agent.rs
index 77c326feec60514d459e6026a39f1bcd5ed8a896..b7aa9d1e311016f572928993e049798c2b5e3bb2 100644
--- a/crates/agent/src/agent.rs
+++ b/crates/agent/src/agent.rs
@@ -82,7 +82,7 @@ struct Session {
/// The ACP thread that handles protocol communication
acp_thread: Entity,
project_id: EntityId,
- pending_save: Task<()>,
+ pending_save: Task>,
_subscriptions: Vec,
}
@@ -387,7 +387,7 @@ impl NativeAgent {
acp_thread: acp_thread.clone(),
project_id,
_subscriptions: subscriptions,
- pending_save: Task::ready(()),
+ pending_save: Task::ready(Ok(())),
},
);
@@ -663,15 +663,18 @@ impl NativeAgent {
return;
};
- if let Some(title) = thread.read(cx).title() {
- let acp_thread = session.acp_thread.downgrade();
- cx.spawn(async move |_, cx| {
+ let thread = thread.downgrade();
+ let acp_thread = session.acp_thread.downgrade();
+ cx.spawn(async move |_, cx| {
+ let title = thread.read_with(cx, |thread, _| thread.title())?;
+ if let Some(title) = title {
let task =
acp_thread.update(cx, |acp_thread, cx| acp_thread.set_title(title, cx))?;
- task.await
- })
- .detach_and_log_err(cx);
- }
+ task.await?;
+ }
+ anyhow::Ok(())
+ })
+ .detach_and_log_err(cx);
}
fn handle_thread_token_usage_updated(
@@ -939,6 +942,9 @@ impl NativeAgent {
NativeAgentConnection::handle_thread_events(events, acp_thread.downgrade(), cx)
})
.await?;
+ acp_thread.update(cx, |thread, cx| {
+ thread.snapshot_completed_plan(cx);
+ });
Ok(acp_thread)
})
}
@@ -1000,7 +1006,7 @@ impl NativeAgent {
let thread_store = self.thread_store.clone();
session.pending_save = cx.spawn(async move |_, cx| {
let Some(database) = database_future.await.map_err(|err| anyhow!(err)).log_err() else {
- return;
+ return Ok(());
};
let db_thread = db_thread.await;
database
@@ -1008,6 +1014,7 @@ impl NativeAgent {
.await
.log_err();
thread_store.update(cx, |store, cx| store.reload(cx));
+ Ok(())
});
}
@@ -1444,18 +1451,23 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
cx: &mut App,
) -> Task> {
self.0.update(cx, |agent, cx| {
+ let thread = agent.sessions.get(session_id).map(|s| s.thread.clone());
+ if let Some(thread) = thread {
+ agent.save_thread(thread, cx);
+ }
+
let Some(session) = agent.sessions.remove(session_id) else {
- return;
+ return Task::ready(Ok(()));
};
let project_id = session.project_id;
- agent.save_thread(session.thread, cx);
let has_remaining = agent.sessions.values().any(|s| s.project_id == project_id);
if !has_remaining {
agent.projects.remove(&project_id);
}
- });
- Task::ready(Ok(()))
+
+ session.pending_save
+ })
}
fn auth_methods(&self) -> &[acp::AuthMethod] {
@@ -2830,7 +2842,9 @@ mod internal_tests {
cx.run_until_parked();
- // Set a draft prompt with rich content blocks before saving.
+ // Set a draft prompt with rich content blocks and scroll position
+ // AFTER run_until_parked, so the only save that captures these
+ // changes is the one performed by close_session itself.
let draft_blocks = vec![
acp::ContentBlock::Text(acp::TextContent::new("Check out ")),
acp::ContentBlock::ResourceLink(acp::ResourceLink::new("b.md", uri.to_string())),
@@ -2845,8 +2859,6 @@ mod internal_tests {
offset_in_item: gpui::px(12.5),
}));
});
- thread.update(cx, |_thread, cx| cx.notify());
- cx.run_until_parked();
// Close the session so it can be reloaded from disk.
cx.update(|cx| connection.clone().close_session(&session_id, cx))
@@ -2912,6 +2924,151 @@ mod internal_tests {
});
}
+ #[gpui::test]
+ async fn test_close_session_saves_thread(cx: &mut TestAppContext) {
+ init_test(cx);
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ "/",
+ json!({
+ "a": {
+ "file.txt": "hello"
+ }
+ }),
+ )
+ .await;
+ let project = Project::test(fs.clone(), [path!("/a").as_ref()], cx).await;
+ let thread_store = cx.new(|cx| ThreadStore::new(cx));
+ let agent = cx.update(|cx| {
+ NativeAgent::new(thread_store.clone(), Templates::new(), None, fs.clone(), cx)
+ });
+ let connection = Rc::new(NativeAgentConnection(agent.clone()));
+
+ let acp_thread = cx
+ .update(|cx| {
+ connection
+ .clone()
+ .new_session(project.clone(), PathList::new(&[Path::new("")]), cx)
+ })
+ .await
+ .unwrap();
+ let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone());
+ let thread = agent.read_with(cx, |agent, _| {
+ agent.sessions.get(&session_id).unwrap().thread.clone()
+ });
+
+ let model = Arc::new(FakeLanguageModel::default());
+ thread.update(cx, |thread, cx| {
+ thread.set_model(model.clone(), cx);
+ });
+
+ // Send a message so the thread is non-empty (empty threads aren't saved).
+ let send = acp_thread.update(cx, |thread, cx| thread.send(vec!["hello".into()], cx));
+ let send = cx.foreground_executor().spawn(send);
+ cx.run_until_parked();
+
+ model.send_last_completion_stream_text_chunk("world");
+ model.end_last_completion_stream();
+ send.await.unwrap();
+ cx.run_until_parked();
+
+ // Set a draft prompt WITHOUT calling run_until_parked afterwards.
+ // This means no observe-triggered save has run for this change.
+ // The only way this data gets persisted is if close_session
+ // itself performs the save.
+ let draft_blocks = vec![acp::ContentBlock::Text(acp::TextContent::new(
+ "unsaved draft",
+ ))];
+ acp_thread.update(cx, |thread, _cx| {
+ thread.set_draft_prompt(Some(draft_blocks.clone()));
+ });
+
+ // Close the session immediately — no run_until_parked in between.
+ cx.update(|cx| connection.clone().close_session(&session_id, cx))
+ .await
+ .unwrap();
+ cx.run_until_parked();
+
+ // Reopen and verify the draft prompt was saved.
+ let reloaded = agent
+ .update(cx, |agent, cx| {
+ agent.open_thread(session_id.clone(), project.clone(), cx)
+ })
+ .await
+ .unwrap();
+ reloaded.read_with(cx, |thread, _| {
+ assert_eq!(
+ thread.draft_prompt(),
+ Some(draft_blocks.as_slice()),
+ "close_session must save the thread; draft prompt was lost"
+ );
+ });
+ }
+
+ #[gpui::test]
+ async fn test_rapid_title_changes_do_not_loop(cx: &mut TestAppContext) {
+ // Regression test: rapid title changes must not cause a propagation loop
+ // between Thread and AcpThread via handle_thread_title_updated.
+ init_test(cx);
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree("/", json!({ "a": {} })).await;
+ let project = Project::test(fs.clone(), [], cx).await;
+ let thread_store = cx.new(|cx| ThreadStore::new(cx));
+ let agent = cx.update(|cx| {
+ NativeAgent::new(thread_store.clone(), Templates::new(), None, fs.clone(), cx)
+ });
+ let connection = Rc::new(NativeAgentConnection(agent.clone()));
+
+ let acp_thread = cx
+ .update(|cx| {
+ connection
+ .clone()
+ .new_session(project.clone(), PathList::new(&[Path::new("")]), cx)
+ })
+ .await
+ .unwrap();
+
+ let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone());
+ let thread = agent.read_with(cx, |agent, _| {
+ agent.sessions.get(&session_id).unwrap().thread.clone()
+ });
+
+ let title_updated_count = Rc::new(std::cell::RefCell::new(0usize));
+ cx.update(|cx| {
+ let count = title_updated_count.clone();
+ cx.subscribe(
+ &thread,
+ move |_entity: Entity, _event: &TitleUpdated, _cx: &mut App| {
+ let new_count = {
+ let mut count = count.borrow_mut();
+ *count += 1;
+ *count
+ };
+ assert!(
+ new_count <= 2,
+ "TitleUpdated fired {new_count} times; \
+ title updates are looping"
+ );
+ },
+ )
+ .detach();
+ });
+
+ thread.update(cx, |thread, cx| thread.set_title("first".into(), cx));
+ thread.update(cx, |thread, cx| thread.set_title("second".into(), cx));
+
+ cx.run_until_parked();
+
+ thread.read_with(cx, |thread, _| {
+ assert_eq!(thread.title(), Some("second".into()));
+ });
+ acp_thread.read_with(cx, |acp_thread, _| {
+ assert_eq!(acp_thread.title(), Some("second".into()));
+ });
+
+ assert_eq!(*title_updated_count.borrow(), 2);
+ }
+
fn thread_entries(
thread_store: &Entity,
cx: &mut TestAppContext,
diff --git a/crates/agent/src/edit_agent.rs b/crates/agent/src/edit_agent.rs
index e122d6b2884a593daa819457835d3d00690f5a7d..6e6cf9735a922695bf089bdcc78798fb086ad364 100644
--- a/crates/agent/src/edit_agent.rs
+++ b/crates/agent/src/edit_agent.rs
@@ -1,6 +1,6 @@
mod create_file_parser;
mod edit_parser;
-#[cfg(test)]
+#[cfg(all(test, feature = "unit-eval"))]
mod evals;
pub mod reindent;
pub mod streaming_fuzzy_matcher;
@@ -8,7 +8,6 @@ pub mod streaming_fuzzy_matcher;
use crate::{Template, Templates};
use action_log::ActionLog;
use anyhow::Result;
-use cloud_llm_client::CompletionIntent;
use create_file_parser::{CreateFileParser, CreateFileParserEvent};
pub use edit_parser::EditFormat;
use edit_parser::{EditParser, EditParserEvent, EditParserMetrics};
@@ -21,8 +20,8 @@ use futures::{
use gpui::{AppContext, AsyncApp, Entity, Task};
use language::{Anchor, Buffer, BufferSnapshot, LineIndent, Point, TextBufferSnapshot};
use language_model::{
- LanguageModel, LanguageModelCompletionError, LanguageModelRequest, LanguageModelRequestMessage,
- LanguageModelToolChoice, MessageContent, Role,
+ CompletionIntent, LanguageModel, LanguageModelCompletionError, LanguageModelRequest,
+ LanguageModelRequestMessage, LanguageModelToolChoice, MessageContent, Role,
};
use project::{AgentLocation, Project};
use reindent::{IndentDelta, Reindenter};
diff --git a/crates/agent/src/edit_agent/evals/fixtures/disable_cursor_blinking/before.rs b/crates/agent/src/edit_agent/evals/fixtures/disable_cursor_blinking/before.rs
index 607daa8ce3a129e0f4bc53a00d1a62f479da3932..198ab45b13faef814e5964892e02e4c9d60de5b0 100644
--- a/crates/agent/src/edit_agent/evals/fixtures/disable_cursor_blinking/before.rs
+++ b/crates/agent/src/edit_agent/evals/fixtures/disable_cursor_blinking/before.rs
@@ -550,7 +550,7 @@ impl Default for EditorStyle {
}
pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
- let show_background = language_settings::language_settings(None, None, cx)
+ let show_background = language_settings::language_settings(cx).get()
.inlay_hints
.show_background;
@@ -5989,7 +5989,7 @@ impl Editor {
let file = buffer.file();
- if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
+ if !language_settings(cx).buffer(buffer).get().show_edit_predictions {
return EditPredictionSettings::Disabled;
};
@@ -7837,7 +7837,7 @@ impl Editor {
h_flex()
.px_0p5()
.when(is_platform_style_mac, |parent| parent.gap_0p5())
- .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+ .font(theme_settings::ThemeSettings::get_global(cx).buffer_font.clone())
.text_size(TextSize::XSmall.rems(cx))
.child(h_flex().children(ui::render_modifiers(
&accept_keystroke.modifiers,
@@ -8149,7 +8149,7 @@ impl Editor {
.px_2()
.child(
h_flex()
- .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+ .font(theme_settings::ThemeSettings::get_global(cx).buffer_font.clone())
.when(is_platform_style_mac, |parent| parent.gap_1())
.child(h_flex().children(ui::render_modifiers(
&accept_keystroke.modifiers,
@@ -8258,7 +8258,7 @@ impl Editor {
.gap_2()
.pr_1()
.overflow_x_hidden()
- .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+ .font(theme_settings::ThemeSettings::get_global(cx).buffer_font.clone())
.child(left)
.child(preview),
)
@@ -11922,6 +11922,7 @@ impl Editor {
scroll_anchor: scroll_state,
scroll_top_row,
}),
+ Some(cursor_position.row),
cx,
);
cx.emit(EditorEvent::PushedToNavHistory {
@@ -18800,7 +18801,7 @@ fn choose_completion_range(
} = &completion.source
{
let completion_mode_setting =
- language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
+ language_settings(cx).buffer(buffer).get()
.completions
.lsp_insert_mode;
@@ -19849,7 +19850,7 @@ fn inlay_hint_settings(
) -> InlayHintSettings {
let file = snapshot.file_at(location);
let language = snapshot.language_at(location).map(|l| l.name());
- language_settings(language, file, cx).inlay_hints
+ language_settings(cx).language(language).file(file).get().inlay_hints
}
fn consume_contiguous_rows(
diff --git a/crates/agent/src/edit_agent/streaming_fuzzy_matcher.rs b/crates/agent/src/edit_agent/streaming_fuzzy_matcher.rs
index 1ce2ca6f361a7e8186711d35d4dc640b8f13ce5a..e6a56099a293215050fa082a0432f216754473af 100644
--- a/crates/agent/src/edit_agent/streaming_fuzzy_matcher.rs
+++ b/crates/agent/src/edit_agent/streaming_fuzzy_matcher.rs
@@ -72,6 +72,18 @@ impl StreamingFuzzyMatcher {
pub fn finish(&mut self) -> Vec> {
// Process any remaining incomplete line
if !self.incomplete_line.is_empty() {
+ if self.matches.len() == 1 {
+ let range = &mut self.matches[0];
+ if range.end < self.snapshot.len()
+ && self
+ .snapshot
+ .contains_str_at(range.end + 1, &self.incomplete_line)
+ {
+ range.end += 1 + self.incomplete_line.len();
+ return self.matches.clone();
+ }
+ }
+
self.query_lines.push(self.incomplete_line.clone());
self.incomplete_line.clear();
self.matches = self.resolve_location_fuzzy();
@@ -722,6 +734,54 @@ mod tests {
);
}
+ #[gpui::test]
+ fn test_prefix_of_last_line_resolves_to_correct_range() {
+ let text = indoc! {r#"
+ fn on_query_change(&mut self, cx: &mut Context) {
+ self.filter(cx);
+ }
+
+
+
+ fn render_search(&self, cx: &mut Context) -> Div {
+ div()
+ }
+ "#};
+
+ let buffer = TextBuffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ text.to_string(),
+ );
+ let snapshot = buffer.snapshot();
+
+ // Query with a partial last line.
+ let query = "}\n\n\n\nfn render_search";
+
+ let mut matcher = StreamingFuzzyMatcher::new(snapshot.clone());
+ matcher.push(query, None);
+ let matches = matcher.finish();
+
+ // The match should include the line containing "fn render_search".
+ let matched_text = matches
+ .first()
+ .map(|range| snapshot.text_for_range(range.clone()).collect::());
+
+ assert!(
+ matches.len() == 1,
+ "Expected exactly one match, got {}: {:?}",
+ matches.len(),
+ matched_text,
+ );
+
+ let matched_text = matched_text.unwrap();
+ pretty_assertions::assert_eq!(
+ matched_text,
+ "}\n\n\n\nfn render_search",
+ "Match should include the render_search line",
+ );
+ }
+
#[track_caller]
fn assert_location_resolution(text_with_expected_range: &str, query: &str, rng: &mut StdRng) {
let (text, expected_ranges) = marked_text_ranges(text_with_expected_range, false);
diff --git a/crates/agent/src/tests/mod.rs b/crates/agent/src/tests/mod.rs
index 5cb2c99bfae222b13fd978e1bc3eba2fe98ca1d6..036a6f1030c43b16d51f864a1d0176891e90b772 100644
--- a/crates/agent/src/tests/mod.rs
+++ b/crates/agent/src/tests/mod.rs
@@ -7,7 +7,6 @@ use agent_client_protocol::{self as acp};
use agent_settings::AgentProfileId;
use anyhow::Result;
use client::{Client, UserStore};
-use cloud_llm_client::CompletionIntent;
use collections::IndexMap;
use context_server::{ContextServer, ContextServerCommand, ContextServerId};
use feature_flags::FeatureFlagAppExt as _;
@@ -26,8 +25,8 @@ use gpui::{
};
use indoc::indoc;
use language_model::{
- LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId,
- LanguageModelProviderName, LanguageModelRegistry, LanguageModelRequest,
+ CompletionIntent, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
+ LanguageModelId, LanguageModelProviderName, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolSchemaFormat,
LanguageModelToolUse, MessageContent, Role, StopReason, TokenUsage,
fake_provider::FakeLanguageModel,
@@ -841,14 +840,20 @@ async fn test_tool_authorization(cx: &mut TestAppContext) {
// Approve the first - send "allow" option_id (UI transforms "once" to "allow")
tool_call_auth_1
.response
- .send(acp::PermissionOptionId::new("allow").into())
+ .send(acp_thread::SelectedPermissionOutcome::new(
+ acp::PermissionOptionId::new("allow"),
+ acp::PermissionOptionKind::AllowOnce,
+ ))
.unwrap();
cx.run_until_parked();
// Reject the second - send "deny" option_id directly since Deny is now a button
tool_call_auth_2
.response
- .send(acp::PermissionOptionId::new("deny").into())
+ .send(acp_thread::SelectedPermissionOutcome::new(
+ acp::PermissionOptionId::new("deny"),
+ acp::PermissionOptionKind::RejectOnce,
+ ))
.unwrap();
cx.run_until_parked();
@@ -892,7 +897,10 @@ async fn test_tool_authorization(cx: &mut TestAppContext) {
let tool_call_auth_3 = next_tool_call_authorization(&mut events).await;
tool_call_auth_3
.response
- .send(acp::PermissionOptionId::new("always_allow:tool_requiring_permission").into())
+ .send(acp_thread::SelectedPermissionOutcome::new(
+ acp::PermissionOptionId::new("always_allow:tool_requiring_permission"),
+ acp::PermissionOptionKind::AllowAlways,
+ ))
.unwrap();
cx.run_until_parked();
let completion = fake_model.pending_completions().pop().unwrap();
@@ -3452,7 +3460,6 @@ async fn test_update_plan_tool_updates_thread_events(cx: &mut TestAppContext) {
{
"step": "Inspect the code",
"status": "completed",
- "priority": "high"
},
{
"step": "Implement the tool",
@@ -3461,7 +3468,6 @@ async fn test_update_plan_tool_updates_thread_events(cx: &mut TestAppContext) {
{
"step": "Run tests",
"status": "pending",
- "priority": "low"
}
]
});
@@ -3488,7 +3494,6 @@ async fn test_update_plan_tool_updates_thread_events(cx: &mut TestAppContext) {
{
"step": "Inspect the code",
"status": "completed",
- "priority": "high"
},
{
"step": "Implement the tool",
@@ -3497,7 +3502,6 @@ async fn test_update_plan_tool_updates_thread_events(cx: &mut TestAppContext) {
{
"step": "Run tests",
"status": "pending",
- "priority": "low"
}
]
}))
@@ -3522,7 +3526,7 @@ async fn test_update_plan_tool_updates_thread_events(cx: &mut TestAppContext) {
acp::Plan::new(vec![
acp::PlanEntry::new(
"Inspect the code",
- acp::PlanEntryPriority::High,
+ acp::PlanEntryPriority::Medium,
acp::PlanEntryStatus::Completed,
),
acp::PlanEntry::new(
@@ -3532,7 +3536,7 @@ async fn test_update_plan_tool_updates_thread_events(cx: &mut TestAppContext) {
),
acp::PlanEntry::new(
"Run tests",
- acp::PlanEntryPriority::Low,
+ acp::PlanEntryPriority::Medium,
acp::PlanEntryStatus::Pending,
),
])
@@ -5186,6 +5190,11 @@ async fn test_subagent_thread_inherits_parent_thread_properties(cx: &mut TestApp
subagent_thread.parent_thread_id(),
Some(parent_thread.read(cx).id().clone())
);
+
+ let request = subagent_thread
+ .build_completion_request(CompletionIntent::UserPrompt, cx)
+ .unwrap();
+ assert_eq!(request.intent, Some(CompletionIntent::Subagent));
});
}
diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs
index 39f5a9df902744875a9faaa1651d65842c1dbf11..627fb37b4d2559e5cda573d849fd0df306c1cc7d 100644
--- a/crates/agent/src/thread.rs
+++ b/crates/agent/src/thread.rs
@@ -20,7 +20,6 @@ use anyhow::{Context as _, Result, anyhow};
use chrono::{DateTime, Utc};
use client::UserStore;
use cloud_api_types::Plan;
-use cloud_llm_client::CompletionIntent;
use collections::{HashMap, HashSet, IndexMap};
use fs::Fs;
use futures::stream;
@@ -35,12 +34,12 @@ use gpui::{
};
use heck::ToSnakeCase as _;
use language_model::{
- LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId,
- LanguageModelImage, LanguageModelProviderId, LanguageModelRegistry, LanguageModelRequest,
- LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult,
- LanguageModelToolResultContent, LanguageModelToolSchemaFormat, LanguageModelToolUse,
- LanguageModelToolUseId, Role, SelectedModel, Speed, StopReason, TokenUsage,
- ZED_CLOUD_PROVIDER_ID,
+ CompletionIntent, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
+ LanguageModelId, LanguageModelImage, LanguageModelProviderId, LanguageModelRegistry,
+ LanguageModelRequest, LanguageModelRequestMessage, LanguageModelRequestTool,
+ LanguageModelToolResult, LanguageModelToolResultContent, LanguageModelToolSchemaFormat,
+ LanguageModelToolUse, LanguageModelToolUseId, Role, SelectedModel, Speed, StopReason,
+ TokenUsage, ZED_CLOUD_PROVIDER_ID,
};
use project::Project;
use prompt_store::ProjectContext;
@@ -1805,14 +1804,6 @@ impl Thread {
cx.notify();
}
- #[cfg(feature = "eval")]
- pub fn proceed(
- &mut self,
- cx: &mut Context,
- ) -> Result>> {
- self.run_turn(cx)
- }
-
fn run_turn(
&mut self,
cx: &mut Context,
@@ -2699,6 +2690,13 @@ impl Thread {
completion_intent: CompletionIntent,
cx: &App,
) -> Result {
+ let completion_intent =
+ if self.is_subagent() && completion_intent == CompletionIntent::UserPrompt {
+ CompletionIntent::Subagent
+ } else {
+ completion_intent
+ };
+
let model = self.model().context("No language model configured")?;
let tools = if let Some(turn) = self.running_turn.as_ref() {
turn.tools
diff --git a/crates/agent/src/thread_store.rs b/crates/agent/src/thread_store.rs
index 379ae675d4bbf3c2a9570365493317178f38a804..e62ff78871c65311627aab8f6a6e3c00481a0c2b 100644
--- a/crates/agent/src/thread_store.rs
+++ b/crates/agent/src/thread_store.rs
@@ -113,6 +113,10 @@ impl ThreadStore {
pub fn entries(&self) -> impl Iterator- + '_ {
self.threads.iter().cloned()
}
+
+ pub fn entry_ids(&self) -> impl Iterator
- + '_ {
+ self.threads.iter().map(|t| t.id.clone())
+ }
}
#[cfg(test)]
diff --git a/crates/agent/src/tool_permissions.rs b/crates/agent/src/tool_permissions.rs
index 345511c5025b25601c630c572980d44a23f724e7..73b3ff842ab6961b22815c902ce9ae79e60cd2e3 100644
--- a/crates/agent/src/tool_permissions.rs
+++ b/crates/agent/src/tool_permissions.rs
@@ -571,6 +571,7 @@ mod tests {
enabled: true,
button: true,
dock: DockPosition::Right,
+ flexible: true,
default_width: px(300.),
default_height: px(600.),
default_model: None,
@@ -596,6 +597,8 @@ mod tests {
tool_permissions,
show_turn_stats: false,
new_thread_location: Default::default(),
+ sidebar_side: Default::default(),
+ thinking_display: Default::default(),
}
}
diff --git a/crates/agent/src/tools.rs b/crates/agent/src/tools.rs
index f172fd3fdbe14babb77e53b63dd79aebf50d2603..f3a6ac7ec6d139a2f464ce5ca4229ffdb4564714 100644
--- a/crates/agent/src/tools.rs
+++ b/crates/agent/src/tools.rs
@@ -4,6 +4,8 @@ mod create_directory_tool;
mod delete_path_tool;
mod diagnostics_tool;
mod edit_file_tool;
+#[cfg(all(test, feature = "unit-eval"))]
+mod evals;
mod fetch_tool;
mod find_path_tool;
mod grep_tool;
diff --git a/crates/agent/src/tools/copy_path_tool.rs b/crates/agent/src/tools/copy_path_tool.rs
index 7955a6cc0755514ba4341e43af980e9b93478134..95688f27dcd8ca04aef72358ce52144f95138e17 100644
--- a/crates/agent/src/tools/copy_path_tool.rs
+++ b/crates/agent/src/tools/copy_path_tool.rs
@@ -266,7 +266,10 @@ mod tests {
);
auth.response
- .send(acp::PermissionOptionId::new("allow").into())
+ .send(acp_thread::SelectedPermissionOutcome::new(
+ acp::PermissionOptionId::new("allow"),
+ acp::PermissionOptionKind::AllowOnce,
+ ))
.unwrap();
let result = task.await;
@@ -372,7 +375,10 @@ mod tests {
);
auth.response
- .send(acp::PermissionOptionId::new("allow").into())
+ .send(acp_thread::SelectedPermissionOutcome::new(
+ acp::PermissionOptionId::new("allow"),
+ acp::PermissionOptionKind::AllowOnce,
+ ))
.unwrap();
assert!(
diff --git a/crates/agent/src/tools/create_directory_tool.rs b/crates/agent/src/tools/create_directory_tool.rs
index 7052b5dfdc2c7d546f5e477430d6de1a0039b03d..d6c59bcce30ab26991edba0fa7181ec45d10e1b0 100644
--- a/crates/agent/src/tools/create_directory_tool.rs
+++ b/crates/agent/src/tools/create_directory_tool.rs
@@ -241,7 +241,10 @@ mod tests {
);
auth.response
- .send(acp::PermissionOptionId::new("allow").into())
+ .send(acp_thread::SelectedPermissionOutcome::new(
+ acp::PermissionOptionId::new("allow"),
+ acp::PermissionOptionKind::AllowOnce,
+ ))
.unwrap();
let result = task.await;
@@ -359,7 +362,10 @@ mod tests {
);
auth.response
- .send(acp::PermissionOptionId::new("allow").into())
+ .send(acp_thread::SelectedPermissionOutcome::new(
+ acp::PermissionOptionId::new("allow"),
+ acp::PermissionOptionKind::AllowOnce,
+ ))
.unwrap();
assert!(
diff --git a/crates/agent/src/tools/delete_path_tool.rs b/crates/agent/src/tools/delete_path_tool.rs
index 9b2c0a20b8a26b57ef77bb91004c079265fc80cf..7433975c7b782a145dd3e5a80ee59cd92945a989 100644
--- a/crates/agent/src/tools/delete_path_tool.rs
+++ b/crates/agent/src/tools/delete_path_tool.rs
@@ -301,7 +301,10 @@ mod tests {
);
auth.response
- .send(acp::PermissionOptionId::new("allow").into())
+ .send(acp_thread::SelectedPermissionOutcome::new(
+ acp::PermissionOptionId::new("allow"),
+ acp::PermissionOptionKind::AllowOnce,
+ ))
.unwrap();
let result = task.await;
@@ -428,7 +431,10 @@ mod tests {
);
auth.response
- .send(acp::PermissionOptionId::new("allow").into())
+ .send(acp_thread::SelectedPermissionOutcome::new(
+ acp::PermissionOptionId::new("allow"),
+ acp::PermissionOptionKind::AllowOnce,
+ ))
.unwrap();
assert!(
diff --git a/crates/agent/src/tools/edit_file_tool.rs b/crates/agent/src/tools/edit_file_tool.rs
index 3325a612a0143070a3fc157976be93276f98cb5f..763efd6724a719b90af93843f203ef8c1c3976bb 100644
--- a/crates/agent/src/tools/edit_file_tool.rs
+++ b/crates/agent/src/tools/edit_file_tool.rs
@@ -8,14 +8,13 @@ use crate::{
use acp_thread::Diff;
use agent_client_protocol::{self as acp, ToolCallLocation, ToolCallUpdateFields};
use anyhow::{Context as _, Result};
-use cloud_llm_client::CompletionIntent;
use collections::HashSet;
use futures::{FutureExt as _, StreamExt as _};
use gpui::{App, AppContext, AsyncApp, Entity, Task, WeakEntity};
use indoc::formatdoc;
use language::language_settings::{self, FormatOnSave};
use language::{LanguageRegistry, ToPoint};
-use language_model::LanguageModelToolResultContent;
+use language_model::{CompletionIntent, LanguageModelToolResultContent};
use project::lsp_store::{FormatTrigger, LspFormatTarget};
use project::{Project, ProjectPath};
use schemars::JsonSchema;
@@ -419,17 +418,6 @@ impl AgentTool for EditFileTool {
EditAgentOutputEvent::AmbiguousEditRange(ranges) => ambiguous_ranges = ranges,
EditAgentOutputEvent::ResolvingEditRange(range) => {
diff.update(cx, |card, cx| card.reveal_range(range.clone(), cx));
- // if !emitted_location {
- // let line = buffer.update(cx, |buffer, _cx| {
- // range.start.to_point(&buffer.snapshot()).row
- // }).ok();
- // if let Some(abs_path) = abs_path.clone() {
- // event_stream.update_fields(ToolCallUpdateFields {
- // locations: Some(vec![ToolCallLocation { path: abs_path, line }]),
- // ..Default::default()
- // });
- // }
- // }
}
}
}
@@ -437,11 +425,7 @@ impl AgentTool for EditFileTool {
output.await?;
let format_on_save_enabled = buffer.read_with(cx, |buffer, cx| {
- let settings = language_settings::language_settings(
- buffer.language().map(|l| l.name()),
- buffer.file(),
- cx,
- );
+ let settings = language_settings::LanguageSettings::for_buffer(buffer, cx);
settings.format_on_save != FormatOnSave::Off
});
@@ -1374,7 +1358,10 @@ mod tests {
event
.response
- .send(acp::PermissionOptionId::new("allow").into())
+ .send(acp_thread::SelectedPermissionOutcome::new(
+ acp::PermissionOptionId::new("allow"),
+ acp::PermissionOptionKind::AllowOnce,
+ ))
.unwrap();
authorize_task.await.unwrap();
}
diff --git a/crates/agent/src/tools/evals.rs b/crates/agent/src/tools/evals.rs
new file mode 100644
index 0000000000000000000000000000000000000000..13b8413de6455c9e5b4f719ba079a136ac857b9d
--- /dev/null
+++ b/crates/agent/src/tools/evals.rs
@@ -0,0 +1,2 @@
+#[cfg(all(test, feature = "unit-eval"))]
+mod streaming_edit_file;
diff --git a/crates/agent/src/tools/evals/fixtures/add_overwrite_test/before.rs b/crates/agent/src/tools/evals/fixtures/add_overwrite_test/before.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0d2a0be1fb889a74d0251e1493e6988aaded068e
--- /dev/null
+++ b/crates/agent/src/tools/evals/fixtures/add_overwrite_test/before.rs
@@ -0,0 +1,1572 @@
+use anyhow::{Context as _, Result};
+use buffer_diff::BufferDiff;
+use collections::BTreeMap;
+use futures::{StreamExt, channel::mpsc};
+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};
+use text::{Edit, Patch, Rope};
+use util::RangeExt;
+
+/// Tracks actions performed by tools in a thread
+pub struct ActionLog {
+ /// Buffers that we want to notify the model about when they change.
+ tracked_buffers: BTreeMap, TrackedBuffer>,
+ /// Has the model edited a file since it last checked diagnostics?
+ edited_since_project_diagnostics_check: bool,
+ /// The project this action log is associated with
+ project: Entity,
+}
+
+impl ActionLog {
+ /// Creates a new, empty action log associated with the given project.
+ pub fn new(project: Entity) -> Self {
+ Self {
+ tracked_buffers: BTreeMap::default(),
+ edited_since_project_diagnostics_check: false,
+ project,
+ }
+ }
+
+ pub fn project(&self) -> &Entity {
+ &self.project
+ }
+
+ /// Notifies a diagnostics check
+ pub fn checked_project_diagnostics(&mut self) {
+ self.edited_since_project_diagnostics_check = false;
+ }
+
+ /// Returns true if any files have been edited since the last project diagnostics check
+ pub fn has_edited_files_since_project_diagnostics_check(&self) -> bool {
+ self.edited_since_project_diagnostics_check
+ }
+
+ fn track_buffer_internal(
+ &mut self,
+ buffer: Entity,
+ is_created: bool,
+ cx: &mut Context,
+ ) -> &mut TrackedBuffer {
+ let tracked_buffer = self
+ .tracked_buffers
+ .entry(buffer.clone())
+ .or_insert_with(|| {
+ let open_lsp_handle = self.project.update(cx, |project, cx| {
+ project.register_buffer_with_language_servers(&buffer, cx)
+ });
+
+ let text_snapshot = buffer.read(cx).text_snapshot();
+ let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
+ let (diff_update_tx, diff_update_rx) = mpsc::unbounded();
+ let base_text;
+ let status;
+ let unreviewed_changes;
+ if is_created {
+ base_text = Rope::default();
+ status = TrackedBufferStatus::Created;
+ unreviewed_changes = Patch::new(vec![Edit {
+ old: 0..1,
+ new: 0..text_snapshot.max_point().row + 1,
+ }])
+ } else {
+ base_text = buffer.read(cx).as_rope().clone();
+ status = TrackedBufferStatus::Modified;
+ unreviewed_changes = Patch::default();
+ }
+ TrackedBuffer {
+ buffer: buffer.clone(),
+ base_text,
+ unreviewed_changes,
+ snapshot: text_snapshot.clone(),
+ status,
+ version: buffer.read(cx).version(),
+ diff,
+ diff_update: diff_update_tx,
+ _open_lsp_handle: open_lsp_handle,
+ _maintain_diff: cx.spawn({
+ let buffer = buffer.clone();
+ async move |this, cx| {
+ Self::maintain_diff(this, buffer, diff_update_rx, cx)
+ .await
+ .ok();
+ }
+ }),
+ _subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
+ }
+ });
+ tracked_buffer.version = buffer.read(cx).version();
+ tracked_buffer
+ }
+
+ fn handle_buffer_event(
+ &mut self,
+ buffer: Entity,
+ event: &BufferEvent,
+ cx: &mut Context,
+ ) {
+ match event {
+ BufferEvent::Edited { .. } => self.handle_buffer_edited(buffer, cx),
+ BufferEvent::FileHandleChanged => {
+ self.handle_buffer_file_changed(buffer, cx);
+ }
+ _ => {}
+ };
+ }
+
+ fn handle_buffer_edited(&mut self, buffer: Entity, cx: &mut Context) {
+ let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else {
+ return;
+ };
+ tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx);
+ }
+
+ fn handle_buffer_file_changed(&mut self, buffer: Entity, cx: &mut Context) {
+ let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else {
+ return;
+ };
+
+ match tracked_buffer.status {
+ TrackedBufferStatus::Created | TrackedBufferStatus::Modified => {
+ if buffer
+ .read(cx)
+ .file()
+ .map_or(false, |file| file.disk_state() == DiskState::Deleted)
+ {
+ // If the buffer had been edited by a tool, but it got
+ // deleted externally, we want to stop tracking it.
+ self.tracked_buffers.remove(&buffer);
+ }
+ cx.notify();
+ }
+ TrackedBufferStatus::Deleted => {
+ if buffer
+ .read(cx)
+ .file()
+ .map_or(false, |file| file.disk_state() != DiskState::Deleted)
+ {
+ // If the buffer had been deleted by a tool, but it got
+ // resurrected externally, we want to clear the changes we
+ // were tracking and reset the buffer's state.
+ self.tracked_buffers.remove(&buffer);
+ self.track_buffer_internal(buffer, false, cx);
+ }
+ cx.notify();
+ }
+ }
+ }
+
+ async fn maintain_diff(
+ this: WeakEntity,
+ buffer: Entity,
+ mut diff_update: mpsc::UnboundedReceiver<(ChangeAuthor, text::BufferSnapshot)>,
+ cx: &mut AsyncApp,
+ ) -> Result<()> {
+ while let Some((author, buffer_snapshot)) = diff_update.next().await {
+ let (rebase, diff, language, language_registry) =
+ this.read_with(cx, |this, cx| {
+ let tracked_buffer = this
+ .tracked_buffers
+ .get(&buffer)
+ .context("buffer not tracked")?;
+
+ let rebase = cx.background_spawn({
+ let mut base_text = tracked_buffer.base_text.clone();
+ let old_snapshot = tracked_buffer.snapshot.clone();
+ let new_snapshot = buffer_snapshot.clone();
+ let unreviewed_changes = tracked_buffer.unreviewed_changes.clone();
+ async move {
+ let edits = diff_snapshots(&old_snapshot, &new_snapshot);
+ if let ChangeAuthor::User = author {
+ apply_non_conflicting_edits(
+ &unreviewed_changes,
+ edits,
+ &mut base_text,
+ new_snapshot.as_rope(),
+ );
+ }
+ (Arc::new(base_text.to_string()), base_text)
+ }
+ });
+
+ anyhow::Ok((
+ rebase,
+ tracked_buffer.diff.clone(),
+ tracked_buffer.buffer.read(cx).language().cloned(),
+ tracked_buffer.buffer.read(cx).language_registry(),
+ ))
+ })??;
+
+ let (new_base_text, new_base_text_rope) = rebase.await;
+ let diff_snapshot = BufferDiff::update_diff(
+ diff.clone(),
+ buffer_snapshot.clone(),
+ Some(new_base_text),
+ true,
+ false,
+ language,
+ language_registry,
+ cx,
+ )
+ .await;
+
+ let mut unreviewed_changes = Patch::default();
+ if let Ok(diff_snapshot) = diff_snapshot {
+ unreviewed_changes = cx
+ .background_spawn({
+ let diff_snapshot = diff_snapshot.clone();
+ let buffer_snapshot = buffer_snapshot.clone();
+ let new_base_text_rope = new_base_text_rope.clone();
+ async move {
+ let mut unreviewed_changes = Patch::default();
+ for hunk in diff_snapshot.hunks_intersecting_range(
+ Anchor::MIN..Anchor::MAX,
+ &buffer_snapshot,
+ ) {
+ let old_range = new_base_text_rope
+ .offset_to_point(hunk.diff_base_byte_range.start)
+ ..new_base_text_rope
+ .offset_to_point(hunk.diff_base_byte_range.end);
+ let new_range = hunk.range.start..hunk.range.end;
+ unreviewed_changes.push(point_to_row_edit(
+ Edit {
+ old: old_range,
+ new: new_range,
+ },
+ &new_base_text_rope,
+ &buffer_snapshot.as_rope(),
+ ));
+ }
+ unreviewed_changes
+ }
+ })
+ .await;
+
+ diff.update(cx, |diff, cx| {
+ diff.set_snapshot(diff_snapshot, &buffer_snapshot, cx)
+ })?;
+ }
+ this.update(cx, |this, cx| {
+ let tracked_buffer = this
+ .tracked_buffers
+ .get_mut(&buffer)
+ .context("buffer not tracked")?;
+ tracked_buffer.base_text = new_base_text_rope;
+ tracked_buffer.snapshot = buffer_snapshot;
+ tracked_buffer.unreviewed_changes = unreviewed_changes;
+ cx.notify();
+ anyhow::Ok(())
+ })??;
+ }
+
+ Ok(())
+ }
+
+ /// Track a buffer as read, so we can notify the model about user edits.
+ pub fn buffer_read(&mut self, buffer: Entity, cx: &mut Context) {
+ self.track_buffer_internal(buffer, false, cx);
+ }
+
+ /// Mark a buffer as edited, so we can refresh it in the context
+ pub fn buffer_created(&mut self, buffer: Entity, cx: &mut Context) {
+ self.edited_since_project_diagnostics_check = true;
+ self.tracked_buffers.remove(&buffer);
+ self.track_buffer_internal(buffer.clone(), true, cx);
+ }
+
+ /// Mark a buffer as edited, so we can refresh it in the context
+ pub fn buffer_edited(&mut self, buffer: Entity, cx: &mut Context) {
+ self.edited_since_project_diagnostics_check = true;
+
+ let tracked_buffer = self.track_buffer_internal(buffer.clone(), false, cx);
+ if let TrackedBufferStatus::Deleted = tracked_buffer.status {
+ tracked_buffer.status = TrackedBufferStatus::Modified;
+ }
+ tracked_buffer.schedule_diff_update(ChangeAuthor::Agent, cx);
+ }
+
+ pub fn will_delete_buffer(&mut self, buffer: Entity, cx: &mut Context) {
+ let tracked_buffer = self.track_buffer_internal(buffer.clone(), false, cx);
+ match tracked_buffer.status {
+ TrackedBufferStatus::Created => {
+ self.tracked_buffers.remove(&buffer);
+ cx.notify();
+ }
+ TrackedBufferStatus::Modified => {
+ buffer.update(cx, |buffer, cx| buffer.set_text("", cx));
+ tracked_buffer.status = TrackedBufferStatus::Deleted;
+ tracked_buffer.schedule_diff_update(ChangeAuthor::Agent, cx);
+ }
+ TrackedBufferStatus::Deleted => {}
+ }
+ cx.notify();
+ }
+
+ pub fn keep_edits_in_range(
+ &mut self,
+ buffer: Entity,
+ buffer_range: Range,
+ cx: &mut Context,
+ ) {
+ let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else {
+ return;
+ };
+
+ match tracked_buffer.status {
+ TrackedBufferStatus::Deleted => {
+ self.tracked_buffers.remove(&buffer);
+ cx.notify();
+ }
+ _ => {
+ let buffer = buffer.read(cx);
+ let buffer_range =
+ buffer_range.start.to_point(buffer)..buffer_range.end.to_point(buffer);
+ let mut delta = 0i32;
+
+ tracked_buffer.unreviewed_changes.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;
+
+ if buffer_range.end.row < edit.new.start
+ || buffer_range.start.row > edit.new.end
+ {
+ true
+ } else {
+ let old_range = tracked_buffer
+ .base_text
+ .point_to_offset(Point::new(edit.old.start, 0))
+ ..tracked_buffer.base_text.point_to_offset(cmp::min(
+ Point::new(edit.old.end, 0),
+ tracked_buffer.base_text.max_point(),
+ ));
+ let new_range = tracked_buffer
+ .snapshot
+ .point_to_offset(Point::new(edit.new.start, 0))
+ ..tracked_buffer.snapshot.point_to_offset(cmp::min(
+ Point::new(edit.new.end, 0),
+ tracked_buffer.snapshot.max_point(),
+ ));
+ tracked_buffer.base_text.replace(
+ old_range,
+ &tracked_buffer
+ .snapshot
+ .text_for_range(new_range)
+ .collect::(),
+ );
+ delta += edit.new_len() as i32 - edit.old_len() as i32;
+ false
+ }
+ });
+ tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx);
+ }
+ }
+ }
+
+ pub fn reject_edits_in_ranges(
+ &mut self,
+ buffer: Entity,
+ buffer_ranges: Vec>,
+ cx: &mut Context,
+ ) -> Task> {
+ let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else {
+ return Task::ready(Ok(()));
+ };
+
+ match tracked_buffer.status {
+ TrackedBufferStatus::Created => {
+ let delete = buffer
+ .read(cx)
+ .entry_id(cx)
+ .and_then(|entry_id| {
+ self.project
+ .update(cx, |project, cx| project.delete_entry(entry_id, false, cx))
+ })
+ .unwrap_or(Task::ready(Ok(())));
+ self.tracked_buffers.remove(&buffer);
+ cx.notify();
+ delete
+ }
+ TrackedBufferStatus::Deleted => {
+ buffer.update(cx, |buffer, cx| {
+ buffer.set_text(tracked_buffer.base_text.to_string(), cx)
+ });
+ let save = self
+ .project
+ .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx));
+
+ // Clear all tracked changes for this buffer and start over as if we just read it.
+ self.tracked_buffers.remove(&buffer);
+ self.buffer_read(buffer.clone(), cx);
+ cx.notify();
+ save
+ }
+ TrackedBufferStatus::Modified => {
+ buffer.update(cx, |buffer, cx| {
+ let mut buffer_row_ranges = buffer_ranges
+ .into_iter()
+ .map(|range| {
+ range.start.to_point(buffer).row..range.end.to_point(buffer).row
+ })
+ .peekable();
+
+ let mut edits_to_revert = Vec::new();
+ for edit in tracked_buffer.unreviewed_changes.edits() {
+ let new_range = tracked_buffer
+ .snapshot
+ .anchor_before(Point::new(edit.new.start, 0))
+ ..tracked_buffer.snapshot.anchor_after(cmp::min(
+ Point::new(edit.new.end, 0),
+ tracked_buffer.snapshot.max_point(),
+ ));
+ let new_row_range = new_range.start.to_point(buffer).row
+ ..new_range.end.to_point(buffer).row;
+
+ let mut revert = false;
+ while let Some(buffer_row_range) = buffer_row_ranges.peek() {
+ if buffer_row_range.end < new_row_range.start {
+ buffer_row_ranges.next();
+ } else if buffer_row_range.start > new_row_range.end {
+ break;
+ } else {
+ revert = true;
+ break;
+ }
+ }
+
+ if revert {
+ let old_range = tracked_buffer
+ .base_text
+ .point_to_offset(Point::new(edit.old.start, 0))
+ ..tracked_buffer.base_text.point_to_offset(cmp::min(
+ Point::new(edit.old.end, 0),
+ tracked_buffer.base_text.max_point(),
+ ));
+ let old_text = tracked_buffer
+ .base_text
+ .chunks_in_range(old_range)
+ .collect::();
+ edits_to_revert.push((new_range, old_text));
+ }
+ }
+
+ buffer.edit(edits_to_revert, None, cx);
+ });
+ self.project
+ .update(cx, |project, cx| project.save_buffer(buffer, cx))
+ }
+ }
+ }
+
+ pub fn keep_all_edits(&mut self, cx: &mut Context) {
+ self.tracked_buffers
+ .retain(|_buffer, tracked_buffer| match tracked_buffer.status {
+ TrackedBufferStatus::Deleted => false,
+ _ => {
+ tracked_buffer.unreviewed_changes.clear();
+ tracked_buffer.base_text = tracked_buffer.snapshot.as_rope().clone();
+ tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx);
+ true
+ }
+ });
+ cx.notify();
+ }
+
+ /// Returns the set of buffers that contain changes that haven't been reviewed by the user.
+ pub fn changed_buffers(&self, cx: &App) -> BTreeMap, Entity> {
+ self.tracked_buffers
+ .iter()
+ .filter(|(_, tracked)| tracked.has_changes(cx))
+ .map(|(buffer, tracked)| (buffer.clone(), tracked.diff.clone()))
+ .collect()
+ }
+
+ /// Iterate over buffers changed since last read or edited by the model
+ pub fn stale_buffers<'a>(&'a self, cx: &'a App) -> impl Iterator
- > {
+ self.tracked_buffers
+ .iter()
+ .filter(|(buffer, tracked)| {
+ let buffer = buffer.read(cx);
+
+ tracked.version != buffer.version
+ && buffer
+ .file()
+ .map_or(false, |file| file.disk_state() != DiskState::Deleted)
+ })
+ .map(|(buffer, _)| buffer)
+ }
+}
+
+fn apply_non_conflicting_edits(
+ patch: &Patch,
+ edits: Vec>,
+ old_text: &mut Rope,
+ new_text: &Rope,
+) {
+ let mut old_edits = patch.edits().iter().cloned().peekable();
+ let mut new_edits = edits.into_iter().peekable();
+ let mut applied_delta = 0i32;
+ let mut rebased_delta = 0i32;
+
+ while let Some(mut new_edit) = new_edits.next() {
+ let mut conflict = false;
+
+ // Push all the old edits that are before this new edit or that intersect with it.
+ while let Some(old_edit) = old_edits.peek() {
+ if new_edit.old.end < old_edit.new.start
+ || (!old_edit.new.is_empty() && new_edit.old.end == old_edit.new.start)
+ {
+ break;
+ } else if new_edit.old.start > old_edit.new.end
+ || (!old_edit.new.is_empty() && new_edit.old.start == old_edit.new.end)
+ {
+ let old_edit = old_edits.next().unwrap();
+ rebased_delta += old_edit.new_len() as i32 - old_edit.old_len() as i32;
+ } else {
+ conflict = true;
+ if new_edits
+ .peek()
+ .map_or(false, |next_edit| next_edit.old.overlaps(&old_edit.new))
+ {
+ new_edit = new_edits.next().unwrap();
+ } else {
+ let old_edit = old_edits.next().unwrap();
+ rebased_delta += old_edit.new_len() as i32 - old_edit.old_len() as i32;
+ }
+ }
+ }
+
+ if !conflict {
+ // This edit doesn't intersect with any old edit, so we can apply it to the old text.
+ new_edit.old.start = (new_edit.old.start as i32 + applied_delta - rebased_delta) as u32;
+ new_edit.old.end = (new_edit.old.end as i32 + applied_delta - rebased_delta) as u32;
+ let old_bytes = old_text.point_to_offset(Point::new(new_edit.old.start, 0))
+ ..old_text.point_to_offset(cmp::min(
+ Point::new(new_edit.old.end, 0),
+ old_text.max_point(),
+ ));
+ let new_bytes = new_text.point_to_offset(Point::new(new_edit.new.start, 0))
+ ..new_text.point_to_offset(cmp::min(
+ Point::new(new_edit.new.end, 0),
+ new_text.max_point(),
+ ));
+
+ old_text.replace(
+ old_bytes,
+ &new_text.chunks_in_range(new_bytes).collect::(),
+ );
+ applied_delta += new_edit.new_len() as i32 - new_edit.old_len() as i32;
+ }
+ }
+}
+
+fn diff_snapshots(
+ old_snapshot: &text::BufferSnapshot,
+ new_snapshot: &text::BufferSnapshot,
+) -> Vec> {
+ let mut edits = new_snapshot
+ .edits_since::(&old_snapshot.version)
+ .map(|edit| point_to_row_edit(edit, old_snapshot.as_rope(), new_snapshot.as_rope()))
+ .peekable();
+ let mut row_edits = Vec::new();
+ while let Some(mut edit) = edits.next() {
+ while let Some(next_edit) = edits.peek() {
+ if edit.old.end >= next_edit.old.start {
+ edit.old.end = next_edit.old.end;
+ edit.new.end = next_edit.new.end;
+ edits.next();
+ } else {
+ break;
+ }
+ }
+ row_edits.push(edit);
+ }
+ row_edits
+}
+
+fn point_to_row_edit(edit: Edit, old_text: &Rope, new_text: &Rope) -> Edit {
+ if edit.old.start.column == old_text.line_len(edit.old.start.row)
+ && new_text
+ .chars_at(new_text.point_to_offset(edit.new.start))
+ .next()
+ == Some('\n')
+ && edit.old.start != old_text.max_point()
+ {
+ Edit {
+ old: edit.old.start.row + 1..edit.old.end.row + 1,
+ new: edit.new.start.row + 1..edit.new.end.row + 1,
+ }
+ } else if edit.old.start.column == 0
+ && edit.old.end.column == 0
+ && edit.new.end.column == 0
+ && edit.old.end != old_text.max_point()
+ {
+ Edit {
+ old: edit.old.start.row..edit.old.end.row,
+ new: edit.new.start.row..edit.new.end.row,
+ }
+ } else {
+ Edit {
+ old: edit.old.start.row..edit.old.end.row + 1,
+ new: edit.new.start.row..edit.new.end.row + 1,
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug)]
+enum ChangeAuthor {
+ User,
+ Agent,
+}
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+enum TrackedBufferStatus {
+ Created,
+ Modified,
+ Deleted,
+}
+
+struct TrackedBuffer {
+ buffer: Entity,
+ base_text: Rope,
+ unreviewed_changes: Patch,
+ status: TrackedBufferStatus,
+ version: clock::Global,
+ diff: Entity,
+ snapshot: text::BufferSnapshot,
+ diff_update: mpsc::UnboundedSender<(ChangeAuthor, text::BufferSnapshot)>,
+ _open_lsp_handle: OpenLspBufferHandle,
+ _maintain_diff: Task<()>,
+ _subscription: Subscription,
+}
+
+impl TrackedBuffer {
+ fn has_changes(&self, cx: &App) -> bool {
+ self.diff
+ .read(cx)
+ .hunks(&self.buffer.read(cx), cx)
+ .next()
+ .is_some()
+ }
+
+ fn schedule_diff_update(&self, author: ChangeAuthor, cx: &App) {
+ self.diff_update
+ .unbounded_send((author, self.buffer.read(cx).text_snapshot()))
+ .ok();
+ }
+}
+
+pub struct ChangedBuffer {
+ pub diff: Entity,
+}
+
+#[cfg(test)]
+mod tests {
+ use std::env;
+
+ use super::*;
+ use buffer_diff::DiffHunkStatusKind;
+ use gpui::TestAppContext;
+ use language::Point;
+ use project::{FakeFs, Fs, Project, RemoveOptions};
+ use rand::prelude::*;
+ use serde_json::json;
+ use settings::SettingsStore;
+ use util::{RandomCharIter, path};
+
+ #[ctor::ctor]
+ fn init_logger() {
+ zlog::init_test();
+ }
+
+ fn init_test(cx: &mut TestAppContext) {
+ cx.update(|cx| {
+ let settings_store = SettingsStore::test(cx);
+ cx.set_global(settings_store);
+ language::init(cx);
+ Project::init_settings(cx);
+ });
+ }
+
+ #[gpui::test(iterations = 10)]
+ async fn test_keep_edits(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(path!("/dir"), json!({"file": "abc\ndef\nghi\njkl\nmno"}))
+ .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();
+
+ 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, 1)..Point::new(1, 2), "E")], None, cx)
+ .unwrap()
+ });
+ buffer.update(cx, |buffer, cx| {
+ buffer
+ .edit([(Point::new(4, 2)..Point::new(4, 3), "O")], 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()),
+ "abc\ndEf\nghi\njkl\nmnO"
+ );
+ assert_eq!(
+ unreviewed_hunks(&action_log, cx),
+ vec![(
+ buffer.clone(),
+ vec![
+ HunkStatus {
+ range: Point::new(1, 0)..Point::new(2, 0),
+ diff_status: DiffHunkStatusKind::Modified,
+ old_text: "def\n".into(),
+ },
+ HunkStatus {
+ range: Point::new(4, 0)..Point::new(4, 3),
+ diff_status: DiffHunkStatusKind::Modified,
+ old_text: "mno".into(),
+ }
+ ],
+ )]
+ );
+
+ action_log.update(cx, |log, cx| {
+ log.keep_edits_in_range(buffer.clone(), Point::new(3, 0)..Point::new(4, 3), cx)
+ });
+ cx.run_until_parked();
+ assert_eq!(
+ unreviewed_hunks(&action_log, cx),
+ vec![(
+ buffer.clone(),
+ vec![HunkStatus {
+ range: Point::new(1, 0)..Point::new(2, 0),
+ diff_status: DiffHunkStatusKind::Modified,
+ old_text: "def\n".into(),
+ }],
+ )]
+ );
+
+ action_log.update(cx, |log, 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![]);
+ }
+
+ #[gpui::test(iterations = 10)]
+ async fn test_deletions(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ path!("/dir"),
+ json!({"file": "abc\ndef\nghi\njkl\nmno\npqr"}),
+ )
+ .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();
+
+ 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, 0)..Point::new(2, 0), "")], None, cx)
+ .unwrap();
+ buffer.finalize_last_transaction();
+ });
+ buffer.update(cx, |buffer, cx| {
+ buffer
+ .edit([(Point::new(3, 0)..Point::new(4, 0), "")], None, cx)
+ .unwrap();
+ buffer.finalize_last_transaction();
+ });
+ 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()),
+ "abc\nghi\njkl\npqr"
+ );
+ assert_eq!(
+ unreviewed_hunks(&action_log, cx),
+ vec![(
+ buffer.clone(),
+ vec![
+ HunkStatus {
+ range: Point::new(1, 0)..Point::new(1, 0),
+ diff_status: DiffHunkStatusKind::Deleted,
+ old_text: "def\n".into(),
+ },
+ HunkStatus {
+ range: Point::new(3, 0)..Point::new(3, 0),
+ diff_status: DiffHunkStatusKind::Deleted,
+ old_text: "mno\n".into(),
+ }
+ ],
+ )]
+ );
+
+ buffer.update(cx, |buffer, cx| buffer.undo(cx));
+ cx.run_until_parked();
+ assert_eq!(
+ buffer.read_with(cx, |buffer, _| buffer.text()),
+ "abc\nghi\njkl\nmno\npqr"
+ );
+ assert_eq!(
+ unreviewed_hunks(&action_log, cx),
+ vec![(
+ buffer.clone(),
+ vec![HunkStatus {
+ range: Point::new(1, 0)..Point::new(1, 0),
+ diff_status: DiffHunkStatusKind::Deleted,
+ old_text: "def\n".into(),
+ }],
+ )]
+ );
+
+ action_log.update(cx, |log, 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![]);
+ }
+
+ #[gpui::test(iterations = 10)]
+ async fn test_overlapping_user_edits(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(path!("/dir"), json!({"file": "abc\ndef\nghi\njkl\nmno"}))
+ .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();
+
+ 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()),
+ "abc\ndeF\nGHI\njkl\nmno"
+ );
+ 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(),
+ }],
+ )]
+ );
+
+ 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()),
+ "abXc\ndeF\nGHI\nYjkl\nmno"
+ );
+ 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(),
+ }],
+ )]
+ );
+
+ buffer.update(cx, |buffer, cx| {
+ buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "Z")], None, cx)
+ });
+ cx.run_until_parked();
+ assert_eq!(
+ buffer.read_with(cx, |buffer, _| buffer.text()),
+ "abXc\ndZeF\nGHI\nYjkl\nmno"
+ );
+ 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(),
+ }],
+ )]
+ );
+
+ 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![]);
+ }
+
+ #[gpui::test(iterations = 10)]
+ async fn test_creating_files(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(path!("/dir"), json!({})).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/file1", cx))
+ .unwrap();
+
+ let buffer = project
+ .update(cx, |project, cx| project.open_buffer(file_path, cx))
+ .await
+ .unwrap();
+ cx.update(|cx| {
+ action_log.update(cx, |log, cx| log.buffer_created(buffer.clone(), cx));
+ buffer.update(cx, |buffer, cx| buffer.set_text("lorem", cx));
+ action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
+ });
+ project
+ .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
+ .await
+ .unwrap();
+ cx.run_until_parked();
+ assert_eq!(
+ unreviewed_hunks(&action_log, cx),
+ vec![(
+ buffer.clone(),
+ vec![HunkStatus {
+ range: Point::new(0, 0)..Point::new(0, 5),
+ diff_status: DiffHunkStatusKind::Added,
+ old_text: "".into(),
+ }],
+ )]
+ );
+
+ buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "X")], None, cx));
+ cx.run_until_parked();
+ assert_eq!(
+ unreviewed_hunks(&action_log, cx),
+ vec![(
+ buffer.clone(),
+ vec![HunkStatus {
+ range: Point::new(0, 0)..Point::new(0, 6),
+ diff_status: DiffHunkStatusKind::Added,
+ old_text: "".into(),
+ }],
+ )]
+ );
+
+ action_log.update(cx, |log, cx| {
+ log.keep_edits_in_range(buffer.clone(), 0..5, cx)
+ });
+ cx.run_until_parked();
+ assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
+ }
+
+ #[gpui::test(iterations = 10)]
+ async fn test_deleting_files(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ path!("/dir"),
+ json!({"file1": "lorem\n", "file2": "ipsum\n"}),
+ )
+ .await;
+
+ let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
+ let file1_path = project
+ .read_with(cx, |project, cx| project.find_project_path("dir/file1", cx))
+ .unwrap();
+ let file2_path = project
+ .read_with(cx, |project, cx| project.find_project_path("dir/file2", cx))
+ .unwrap();
+
+ let action_log = cx.new(|_| ActionLog::new(project.clone()));
+ let buffer1 = project
+ .update(cx, |project, cx| {
+ project.open_buffer(file1_path.clone(), cx)
+ })
+ .await
+ .unwrap();
+ let buffer2 = project
+ .update(cx, |project, cx| {
+ project.open_buffer(file2_path.clone(), cx)
+ })
+ .await
+ .unwrap();
+
+ action_log.update(cx, |log, cx| log.will_delete_buffer(buffer1.clone(), cx));
+ action_log.update(cx, |log, cx| log.will_delete_buffer(buffer2.clone(), cx));
+ project
+ .update(cx, |project, cx| {
+ project.delete_file(file1_path.clone(), false, cx)
+ })
+ .unwrap()
+ .await
+ .unwrap();
+ project
+ .update(cx, |project, cx| {
+ project.delete_file(file2_path.clone(), false, cx)
+ })
+ .unwrap()
+ .await
+ .unwrap();
+ cx.run_until_parked();
+ assert_eq!(
+ unreviewed_hunks(&action_log, cx),
+ vec![
+ (
+ buffer1.clone(),
+ vec![HunkStatus {
+ range: Point::new(0, 0)..Point::new(0, 0),
+ diff_status: DiffHunkStatusKind::Deleted,
+ old_text: "lorem\n".into(),
+ }]
+ ),
+ (
+ buffer2.clone(),
+ vec![HunkStatus {
+ range: Point::new(0, 0)..Point::new(0, 0),
+ diff_status: DiffHunkStatusKind::Deleted,
+ old_text: "ipsum\n".into(),
+ }],
+ )
+ ]
+ );
+
+ // Simulate file1 being recreated externally.
+ fs.insert_file(path!("/dir/file1"), "LOREM".as_bytes().to_vec())
+ .await;
+
+ // Simulate file2 being recreated by a tool.
+ let buffer2 = project
+ .update(cx, |project, cx| project.open_buffer(file2_path, cx))
+ .await
+ .unwrap();
+ action_log.update(cx, |log, cx| log.buffer_read(buffer2.clone(), cx));
+ buffer2.update(cx, |buffer, cx| buffer.set_text("IPSUM", cx));
+ action_log.update(cx, |log, cx| log.buffer_edited(buffer2.clone(), cx));
+ project
+ .update(cx, |project, cx| project.save_buffer(buffer2.clone(), cx))
+ .await
+ .unwrap();
+
+ cx.run_until_parked();
+ assert_eq!(
+ unreviewed_hunks(&action_log, cx),
+ vec![(
+ buffer2.clone(),
+ vec![HunkStatus {
+ range: Point::new(0, 0)..Point::new(0, 5),
+ diff_status: DiffHunkStatusKind::Modified,
+ old_text: "ipsum\n".into(),
+ }],
+ )]
+ );
+
+ // Simulate file2 being deleted externally.
+ fs.remove_file(path!("/dir/file2").as_ref(), RemoveOptions::default())
+ .await
+ .unwrap();
+ cx.run_until_parked();
+ assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
+ }
+
+ #[gpui::test(iterations = 10)]
+ async fn test_reject_edits(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(path!("/dir"), json!({"file": "abc\ndef\nghi\njkl\nmno"}))
+ .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();
+
+ 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, 1)..Point::new(1, 2), "E\nXYZ")], None, cx)
+ .unwrap()
+ });
+ buffer.update(cx, |buffer, cx| {
+ buffer
+ .edit([(Point::new(5, 2)..Point::new(5, 3), "O")], 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()),
+ "abc\ndE\nXYZf\nghi\njkl\nmnO"
+ );
+ 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\n".into(),
+ },
+ HunkStatus {
+ range: Point::new(5, 0)..Point::new(5, 3),
+ diff_status: DiffHunkStatusKind::Modified,
+ old_text: "mno".into(),
+ }
+ ],
+ )]
+ );
+
+ // If the rejected range doesn't overlap with any hunk, we ignore it.
+ action_log
+ .update(cx, |log, cx| {
+ log.reject_edits_in_ranges(
+ buffer.clone(),
+ vec![Point::new(4, 0)..Point::new(4, 0)],
+ cx,
+ )
+ })
+ .await
+ .unwrap();
+ cx.run_until_parked();
+ assert_eq!(
+ buffer.read_with(cx, |buffer, _| buffer.text()),
+ "abc\ndE\nXYZf\nghi\njkl\nmnO"
+ );
+ 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\n".into(),
+ },
+ HunkStatus {
+ range: Point::new(5, 0)..Point::new(5, 3),
+ diff_status: DiffHunkStatusKind::Modified,
+ old_text: "mno".into(),
+ }
+ ],
+ )]
+ );
+
+ action_log
+ .update(cx, |log, cx| {
+ log.reject_edits_in_ranges(
+ buffer.clone(),
+ vec![Point::new(0, 0)..Point::new(1, 0)],
+ cx,
+ )
+ })
+ .await
+ .unwrap();
+ cx.run_until_parked();
+ assert_eq!(
+ buffer.read_with(cx, |buffer, _| buffer.text()),
+ "abc\ndef\nghi\njkl\nmnO"
+ );
+ assert_eq!(
+ unreviewed_hunks(&action_log, cx),
+ vec![(
+ buffer.clone(),
+ vec![HunkStatus {
+ range: Point::new(4, 0)..Point::new(4, 3),
+ diff_status: DiffHunkStatusKind::Modified,
+ old_text: "mno".into(),
+ }],
+ )]
+ );
+
+ action_log
+ .update(cx, |log, cx| {
+ log.reject_edits_in_ranges(
+ buffer.clone(),
+ vec![Point::new(4, 0)..Point::new(4, 0)],
+ cx,
+ )
+ })
+ .await
+ .unwrap();
+ cx.run_until_parked();
+ assert_eq!(
+ buffer.read_with(cx, |buffer, _| buffer.text()),
+ "abc\ndef\nghi\njkl\nmno"
+ );
+ assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
+ }
+
+ #[gpui::test(iterations = 10)]
+ async fn test_reject_multiple_edits(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(path!("/dir"), json!({"file": "abc\ndef\nghi\njkl\nmno"}))
+ .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();
+
+ 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, 1)..Point::new(1, 2), "E\nXYZ")], None, cx)
+ .unwrap()
+ });
+ buffer.update(cx, |buffer, cx| {
+ buffer
+ .edit([(Point::new(5, 2)..Point::new(5, 3), "O")], 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()),
+ "abc\ndE\nXYZf\nghi\njkl\nmnO"
+ );
+ 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\n".into(),
+ },
+ HunkStatus {
+ range: Point::new(5, 0)..Point::new(5, 3),
+ diff_status: DiffHunkStatusKind::Modified,
+ old_text: "mno".into(),
+ }
+ ],
+ )]
+ );
+
+ action_log.update(cx, |log, cx| {
+ let range_1 = buffer.read(cx).anchor_before(Point::new(0, 0))
+ ..buffer.read(cx).anchor_before(Point::new(1, 0));
+ 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], cx)
+ .detach();
+ assert_eq!(
+ buffer.read_with(cx, |buffer, _| buffer.text()),
+ "abc\ndef\nghi\njkl\nmno"
+ );
+ });
+ cx.run_until_parked();
+ assert_eq!(
+ buffer.read_with(cx, |buffer, _| buffer.text()),
+ "abc\ndef\nghi\njkl\nmno"
+ );
+ assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
+ }
+
+ #[gpui::test(iterations = 10)]
+ async fn test_reject_deleted_file(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(path!("/dir"), json!({"file": "content"}))
+ .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.clone(), cx))
+ .await
+ .unwrap();
+
+ cx.update(|cx| {
+ action_log.update(cx, |log, cx| log.will_delete_buffer(buffer.clone(), cx));
+ });
+ project
+ .update(cx, |project, cx| {
+ project.delete_file(file_path.clone(), false, cx)
+ })
+ .unwrap()
+ .await
+ .unwrap();
+ cx.run_until_parked();
+ assert!(!fs.is_file(path!("/dir/file").as_ref()).await);
+ assert_eq!(
+ unreviewed_hunks(&action_log, cx),
+ vec![(
+ buffer.clone(),
+ vec![HunkStatus {
+ range: Point::new(0, 0)..Point::new(0, 0),
+ diff_status: DiffHunkStatusKind::Deleted,
+ old_text: "content".into(),
+ }]
+ )]
+ );
+
+ action_log
+ .update(cx, |log, cx| {
+ log.reject_edits_in_ranges(
+ buffer.clone(),
+ vec![Point::new(0, 0)..Point::new(0, 0)],
+ cx,
+ )
+ })
+ .await
+ .unwrap();
+ cx.run_until_parked();
+ assert_eq!(buffer.read_with(cx, |buffer, _| buffer.text()), "content");
+ assert!(fs.is_file(path!("/dir/file").as_ref()).await);
+ assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
+ }
+
+ #[gpui::test(iterations = 10)]
+ async fn test_reject_created_file(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ 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/new_file", cx)
+ })
+ .unwrap();
+
+ let buffer = project
+ .update(cx, |project, cx| project.open_buffer(file_path, cx))
+ .await
+ .unwrap();
+ cx.update(|cx| {
+ action_log.update(cx, |log, cx| log.buffer_created(buffer.clone(), cx));
+ buffer.update(cx, |buffer, cx| buffer.set_text("content", cx));
+ action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
+ });
+ project
+ .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
+ .await
+ .unwrap();
+ assert!(fs.is_file(path!("/dir/new_file").as_ref()).await);
+ cx.run_until_parked();
+ assert_eq!(
+ unreviewed_hunks(&action_log, cx),
+ vec![(
+ buffer.clone(),
+ vec![HunkStatus {
+ range: Point::new(0, 0)..Point::new(0, 7),
+ diff_status: DiffHunkStatusKind::Added,
+ old_text: "".into(),
+ }],
+ )]
+ );
+
+ action_log
+ .update(cx, |log, cx| {
+ log.reject_edits_in_ranges(
+ buffer.clone(),
+ vec![Point::new(0, 0)..Point::new(0, 11)],
+ cx,
+ )
+ })
+ .await
+ .unwrap();
+ cx.run_until_parked();
+ assert!(!fs.is_file(path!("/dir/new_file").as_ref()).await);
+ assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
+ }
+
+ #[gpui::test(iterations = 100)]
+ async fn test_random_diffs(mut rng: StdRng, cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let operations = env::var("OPERATIONS")
+ .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
+ .unwrap_or(20);
+
+ let text = RandomCharIter::new(&mut rng).take(50).collect::();
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(path!("/dir"), json!({"file": text})).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();
+
+ action_log.update(cx, |log, cx| log.buffer_read(buffer.clone(), cx));
+
+ for _ in 0..operations {
+ match rng.gen_range(0..100) {
+ 0..25 => {
+ 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, cx)
+ });
+ }
+ 25..50 => {
+ action_log
+ .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], cx)
+ })
+ .await
+ .unwrap();
+ }
+ _ => {
+ let is_agent_change = rng.gen_bool(0.5);
+ if is_agent_change {
+ log::info!("agent edit");
+ } else {
+ log::info!("user edit");
+ }
+ cx.update(|cx| {
+ buffer.update(cx, |buffer, cx| buffer.randomly_edit(&mut rng, 1, cx));
+ if is_agent_change {
+ action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
+ }
+ });
+ }
+ }
+
+ if rng.gen_bool(0.2) {
+ quiesce(&action_log, &buffer, cx);
+ }
+ }
+
+ quiesce(&action_log, &buffer, cx);
+
+ fn quiesce(
+ action_log: &Entity,
+ buffer: &Entity,
+ cx: &mut TestAppContext,
+ ) {
+ log::info!("quiescing...");
+ cx.run_until_parked();
+ action_log.update(cx, |log, cx| {
+ let tracked_buffer = log.tracked_buffers.get(&buffer).unwrap();
+ let mut old_text = tracked_buffer.base_text.clone();
+ let new_text = buffer.read(cx).as_rope();
+ for edit in tracked_buffer.unreviewed_changes.edits() {
+ let old_start = old_text.point_to_offset(Point::new(edit.new.start, 0));
+ let old_end = old_text.point_to_offset(cmp::min(
+ Point::new(edit.new.start + edit.old_len(), 0),
+ old_text.max_point(),
+ ));
+ old_text.replace(
+ old_start..old_end,
+ &new_text.slice_rows(edit.new.clone()).to_string(),
+ );
+ }
+ pretty_assertions::assert_eq!(old_text.to_string(), new_text.to_string());
+ })
+ }
+ }
+
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ struct HunkStatus {
+ range: Range,
+ diff_status: DiffHunkStatusKind,
+ old_text: String,
+ }
+
+ fn unreviewed_hunks(
+ action_log: &Entity,
+ cx: &TestAppContext,
+ ) -> Vec<(Entity, Vec)> {
+ cx.read(|cx| {
+ action_log
+ .read(cx)
+ .changed_buffers(cx)
+ .into_iter()
+ .map(|(buffer, diff)| {
+ let snapshot = buffer.read(cx).snapshot();
+ (
+ buffer,
+ diff.read(cx)
+ .hunks(&snapshot, cx)
+ .map(|hunk| HunkStatus {
+ diff_status: hunk.status().kind,
+ range: hunk.range,
+ old_text: diff
+ .read(cx)
+ .base_text()
+ .text_for_range(hunk.diff_base_byte_range)
+ .collect(),
+ })
+ .collect(),
+ )
+ })
+ .collect()
+ })
+ }
+}
diff --git a/crates/agent/src/tools/evals/fixtures/delete_run_git_blame/after.rs b/crates/agent/src/tools/evals/fixtures/delete_run_git_blame/after.rs
new file mode 100644
index 0000000000000000000000000000000000000000..89277be4436bf000f4b061d8b89fef5f489f9fea
--- /dev/null
+++ b/crates/agent/src/tools/evals/fixtures/delete_run_git_blame/after.rs
@@ -0,0 +1,328 @@
+use crate::commit::get_messages;
+use crate::{GitRemote, Oid};
+use anyhow::{Context as _, Result, anyhow};
+use collections::{HashMap, HashSet};
+use futures::AsyncWriteExt;
+use gpui::SharedString;
+use serde::{Deserialize, Serialize};
+use std::process::Stdio;
+use std::{ops::Range, path::Path};
+use text::Rope;
+use time::OffsetDateTime;
+use time::UtcOffset;
+use time::macros::format_description;
+
+pub use git2 as libgit;
+
+#[derive(Debug, Clone, Default)]
+pub struct Blame {
+ pub entries: Vec,
+ pub messages: HashMap,
+ pub remote_url: Option,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct ParsedCommitMessage {
+ pub message: SharedString,
+ pub permalink: Option,
+ pub pull_request: Option,
+ pub remote: Option,
+}
+
+impl Blame {
+ pub async fn for_path(
+ git_binary: &Path,
+ working_directory: &Path,
+ path: &Path,
+ content: &Rope,
+ remote_url: Option,
+ ) -> Result {
+ let output = run_git_blame(git_binary, working_directory, path, content).await?;
+ let mut entries = parse_git_blame(&output)?;
+ entries.sort_unstable_by(|a, b| a.range.start.cmp(&b.range.start));
+
+ let mut unique_shas = HashSet::default();
+
+ for entry in entries.iter_mut() {
+ unique_shas.insert(entry.sha);
+ }
+
+ let shas = unique_shas.into_iter().collect::>();
+ let messages = get_messages(working_directory, &shas)
+ .await
+ .context("failed to get commit messages")?;
+
+ Ok(Self {
+ entries,
+ messages,
+ remote_url,
+ })
+ }
+}
+
+const GIT_BLAME_NO_COMMIT_ERROR: &str = "fatal: no such ref: HEAD";
+const GIT_BLAME_NO_PATH: &str = "fatal: no such path";
+
+#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
+pub struct BlameEntry {
+ pub sha: Oid,
+
+ pub range: Range,
+
+ pub original_line_number: u32,
+
+ pub author: Option,
+ pub author_mail: Option,
+ pub author_time: Option,
+ pub author_tz: Option,
+
+ pub committer_name: Option,
+ pub committer_email: Option,
+ pub committer_time: Option,
+ pub committer_tz: Option,
+
+ pub summary: Option,
+
+ pub previous: Option,
+ pub filename: String,
+}
+
+impl BlameEntry {
+ // Returns a BlameEntry by parsing the first line of a `git blame --incremental`
+ // entry. The line MUST have this format:
+ //
+ // <40-byte-hex-sha1>
+ fn new_from_blame_line(line: &str) -> Result {
+ let mut parts = line.split_whitespace();
+
+ let sha = parts
+ .next()
+ .and_then(|line| line.parse::().ok())
+ .with_context(|| format!("parsing sha from {line}"))?;
+
+ let original_line_number = parts
+ .next()
+ .and_then(|line| line.parse::().ok())
+ .with_context(|| format!("parsing original line number from {line}"))?;
+ let final_line_number = parts
+ .next()
+ .and_then(|line| line.parse::().ok())
+ .with_context(|| format!("parsing final line number from {line}"))?;
+
+ let line_count = parts
+ .next()
+ .and_then(|line| line.parse::().ok())
+ .with_context(|| format!("parsing line count from {line}"))?;
+
+ let start_line = final_line_number.saturating_sub(1);
+ let end_line = start_line + line_count;
+ let range = start_line..end_line;
+
+ Ok(Self {
+ sha,
+ range,
+ original_line_number,
+ ..Default::default()
+ })
+ }
+
+ pub fn author_offset_date_time(&self) -> Result {
+ if let (Some(author_time), Some(author_tz)) = (self.author_time, &self.author_tz) {
+ let format = format_description!("[offset_hour][offset_minute]");
+ let offset = UtcOffset::parse(author_tz, &format)?;
+ let date_time_utc = OffsetDateTime::from_unix_timestamp(author_time)?;
+
+ Ok(date_time_utc.to_offset(offset))
+ } else {
+ // Directly return current time in UTC if there's no committer time or timezone
+ Ok(time::OffsetDateTime::now_utc())
+ }
+ }
+}
+
+// parse_git_blame parses the output of `git blame --incremental`, which returns
+// all the blame-entries for a given path incrementally, as it finds them.
+//
+// Each entry *always* starts with:
+//
+// <40-byte-hex-sha1>
+//
+// Each entry *always* ends with:
+//
+// filename
+//
+// Line numbers are 1-indexed.
+//
+// A `git blame --incremental` entry looks like this:
+//
+// 6ad46b5257ba16d12c5ca9f0d4900320959df7f4 2 2 1
+// author Joe Schmoe
+// author-mail
+// author-time 1709741400
+// author-tz +0100
+// committer Joe Schmoe
+// committer-mail
+// committer-time 1709741400
+// committer-tz +0100
+// summary Joe's cool commit
+// previous 486c2409237a2c627230589e567024a96751d475 index.js
+// filename index.js
+//
+// If the entry has the same SHA as an entry that was already printed then no
+// signature information is printed:
+//
+// 6ad46b5257ba16d12c5ca9f0d4900320959df7f4 3 4 1
+// previous 486c2409237a2c627230589e567024a96751d475 index.js
+// filename index.js
+//
+// More about `--incremental` output: https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-blame.html
+fn parse_git_blame(output: &str) -> Result> {
+ let mut entries: Vec = Vec::new();
+ let mut index: HashMap = HashMap::default();
+
+ let mut current_entry: Option = None;
+
+ for line in output.lines() {
+ let mut done = false;
+
+ match &mut current_entry {
+ None => {
+ let mut new_entry = BlameEntry::new_from_blame_line(line)?;
+
+ if let Some(existing_entry) = index
+ .get(&new_entry.sha)
+ .and_then(|slot| entries.get(*slot))
+ {
+ new_entry.author.clone_from(&existing_entry.author);
+ new_entry
+ .author_mail
+ .clone_from(&existing_entry.author_mail);
+ new_entry.author_time = existing_entry.author_time;
+ new_entry.author_tz.clone_from(&existing_entry.author_tz);
+ new_entry
+ .committer_name
+ .clone_from(&existing_entry.committer_name);
+ new_entry
+ .committer_email
+ .clone_from(&existing_entry.committer_email);
+ new_entry.committer_time = existing_entry.committer_time;
+ new_entry
+ .committer_tz
+ .clone_from(&existing_entry.committer_tz);
+ new_entry.summary.clone_from(&existing_entry.summary);
+ }
+
+ current_entry.replace(new_entry);
+ }
+ Some(entry) => {
+ let Some((key, value)) = line.split_once(' ') else {
+ continue;
+ };
+ let is_committed = !entry.sha.is_zero();
+ match key {
+ "filename" => {
+ entry.filename = value.into();
+ done = true;
+ }
+ "previous" => entry.previous = Some(value.into()),
+
+ "summary" if is_committed => entry.summary = Some(value.into()),
+ "author" if is_committed => entry.author = Some(value.into()),
+ "author-mail" if is_committed => entry.author_mail = Some(value.into()),
+ "author-time" if is_committed => {
+ entry.author_time = Some(value.parse::()?)
+ }
+ "author-tz" if is_committed => entry.author_tz = Some(value.into()),
+
+ "committer" if is_committed => entry.committer_name = Some(value.into()),
+ "committer-mail" if is_committed => entry.committer_email = Some(value.into()),
+ "committer-time" if is_committed => {
+ entry.committer_time = Some(value.parse::()?)
+ }
+ "committer-tz" if is_committed => entry.committer_tz = Some(value.into()),
+ _ => {}
+ }
+ }
+ };
+
+ if done {
+ if let Some(entry) = current_entry.take() {
+ index.insert(entry.sha, entries.len());
+
+ // We only want annotations that have a commit.
+ if !entry.sha.is_zero() {
+ entries.push(entry);
+ }
+ }
+ }
+ }
+
+ Ok(entries)
+}
+
+#[cfg(test)]
+mod tests {
+ use std::path::PathBuf;
+
+ use super::BlameEntry;
+ use super::parse_git_blame;
+
+ fn read_test_data(filename: &str) -> String {
+ let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ path.push("test_data");
+ path.push(filename);
+
+ std::fs::read_to_string(&path)
+ .unwrap_or_else(|_| panic!("Could not read test data at {:?}. Is it generated?", path))
+ }
+
+ fn assert_eq_golden(entries: &Vec, golden_filename: &str) {
+ let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ path.push("test_data");
+ path.push("golden");
+ path.push(format!("{}.json", golden_filename));
+
+ let mut have_json =
+ serde_json::to_string_pretty(&entries).expect("could not serialize entries to JSON");
+ // We always want to save with a trailing newline.
+ have_json.push('\n');
+
+ let update = std::env::var("UPDATE_GOLDEN")
+ .map(|val| val.eq_ignore_ascii_case("true"))
+ .unwrap_or(false);
+
+ if update {
+ std::fs::create_dir_all(path.parent().unwrap())
+ .expect("could not create golden test data directory");
+ std::fs::write(&path, have_json).expect("could not write out golden data");
+ } else {
+ let want_json =
+ std::fs::read_to_string(&path).unwrap_or_else(|_| {
+ panic!("could not read golden test data file at {:?}. Did you run the test with UPDATE_GOLDEN=true before?", path);
+ }).replace("\r\n", "\n");
+
+ pretty_assertions::assert_eq!(have_json, want_json, "wrong blame entries");
+ }
+ }
+
+ #[test]
+ fn test_parse_git_blame_not_committed() {
+ let output = read_test_data("blame_incremental_not_committed");
+ let entries = parse_git_blame(&output).unwrap();
+ assert_eq_golden(&entries, "blame_incremental_not_committed");
+ }
+
+ #[test]
+ fn test_parse_git_blame_simple() {
+ let output = read_test_data("blame_incremental_simple");
+ let entries = parse_git_blame(&output).unwrap();
+ assert_eq_golden(&entries, "blame_incremental_simple");
+ }
+
+ #[test]
+ fn test_parse_git_blame_complex() {
+ let output = read_test_data("blame_incremental_complex");
+ let entries = parse_git_blame(&output).unwrap();
+ assert_eq_golden(&entries, "blame_incremental_complex");
+ }
+}
diff --git a/crates/agent/src/tools/evals/fixtures/delete_run_git_blame/before.rs b/crates/agent/src/tools/evals/fixtures/delete_run_git_blame/before.rs
new file mode 100644
index 0000000000000000000000000000000000000000..36fccb513271265ff7ae3d54b6f974beeb809737
--- /dev/null
+++ b/crates/agent/src/tools/evals/fixtures/delete_run_git_blame/before.rs
@@ -0,0 +1,371 @@
+use crate::commit::get_messages;
+use crate::{GitRemote, Oid};
+use anyhow::{Context as _, Result, anyhow};
+use collections::{HashMap, HashSet};
+use futures::AsyncWriteExt;
+use gpui::SharedString;
+use serde::{Deserialize, Serialize};
+use std::process::Stdio;
+use std::{ops::Range, path::Path};
+use text::Rope;
+use time::OffsetDateTime;
+use time::UtcOffset;
+use time::macros::format_description;
+
+pub use git2 as libgit;
+
+#[derive(Debug, Clone, Default)]
+pub struct Blame {
+ pub entries: Vec,
+ pub messages: HashMap,
+ pub remote_url: Option,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct ParsedCommitMessage {
+ pub message: SharedString,
+ pub permalink: Option,
+ pub pull_request: Option,
+ pub remote: Option,
+}
+
+impl Blame {
+ pub async fn for_path(
+ git_binary: &Path,
+ working_directory: &Path,
+ path: &Path,
+ content: &Rope,
+ remote_url: Option,
+ ) -> Result {
+ let output = run_git_blame(git_binary, working_directory, path, content).await?;
+ let mut entries = parse_git_blame(&output)?;
+ entries.sort_unstable_by(|a, b| a.range.start.cmp(&b.range.start));
+
+ let mut unique_shas = HashSet::default();
+
+ for entry in entries.iter_mut() {
+ unique_shas.insert(entry.sha);
+ }
+
+ let shas = unique_shas.into_iter().collect::>();
+ let messages = get_messages(working_directory, &shas)
+ .await
+ .context("failed to get commit messages")?;
+
+ Ok(Self {
+ entries,
+ messages,
+ remote_url,
+ })
+ }
+}
+
+const GIT_BLAME_NO_COMMIT_ERROR: &str = "fatal: no such ref: HEAD";
+const GIT_BLAME_NO_PATH: &str = "fatal: no such path";
+
+async fn run_git_blame(
+ git_binary: &Path,
+ working_directory: &Path,
+ path: &Path,
+ contents: &Rope,
+) -> Result {
+ let mut child = util::command::new_smol_command(git_binary)
+ .current_dir(working_directory)
+ .arg("blame")
+ .arg("--incremental")
+ .arg("--contents")
+ .arg("-")
+ .arg(path.as_os_str())
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .spawn()
+ .context("starting git blame process")?;
+
+ let stdin = child
+ .stdin
+ .as_mut()
+ .context("failed to get pipe to stdin of git blame command")?;
+
+ for chunk in contents.chunks() {
+ stdin.write_all(chunk.as_bytes()).await?;
+ }
+ stdin.flush().await?;
+
+ let output = child.output().await.context("reading git blame output")?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ let trimmed = stderr.trim();
+ if trimmed == GIT_BLAME_NO_COMMIT_ERROR || trimmed.contains(GIT_BLAME_NO_PATH) {
+ return Ok(String::new());
+ }
+ anyhow::bail!("git blame process failed: {stderr}");
+ }
+
+ Ok(String::from_utf8(output.stdout)?)
+}
+
+#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
+pub struct BlameEntry {
+ pub sha: Oid,
+
+ pub range: Range,
+
+ pub original_line_number: u32,
+
+ pub author: Option,
+ pub author_mail: Option,
+ pub author_time: Option,
+ pub author_tz: Option,
+
+ pub committer_name: Option,
+ pub committer_email: Option,
+ pub committer_time: Option,
+ pub committer_tz: Option,
+
+ pub summary: Option,
+
+ pub previous: Option,
+ pub filename: String,
+}
+
+impl BlameEntry {
+ // Returns a BlameEntry by parsing the first line of a `git blame --incremental`
+ // entry. The line MUST have this format:
+ //
+ // <40-byte-hex-sha1>
+ fn new_from_blame_line(line: &str) -> Result {
+ let mut parts = line.split_whitespace();
+
+ let sha = parts
+ .next()
+ .and_then(|line| line.parse::().ok())
+ .with_context(|| format!("parsing sha from {line}"))?;
+
+ let original_line_number = parts
+ .next()
+ .and_then(|line| line.parse::().ok())
+ .with_context(|| format!("parsing original line number from {line}"))?;
+ let final_line_number = parts
+ .next()
+ .and_then(|line| line.parse::().ok())
+ .with_context(|| format!("parsing final line number from {line}"))?;
+
+ let line_count = parts
+ .next()
+ .and_then(|line| line.parse::().ok())
+ .with_context(|| format!("parsing line count from {line}"))?;
+
+ let start_line = final_line_number.saturating_sub(1);
+ let end_line = start_line + line_count;
+ let range = start_line..end_line;
+
+ Ok(Self {
+ sha,
+ range,
+ original_line_number,
+ ..Default::default()
+ })
+ }
+
+ pub fn author_offset_date_time(&self) -> Result {
+ if let (Some(author_time), Some(author_tz)) = (self.author_time, &self.author_tz) {
+ let format = format_description!("[offset_hour][offset_minute]");
+ let offset = UtcOffset::parse(author_tz, &format)?;
+ let date_time_utc = OffsetDateTime::from_unix_timestamp(author_time)?;
+
+ Ok(date_time_utc.to_offset(offset))
+ } else {
+ // Directly return current time in UTC if there's no committer time or timezone
+ Ok(time::OffsetDateTime::now_utc())
+ }
+ }
+}
+
+// parse_git_blame parses the output of `git blame --incremental`, which returns
+// all the blame-entries for a given path incrementally, as it finds them.
+//
+// Each entry *always* starts with:
+//
+// <40-byte-hex-sha1>
+//
+// Each entry *always* ends with:
+//
+// filename
+//
+// Line numbers are 1-indexed.
+//
+// A `git blame --incremental` entry looks like this:
+//
+// 6ad46b5257ba16d12c5ca9f0d4900320959df7f4 2 2 1
+// author Joe Schmoe
+// author-mail
+// author-time 1709741400
+// author-tz +0100
+// committer Joe Schmoe
+// committer-mail
+// committer-time 1709741400
+// committer-tz +0100
+// summary Joe's cool commit
+// previous 486c2409237a2c627230589e567024a96751d475 index.js
+// filename index.js
+//
+// If the entry has the same SHA as an entry that was already printed then no
+// signature information is printed:
+//
+// 6ad46b5257ba16d12c5ca9f0d4900320959df7f4 3 4 1
+// previous 486c2409237a2c627230589e567024a96751d475 index.js
+// filename index.js
+//
+// More about `--incremental` output: https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-blame.html
+fn parse_git_blame(output: &str) -> Result> {
+ let mut entries: Vec = Vec::new();
+ let mut index: HashMap = HashMap::default();
+
+ let mut current_entry: Option = None;
+
+ for line in output.lines() {
+ let mut done = false;
+
+ match &mut current_entry {
+ None => {
+ let mut new_entry = BlameEntry::new_from_blame_line(line)?;
+
+ if let Some(existing_entry) = index
+ .get(&new_entry.sha)
+ .and_then(|slot| entries.get(*slot))
+ {
+ new_entry.author.clone_from(&existing_entry.author);
+ new_entry
+ .author_mail
+ .clone_from(&existing_entry.author_mail);
+ new_entry.author_time = existing_entry.author_time;
+ new_entry.author_tz.clone_from(&existing_entry.author_tz);
+ new_entry
+ .committer_name
+ .clone_from(&existing_entry.committer_name);
+ new_entry
+ .committer_email
+ .clone_from(&existing_entry.committer_email);
+ new_entry.committer_time = existing_entry.committer_time;
+ new_entry
+ .committer_tz
+ .clone_from(&existing_entry.committer_tz);
+ new_entry.summary.clone_from(&existing_entry.summary);
+ }
+
+ current_entry.replace(new_entry);
+ }
+ Some(entry) => {
+ let Some((key, value)) = line.split_once(' ') else {
+ continue;
+ };
+ let is_committed = !entry.sha.is_zero();
+ match key {
+ "filename" => {
+ entry.filename = value.into();
+ done = true;
+ }
+ "previous" => entry.previous = Some(value.into()),
+
+ "summary" if is_committed => entry.summary = Some(value.into()),
+ "author" if is_committed => entry.author = Some(value.into()),
+ "author-mail" if is_committed => entry.author_mail = Some(value.into()),
+ "author-time" if is_committed => {
+ entry.author_time = Some(value.parse::()?)
+ }
+ "author-tz" if is_committed => entry.author_tz = Some(value.into()),
+
+ "committer" if is_committed => entry.committer_name = Some(value.into()),
+ "committer-mail" if is_committed => entry.committer_email = Some(value.into()),
+ "committer-time" if is_committed => {
+ entry.committer_time = Some(value.parse::()?)
+ }
+ "committer-tz" if is_committed => entry.committer_tz = Some(value.into()),
+ _ => {}
+ }
+ }
+ };
+
+ if done {
+ if let Some(entry) = current_entry.take() {
+ index.insert(entry.sha, entries.len());
+
+ // We only want annotations that have a commit.
+ if !entry.sha.is_zero() {
+ entries.push(entry);
+ }
+ }
+ }
+ }
+
+ Ok(entries)
+}
+
+#[cfg(test)]
+mod tests {
+ use std::path::PathBuf;
+
+ use super::BlameEntry;
+ use super::parse_git_blame;
+
+ fn read_test_data(filename: &str) -> String {
+ let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ path.push("test_data");
+ path.push(filename);
+
+ std::fs::read_to_string(&path)
+ .unwrap_or_else(|_| panic!("Could not read test data at {:?}. Is it generated?", path))
+ }
+
+ fn assert_eq_golden(entries: &Vec, golden_filename: &str) {
+ let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ path.push("test_data");
+ path.push("golden");
+ path.push(format!("{}.json", golden_filename));
+
+ let mut have_json =
+ serde_json::to_string_pretty(&entries).expect("could not serialize entries to JSON");
+ // We always want to save with a trailing newline.
+ have_json.push('\n');
+
+ let update = std::env::var("UPDATE_GOLDEN")
+ .map(|val| val.eq_ignore_ascii_case("true"))
+ .unwrap_or(false);
+
+ if update {
+ std::fs::create_dir_all(path.parent().unwrap())
+ .expect("could not create golden test data directory");
+ std::fs::write(&path, have_json).expect("could not write out golden data");
+ } else {
+ let want_json =
+ std::fs::read_to_string(&path).unwrap_or_else(|_| {
+ panic!("could not read golden test data file at {:?}. Did you run the test with UPDATE_GOLDEN=true before?", path);
+ }).replace("\r\n", "\n");
+
+ pretty_assertions::assert_eq!(have_json, want_json, "wrong blame entries");
+ }
+ }
+
+ #[test]
+ fn test_parse_git_blame_not_committed() {
+ let output = read_test_data("blame_incremental_not_committed");
+ let entries = parse_git_blame(&output).unwrap();
+ assert_eq_golden(&entries, "blame_incremental_not_committed");
+ }
+
+ #[test]
+ fn test_parse_git_blame_simple() {
+ let output = read_test_data("blame_incremental_simple");
+ let entries = parse_git_blame(&output).unwrap();
+ assert_eq_golden(&entries, "blame_incremental_simple");
+ }
+
+ #[test]
+ fn test_parse_git_blame_complex() {
+ let output = read_test_data("blame_incremental_complex");
+ let entries = parse_git_blame(&output).unwrap();
+ assert_eq_golden(&entries, "blame_incremental_complex");
+ }
+}
diff --git a/crates/agent/src/tools/evals/fixtures/disable_cursor_blinking/before.rs b/crates/agent/src/tools/evals/fixtures/disable_cursor_blinking/before.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bdf160d8ffe2c605a9e995d6efe7227dce34eaab
--- /dev/null
+++ b/crates/agent/src/tools/evals/fixtures/disable_cursor_blinking/before.rs
@@ -0,0 +1,21343 @@
+#![allow(rustdoc::private_intra_doc_links)]
+//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
+//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
+//! It comes in different flavors: single line, multiline and a fixed height one.
+//!
+//! Editor contains of multiple large submodules:
+//! * [`element`] — the place where all rendering happens
+//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
+//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
+//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
+//!
+//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
+//!
+//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
+pub mod actions;
+mod blink_manager;
+mod clangd_ext;
+mod code_context_menus;
+pub mod display_map;
+mod editor_settings;
+mod editor_settings_controls;
+mod element;
+mod git;
+mod highlight_matching_bracket;
+mod hover_links;
+pub mod hover_popover;
+mod indent_guides;
+mod inlay_hint_cache;
+pub mod items;
+mod jsx_tag_auto_close;
+mod linked_editing_ranges;
+mod lsp_ext;
+mod mouse_context_menu;
+pub mod movement;
+mod persistence;
+mod proposed_changes_editor;
+mod rust_analyzer_ext;
+pub mod scroll;
+mod selections_collection;
+pub mod tasks;
+
+#[cfg(test)]
+mod code_completion_tests;
+#[cfg(test)]
+mod editor_tests;
+#[cfg(test)]
+mod inline_completion_tests;
+mod signature_help;
+#[cfg(any(test, feature = "test-support"))]
+pub mod test;
+
+pub(crate) use actions::*;
+pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
+use aho_corasick::AhoCorasick;
+use anyhow::{Context as _, Result, anyhow};
+use blink_manager::BlinkManager;
+use buffer_diff::DiffHunkStatus;
+use client::{Collaborator, ParticipantIndex};
+use clock::ReplicaId;
+use collections::{BTreeMap, HashMap, HashSet, VecDeque};
+use convert_case::{Case, Casing};
+use display_map::*;
+pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
+use editor_settings::GoToDefinitionFallback;
+pub use editor_settings::{
+ CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
+ ShowScrollbar,
+};
+pub use editor_settings_controls::*;
+use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
+pub use element::{
+ CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
+};
+use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
+use futures::{
+ FutureExt,
+ future::{self, Shared, join},
+};
+use fuzzy::StringMatchCandidate;
+
+use ::git::blame::BlameEntry;
+use ::git::{Restore, blame::ParsedCommitMessage};
+use code_context_menus::{
+ AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
+ CompletionsMenu, ContextMenuOrigin,
+};
+use git::blame::{GitBlame, GlobalBlameRenderer};
+use gpui::{
+ Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
+ AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
+ DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
+ Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
+ MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
+ SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
+ UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
+ div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
+};
+use highlight_matching_bracket::refresh_matching_bracket_highlights;
+use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
+pub use hover_popover::hover_markdown_style;
+use hover_popover::{HoverState, hide_hover};
+use indent_guides::ActiveIndentGuidesState;
+use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
+pub use inline_completion::Direction;
+use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
+pub use items::MAX_TAB_TITLE_LEN;
+use itertools::Itertools;
+use language::{
+ AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
+ CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
+ IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
+ TransactionId, TreeSitterOptions, WordsQuery,
+ language_settings::{
+ self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
+ all_language_settings, language_settings,
+ },
+ point_from_lsp, text_diff_with_options,
+};
+use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
+use linked_editing_ranges::refresh_linked_ranges;
+use markdown::Markdown;
+use mouse_context_menu::MouseContextMenu;
+use persistence::DB;
+use project::{
+ ProjectPath,
+ debugger::{
+ breakpoint_store::{
+ BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
+ },
+ session::{Session, SessionEvent},
+ },
+};
+
+pub use git::blame::BlameRenderer;
+pub use proposed_changes_editor::{
+ ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
+};
+use smallvec::smallvec;
+use std::{cell::OnceCell, iter::Peekable};
+use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
+
+pub use lsp::CompletionContext;
+use lsp::{
+ CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
+ InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
+};
+
+use language::BufferSnapshot;
+pub use lsp_ext::lsp_tasks;
+use movement::TextLayoutDetails;
+pub use multi_buffer::{
+ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
+ RowInfo, ToOffset, ToPoint,
+};
+use multi_buffer::{
+ ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
+ MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
+};
+use parking_lot::Mutex;
+use project::{
+ CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
+ Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
+ TaskSourceKind,
+ debugger::breakpoint_store::Breakpoint,
+ lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
+ project_settings::{GitGutterSetting, ProjectSettings},
+};
+use rand::prelude::*;
+use rpc::{ErrorExt, proto::*};
+use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
+use selections_collection::{
+ MutableSelectionsCollection, SelectionsCollection, resolve_selections,
+};
+use serde::{Deserialize, Serialize};
+use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
+use smallvec::SmallVec;
+use snippet::Snippet;
+use std::sync::Arc;
+use std::{
+ any::TypeId,
+ borrow::Cow,
+ cell::RefCell,
+ cmp::{self, Ordering, Reverse},
+ mem,
+ num::NonZeroU32,
+ ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
+ path::{Path, PathBuf},
+ rc::Rc,
+ time::{Duration, Instant},
+};
+pub use sum_tree::Bias;
+use sum_tree::TreeMap;
+use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
+use theme::{
+ ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
+ observe_buffer_font_size_adjustment,
+};
+use ui::{
+ ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
+ IconSize, Key, Tooltip, h_flex, prelude::*,
+};
+use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
+use workspace::{
+ Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
+ RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
+ ViewId, Workspace, WorkspaceId, WorkspaceSettings,
+ item::{ItemHandle, PreviewTabsSettings},
+ notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
+ searchable::SearchEvent,
+};
+
+use crate::hover_links::{find_url, find_url_from_range};
+use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
+
+pub const FILE_HEADER_HEIGHT: u32 = 2;
+pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
+pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
+const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
+const MAX_LINE_LEN: usize = 1024;
+const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
+const MAX_SELECTION_HISTORY_LEN: usize = 1024;
+pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
+#[doc(hidden)]
+pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
+const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
+
+pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
+pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
+pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
+
+pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
+pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
+pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
+
+pub type RenderDiffHunkControlsFn = Arc<
+ dyn Fn(
+ u32,
+ &DiffHunkStatus,
+ Range,
+ bool,
+ Pixels,
+ &Entity,
+ &mut Window,
+ &mut App,
+ ) -> AnyElement,
+>;
+
+const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
+ alt: true,
+ shift: true,
+ control: false,
+ platform: false,
+ function: false,
+};
+
+struct InlineValueCache {
+ enabled: bool,
+ inlays: Vec,
+ refresh_task: Task