Compare commits
10 Commits
languages-
...
multi-auth
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74e80993a3 | ||
|
|
70993b3ce3 | ||
|
|
a47a387186 | ||
|
|
5f6f7edc99 | ||
|
|
4f46ac8c2d | ||
|
|
946c41b4b3 | ||
|
|
7f7312ca8f | ||
|
|
4232b59a59 | ||
|
|
b00901c126 | ||
|
|
e1a83a5fe6 |
@@ -5,16 +5,12 @@
|
||||
# Arrays are merged together though. See: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
|
||||
# The intent for this file is to configure CI build process with a divergance from Zed developers experience; for example, in this config file
|
||||
# we use `-D warnings` for rustflags (which makes compilation fail in presence of warnings during build process). Placing that in developers `config.toml`
|
||||
# would be inconvenient.
|
||||
# would be incovenient.
|
||||
# The reason for not using the RUSTFLAGS environment variable is that doing so would override all the settings in the config.toml file, even if the contents of the latter are completely nonsensical. See: https://github.com/rust-lang/cargo/issues/5376
|
||||
# Here, we opted to use `[target.'cfg(all())']` instead of `[build]` because `[target.'**']` is guaranteed to be cumulative.
|
||||
[target.'cfg(all())']
|
||||
rustflags = ["-D", "warnings"]
|
||||
|
||||
# We don't need fullest debug information for dev stuff (tests etc.) in CI.
|
||||
[profile.dev]
|
||||
debug = "limited"
|
||||
|
||||
# Use Mold on Linux, because it's faster than GNU ld and LLD.
|
||||
#
|
||||
# We no longer set this in the default `config.toml` so that developers can opt in to Wild, which
|
||||
|
||||
@@ -4,17 +4,3 @@ sequential-db-tests = { max-threads = 1 }
|
||||
[[profile.default.overrides]]
|
||||
filter = 'package(db)'
|
||||
test-group = 'sequential-db-tests'
|
||||
|
||||
# Run slowest tests first.
|
||||
#
|
||||
[[profile.default.overrides]]
|
||||
filter = 'package(worktree) and test(test_random_worktree_changes)'
|
||||
priority = 100
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = 'package(collab) and (test(random_project_collaboration_tests) or test(random_channel_buffer_tests) or test(test_contact_requests) or test(test_basic_following))'
|
||||
priority = 99
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = 'package(extension_host) and test(test_extension_store_with_test_extension)'
|
||||
priority = 99
|
||||
|
||||
35
.github/ISSUE_TEMPLATE/06_bug_git.yml
vendored
35
.github/ISSUE_TEMPLATE/06_bug_git.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Bug Report (Git)
|
||||
description: Zed Git Related Bugs
|
||||
type: "Bug"
|
||||
labels: ["git"]
|
||||
title: "Git: <a short description of the Git bug>"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the bug with a one-line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one-line summary of the issue below -->
|
||||
SUMMARY_SENTENCE_HERE
|
||||
|
||||
### Description
|
||||
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
|
||||
Steps to trigger the problem:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected Behavior**:
|
||||
**Actual Behavior**:
|
||||
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Zed Version and System Specs
|
||||
description: 'Open Zed, and in the command palette select "zed: copy system specs into clipboard"'
|
||||
placeholder: |
|
||||
Output of "zed: copy system specs into clipboard"
|
||||
validations:
|
||||
required: true
|
||||
3
.github/ISSUE_TEMPLATE/11_crash_report.yml
vendored
3
.github/ISSUE_TEMPLATE/11_crash_report.yml
vendored
@@ -33,10 +33,9 @@ body:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, attach your `Zed.log` file to this issue.
|
||||
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||
description: |
|
||||
macOS: `~/Library/Logs/Zed/Zed.log`
|
||||
Windows: `C:\Users\YOU\AppData\Local\Zed\logs\Zed.log`
|
||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
value: |
|
||||
|
||||
5
.github/actions/run_tests/action.yml
vendored
5
.github/actions/run_tests/action.yml
vendored
@@ -15,11 +15,8 @@ runs:
|
||||
node-version: "18"
|
||||
|
||||
- name: Limit target directory size
|
||||
env:
|
||||
MAX_SIZE: ${{ runner.os == 'macOS' && 300 || 100 }}
|
||||
shell: bash -euxo pipefail {0}
|
||||
# Use the variable in the run command
|
||||
run: script/clear-target-dir-if-larger-than ${{ env.MAX_SIZE }}
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Run tests
|
||||
shell: bash -euxo pipefail {0}
|
||||
|
||||
135
.github/workflows/ci.yml
vendored
135
.github/workflows/ci.yml
vendored
@@ -177,7 +177,7 @@ jobs:
|
||||
uses: ./.github/actions/check_style
|
||||
|
||||
- name: Check for typos
|
||||
uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1 # v1.38.1
|
||||
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6
|
||||
with:
|
||||
config: ./typos.toml
|
||||
|
||||
@@ -516,7 +516,9 @@ jobs:
|
||||
name: Create a macOS bundle
|
||||
runs-on:
|
||||
- self-mini-macos
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
if: |
|
||||
( startsWith(github.ref, 'refs/tags/v')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
|
||||
needs: [macos_tests]
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
@@ -547,14 +549,16 @@ jobs:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 300
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Determine version and release channel
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
||||
script/determine-release-channel
|
||||
|
||||
- name: Draft release notes
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
mkdir -p target/
|
||||
# Ignore any errors that occur while drafting release notes to not fail the build.
|
||||
@@ -563,17 +567,29 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create macOS app bundle (aarch64)
|
||||
run: script/bundle-mac aarch64-apple-darwin
|
||||
|
||||
- name: Create macOS app bundle (x64)
|
||||
run: script/bundle-mac x86_64-apple-darwin
|
||||
- name: Create macOS app bundle
|
||||
run: script/bundle-mac
|
||||
|
||||
- name: Rename binaries
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
run: |
|
||||
mv target/aarch64-apple-darwin/release/Zed.dmg target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
|
||||
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
|
||||
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
|
||||
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
|
||||
path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
|
||||
- uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||
name: Upload app bundle to release
|
||||
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
|
||||
@@ -594,7 +610,8 @@ jobs:
|
||||
runs-on:
|
||||
- namespace-profile-16x32-ubuntu-2004 # ubuntu 20.04 for minimal glibc
|
||||
if: |
|
||||
( startsWith(github.ref, 'refs/tags/v') )
|
||||
( startsWith(github.ref, 'refs/tags/v')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
|
||||
needs: [linux_tests]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
@@ -611,6 +628,7 @@ jobs:
|
||||
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
- name: Determine version and release channel
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
run: |
|
||||
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
||||
script/determine-release-channel
|
||||
@@ -618,8 +636,23 @@ jobs:
|
||||
- name: Create Linux .tar.gz bundle
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Artifact to Workflow - zed (run-bundling)
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
path: target/release/zed-*.tar.gz
|
||||
|
||||
- name: Upload Artifact to Workflow - zed-remote-server (run-bundling)
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
with:
|
||||
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.gz
|
||||
path: target/zed-remote-server-linux-x86_64.gz
|
||||
|
||||
- name: Upload Artifacts to release
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
@@ -636,6 +669,7 @@ jobs:
|
||||
- namespace-profile-8x32-ubuntu-2004-arm-m4 # ubuntu 20.04 for minimal glibc
|
||||
if: |
|
||||
startsWith(github.ref, 'refs/tags/v')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
needs: [linux_tests]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
@@ -652,6 +686,7 @@ jobs:
|
||||
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
- name: Determine version and release channel
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
run: |
|
||||
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
||||
script/determine-release-channel
|
||||
@@ -659,8 +694,23 @@ jobs:
|
||||
- name: Create and upload Linux .tar.gz bundles
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Artifact to Workflow - zed (run-bundling)
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
||||
path: target/release/zed-*.tar.gz
|
||||
|
||||
- name: Upload Artifact to Workflow - zed-remote-server (run-bundling)
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
with:
|
||||
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.gz
|
||||
path: target/zed-remote-server-linux-aarch64.gz
|
||||
|
||||
- name: Upload Artifacts to release
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
@@ -674,7 +724,8 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
runs-on: github-8vcpu-ubuntu-2404
|
||||
if: |
|
||||
false && ( startsWith(github.ref, 'refs/tags/v') )
|
||||
false && ( startsWith(github.ref, 'refs/tags/v')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
|
||||
needs: [linux_tests]
|
||||
name: Build Zed on FreeBSD
|
||||
steps:
|
||||
@@ -725,7 +776,7 @@ jobs:
|
||||
|
||||
nix-build:
|
||||
name: Build with Nix
|
||||
uses: ./.github/workflows/nix_build.yml
|
||||
uses: ./.github/workflows/nix.yml
|
||||
needs: [job_spec]
|
||||
if: github.repository_owner == 'zed-industries' &&
|
||||
(contains(github.event.pull_request.labels.*.name, 'run-nix') ||
|
||||
@@ -738,10 +789,11 @@ jobs:
|
||||
|
||||
bundle-windows-x64:
|
||||
timeout-minutes: 120
|
||||
name: Create a Windows installer for x86_64
|
||||
name: Create a Windows installer
|
||||
runs-on: [self-32vcpu-windows-2022]
|
||||
if: |
|
||||
( startsWith(github.ref, 'refs/tags/v') )
|
||||
( startsWith(github.ref, 'refs/tags/v')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
|
||||
needs: [windows_tests]
|
||||
env:
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
||||
@@ -766,6 +818,7 @@ jobs:
|
||||
|
||||
- name: Determine version and release channel
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
||||
script/determine-release-channel.ps1
|
||||
@@ -774,55 +827,16 @@ jobs:
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: script/bundle-windows.ps1
|
||||
|
||||
- name: Upload Artifacts to release
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||
- name: Upload installer (x86_64) to Workflow - zed (run-bundling)
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
files: ${{ env.SETUP_PATH }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
bundle-windows-aarch64:
|
||||
timeout-minutes: 120
|
||||
name: Create a Windows installer for aarch64
|
||||
runs-on: [self-32vcpu-windows-2022]
|
||||
if: |
|
||||
( startsWith(github.ref, 'refs/tags/v') )
|
||||
needs: [windows_tests]
|
||||
env:
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
|
||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
|
||||
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
|
||||
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
|
||||
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
|
||||
FILE_DIGEST: SHA256
|
||||
TIMESTAMP_DIGEST: SHA256
|
||||
TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com"
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Setup Sentry CLI
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||
with:
|
||||
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
- name: Determine version and release channel
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: |
|
||||
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
|
||||
script/determine-release-channel.ps1
|
||||
|
||||
- name: Build Zed installer
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: script/bundle-windows.ps1 -Architecture aarch64
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.exe
|
||||
path: ${{ env.SETUP_PATH }}
|
||||
|
||||
- name: Upload Artifacts to release
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
@@ -833,10 +847,9 @@ jobs:
|
||||
auto-release-preview:
|
||||
name: Auto release preview
|
||||
if: |
|
||||
false
|
||||
&& startsWith(github.ref, 'refs/tags/v')
|
||||
startsWith(github.ref, 'refs/tags/v')
|
||||
&& endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
|
||||
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, bundle-windows-x64, bundle-windows-aarch64]
|
||||
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, bundle-windows-x64]
|
||||
runs-on:
|
||||
- self-mini-macos
|
||||
steps:
|
||||
|
||||
66
.github/workflows/danger.yml
vendored
66
.github/workflows/danger.yml
vendored
@@ -1,40 +1,42 @@
|
||||
# Generated from xtask::workflows::danger
|
||||
# Rebuild with `cargo xtask workflows`.
|
||||
name: danger
|
||||
name: Danger
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- edited
|
||||
branches:
|
||||
- main
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- edited
|
||||
|
||||
jobs:
|
||||
danger:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_pnpm
|
||||
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
|
||||
with:
|
||||
version: '9'
|
||||
- name: steps::setup_node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: pnpm
|
||||
cache-dependency-path: script/danger/pnpm-lock.yaml
|
||||
- name: danger::install_deps
|
||||
run: pnpm install --dir script/danger
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: danger::run
|
||||
run: pnpm run --dir script/danger danger ci
|
||||
shell: bash -euxo pipefail {0}
|
||||
env:
|
||||
GITHUB_TOKEN: not_a_real_token
|
||||
DANGER_GITHUB_API_BASE_URL: https://danger-proxy.fly.dev/github
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "script/danger/pnpm-lock.yaml"
|
||||
|
||||
- run: pnpm install --dir script/danger
|
||||
|
||||
- name: Run Danger
|
||||
run: pnpm run --dir script/danger danger ci
|
||||
env:
|
||||
# This GitHub token is not used, but the value needs to be here to prevent
|
||||
# Danger from throwing an error.
|
||||
GITHUB_TOKEN: "not_a_real_token"
|
||||
# All requests are instead proxied through an instance of
|
||||
# https://github.com/maxdeviant/danger-proxy that allows Danger to securely
|
||||
# authenticate with GitHub while still being able to run on PRs from forks.
|
||||
DANGER_GITHUB_API_BASE_URL: "https://danger-proxy.fly.dev/github"
|
||||
|
||||
2
.github/workflows/deploy_collab.yml
vendored
2
.github/workflows/deploy_collab.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Limit target directory size
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clear-target-dir-if-larger-than 300
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Run tests
|
||||
shell: bash -euxo pipefail {0}
|
||||
|
||||
69
.github/workflows/nix.yml
vendored
Normal file
69
.github/workflows/nix.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: "Nix build"
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
flake-output:
|
||||
type: string
|
||||
default: "default"
|
||||
cachix-filter:
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
jobs:
|
||||
nix-build:
|
||||
timeout-minutes: 60
|
||||
name: (${{ matrix.system.os }}) Nix Build
|
||||
continue-on-error: true # TODO: remove when we want this to start blocking CI
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
system:
|
||||
- os: x86 Linux
|
||||
runner: namespace-profile-16x32-ubuntu-2204
|
||||
install_nix: true
|
||||
- os: arm Mac
|
||||
runner: [macOS, ARM64, test]
|
||||
install_nix: false
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: ${{ matrix.system.runner }}
|
||||
env:
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
|
||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||
GIT_LFS_SKIP_SMUDGE: 1 # breaks the livekit rust sdk examples which we don't actually depend on
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
# on our macs we manually install nix. for some reason the cachix action is running
|
||||
# under a non-login /bin/bash shell which doesn't source the proper script to add the
|
||||
# nix profile to PATH, so we manually add them here
|
||||
- name: Set path
|
||||
if: ${{ ! matrix.system.install_nix }}
|
||||
run: |
|
||||
echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH"
|
||||
echo "/Users/administrator/.nix-profile/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
|
||||
if: ${{ matrix.system.install_nix }}
|
||||
with:
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: zed
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
pushFilter: "${{ inputs.cachix-filter }}"
|
||||
cachixArgs: "-v"
|
||||
|
||||
- run: nix build .#${{ inputs.flake-output }} -L --accept-flake-config
|
||||
|
||||
- name: Limit /nix/store to 50GB on macs
|
||||
if: ${{ ! matrix.system.install_nix }}
|
||||
run: |
|
||||
if [ "$(du -sm /nix/store | cut -f1)" -gt 50000 ]; then
|
||||
nix-collect-garbage -d || true
|
||||
fi
|
||||
77
.github/workflows/nix_build.yml
vendored
77
.github/workflows/nix_build.yml
vendored
@@ -1,77 +0,0 @@
|
||||
# Generated from xtask::workflows::nix_build
|
||||
# Rebuild with `cargo xtask workflows`.
|
||||
name: nix_build
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
flake-output:
|
||||
type: string
|
||||
default: default
|
||||
cachix-filter:
|
||||
type: string
|
||||
jobs:
|
||||
build_nix_linux_x86_64:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: namespace-profile-32x64-ubuntu-2004
|
||||
env:
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
|
||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||
GIT_LFS_SKIP_SMUDGE: '1'
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: nix_build::install_nix
|
||||
uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f
|
||||
with:
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: nix_build::cachix_action
|
||||
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
|
||||
with:
|
||||
name: zed
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
cachixArgs: -v
|
||||
pushFilter: ${{ inputs.cachix-filter }}
|
||||
- name: nix_build::build
|
||||
run: nix build .#${{ inputs.flake-output }} -L --accept-flake-config
|
||||
shell: bash -euxo pipefail {0}
|
||||
timeout-minutes: 60
|
||||
continue-on-error: true
|
||||
build_nix_mac_aarch64:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: self-mini-macos
|
||||
env:
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
|
||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||
GIT_LFS_SKIP_SMUDGE: '1'
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: nix_build::set_path
|
||||
run: |
|
||||
echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH"
|
||||
echo "/Users/administrator/.nix-profile/bin" >> "$GITHUB_PATH"
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: nix_build::cachix_action
|
||||
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
|
||||
with:
|
||||
name: zed
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
cachixArgs: -v
|
||||
pushFilter: ${{ inputs.cachix-filter }}
|
||||
- name: nix_build::build
|
||||
run: nix build .#${{ inputs.flake-output }} -L --accept-flake-config
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: nix_build::limit_store
|
||||
run: |-
|
||||
if [ "$(du -sm /nix/store | cut -f1)" -gt 50000 ]; then
|
||||
nix-collect-garbage -d || true
|
||||
fi
|
||||
shell: bash -euxo pipefail {0}
|
||||
timeout-minutes: 60
|
||||
continue-on-error: true
|
||||
683
.github/workflows/release_nightly.yml
vendored
683
.github/workflows/release_nightly.yml
vendored
@@ -1,107 +1,93 @@
|
||||
# Generated from xtask::workflows::release_nightly
|
||||
# Rebuild with `cargo xtask workflows`.
|
||||
name: release_nightly
|
||||
name: Release Nightly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Fire every day at 7:00am UTC (Roughly before EU workday and after US workday)
|
||||
- cron: "0 7 * * *"
|
||||
push:
|
||||
tags:
|
||||
- "nightly"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: '0'
|
||||
RUST_BACKTRACE: '1'
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUST_BACKTRACE: 1
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- nightly
|
||||
schedule:
|
||||
- cron: 0 7 * * *
|
||||
|
||||
jobs:
|
||||
check_style:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: self-mini-macos
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
fetch-depth: 0
|
||||
- name: steps::cargo_fmt
|
||||
run: cargo fmt --all -- --check
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: ./script/clippy
|
||||
run: ./script/clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
style:
|
||||
timeout-minutes: 60
|
||||
run_tests_mac:
|
||||
name: Check formatting and Clippy lints
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: self-mini-macos
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- macOS
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_cargo_config
|
||||
run: |
|
||||
mkdir -p ./../.cargo
|
||||
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: steps::setup_node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: steps::cargo_install_nextest
|
||||
run: cargo install cargo-nextest --locked
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: steps::clear_target_dir_if_large
|
||||
run: ./script/clear-target-dir-if-larger-than 300
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: steps::cargo_nextest
|
||||
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: steps::cleanup_cargo_config
|
||||
if: always()
|
||||
run: |
|
||||
rm -rf ./../.cargo
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
|
||||
- name: Run clippy
|
||||
run: ./script/clippy
|
||||
|
||||
tests:
|
||||
timeout-minutes: 60
|
||||
run_tests_windows:
|
||||
name: Run tests
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: self-32vcpu-windows-2022
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- macOS
|
||||
needs: style
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_cargo_config
|
||||
run: |
|
||||
New-Item -ItemType Directory -Path "./../.cargo" -Force
|
||||
Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
|
||||
shell: pwsh
|
||||
- name: steps::setup_node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: steps::cargo_install_nextest
|
||||
run: cargo install cargo-nextest --locked
|
||||
shell: pwsh
|
||||
- name: steps::clear_target_dir_if_large
|
||||
run: ./script/clear-target-dir-if-larger-than.ps1 250
|
||||
shell: pwsh
|
||||
- name: steps::cargo_nextest
|
||||
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
|
||||
shell: pwsh
|
||||
- name: steps::cleanup_cargo_config
|
||||
if: always()
|
||||
run: |
|
||||
Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
|
||||
shell: pwsh
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
windows-tests:
|
||||
timeout-minutes: 60
|
||||
bundle_mac_nightly_x86_64:
|
||||
needs:
|
||||
- check_style
|
||||
- run_tests_mac
|
||||
name: Run tests on Windows
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: self-mini-macos
|
||||
runs-on: [self-32vcpu-windows-2022]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Configure CI
|
||||
run: |
|
||||
New-Item -ItemType Directory -Path "./../.cargo" -Force
|
||||
Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests_windows
|
||||
|
||||
- name: Limit target directory size
|
||||
run: ./script/clear-target-dir-if-larger-than.ps1 1024
|
||||
|
||||
- name: Clean CI config file
|
||||
if: always()
|
||||
run: Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
|
||||
|
||||
bundle-mac:
|
||||
timeout-minutes: 60
|
||||
name: Create a macOS bundle
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-mini-macos
|
||||
needs: tests
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
@@ -109,162 +95,161 @@ jobs:
|
||||
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
||||
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: steps::setup_sentry
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
|
||||
with:
|
||||
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
- name: steps::clear_target_dir_if_large
|
||||
run: ./script/clear-target-dir-if-larger-than 300
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: release_nightly::set_release_channel_to_nightly
|
||||
run: |
|
||||
set -eu
|
||||
version=$(git rev-parse --short HEAD)
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: run_bundling::bundle_mac
|
||||
run: ./script/bundle-mac x86_64-apple-darwin
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: release_nightly::upload_zed_nightly
|
||||
run: script/upload-nightly macos x86_64
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Set release channel to nightly
|
||||
run: |
|
||||
set -eu
|
||||
version=$(git rev-parse --short HEAD)
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
|
||||
- name: Setup Sentry CLI
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||
with:
|
||||
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
- name: Create macOS app bundle
|
||||
run: script/bundle-mac
|
||||
|
||||
- name: Upload Zed Nightly
|
||||
run: script/upload-nightly macos
|
||||
|
||||
bundle-linux-x86:
|
||||
timeout-minutes: 60
|
||||
bundle_mac_nightly_aarch64:
|
||||
needs:
|
||||
- check_style
|
||||
- run_tests_mac
|
||||
name: Create a Linux *.tar.gz bundle for x86
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: self-mini-macos
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
|
||||
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
||||
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
||||
runs-on:
|
||||
- namespace-profile-16x32-ubuntu-2004 # ubuntu 20.04 for minimal glibc
|
||||
needs: tests
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: steps::setup_sentry
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
|
||||
with:
|
||||
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
- name: steps::clear_target_dir_if_large
|
||||
run: ./script/clear-target-dir-if-larger-than 300
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: release_nightly::set_release_channel_to_nightly
|
||||
run: |
|
||||
set -eu
|
||||
version=$(git rev-parse --short HEAD)
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: run_bundling::bundle_mac
|
||||
run: ./script/bundle-mac aarch64-apple-darwin
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: release_nightly::upload_zed_nightly
|
||||
run: script/upload-nightly macos aarch64
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux && ./script/install-mold 2.34.0
|
||||
|
||||
- name: Setup Sentry CLI
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||
with:
|
||||
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Set release channel to nightly
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version=$(git rev-parse --short HEAD)
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
|
||||
- name: Create Linux .tar.gz bundle
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Zed Nightly
|
||||
run: script/upload-nightly linux-targz
|
||||
|
||||
bundle-linux-arm:
|
||||
timeout-minutes: 60
|
||||
bundle_linux_nightly_x86_64:
|
||||
needs:
|
||||
- check_style
|
||||
- run_tests_mac
|
||||
name: Create a Linux *.tar.gz bundle for ARM
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: namespace-profile-32x64-ubuntu-2004
|
||||
runs-on:
|
||||
- namespace-profile-8x32-ubuntu-2004-arm-m4 # ubuntu 20.04 for minimal glibc
|
||||
needs: tests
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_sentry
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
|
||||
with:
|
||||
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
- name: release_nightly::add_rust_to_path
|
||||
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: ./script/linux
|
||||
run: ./script/linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: ./script/install-mold
|
||||
run: ./script/install-mold
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: steps::clear_target_dir_if_large
|
||||
run: ./script/clear-target-dir-if-larger-than 100
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: release_nightly::set_release_channel_to_nightly
|
||||
run: |
|
||||
set -eu
|
||||
version=$(git rev-parse --short HEAD)
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: ./script/bundle-linux
|
||||
run: ./script/bundle-linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: release_nightly::upload_zed_nightly
|
||||
run: script/upload-nightly linux-targz x86_64
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux
|
||||
|
||||
- name: Setup Sentry CLI
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||
with:
|
||||
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Set release channel to nightly
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version=$(git rev-parse --short HEAD)
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
|
||||
- name: Create Linux .tar.gz bundle
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Zed Nightly
|
||||
run: script/upload-nightly linux-targz
|
||||
|
||||
freebsd:
|
||||
timeout-minutes: 60
|
||||
bundle_linux_nightly_aarch64:
|
||||
needs:
|
||||
- check_style
|
||||
- run_tests_mac
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
|
||||
if: false && github.repository_owner == 'zed-industries'
|
||||
runs-on: github-8vcpu-ubuntu-2404
|
||||
needs: tests
|
||||
name: Build Zed on FreeBSD
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_sentry
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
|
||||
with:
|
||||
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
- name: release_nightly::add_rust_to_path
|
||||
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: ./script/linux
|
||||
run: ./script/linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: steps::clear_target_dir_if_large
|
||||
run: ./script/clear-target-dir-if-larger-than 100
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: release_nightly::set_release_channel_to_nightly
|
||||
run: |
|
||||
set -eu
|
||||
version=$(git rev-parse --short HEAD)
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: ./script/bundle-linux
|
||||
run: ./script/bundle-linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: release_nightly::upload_zed_nightly
|
||||
run: script/upload-nightly linux-targz aarch64
|
||||
shell: bash -euxo pipefail {0}
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build FreeBSD remote-server
|
||||
id: freebsd-build
|
||||
uses: vmactions/freebsd-vm@c3ae29a132c8ef1924775414107a97cac042aad5 # v1.2.0
|
||||
with:
|
||||
# envs: "MYTOKEN MYTOKEN2"
|
||||
usesh: true
|
||||
release: 13.5
|
||||
copyback: true
|
||||
prepare: |
|
||||
pkg install -y \
|
||||
bash curl jq git \
|
||||
rustup-init cmake-core llvm-devel-lite pkgconf protobuf # ibx11 alsa-lib rust-bindgen-cli
|
||||
run: |
|
||||
freebsd-version
|
||||
sysctl hw.model
|
||||
sysctl hw.ncpu
|
||||
sysctl hw.physmem
|
||||
sysctl hw.usermem
|
||||
git config --global --add safe.directory /home/runner/work/zed/zed
|
||||
rustup-init --profile minimal --default-toolchain none -y
|
||||
. "$HOME/.cargo/env"
|
||||
./script/bundle-freebsd
|
||||
mkdir -p out/
|
||||
mv "target/zed-remote-server-freebsd-x86_64.gz" out/
|
||||
rm -rf target/
|
||||
cargo clean
|
||||
|
||||
- name: Upload Zed Nightly
|
||||
run: script/upload-nightly freebsd
|
||||
|
||||
bundle-nix:
|
||||
name: Build and cache Nix package
|
||||
needs: tests
|
||||
secrets: inherit
|
||||
uses: ./.github/workflows/nix.yml
|
||||
|
||||
bundle-windows-x64:
|
||||
timeout-minutes: 60
|
||||
bundle_windows_nightly_x86_64:
|
||||
needs:
|
||||
- check_style
|
||||
- run_tests_windows
|
||||
name: Create a Windows installer
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: self-32vcpu-windows-2022
|
||||
runs-on: [self-32vcpu-windows-2022]
|
||||
needs: windows-tests
|
||||
env:
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
|
||||
@@ -274,177 +259,65 @@ jobs:
|
||||
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
|
||||
FILE_DIGEST: SHA256
|
||||
TIMESTAMP_DIGEST: SHA256
|
||||
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
|
||||
TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com"
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_sentry
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
|
||||
with:
|
||||
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
- name: release_nightly::set_release_channel_to_nightly
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$version = git rev-parse --short HEAD
|
||||
Write-Host "Publishing version: $version on release channel nightly"
|
||||
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
|
||||
shell: pwsh
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
- name: release_nightly::build_zed_installer
|
||||
run: script/bundle-windows.ps1 -Architecture x86_64
|
||||
shell: pwsh
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
- name: release_nightly::upload_zed_nightly_windows
|
||||
run: script/upload-nightly.ps1 -Architecture x86_64
|
||||
shell: pwsh
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
timeout-minutes: 60
|
||||
bundle_windows_nightly_aarch64:
|
||||
needs:
|
||||
- check_style
|
||||
- run_tests_windows
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: self-32vcpu-windows-2022
|
||||
env:
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
|
||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
|
||||
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
|
||||
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
|
||||
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
|
||||
FILE_DIGEST: SHA256
|
||||
TIMESTAMP_DIGEST: SHA256
|
||||
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_sentry
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
|
||||
with:
|
||||
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
- name: release_nightly::set_release_channel_to_nightly
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$version = git rev-parse --short HEAD
|
||||
Write-Host "Publishing version: $version on release channel nightly"
|
||||
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
|
||||
shell: pwsh
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
- name: release_nightly::build_zed_installer
|
||||
run: script/bundle-windows.ps1 -Architecture aarch64
|
||||
shell: pwsh
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
- name: release_nightly::upload_zed_nightly_windows
|
||||
run: script/upload-nightly.ps1 -Architecture aarch64
|
||||
shell: pwsh
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
timeout-minutes: 60
|
||||
build_nix_linux_x86_64:
|
||||
needs:
|
||||
- check_style
|
||||
- run_tests_mac
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: namespace-profile-32x64-ubuntu-2004
|
||||
env:
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
|
||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||
GIT_LFS_SKIP_SMUDGE: '1'
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: nix_build::install_nix
|
||||
uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f
|
||||
with:
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: nix_build::cachix_action
|
||||
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
|
||||
with:
|
||||
name: zed
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
cachixArgs: -v
|
||||
- name: nix_build::build
|
||||
run: nix build .#default -L --accept-flake-config
|
||||
shell: bash -euxo pipefail {0}
|
||||
timeout-minutes: 60
|
||||
continue-on-error: true
|
||||
build_nix_mac_aarch64:
|
||||
needs:
|
||||
- check_style
|
||||
- run_tests_mac
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: self-mini-macos
|
||||
env:
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
|
||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||
GIT_LFS_SKIP_SMUDGE: '1'
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: nix_build::set_path
|
||||
run: |
|
||||
echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH"
|
||||
echo "/Users/administrator/.nix-profile/bin" >> "$GITHUB_PATH"
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: nix_build::cachix_action
|
||||
uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad
|
||||
with:
|
||||
name: zed
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
cachixArgs: -v
|
||||
- name: nix_build::build
|
||||
run: nix build .#default -L --accept-flake-config
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: nix_build::limit_store
|
||||
run: |-
|
||||
if [ "$(du -sm /nix/store | cut -f1)" -gt 50000 ]; then
|
||||
nix-collect-garbage -d || true
|
||||
fi
|
||||
shell: bash -euxo pipefail {0}
|
||||
timeout-minutes: 60
|
||||
continue-on-error: true
|
||||
update_nightly_tag:
|
||||
needs:
|
||||
- bundle_mac_nightly_x86_64
|
||||
- bundle_mac_nightly_aarch64
|
||||
- bundle_linux_nightly_x86_64
|
||||
- bundle_linux_nightly_aarch64
|
||||
- bundle_windows_nightly_x86_64
|
||||
- bundle_windows_nightly_aarch64
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Set release channel to nightly
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$version = git rev-parse --short HEAD
|
||||
Write-Host "Publishing version: $version on release channel nightly"
|
||||
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
|
||||
|
||||
- name: Setup Sentry CLI
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||
with:
|
||||
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
- name: Build Zed installer
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: script/bundle-windows.ps1
|
||||
|
||||
- name: Upload Zed Nightly
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: script/upload-nightly.ps1 windows
|
||||
|
||||
update-nightly-tag:
|
||||
name: Update nightly tag
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||
needs:
|
||||
- bundle-mac
|
||||
- bundle-linux-x86
|
||||
- bundle-linux-arm
|
||||
- bundle-windows-x64
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
fetch-depth: 0
|
||||
- name: release_nightly::update_nightly_tag
|
||||
run: |
|
||||
if [ "$(git rev-parse nightly)" = "$(git rev-parse HEAD)" ]; then
|
||||
echo "Nightly tag already points to current commit. Skipping tagging."
|
||||
exit 0
|
||||
fi
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
git tag -f nightly
|
||||
git push origin nightly --force
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: release_nightly::create_sentry_release
|
||||
uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c
|
||||
with:
|
||||
environment: production
|
||||
env:
|
||||
SENTRY_ORG: zed-dev
|
||||
SENTRY_PROJECT: zed
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
timeout-minutes: 60
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Update nightly tag
|
||||
run: |
|
||||
if [ "$(git rev-parse nightly)" = "$(git rev-parse HEAD)" ]; then
|
||||
echo "Nightly tag already points to current commit. Skipping tagging."
|
||||
exit 0
|
||||
fi
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
git tag -f nightly
|
||||
git push origin nightly --force
|
||||
|
||||
- name: Create Sentry release
|
||||
uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c # v3
|
||||
env:
|
||||
SENTRY_ORG: zed-dev
|
||||
SENTRY_PROJECT: zed
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
with:
|
||||
environment: production
|
||||
|
||||
233
.github/workflows/run_bundling.yml
vendored
233
.github/workflows/run_bundling.yml
vendored
@@ -1,233 +0,0 @@
|
||||
# Generated from xtask::workflows::run_bundling
|
||||
# Rebuild with `cargo xtask workflows`.
|
||||
name: run_bundling
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: '0'
|
||||
RUST_BACKTRACE: '1'
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- labeled
|
||||
- synchronize
|
||||
jobs:
|
||||
bundle_mac_x86_64:
|
||||
if: |-
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
runs-on: self-mini-macos
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
|
||||
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
||||
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: steps::setup_sentry
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
|
||||
with:
|
||||
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
- name: steps::clear_target_dir_if_large
|
||||
run: ./script/clear-target-dir-if-larger-than 300
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: run_bundling::bundle_mac
|
||||
run: ./script/bundle-mac x86_64-apple-darwin
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: '@actions/upload-artifact Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg'
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
|
||||
path: target/x86_64-apple-darwin/release/Zed.dmg
|
||||
- name: '@actions/upload-artifact zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-macos-x86_64.gz'
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
with:
|
||||
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-macos-x86_64.gz
|
||||
path: target/zed-remote-server-macos-x86_64.gz
|
||||
timeout-minutes: 60
|
||||
bundle_mac_arm64:
|
||||
if: |-
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
runs-on: self-mini-macos
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
|
||||
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
||||
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: steps::setup_sentry
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
|
||||
with:
|
||||
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
- name: steps::clear_target_dir_if_large
|
||||
run: ./script/clear-target-dir-if-larger-than 300
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: run_bundling::bundle_mac
|
||||
run: ./script/bundle-mac aarch64-apple-darwin
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: '@actions/upload-artifact Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg'
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
|
||||
path: target/aarch64-apple-darwin/release/Zed.dmg
|
||||
- name: '@actions/upload-artifact zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-macos-aarch64.gz'
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
with:
|
||||
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-macos-aarch64.gz
|
||||
path: target/zed-remote-server-macos-aarch64.gz
|
||||
timeout-minutes: 60
|
||||
bundle_linux_x86_64:
|
||||
if: |-
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
runs-on: namespace-profile-32x64-ubuntu-2004
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_sentry
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
|
||||
with:
|
||||
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
- name: ./script/linux
|
||||
run: ./script/linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: ./script/install-mold
|
||||
run: ./script/install-mold
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: ./script/bundle-linux
|
||||
run: ./script/bundle-linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: '@actions/upload-artifact zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz'
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
path: target/release/zed-*.tar.gz
|
||||
- name: '@actions/upload-artifact zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz'
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
with:
|
||||
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
path: target/release/zed-remote-server-*.tar.gz
|
||||
timeout-minutes: 60
|
||||
bundle_linux_arm64:
|
||||
if: |-
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_sentry
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
|
||||
with:
|
||||
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
- name: ./script/linux
|
||||
run: ./script/linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: ./script/bundle-linux
|
||||
run: ./script/bundle-linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: '@actions/upload-artifact zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz'
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
||||
path: target/release/zed-*.tar.gz
|
||||
- name: '@actions/upload-artifact zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz'
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
with:
|
||||
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
||||
path: target/release/zed-remote-server-*.tar.gz
|
||||
timeout-minutes: 60
|
||||
bundle_windows_x86_64:
|
||||
if: |-
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
runs-on: self-32vcpu-windows-2022
|
||||
env:
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
|
||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
|
||||
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
|
||||
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
|
||||
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
|
||||
FILE_DIGEST: SHA256
|
||||
TIMESTAMP_DIGEST: SHA256
|
||||
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_sentry
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
|
||||
with:
|
||||
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
- name: run_bundling::bundle_windows
|
||||
run: script/bundle-windows.ps1 -Architecture x86_64
|
||||
shell: pwsh
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
- name: '@actions/upload-artifact Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.exe'
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.exe
|
||||
path: ${{ env.SETUP_PATH }}
|
||||
timeout-minutes: 60
|
||||
bundle_windows_arm64:
|
||||
if: |-
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'run-bundling') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
runs-on: self-32vcpu-windows-2022
|
||||
env:
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
|
||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
|
||||
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
|
||||
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
|
||||
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
|
||||
FILE_DIGEST: SHA256
|
||||
TIMESTAMP_DIGEST: SHA256
|
||||
TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
- name: steps::setup_sentry
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
|
||||
with:
|
||||
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
- name: run_bundling::bundle_windows
|
||||
run: script/bundle-windows.ps1 -Architecture aarch64
|
||||
shell: pwsh
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
- name: '@actions/upload-artifact Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.exe'
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.exe
|
||||
path: ${{ env.SETUP_PATH }}
|
||||
timeout-minutes: 60
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
2
.github/workflows/unit_evals.yml
vendored
2
.github/workflows/unit_evals.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
||||
|
||||
- name: Run unit evals
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: cargo nextest run --workspace --no-fail-fast --features unit-eval --no-capture -E 'test(::eval_)'
|
||||
run: cargo nextest run --workspace --no-fail-fast --features eval --no-capture -E 'test(::eval_)'
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -25,7 +25,6 @@
|
||||
/crates/collab/seed.json
|
||||
/crates/theme/schemas/theme.json
|
||||
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
|
||||
/crates/project_panel/benches/linux_repo_snapshot.txt
|
||||
/dev.zed.Zed*.json
|
||||
/node_modules/
|
||||
/plugins/bin
|
||||
|
||||
466
Cargo.lock
generated
466
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
35
Cargo.toml
35
Cargo.toml
@@ -13,7 +13,7 @@ members = [
|
||||
"crates/anthropic",
|
||||
"crates/askpass",
|
||||
"crates/assets",
|
||||
"crates/assistant_text_thread",
|
||||
"crates/assistant_context",
|
||||
"crates/assistant_slash_command",
|
||||
"crates/assistant_slash_commands",
|
||||
"crates/audio",
|
||||
@@ -58,7 +58,7 @@ members = [
|
||||
"crates/edit_prediction_context",
|
||||
"crates/zeta2_tools",
|
||||
"crates/editor",
|
||||
"crates/eval",
|
||||
# "crates/eval",
|
||||
"crates/explorer_command_injector",
|
||||
"crates/extension",
|
||||
"crates/extension_api",
|
||||
@@ -70,7 +70,6 @@ members = [
|
||||
"crates/file_finder",
|
||||
"crates/file_icons",
|
||||
"crates/fs",
|
||||
"crates/fs_benchmarks",
|
||||
"crates/fsevent",
|
||||
"crates/fuzzy",
|
||||
"crates/git",
|
||||
@@ -148,7 +147,6 @@ members = [
|
||||
"crates/semantic_version",
|
||||
"crates/session",
|
||||
"crates/settings",
|
||||
"crates/settings_json",
|
||||
"crates/settings_macros",
|
||||
"crates/settings_profile_selector",
|
||||
"crates/settings_ui",
|
||||
@@ -247,7 +245,7 @@ ai_onboarding = { path = "crates/ai_onboarding" }
|
||||
anthropic = { path = "crates/anthropic" }
|
||||
askpass = { path = "crates/askpass" }
|
||||
assets = { path = "crates/assets" }
|
||||
assistant_text_thread = { path = "crates/assistant_text_thread" }
|
||||
assistant_context = { path = "crates/assistant_context" }
|
||||
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
||||
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
|
||||
audio = { path = "crates/audio" }
|
||||
@@ -381,7 +379,6 @@ search = { path = "crates/search" }
|
||||
semantic_version = { path = "crates/semantic_version" }
|
||||
session = { path = "crates/session" }
|
||||
settings = { path = "crates/settings" }
|
||||
settings_json = { path = "crates/settings_json" }
|
||||
settings_macros = { path = "crates/settings_macros" }
|
||||
settings_ui = { path = "crates/settings_ui" }
|
||||
snippet = { path = "crates/snippet" }
|
||||
@@ -440,7 +437,7 @@ zlog_settings = { path = "crates/zlog_settings" }
|
||||
# External crates
|
||||
#
|
||||
|
||||
agent-client-protocol = { version = "0.7.0", features = ["unstable"] }
|
||||
agent-client-protocol = { version = "=0.4.3", features = ["unstable"] }
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = "0.25.1-rc1"
|
||||
any_vec = "0.14"
|
||||
@@ -454,7 +451,7 @@ async-fs = "2.1"
|
||||
async-lock = "2.1"
|
||||
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
|
||||
async-recursion = "1.0.0"
|
||||
async-tar = "0.5.1"
|
||||
async-tar = "0.5.0"
|
||||
async-task = "4.7"
|
||||
async-trait = "0.1"
|
||||
async-tungstenite = "0.31.0"
|
||||
@@ -508,7 +505,6 @@ fork = "0.2.0"
|
||||
futures = "0.3"
|
||||
futures-batch = "0.6.1"
|
||||
futures-lite = "1.13"
|
||||
gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "0090c6b6ef82fff02bc8616645953e778d1acc08" }
|
||||
git2 = { version = "0.20.1", default-features = false }
|
||||
globset = "0.4"
|
||||
handlebars = "4.3"
|
||||
@@ -537,7 +533,7 @@ libc = "0.2"
|
||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||
linkify = "0.10.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "b71ab4eeb27d9758be8092020a46fe33fbca4e33" }
|
||||
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "0874f8742fe55b4dc94308c1e3c0069710d8eeaf" }
|
||||
mach2 = "0.5"
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
metal = "0.29"
|
||||
@@ -583,14 +579,14 @@ partial-json-fixer = "0.5.3"
|
||||
parse_int = "0.9"
|
||||
pciid-parser = "0.8.0"
|
||||
pathdiff = "0.2"
|
||||
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
|
||||
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||
portable-pty = "0.9.0"
|
||||
postage = { version = "0.5", features = ["futures-traits"] }
|
||||
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
|
||||
@@ -778,8 +774,6 @@ windows-capture = { git = "https://github.com/zed-industries/windows-capture.git
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
# https://github.com/rust-lang/cargo/issues/16104
|
||||
incremental = false
|
||||
codegen-units = 16
|
||||
|
||||
# mirror configuration for crates compiled for the build platform
|
||||
@@ -905,5 +899,4 @@ ignored = [
|
||||
"serde",
|
||||
"component",
|
||||
"documented",
|
||||
"sea-orm-macros",
|
||||
]
|
||||
|
||||
2
Procfile.postgrest
Normal file
2
Procfile.postgrest
Normal file
@@ -0,0 +1,2 @@
|
||||
app: postgrest crates/collab/postgrest_app.conf
|
||||
llm: postgrest crates/collab/postgrest_llm.conf
|
||||
@@ -1 +1,2 @@
|
||||
postgrest_llm: postgrest crates/collab/postgrest_llm.conf
|
||||
website: cd ../zed.dev; npm run dev -- --port=3000
|
||||
|
||||
112
REVIEWERS.conl
112
REVIEWERS.conl
@@ -1,112 +0,0 @@
|
||||
; This file contains a list of people who're interested in reviewing pull requests
|
||||
; to certain parts of the code-base.
|
||||
;
|
||||
; This is mostly used internally for PR assignment, and may change over time.
|
||||
;
|
||||
; If you have permission to merge PRs (mostly equivalent to "do you work at Zed Industries"),
|
||||
; we strongly encourage you to put your name in the "all" bucket, but you can also add yourself
|
||||
; to other areas too.
|
||||
|
||||
<all>
|
||||
= @ConradIrwin
|
||||
= @maxdeviant
|
||||
= @SomeoneToIgnore
|
||||
= @probably-neb
|
||||
= @danilo-leal
|
||||
= @Veykril
|
||||
= @kubkon
|
||||
= @p1n3appl3
|
||||
= @dinocosta
|
||||
= @smitbarmase
|
||||
= @cole-miller
|
||||
|
||||
vim
|
||||
= @ConradIrwin
|
||||
= @probably-neb
|
||||
= @p1n3appl3
|
||||
= @dinocosta
|
||||
|
||||
gpui
|
||||
= @mikayla-maki
|
||||
|
||||
git
|
||||
= @cole-miller
|
||||
= @danilo-leal
|
||||
|
||||
linux
|
||||
= @dvdsk
|
||||
= @smitbarmase
|
||||
= @p1n3appl3
|
||||
= @cole-miller
|
||||
= @probably-neb
|
||||
|
||||
windows
|
||||
= @reflectronic
|
||||
= @localcc
|
||||
|
||||
pickers
|
||||
= @p1n3appl3
|
||||
= @dvdsk
|
||||
= @SomeoneToIgnore
|
||||
|
||||
audio
|
||||
= @dvdsk
|
||||
|
||||
helix
|
||||
= @kubkon
|
||||
|
||||
terminal
|
||||
= @kubkon
|
||||
= @Veykril
|
||||
|
||||
debugger
|
||||
= @kubkon
|
||||
= @osiewicz
|
||||
= @Anthony-Eid
|
||||
|
||||
extension
|
||||
= @kubkon
|
||||
|
||||
settings_ui
|
||||
= @probably-neb
|
||||
= @danilo-leal
|
||||
= @Anthony-Eid
|
||||
|
||||
crashes
|
||||
= @p1n3appl3
|
||||
= @Veykril
|
||||
|
||||
ai
|
||||
= @rtfeldman
|
||||
= @danilo-leal
|
||||
= @benbrandt
|
||||
|
||||
design
|
||||
= @danilo-leal
|
||||
|
||||
multi_buffer
|
||||
= @Veykril
|
||||
= @SomeoneToIgnore
|
||||
|
||||
lsp
|
||||
= @osiewicz
|
||||
= @Veykril
|
||||
= @smitbarmase
|
||||
= @SomeoneToIgnore
|
||||
|
||||
languages
|
||||
= @osiewicz
|
||||
= @Veykril
|
||||
= @smitbarmase
|
||||
= @SomeoneToIgnore
|
||||
= @probably-neb
|
||||
|
||||
project_panel
|
||||
= @smitbarmase
|
||||
|
||||
tasks
|
||||
= @SomeoneToIgnore
|
||||
= @Veykril
|
||||
|
||||
docs
|
||||
= @probably-neb
|
||||
@@ -139,7 +139,7 @@
|
||||
"find": "buffer_search::Deploy",
|
||||
"ctrl-f": "buffer_search::Deploy",
|
||||
"ctrl-h": "buffer_search::DeployReplace",
|
||||
"ctrl->": "agent::AddSelectionToThread",
|
||||
"ctrl->": "agent::QuoteSelection",
|
||||
"ctrl-<": "assistant::InsertIntoEditor",
|
||||
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
|
||||
"ctrl-shift-backspace": "editor::GoToPreviousChange",
|
||||
@@ -243,7 +243,7 @@
|
||||
"ctrl-shift-i": "agent::ToggleOptionsMenu",
|
||||
"ctrl-alt-shift-n": "agent::ToggleNewThreadMenu",
|
||||
"shift-alt-escape": "agent::ExpandMessageEditor",
|
||||
"ctrl->": "agent::AddSelectionToThread",
|
||||
"ctrl->": "agent::QuoteSelection",
|
||||
"ctrl-alt-e": "agent::RemoveAllContext",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-enter": "agent::ContinueThread",
|
||||
@@ -366,7 +366,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "RulesLibrary",
|
||||
"context": "PromptLibrary",
|
||||
"bindings": {
|
||||
"new": "rules_library::NewRule",
|
||||
"ctrl-n": "rules_library::NewRule",
|
||||
@@ -539,7 +539,7 @@
|
||||
"ctrl-k ctrl-0": "editor::FoldAll",
|
||||
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
"ctrl-shift-space": "editor::ShowWordCompletions",
|
||||
"ctrl-shift-space": "editor::ShowSignatureHelp",
|
||||
"ctrl-.": "editor::ToggleCodeActions",
|
||||
"ctrl-k r": "editor::RevealInFileManager",
|
||||
"ctrl-k p": "editor::CopyPath",
|
||||
@@ -609,7 +609,7 @@
|
||||
"ctrl-alt-b": "workspace::ToggleRightDock",
|
||||
"ctrl-b": "workspace::ToggleLeftDock",
|
||||
"ctrl-j": "workspace::ToggleBottomDock",
|
||||
"ctrl-alt-y": "workspace::ToggleAllDocks",
|
||||
"ctrl-alt-y": "workspace::CloseAllDocks",
|
||||
"ctrl-alt-0": "workspace::ResetActiveDockSize",
|
||||
// For 0px parameter, uses UI font size value.
|
||||
"ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }],
|
||||
@@ -731,14 +731,6 @@
|
||||
"tab": "editor::ComposeCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && in_snippet",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-right": "editor::NextSnippetTabstop",
|
||||
"alt-left": "editor::PreviousSnippetTabstop"
|
||||
}
|
||||
},
|
||||
// Bindings for accepting edit predictions
|
||||
//
|
||||
// alt-l is provided as an alternative to tab/alt-tab. and will be displayed in the UI. This is
|
||||
@@ -807,7 +799,7 @@
|
||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||
"ctrl-f8": "editor::GoToHunk",
|
||||
"ctrl-shift-f8": "editor::GoToPreviousHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-i": "assistant::InlineAssist",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
@@ -1088,8 +1080,7 @@
|
||||
{
|
||||
"context": "StashList || (StashList > Picker > Editor)",
|
||||
"bindings": {
|
||||
"ctrl-shift-backspace": "stash_picker::DropStashItem",
|
||||
"ctrl-shift-v": "stash_picker::ShowStashItem"
|
||||
"ctrl-shift-backspace": "stash_picker::DropStashItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1102,7 +1093,7 @@
|
||||
"paste": "terminal::Paste",
|
||||
"shift-insert": "terminal::Paste",
|
||||
"ctrl-shift-v": "terminal::Paste",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-i": "assistant::InlineAssist",
|
||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||
"alt-.": ["terminal::SendText", "\u001b."],
|
||||
@@ -1275,22 +1266,12 @@
|
||||
"ctrl-pagedown": "settings_editor::FocusNextFile"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "StashDiff > Editor",
|
||||
"bindings": {
|
||||
"ctrl-space": "git::ApplyCurrentStash",
|
||||
"ctrl-shift-space": "git::PopCurrentStash",
|
||||
"ctrl-shift-backspace": "git::DropCurrentStash"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "SettingsWindow > NavigationMenu",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"up": "settings_editor::FocusPreviousNavEntry",
|
||||
"shift-tab": "settings_editor::FocusPreviousNavEntry",
|
||||
"down": "settings_editor::FocusNextNavEntry",
|
||||
"tab": "settings_editor::FocusNextNavEntry",
|
||||
"right": "settings_editor::ExpandNavEntry",
|
||||
"left": "settings_editor::CollapseNavEntry",
|
||||
"pageup": "settings_editor::FocusPreviousRootNavEntry",
|
||||
@@ -1298,20 +1279,5 @@
|
||||
"home": "settings_editor::FocusFirstNavEntry",
|
||||
"end": "settings_editor::FocusLastNavEntry"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Zeta2Feedback > Editor",
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"ctrl-enter up": "dev::Zeta2RatePredictionPositive",
|
||||
"ctrl-enter down": "dev::Zeta2RatePredictionNegative"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Zeta2Context > Editor",
|
||||
"bindings": {
|
||||
"alt-left": "dev::Zeta2ContextGoBack",
|
||||
"alt-right": "dev::Zeta2ContextGoForward"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
"cmd-\"": "editor::ExpandAllDiffHunks",
|
||||
"cmd-alt-g b": "git::Blame",
|
||||
"cmd-alt-g m": "git::OpenModifiedFiles",
|
||||
"cmd-i": "editor::ShowSignatureHelp",
|
||||
"cmd-shift-space": "editor::ShowSignatureHelp",
|
||||
"f9": "editor::ToggleBreakpoint",
|
||||
"shift-f9": "editor::EditLogBreakpoint",
|
||||
"ctrl-f12": "editor::GoToDeclaration",
|
||||
@@ -163,7 +163,7 @@
|
||||
"cmd-alt-f": "buffer_search::DeployReplace",
|
||||
"cmd-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
|
||||
"cmd-e": ["buffer_search::Deploy", { "focus": false }],
|
||||
"cmd->": "agent::AddSelectionToThread",
|
||||
"cmd->": "agent::QuoteSelection",
|
||||
"cmd-<": "assistant::InsertIntoEditor",
|
||||
"cmd-alt-e": "editor::SelectEnclosingSymbol",
|
||||
"alt-enter": "editor::OpenSelectionsInMultibuffer"
|
||||
@@ -282,7 +282,7 @@
|
||||
"cmd-shift-i": "agent::ToggleOptionsMenu",
|
||||
"cmd-alt-shift-n": "agent::ToggleNewThreadMenu",
|
||||
"shift-alt-escape": "agent::ExpandMessageEditor",
|
||||
"cmd->": "agent::AddSelectionToThread",
|
||||
"cmd->": "agent::QuoteSelection",
|
||||
"cmd-alt-e": "agent::RemoveAllContext",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-ctrl-b": "agent::ToggleBurnMode",
|
||||
@@ -423,7 +423,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "RulesLibrary",
|
||||
"context": "PromptLibrary",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-n": "rules_library::NewRule",
|
||||
@@ -679,7 +679,7 @@
|
||||
"cmd-alt-b": "workspace::ToggleRightDock",
|
||||
"cmd-r": "workspace::ToggleRightDock",
|
||||
"cmd-j": "workspace::ToggleBottomDock",
|
||||
"alt-cmd-y": "workspace::ToggleAllDocks",
|
||||
"alt-cmd-y": "workspace::CloseAllDocks",
|
||||
// For 0px parameter, uses UI font size value.
|
||||
"ctrl-alt-0": "workspace::ResetActiveDockSize",
|
||||
"ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }],
|
||||
@@ -801,14 +801,6 @@
|
||||
"tab": "editor::ComposeCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && in_snippet",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-right": "editor::NextSnippetTabstop",
|
||||
"alt-left": "editor::PreviousSnippetTabstop"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && edit_prediction",
|
||||
"bindings": {
|
||||
@@ -872,7 +864,7 @@
|
||||
"cmd-shift-e": "pane::RevealInProjectPanel",
|
||||
"cmd-f8": "editor::GoToHunk",
|
||||
"cmd-shift-f8": "editor::GoToPreviousHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"cmd-i": "assistant::InlineAssist",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
@@ -1161,8 +1153,7 @@
|
||||
"context": "StashList || (StashList > Picker > Editor)",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-shift-backspace": "stash_picker::DropStashItem",
|
||||
"ctrl-shift-v": "stash_picker::ShowStashItem"
|
||||
"ctrl-shift-backspace": "stash_picker::DropStashItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1176,7 +1167,7 @@
|
||||
"cmd-a": "editor::SelectAll",
|
||||
"cmd-k": "terminal::Clear",
|
||||
"cmd-n": "workspace::NewTerminal",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"cmd-i": "assistant::InlineAssist",
|
||||
"ctrl-_": null, // emacs undo
|
||||
// Some nice conveniences
|
||||
"cmd-backspace": ["terminal::SendText", "\u0015"], // ctrl-u: clear line
|
||||
@@ -1380,23 +1371,12 @@
|
||||
"cmd-}": "settings_editor::FocusNextFile"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "StashDiff > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-space": "git::ApplyCurrentStash",
|
||||
"ctrl-shift-space": "git::PopCurrentStash",
|
||||
"ctrl-shift-backspace": "git::DropCurrentStash"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "SettingsWindow > NavigationMenu",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"up": "settings_editor::FocusPreviousNavEntry",
|
||||
"shift-tab": "settings_editor::FocusPreviousNavEntry",
|
||||
"down": "settings_editor::FocusNextNavEntry",
|
||||
"tab": "settings_editor::FocusNextNavEntry",
|
||||
"right": "settings_editor::ExpandNavEntry",
|
||||
"left": "settings_editor::CollapseNavEntry",
|
||||
"pageup": "settings_editor::FocusPreviousRootNavEntry",
|
||||
@@ -1404,20 +1384,5 @@
|
||||
"home": "settings_editor::FocusFirstNavEntry",
|
||||
"end": "settings_editor::FocusLastNavEntry"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Zeta2Feedback > Editor",
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"cmd-enter up": "dev::Zeta2RatePredictionPositive",
|
||||
"cmd-enter down": "dev::Zeta2RatePredictionNegative"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Zeta2Context > Editor",
|
||||
"bindings": {
|
||||
"alt-left": "dev::Zeta2ContextGoBack",
|
||||
"alt-right": "dev::Zeta2ContextGoForward"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
"ctrl-k z": "editor::ToggleSoftWrap",
|
||||
"ctrl-f": "buffer_search::Deploy",
|
||||
"ctrl-h": "buffer_search::DeployReplace",
|
||||
"ctrl-shift-.": "agent::AddSelectionToThread",
|
||||
"ctrl-shift-.": "agent::QuoteSelection",
|
||||
"ctrl-shift-,": "assistant::InsertIntoEditor",
|
||||
"shift-alt-e": "editor::SelectEnclosingSymbol",
|
||||
"ctrl-shift-backspace": "editor::GoToPreviousChange",
|
||||
@@ -244,7 +244,7 @@
|
||||
"ctrl-shift-i": "agent::ToggleOptionsMenu",
|
||||
// "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
|
||||
"shift-alt-escape": "agent::ExpandMessageEditor",
|
||||
"ctrl-shift-.": "agent::AddSelectionToThread",
|
||||
"ctrl-shift-.": "agent::QuoteSelection",
|
||||
"shift-alt-e": "agent::RemoveAllContext",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-enter": "agent::ContinueThread",
|
||||
@@ -375,7 +375,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "RulesLibrary",
|
||||
"context": "PromptLibrary",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-n": "rules_library::NewRule",
|
||||
@@ -548,7 +548,7 @@
|
||||
"ctrl-k ctrl-0": "editor::FoldAll",
|
||||
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
"ctrl-shift-space": "editor::ShowWordCompletions",
|
||||
"ctrl-shift-space": "editor::ShowSignatureHelp",
|
||||
"ctrl-.": "editor::ToggleCodeActions",
|
||||
"ctrl-k r": "editor::RevealInFileManager",
|
||||
"ctrl-k p": "editor::CopyPath",
|
||||
@@ -614,7 +614,7 @@
|
||||
"ctrl-alt-b": "workspace::ToggleRightDock",
|
||||
"ctrl-b": "workspace::ToggleLeftDock",
|
||||
"ctrl-j": "workspace::ToggleBottomDock",
|
||||
"ctrl-shift-y": "workspace::ToggleAllDocks",
|
||||
"ctrl-shift-y": "workspace::CloseAllDocks",
|
||||
"alt-r": "workspace::ResetActiveDockSize",
|
||||
// For 0px parameter, uses UI font size value.
|
||||
"shift-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }],
|
||||
@@ -736,14 +736,6 @@
|
||||
"tab": "editor::ComposeCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && in_snippet",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-right": "editor::NextSnippetTabstop",
|
||||
"alt-left": "editor::PreviousSnippetTabstop"
|
||||
}
|
||||
},
|
||||
// Bindings for accepting edit predictions
|
||||
//
|
||||
// alt-l is provided as an alternative to tab/alt-tab. and will be displayed in the UI. This is
|
||||
@@ -820,7 +812,7 @@
|
||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||
"ctrl-f8": "editor::GoToHunk",
|
||||
"ctrl-shift-f8": "editor::GoToPreviousHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-i": "assistant::InlineAssist",
|
||||
"ctrl-shift-;": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
@@ -1114,8 +1106,7 @@
|
||||
"context": "StashList || (StashList > Picker > Editor)",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-shift-backspace": "stash_picker::DropStashItem",
|
||||
"ctrl-shift-v": "stash_picker::ShowStashItem"
|
||||
"ctrl-shift-backspace": "stash_picker::DropStashItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1128,7 +1119,7 @@
|
||||
"shift-insert": "terminal::Paste",
|
||||
"ctrl-v": "terminal::Paste",
|
||||
"ctrl-shift-v": "terminal::Paste",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-i": "assistant::InlineAssist",
|
||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||
"alt-.": ["terminal::SendText", "\u001b."],
|
||||
@@ -1303,23 +1294,12 @@
|
||||
"ctrl-pagedown": "settings_editor::FocusNextFile"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "StashDiff > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-space": "git::ApplyCurrentStash",
|
||||
"ctrl-shift-space": "git::PopCurrentStash",
|
||||
"ctrl-shift-backspace": "git::DropCurrentStash"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "SettingsWindow > NavigationMenu",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"up": "settings_editor::FocusPreviousNavEntry",
|
||||
"shift-tab": "settings_editor::FocusPreviousNavEntry",
|
||||
"down": "settings_editor::FocusNextNavEntry",
|
||||
"tab": "settings_editor::FocusNextNavEntry",
|
||||
"right": "settings_editor::ExpandNavEntry",
|
||||
"left": "settings_editor::CollapseNavEntry",
|
||||
"pageup": "settings_editor::FocusPreviousRootNavEntry",
|
||||
@@ -1327,20 +1307,5 @@
|
||||
"home": "settings_editor::FocusFirstNavEntry",
|
||||
"end": "settings_editor::FocusLastNavEntry"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Zeta2Feedback > Editor",
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"ctrl-enter up": "dev::Zeta2RatePredictionPositive",
|
||||
"ctrl-enter down": "dev::Zeta2RatePredictionNegative"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Zeta2Context > Editor",
|
||||
"bindings": {
|
||||
"alt-left": "dev::Zeta2ContextGoBack",
|
||||
"alt-right": "dev::Zeta2ContextGoForward"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
"bindings": {
|
||||
"ctrl-i": "agent::ToggleFocus",
|
||||
"ctrl-shift-i": "agent::ToggleFocus",
|
||||
"ctrl-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
|
||||
"ctrl-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
|
||||
"ctrl-shift-l": "agent::QuoteSelection", // In cursor uses "Ask" mode
|
||||
"ctrl-l": "agent::QuoteSelection", // In cursor uses "Agent" mode
|
||||
"ctrl-k": "assistant::InlineAssist",
|
||||
"ctrl-shift-k": "assistant::InsertIntoEditor"
|
||||
}
|
||||
|
||||
@@ -8,23 +8,13 @@
|
||||
"ctrl-g": "menu::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Workaround to avoid falling back to default bindings.
|
||||
// Unbind so Zed ignores these keys and lets emacs handle them.
|
||||
// NOTE: must be declared before the `Editor` override.
|
||||
// NOTE: in macos the 'ctrl-x' 'ctrl-p' and 'ctrl-n' rebindings are not needed, since they default to 'cmd'.
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-g": null, // currently activates `go_to_line::Toggle` when there is nothing to cancel
|
||||
"ctrl-x": null, // currently activates `editor::Cut` if no following key is pressed for 1 second
|
||||
"ctrl-p": null, // currently activates `file_finder::Toggle` when the cursor is on the first character of the buffer
|
||||
"ctrl-n": null // currently activates `workspace::NewFile` when the cursor is on the last character of the buffer
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"alt-x": "command_palette::Toggle",
|
||||
"ctrl-g": "editor::Cancel",
|
||||
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
|
||||
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
|
||||
"alt-g g": "go_to_line::Toggle", // goto-line
|
||||
"alt-g alt-g": "go_to_line::Toggle", // goto-line
|
||||
"ctrl-space": "editor::SetMark", // set-mark
|
||||
@@ -43,8 +33,8 @@
|
||||
"alt-m": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }], // back-to-indentation
|
||||
"alt-left": "editor::MoveToPreviousWordStart", // left-word
|
||||
"alt-right": "editor::MoveToNextWordEnd", // right-word
|
||||
"alt-f": "editor::MoveToNextWordEnd", // forward-word
|
||||
"alt-b": "editor::MoveToPreviousWordStart", // backward-word
|
||||
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
|
||||
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
|
||||
"alt-u": "editor::ConvertToUpperCase", // upcase-word
|
||||
"alt-l": "editor::ConvertToLowerCase", // downcase-word
|
||||
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
|
||||
@@ -108,7 +98,7 @@
|
||||
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
|
||||
"alt-m": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }],
|
||||
"alt-f": "editor::SelectToNextWordEnd",
|
||||
"alt-b": "editor::SelectToPreviousWordStart",
|
||||
"alt-b": "editor::SelectToPreviousSubwordStart",
|
||||
"alt-{": "editor::SelectToStartOfParagraph",
|
||||
"alt-}": "editor::SelectToEndOfParagraph",
|
||||
"ctrl-up": "editor::SelectToStartOfParagraph",
|
||||
@@ -136,28 +126,15 @@
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
// Example setting for using emacs-style tab
|
||||
// (i.e. indent the current line / selection or perform symbol completion depending on context)
|
||||
// {
|
||||
// "context": "Editor && !showing_code_actions && !showing_completions",
|
||||
// "bindings": {
|
||||
// "tab": "editor::AutoIndent" // indent-for-tab-command
|
||||
// }
|
||||
// },
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"alt-x": "command_palette::Toggle", // execute-extended-command
|
||||
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
|
||||
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
|
||||
// "ctrl-x ctrl-c": "workspace::CloseWindow" // in case you only want to exit the current Zed instance
|
||||
"ctrl-x ctrl-c": "zed::Quit", // save-buffers-kill-terminal
|
||||
"ctrl-x 5 0": "workspace::CloseWindow", // delete-frame
|
||||
"ctrl-x 5 2": "workspace::NewWindow", // make-frame-command
|
||||
"ctrl-x o": "workspace::ActivateNextPane", // other-window
|
||||
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
|
||||
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
|
||||
// "ctrl-x 1": "pane::JoinAll", // in case you prefer to delete the splits but keep the buffers open
|
||||
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
|
||||
"ctrl-x 2": "pane::SplitDown", // split-window-below
|
||||
"ctrl-x 3": "pane::SplitRight", // split-window-right
|
||||
@@ -168,19 +145,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
// Workaround to enable using native emacs from the Zed terminal.
|
||||
// Workaround to enable using emacs in the Zed terminal.
|
||||
// Unbind so Zed ignores these keys and lets emacs handle them.
|
||||
// NOTE:
|
||||
// "terminal::SendKeystroke" only works for a single key stroke (e.g. ctrl-x),
|
||||
// so override with null for compound sequences (e.g. ctrl-x ctrl-c).
|
||||
"context": "Terminal",
|
||||
"bindings": {
|
||||
// If you want to perfect your emacs-in-zed setup, also consider the following.
|
||||
// You may need to enable "option_as_meta" from the Zed settings for "alt-x" to work.
|
||||
// "alt-x": ["terminal::SendKeystroke", "alt-x"],
|
||||
// "ctrl-x": ["terminal::SendKeystroke", "ctrl-x"],
|
||||
// "ctrl-n": ["terminal::SendKeystroke", "ctrl-n"],
|
||||
// ...
|
||||
"ctrl-x ctrl-c": null, // save-buffers-kill-terminal
|
||||
"ctrl-x ctrl-f": null, // find-file
|
||||
"ctrl-x ctrl-s": null, // save-buffer
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"ctrl-shift-f12": "workspace::ToggleAllDocks",
|
||||
"ctrl-shift-f12": "workspace::CloseAllDocks",
|
||||
"ctrl-shift-r": ["pane::DeploySearch", { "replace_enabled": true }],
|
||||
"alt-shift-f10": "task::Spawn",
|
||||
"ctrl-e": "file_finder::Toggle",
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
"bindings": {
|
||||
"cmd-i": "agent::ToggleFocus",
|
||||
"cmd-shift-i": "agent::ToggleFocus",
|
||||
"cmd-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
|
||||
"cmd-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
|
||||
"cmd-shift-l": "agent::QuoteSelection", // In cursor uses "Ask" mode
|
||||
"cmd-l": "agent::QuoteSelection", // In cursor uses "Agent" mode
|
||||
"cmd-k": "assistant::InlineAssist",
|
||||
"cmd-shift-k": "assistant::InsertIntoEditor"
|
||||
}
|
||||
|
||||
@@ -9,19 +9,13 @@
|
||||
"ctrl-g": "menu::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Workaround to avoid falling back to default bindings.
|
||||
// Unbind so Zed ignores these keys and lets emacs handle them.
|
||||
// NOTE: must be declared before the `Editor` override.
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-g": null // currently activates `go_to_line::Toggle` when there is nothing to cancel
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"alt-x": "command_palette::Toggle",
|
||||
"ctrl-g": "editor::Cancel",
|
||||
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
|
||||
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
|
||||
"alt-g g": "go_to_line::Toggle", // goto-line
|
||||
"alt-g alt-g": "go_to_line::Toggle", // goto-line
|
||||
"ctrl-space": "editor::SetMark", // set-mark
|
||||
@@ -40,8 +34,8 @@
|
||||
"alt-m": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }], // back-to-indentation
|
||||
"alt-left": "editor::MoveToPreviousWordStart", // left-word
|
||||
"alt-right": "editor::MoveToNextWordEnd", // right-word
|
||||
"alt-f": "editor::MoveToNextWordEnd", // forward-word
|
||||
"alt-b": "editor::MoveToPreviousWordStart", // backward-word
|
||||
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
|
||||
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
|
||||
"alt-u": "editor::ConvertToUpperCase", // upcase-word
|
||||
"alt-l": "editor::ConvertToLowerCase", // downcase-word
|
||||
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
|
||||
@@ -105,7 +99,7 @@
|
||||
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
|
||||
"alt-m": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }],
|
||||
"alt-f": "editor::SelectToNextWordEnd",
|
||||
"alt-b": "editor::SelectToPreviousWordStart",
|
||||
"alt-b": "editor::SelectToPreviousSubwordStart",
|
||||
"alt-{": "editor::SelectToStartOfParagraph",
|
||||
"alt-}": "editor::SelectToEndOfParagraph",
|
||||
"ctrl-up": "editor::SelectToStartOfParagraph",
|
||||
@@ -133,28 +127,15 @@
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
// Example setting for using emacs-style tab
|
||||
// (i.e. indent the current line / selection or perform symbol completion depending on context)
|
||||
// {
|
||||
// "context": "Editor && !showing_code_actions && !showing_completions",
|
||||
// "bindings": {
|
||||
// "tab": "editor::AutoIndent" // indent-for-tab-command
|
||||
// }
|
||||
// },
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"alt-x": "command_palette::Toggle", // execute-extended-command
|
||||
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
|
||||
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
|
||||
// "ctrl-x ctrl-c": "workspace::CloseWindow" // in case you only want to exit the current Zed instance
|
||||
"ctrl-x ctrl-c": "zed::Quit", // save-buffers-kill-terminal
|
||||
"ctrl-x 5 0": "workspace::CloseWindow", // delete-frame
|
||||
"ctrl-x 5 2": "workspace::NewWindow", // make-frame-command
|
||||
"ctrl-x o": "workspace::ActivateNextPane", // other-window
|
||||
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
|
||||
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
|
||||
// "ctrl-x 1": "pane::JoinAll", // in case you prefer to delete the splits but keep the buffers open
|
||||
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
|
||||
"ctrl-x 2": "pane::SplitDown", // split-window-below
|
||||
"ctrl-x 3": "pane::SplitRight", // split-window-right
|
||||
@@ -165,19 +146,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
// Workaround to enable using native emacs from the Zed terminal.
|
||||
// Workaround to enable using emacs in the Zed terminal.
|
||||
// Unbind so Zed ignores these keys and lets emacs handle them.
|
||||
// NOTE:
|
||||
// "terminal::SendKeystroke" only works for a single key stroke (e.g. ctrl-x),
|
||||
// so override with null for compound sequences (e.g. ctrl-x ctrl-c).
|
||||
"context": "Terminal",
|
||||
"bindings": {
|
||||
// If you want to perfect your emacs-in-zed setup, also consider the following.
|
||||
// You may need to enable "option_as_meta" from the Zed settings for "alt-x" to work.
|
||||
// "alt-x": ["terminal::SendKeystroke", "alt-x"],
|
||||
// "ctrl-x": ["terminal::SendKeystroke", "ctrl-x"],
|
||||
// "ctrl-n": ["terminal::SendKeystroke", "ctrl-n"],
|
||||
// ...
|
||||
"ctrl-x ctrl-c": null, // save-buffers-kill-terminal
|
||||
"ctrl-x ctrl-f": null, // find-file
|
||||
"ctrl-x ctrl-s": null, // save-buffer
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"cmd-shift-f12": "workspace::ToggleAllDocks",
|
||||
"cmd-shift-f12": "workspace::CloseAllDocks",
|
||||
"cmd-shift-r": ["pane::DeploySearch", { "replace_enabled": true }],
|
||||
"ctrl-alt-r": "task::Spawn",
|
||||
"cmd-e": "file_finder::Toggle",
|
||||
|
||||
@@ -220,8 +220,6 @@
|
||||
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
|
||||
"] )": ["vim::UnmatchedForward", { "char": ")" }],
|
||||
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
|
||||
"[ r": "vim::GoToPreviousReference",
|
||||
"] r": "vim::GoToNextReference",
|
||||
// tree-sitter related commands
|
||||
"[ x": "vim::SelectLargerSyntaxNode",
|
||||
"] x": "vim::SelectSmallerSyntaxNode"
|
||||
@@ -424,66 +422,56 @@
|
||||
{
|
||||
"context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
|
||||
"bindings": {
|
||||
// Movement
|
||||
"h": "vim::WrappingLeft",
|
||||
";": "vim::HelixCollapseSelection",
|
||||
":": "command_palette::Toggle",
|
||||
"m": "vim::PushHelixMatch",
|
||||
"s": "vim::HelixSelectRegex",
|
||||
"]": ["vim::PushHelixNext", { "around": true }],
|
||||
"[": ["vim::PushHelixPrevious", { "around": true }],
|
||||
"left": "vim::WrappingLeft",
|
||||
"l": "vim::WrappingRight",
|
||||
"right": "vim::WrappingRight",
|
||||
"t": ["vim::PushFindForward", { "before": true, "multiline": true }],
|
||||
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
|
||||
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
|
||||
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
|
||||
"alt-.": "vim::RepeatFind",
|
||||
|
||||
// Changes
|
||||
"shift-r": "editor::Paste",
|
||||
"`": "vim::ConvertToLowerCase",
|
||||
"alt-`": "vim::ConvertToUpperCase",
|
||||
"insert": "vim::InsertBefore",
|
||||
"shift-u": "editor::Redo",
|
||||
"ctrl-r": "vim::Redo",
|
||||
"h": "vim::WrappingLeft",
|
||||
"l": "vim::WrappingRight",
|
||||
"y": "vim::HelixYank",
|
||||
"p": "vim::HelixPaste",
|
||||
"shift-p": ["vim::HelixPaste", { "before": true }],
|
||||
"alt-;": "vim::OtherEnd",
|
||||
"ctrl-r": "vim::Redo",
|
||||
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
|
||||
"t": ["vim::PushFindForward", { "before": true, "multiline": true }],
|
||||
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
|
||||
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
|
||||
">": "vim::Indent",
|
||||
"<": "vim::Outdent",
|
||||
"=": "vim::AutoIndent",
|
||||
"d": "vim::HelixDelete",
|
||||
"c": "vim::HelixSubstitute",
|
||||
"alt-c": "vim::HelixSubstituteNoYank",
|
||||
|
||||
// Selection manipulation
|
||||
"s": "vim::HelixSelectRegex",
|
||||
"`": "vim::ConvertToLowerCase",
|
||||
"alt-`": "vim::ConvertToUpperCase",
|
||||
"g q": "vim::PushRewrap",
|
||||
"g w": "vim::PushRewrap",
|
||||
"insert": "vim::InsertBefore",
|
||||
"alt-.": "vim::RepeatFind",
|
||||
"alt-s": ["editor::SplitSelectionIntoLines", { "keep_selections": true }],
|
||||
";": "vim::HelixCollapseSelection",
|
||||
"alt-;": "vim::OtherEnd",
|
||||
",": "vim::HelixKeepNewestSelection",
|
||||
"shift-c": "vim::HelixDuplicateBelow",
|
||||
"alt-shift-c": "vim::HelixDuplicateAbove",
|
||||
"%": "editor::SelectAll",
|
||||
"x": "vim::HelixSelectLine",
|
||||
"shift-x": "editor::SelectLine",
|
||||
"ctrl-c": "editor::ToggleComments",
|
||||
"alt-o": "editor::SelectLargerSyntaxNode",
|
||||
"alt-i": "editor::SelectSmallerSyntaxNode",
|
||||
"alt-p": "editor::SelectPreviousSyntaxNode",
|
||||
"alt-n": "editor::SelectNextSyntaxNode",
|
||||
|
||||
// Goto mode
|
||||
"g e": "vim::EndOfDocument",
|
||||
"g h": "vim::StartOfLine",
|
||||
"g n": "pane::ActivateNextItem",
|
||||
"g p": "pane::ActivatePreviousItem",
|
||||
// "tab": "pane::ActivateNextItem",
|
||||
// "shift-tab": "pane::ActivatePrevItem",
|
||||
"shift-h": "pane::ActivatePreviousItem",
|
||||
"shift-l": "pane::ActivateNextItem",
|
||||
"g l": "vim::EndOfLine",
|
||||
"g h": "vim::StartOfLine",
|
||||
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
|
||||
"g e": "vim::EndOfDocument",
|
||||
"g .": "vim::HelixGotoLastModification", // go to last modification
|
||||
"g r": "editor::FindAllReferences", // zed specific
|
||||
"g t": "vim::WindowTop",
|
||||
"g c": "vim::WindowMiddle",
|
||||
"g b": "vim::WindowBottom",
|
||||
"g r": "editor::FindAllReferences", // zed specific
|
||||
"g n": "pane::ActivateNextItem",
|
||||
"shift-l": "pane::ActivateNextItem",
|
||||
"g p": "pane::ActivatePreviousItem",
|
||||
"shift-h": "pane::ActivatePreviousItem",
|
||||
"g .": "vim::HelixGotoLastModification", // go to last modification
|
||||
|
||||
"shift-r": "editor::Paste",
|
||||
"x": "vim::HelixSelectLine",
|
||||
"shift-x": "editor::SelectLine",
|
||||
"%": "editor::SelectAll",
|
||||
// Window mode
|
||||
"space w h": "workspace::ActivatePaneLeft",
|
||||
"space w l": "workspace::ActivatePaneRight",
|
||||
@@ -494,7 +482,6 @@
|
||||
"space w r": "pane::SplitRight",
|
||||
"space w v": "pane::SplitDown",
|
||||
"space w d": "pane::SplitDown",
|
||||
|
||||
// Space mode
|
||||
"space f": "file_finder::Toggle",
|
||||
"space k": "editor::Hover",
|
||||
@@ -505,18 +492,16 @@
|
||||
"space a": "editor::ToggleCodeActions",
|
||||
"space h": "editor::SelectAllMatches",
|
||||
"space c": "editor::ToggleComments",
|
||||
"space p": "editor::Paste",
|
||||
"space y": "editor::Copy",
|
||||
|
||||
// Other
|
||||
":": "command_palette::Toggle",
|
||||
"m": "vim::PushHelixMatch",
|
||||
"]": ["vim::PushHelixNext", { "around": true }],
|
||||
"[": ["vim::PushHelixPrevious", { "around": true }],
|
||||
"g q": "vim::PushRewrap",
|
||||
"g w": "vim::PushRewrap"
|
||||
// "tab": "pane::ActivateNextItem",
|
||||
// "shift-tab": "pane::ActivatePrevItem",
|
||||
"space p": "editor::Paste",
|
||||
"shift-u": "editor::Redo",
|
||||
"ctrl-c": "editor::ToggleComments",
|
||||
"d": "vim::HelixDelete",
|
||||
"c": "vim::HelixSubstitute",
|
||||
"alt-c": "vim::HelixSubstituteNoYank",
|
||||
"shift-c": "vim::HelixDuplicateBelow",
|
||||
"alt-shift-c": "vim::HelixDuplicateAbove",
|
||||
",": "vim::HelixKeepNewestSelection"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -985,9 +970,7 @@
|
||||
"bindings": {
|
||||
"ctrl-h": "editor::Backspace",
|
||||
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
||||
"ctrl-w": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-p": "menu::SelectPrevious",
|
||||
"ctrl-n": "menu::SelectNext"
|
||||
"ctrl-w": "editor::DeleteToPreviousWordStart"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1019,16 +1002,5 @@
|
||||
// and Windows.
|
||||
"alt-l": "editor::AcceptEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "SettingsWindow > NavigationMenu && !search",
|
||||
"bindings": {
|
||||
"l": "settings_editor::ExpandNavEntry",
|
||||
"h": "settings_editor::CollapseNavEntry",
|
||||
"k": "settings_editor::FocusPreviousNavEntry",
|
||||
"j": "settings_editor::FocusNextNavEntry",
|
||||
"g g": "settings_editor::FocusFirstNavEntry",
|
||||
"shift-g": "settings_editor::FocusLastNavEntry"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"$schema": "zed://schemas/settings",
|
||||
/// The displayed name of this project. If not set or null, the root directory name
|
||||
/// The displayed name of this project. If not set or empty, the root directory name
|
||||
/// will be displayed.
|
||||
"project_name": null,
|
||||
"project_name": "",
|
||||
// The name of the Zed theme to use for the UI.
|
||||
//
|
||||
// `mode` is one of:
|
||||
@@ -1091,10 +1091,10 @@
|
||||
// Only the file Zed had indexed will be used, not necessary all the gitignored files.
|
||||
//
|
||||
// Can accept 3 values:
|
||||
// * "all": Use all gitignored files
|
||||
// * "indexed": Use only the files Zed had indexed
|
||||
// * "smart": Be smart and search for ignored when called from a gitignored worktree
|
||||
"include_ignored": "smart"
|
||||
// * `true`: Use all gitignored files
|
||||
// * `false`: Use only the files Zed had indexed
|
||||
// * `null`: Be smart and search for ignored when called from a gitignored worktree
|
||||
"include_ignored": null
|
||||
},
|
||||
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||
// before saving it.
|
||||
@@ -1350,9 +1350,7 @@
|
||||
// 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.
|
||||
"cursor_position_button": true,
|
||||
// Whether to show active line endings button in the status bar.
|
||||
"line_endings_button": false
|
||||
"cursor_position_button": true
|
||||
},
|
||||
// Settings specific to the terminal
|
||||
"terminal": {
|
||||
@@ -1700,7 +1698,6 @@
|
||||
"preferred_line_length": 72
|
||||
},
|
||||
"Go": {
|
||||
"hard_tabs": true,
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true
|
||||
},
|
||||
@@ -1742,7 +1739,7 @@
|
||||
}
|
||||
},
|
||||
"Kotlin": {
|
||||
"language_servers": ["!kotlin-language-server", "kotlin-lsp", "..."]
|
||||
"language_servers": ["kotlin-language-server", "!kotlin-lsp", "..."]
|
||||
},
|
||||
"LaTeX": {
|
||||
"formatter": "language_server",
|
||||
@@ -1770,13 +1767,9 @@
|
||||
}
|
||||
},
|
||||
"Plain Text": {
|
||||
"allow_rewrap": "anywhere",
|
||||
"soft_wrap": "editor_width"
|
||||
"allow_rewrap": "anywhere"
|
||||
},
|
||||
"Python": {
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports.ruff": true
|
||||
},
|
||||
"formatter": {
|
||||
"language_server": {
|
||||
"name": "ruff"
|
||||
@@ -1825,11 +1818,10 @@
|
||||
},
|
||||
"SystemVerilog": {
|
||||
"format_on_save": "off",
|
||||
"language_servers": ["!slang", "..."],
|
||||
"use_on_type_format": false
|
||||
},
|
||||
"Vue.js": {
|
||||
"language_servers": ["vue-language-server", "vtsls", "..."],
|
||||
"language_servers": ["vue-language-server", "..."],
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
|
||||
@@ -49,9 +49,8 @@
|
||||
"panel.background": "#3a3735ff",
|
||||
"panel.focused_border": "#83a598ff",
|
||||
"pane.focused_border": null,
|
||||
"scrollbar.thumb.active_background": "#83a598ac",
|
||||
"scrollbar.thumb.hover_background": "#fbf1c74c",
|
||||
"scrollbar.thumb.background": "#a899844c",
|
||||
"scrollbar.thumb.background": "#fbf1c74c",
|
||||
"scrollbar.thumb.hover_background": "#494340ff",
|
||||
"scrollbar.thumb.border": "#494340ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#373432ff",
|
||||
@@ -455,9 +454,8 @@
|
||||
"panel.background": "#393634ff",
|
||||
"panel.focused_border": "#83a598ff",
|
||||
"pane.focused_border": null,
|
||||
"scrollbar.thumb.active_background": "#83a598ac",
|
||||
"scrollbar.thumb.hover_background": "#fbf1c74c",
|
||||
"scrollbar.thumb.background": "#a899844c",
|
||||
"scrollbar.thumb.background": "#fbf1c74c",
|
||||
"scrollbar.thumb.hover_background": "#494340ff",
|
||||
"scrollbar.thumb.border": "#494340ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#343130ff",
|
||||
@@ -861,9 +859,8 @@
|
||||
"panel.background": "#3b3735ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar.thumb.active_background": "#83a598ac",
|
||||
"scrollbar.thumb.hover_background": "#fbf1c74c",
|
||||
"scrollbar.thumb.background": "#a899844c",
|
||||
"scrollbar.thumb.background": "#fbf1c74c",
|
||||
"scrollbar.thumb.hover_background": "#494340ff",
|
||||
"scrollbar.thumb.border": "#494340ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#393634ff",
|
||||
@@ -1267,9 +1264,8 @@
|
||||
"panel.background": "#ecddb4ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar.thumb.active_background": "#458588ac",
|
||||
"scrollbar.thumb.hover_background": "#2828284c",
|
||||
"scrollbar.thumb.background": "#7c6f644c",
|
||||
"scrollbar.thumb.background": "#2828284c",
|
||||
"scrollbar.thumb.hover_background": "#ddcca7ff",
|
||||
"scrollbar.thumb.border": "#ddcca7ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#eee0b7ff",
|
||||
@@ -1673,9 +1669,8 @@
|
||||
"panel.background": "#ecddb5ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar.thumb.active_background": "#458588ac",
|
||||
"scrollbar.thumb.hover_background": "#2828284c",
|
||||
"scrollbar.thumb.background": "#7c6f644c",
|
||||
"scrollbar.thumb.background": "#2828284c",
|
||||
"scrollbar.thumb.hover_background": "#ddcca7ff",
|
||||
"scrollbar.thumb.border": "#ddcca7ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#eee1bbff",
|
||||
@@ -2079,9 +2074,8 @@
|
||||
"panel.background": "#ecdcb3ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar.thumb.active_background": "#458588ac",
|
||||
"scrollbar.thumb.hover_background": "#2828284c",
|
||||
"scrollbar.thumb.background": "#7c6f644c",
|
||||
"scrollbar.thumb.background": "#2828284c",
|
||||
"scrollbar.thumb.hover_background": "#ddcca7ff",
|
||||
"scrollbar.thumb.border": "#ddcca7ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#eddeb5ff",
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
ARG NAMESPACE_BASE_IMAGE_REF=""
|
||||
|
||||
# Your image must build FROM NAMESPACE_BASE_IMAGE_REF
|
||||
FROM ${NAMESPACE_BASE_IMAGE_REF} AS base
|
||||
|
||||
# Remove problematic git-lfs packagecloud source
|
||||
RUN sudo rm -f /etc/apt/sources.list.d/*git-lfs*.list
|
||||
# Install git and SSH for cloning private repositories
|
||||
RUN sudo apt-get update && \
|
||||
sudo apt-get install -y git openssh-client
|
||||
|
||||
# Clone the Zed repository
|
||||
RUN git clone https://github.com/zed-industries/zed.git ~/zed
|
||||
|
||||
# Run the Linux installation script
|
||||
WORKDIR /home/runner/zed
|
||||
RUN ./script/linux
|
||||
|
||||
# Clean up unnecessary files to reduce image size
|
||||
RUN sudo apt-get clean && sudo rm -rf \
|
||||
/home/runner/zed
|
||||
@@ -9,9 +9,6 @@ disallowed-methods = [
|
||||
{ path = "std::process::Command::spawn", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::spawn" },
|
||||
{ path = "std::process::Command::output", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::output" },
|
||||
{ path = "std::process::Command::status", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::status" },
|
||||
{ path = "std::process::Command::stdin", reason = "`smol::process::Command::from()` does not preserve stdio configuration", replacement = "smol::process::Command::stdin" },
|
||||
{ path = "std::process::Command::stdout", reason = "`smol::process::Command::from()` does not preserve stdio configuration", replacement = "smol::process::Command::stdout" },
|
||||
{ path = "std::process::Command::stderr", reason = "`smol::process::Command::from()` does not preserve stdio configuration", replacement = "smol::process::Command::stderr" },
|
||||
{ path = "serde_json::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892. Use `serde_json::from_slice` instead." },
|
||||
{ path = "serde_json_lenient::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892, Use `serde_json_lenient::from_slice` instead." },
|
||||
]
|
||||
|
||||
26
compose.yml
26
compose.yml
@@ -33,6 +33,32 @@ services:
|
||||
volumes:
|
||||
- ./livekit.yaml:/livekit.yaml
|
||||
|
||||
postgrest_app:
|
||||
image: docker.io/postgrest/postgrest
|
||||
container_name: postgrest_app
|
||||
ports:
|
||||
- 8081:8081
|
||||
environment:
|
||||
PGRST_DB_URI: postgres://postgres@postgres:5432/zed
|
||||
volumes:
|
||||
- ./crates/collab/postgrest_app.conf:/etc/postgrest.conf
|
||||
command: postgrest /etc/postgrest.conf
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
postgrest_llm:
|
||||
image: docker.io/postgrest/postgrest
|
||||
container_name: postgrest_llm
|
||||
ports:
|
||||
- 8082:8082
|
||||
environment:
|
||||
PGRST_DB_URI: postgres://postgres@postgres:5432/zed_llm
|
||||
volumes:
|
||||
- ./crates/collab/postgrest_llm.conf:/etc/postgrest.conf
|
||||
command: postgrest /etc/postgrest.conf
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
stripe-mock:
|
||||
image: docker.io/stripe/stripe-mock:v0.178.0
|
||||
ports:
|
||||
|
||||
@@ -35,7 +35,7 @@ use std::rc::Rc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{fmt::Display, mem, path::PathBuf, sync::Arc};
|
||||
use ui::App;
|
||||
use util::{ResultExt, get_default_system_shell_preferring_bash, paths::PathStyle};
|
||||
use util::{ResultExt, get_default_system_shell_preferring_bash};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -95,14 +95,9 @@ pub enum AssistantMessageChunk {
|
||||
}
|
||||
|
||||
impl AssistantMessageChunk {
|
||||
pub fn from_str(
|
||||
chunk: &str,
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
path_style: PathStyle,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
pub fn from_str(chunk: &str, language_registry: &Arc<LanguageRegistry>, cx: &mut App) -> Self {
|
||||
Self::Message {
|
||||
block: ContentBlock::new(chunk.into(), language_registry, path_style, cx),
|
||||
block: ContentBlock::new(chunk.into(), language_registry, cx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +186,6 @@ impl ToolCall {
|
||||
tool_call: acp::ToolCall,
|
||||
status: ToolCallStatus,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
path_style: PathStyle,
|
||||
terminals: &HashMap<acp::TerminalId, Entity<Terminal>>,
|
||||
cx: &mut App,
|
||||
) -> Result<Self> {
|
||||
@@ -205,7 +199,6 @@ impl ToolCall {
|
||||
content.push(ToolCallContent::from_acp(
|
||||
item,
|
||||
language_registry.clone(),
|
||||
path_style,
|
||||
terminals,
|
||||
cx,
|
||||
)?);
|
||||
@@ -230,7 +223,6 @@ impl ToolCall {
|
||||
&mut self,
|
||||
fields: acp::ToolCallUpdateFields,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
path_style: PathStyle,
|
||||
terminals: &HashMap<acp::TerminalId, Entity<Terminal>>,
|
||||
cx: &mut App,
|
||||
) -> Result<()> {
|
||||
@@ -268,13 +260,12 @@ impl ToolCall {
|
||||
|
||||
// Reuse existing content if we can
|
||||
for (old, new) in self.content.iter_mut().zip(content.by_ref()) {
|
||||
old.update_from_acp(new, language_registry.clone(), path_style, terminals, cx)?;
|
||||
old.update_from_acp(new, language_registry.clone(), terminals, cx)?;
|
||||
}
|
||||
for new in content {
|
||||
self.content.push(ToolCallContent::from_acp(
|
||||
new,
|
||||
language_registry.clone(),
|
||||
path_style,
|
||||
terminals,
|
||||
cx,
|
||||
)?)
|
||||
@@ -337,7 +328,7 @@ impl ToolCall {
|
||||
location: acp::ToolCallLocation,
|
||||
project: WeakEntity<Project>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Option<ResolvedLocation> {
|
||||
) -> Option<AgentLocation> {
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project
|
||||
@@ -359,14 +350,17 @@ impl ToolCall {
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
Some(ResolvedLocation { buffer, position })
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position,
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_locations(
|
||||
&self,
|
||||
project: Entity<Project>,
|
||||
cx: &mut App,
|
||||
) -> Task<Vec<Option<ResolvedLocation>>> {
|
||||
) -> Task<Vec<Option<AgentLocation>>> {
|
||||
let locations = self.locations.clone();
|
||||
project.update(cx, |_, cx| {
|
||||
cx.spawn(async move |project, cx| {
|
||||
@@ -380,23 +374,6 @@ impl ToolCall {
|
||||
}
|
||||
}
|
||||
|
||||
// Separate so we can hold a strong reference to the buffer
|
||||
// for saving on the thread
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
struct ResolvedLocation {
|
||||
buffer: Entity<Buffer>,
|
||||
position: Anchor,
|
||||
}
|
||||
|
||||
impl From<&ResolvedLocation> for AgentLocation {
|
||||
fn from(value: &ResolvedLocation) -> Self {
|
||||
Self {
|
||||
buffer: value.buffer.downgrade(),
|
||||
position: value.position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ToolCallStatus {
|
||||
/// The tool call hasn't started running yet, but we start showing it to
|
||||
@@ -459,23 +436,21 @@ impl ContentBlock {
|
||||
pub fn new(
|
||||
block: acp::ContentBlock,
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
path_style: PathStyle,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let mut this = Self::Empty;
|
||||
this.append(block, language_registry, path_style, cx);
|
||||
this.append(block, language_registry, cx);
|
||||
this
|
||||
}
|
||||
|
||||
pub fn new_combined(
|
||||
blocks: impl IntoIterator<Item = acp::ContentBlock>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
path_style: PathStyle,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let mut this = Self::Empty;
|
||||
for block in blocks {
|
||||
this.append(block, &language_registry, path_style, cx);
|
||||
this.append(block, &language_registry, cx);
|
||||
}
|
||||
this
|
||||
}
|
||||
@@ -484,7 +459,6 @@ impl ContentBlock {
|
||||
&mut self,
|
||||
block: acp::ContentBlock,
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
path_style: PathStyle,
|
||||
cx: &mut App,
|
||||
) {
|
||||
if matches!(self, ContentBlock::Empty)
|
||||
@@ -494,7 +468,7 @@ impl ContentBlock {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_content = self.block_string_contents(block, path_style);
|
||||
let new_content = self.block_string_contents(block);
|
||||
|
||||
match self {
|
||||
ContentBlock::Empty => {
|
||||
@@ -504,7 +478,7 @@ impl ContentBlock {
|
||||
markdown.update(cx, |markdown, cx| markdown.append(&new_content, cx));
|
||||
}
|
||||
ContentBlock::ResourceLink { resource_link } => {
|
||||
let existing_content = Self::resource_link_md(&resource_link.uri, path_style);
|
||||
let existing_content = Self::resource_link_md(&resource_link.uri);
|
||||
let combined = format!("{}\n{}", existing_content, new_content);
|
||||
|
||||
*self = Self::create_markdown_block(combined, language_registry, cx);
|
||||
@@ -523,11 +497,11 @@ impl ContentBlock {
|
||||
}
|
||||
}
|
||||
|
||||
fn block_string_contents(&self, block: acp::ContentBlock, path_style: PathStyle) -> String {
|
||||
fn block_string_contents(&self, block: acp::ContentBlock) -> String {
|
||||
match block {
|
||||
acp::ContentBlock::Text(text_content) => text_content.text,
|
||||
acp::ContentBlock::ResourceLink(resource_link) => {
|
||||
Self::resource_link_md(&resource_link.uri, path_style)
|
||||
Self::resource_link_md(&resource_link.uri)
|
||||
}
|
||||
acp::ContentBlock::Resource(acp::EmbeddedResource {
|
||||
resource:
|
||||
@@ -536,14 +510,14 @@ impl ContentBlock {
|
||||
..
|
||||
}),
|
||||
..
|
||||
}) => Self::resource_link_md(&uri, path_style),
|
||||
}) => Self::resource_link_md(&uri),
|
||||
acp::ContentBlock::Image(image) => Self::image_md(&image),
|
||||
acp::ContentBlock::Audio(_) | acp::ContentBlock::Resource(_) => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn resource_link_md(uri: &str, path_style: PathStyle) -> String {
|
||||
if let Some(uri) = MentionUri::parse(uri, path_style).log_err() {
|
||||
fn resource_link_md(uri: &str) -> String {
|
||||
if let Some(uri) = MentionUri::parse(uri).log_err() {
|
||||
uri.as_link().to_string()
|
||||
} else {
|
||||
uri.to_string()
|
||||
@@ -589,7 +563,6 @@ impl ToolCallContent {
|
||||
pub fn from_acp(
|
||||
content: acp::ToolCallContent,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
path_style: PathStyle,
|
||||
terminals: &HashMap<acp::TerminalId, Entity<Terminal>>,
|
||||
cx: &mut App,
|
||||
) -> Result<Self> {
|
||||
@@ -597,7 +570,6 @@ impl ToolCallContent {
|
||||
acp::ToolCallContent::Content { content } => Ok(Self::ContentBlock(ContentBlock::new(
|
||||
content,
|
||||
&language_registry,
|
||||
path_style,
|
||||
cx,
|
||||
))),
|
||||
acp::ToolCallContent::Diff { diff } => Ok(Self::Diff(cx.new(|cx| {
|
||||
@@ -621,7 +593,6 @@ impl ToolCallContent {
|
||||
&mut self,
|
||||
new: acp::ToolCallContent,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
path_style: PathStyle,
|
||||
terminals: &HashMap<acp::TerminalId, Entity<Terminal>>,
|
||||
cx: &mut App,
|
||||
) -> Result<()> {
|
||||
@@ -637,7 +608,7 @@ impl ToolCallContent {
|
||||
};
|
||||
|
||||
if needs_update {
|
||||
*self = Self::from_acp(new, language_registry, path_style, terminals, cx)?;
|
||||
*self = Self::from_acp(new, language_registry, terminals, cx)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1120,13 +1091,13 @@ impl AcpThread {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<(), acp::Error> {
|
||||
match update {
|
||||
acp::SessionUpdate::UserMessageChunk(acp::ContentChunk { content, .. }) => {
|
||||
acp::SessionUpdate::UserMessageChunk { content } => {
|
||||
self.push_user_content_block(None, content, cx);
|
||||
}
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk { content, .. }) => {
|
||||
acp::SessionUpdate::AgentMessageChunk { content } => {
|
||||
self.push_assistant_content_block(content, false, cx);
|
||||
}
|
||||
acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk { content, .. }) => {
|
||||
acp::SessionUpdate::AgentThoughtChunk { content } => {
|
||||
self.push_assistant_content_block(content, true, cx);
|
||||
}
|
||||
acp::SessionUpdate::ToolCall(tool_call) => {
|
||||
@@ -1138,14 +1109,12 @@ impl AcpThread {
|
||||
acp::SessionUpdate::Plan(plan) => {
|
||||
self.update_plan(plan, cx);
|
||||
}
|
||||
acp::SessionUpdate::AvailableCommandsUpdate(acp::AvailableCommandsUpdate {
|
||||
available_commands,
|
||||
..
|
||||
}) => cx.emit(AcpThreadEvent::AvailableCommandsUpdated(available_commands)),
|
||||
acp::SessionUpdate::CurrentModeUpdate(acp::CurrentModeUpdate {
|
||||
current_mode_id,
|
||||
..
|
||||
}) => cx.emit(AcpThreadEvent::ModeUpdated(current_mode_id)),
|
||||
acp::SessionUpdate::AvailableCommandsUpdate { available_commands } => {
|
||||
cx.emit(AcpThreadEvent::AvailableCommandsUpdated(available_commands))
|
||||
}
|
||||
acp::SessionUpdate::CurrentModeUpdate { current_mode_id } => {
|
||||
cx.emit(AcpThreadEvent::ModeUpdated(current_mode_id))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1157,7 +1126,6 @@ impl AcpThread {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let language_registry = self.project.read(cx).languages().clone();
|
||||
let path_style = self.project.read(cx).path_style(cx);
|
||||
let entries_len = self.entries.len();
|
||||
|
||||
if let Some(last_entry) = self.entries.last_mut()
|
||||
@@ -1169,12 +1137,12 @@ impl AcpThread {
|
||||
}) = last_entry
|
||||
{
|
||||
*id = message_id.or(id.take());
|
||||
content.append(chunk.clone(), &language_registry, path_style, cx);
|
||||
content.append(chunk.clone(), &language_registry, cx);
|
||||
chunks.push(chunk);
|
||||
let idx = entries_len - 1;
|
||||
cx.emit(AcpThreadEvent::EntryUpdated(idx));
|
||||
} else {
|
||||
let content = ContentBlock::new(chunk.clone(), &language_registry, path_style, cx);
|
||||
let content = ContentBlock::new(chunk.clone(), &language_registry, cx);
|
||||
self.push_entry(
|
||||
AgentThreadEntry::UserMessage(UserMessage {
|
||||
id: message_id,
|
||||
@@ -1194,7 +1162,6 @@ impl AcpThread {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let language_registry = self.project.read(cx).languages().clone();
|
||||
let path_style = self.project.read(cx).path_style(cx);
|
||||
let entries_len = self.entries.len();
|
||||
if let Some(last_entry) = self.entries.last_mut()
|
||||
&& let AgentThreadEntry::AssistantMessage(AssistantMessage { chunks }) = last_entry
|
||||
@@ -1204,10 +1171,10 @@ impl AcpThread {
|
||||
match (chunks.last_mut(), is_thought) {
|
||||
(Some(AssistantMessageChunk::Message { block }), false)
|
||||
| (Some(AssistantMessageChunk::Thought { block }), true) => {
|
||||
block.append(chunk, &language_registry, path_style, cx)
|
||||
block.append(chunk, &language_registry, cx)
|
||||
}
|
||||
_ => {
|
||||
let block = ContentBlock::new(chunk, &language_registry, path_style, cx);
|
||||
let block = ContentBlock::new(chunk, &language_registry, cx);
|
||||
if is_thought {
|
||||
chunks.push(AssistantMessageChunk::Thought { block })
|
||||
} else {
|
||||
@@ -1216,7 +1183,7 @@ impl AcpThread {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let block = ContentBlock::new(chunk, &language_registry, path_style, cx);
|
||||
let block = ContentBlock::new(chunk, &language_registry, cx);
|
||||
let chunk = if is_thought {
|
||||
AssistantMessageChunk::Thought { block }
|
||||
} else {
|
||||
@@ -1268,7 +1235,6 @@ impl AcpThread {
|
||||
) -> Result<()> {
|
||||
let update = update.into();
|
||||
let languages = self.project.read(cx).languages().clone();
|
||||
let path_style = self.project.read(cx).path_style(cx);
|
||||
|
||||
let ix = match self.index_for_tool_call(update.id()) {
|
||||
Some(ix) => ix,
|
||||
@@ -1285,7 +1251,6 @@ impl AcpThread {
|
||||
meta: None,
|
||||
}),
|
||||
&languages,
|
||||
path_style,
|
||||
cx,
|
||||
))],
|
||||
status: ToolCallStatus::Failed,
|
||||
@@ -1305,7 +1270,7 @@ impl AcpThread {
|
||||
match update {
|
||||
ToolCallUpdate::UpdateFields(update) => {
|
||||
let location_updated = update.fields.locations.is_some();
|
||||
call.update_fields(update.fields, languages, path_style, &self.terminals, cx)?;
|
||||
call.update_fields(update.fields, languages, &self.terminals, cx)?;
|
||||
if location_updated {
|
||||
self.resolve_locations(update.id, cx);
|
||||
}
|
||||
@@ -1344,7 +1309,6 @@ impl AcpThread {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<(), acp::Error> {
|
||||
let language_registry = self.project.read(cx).languages().clone();
|
||||
let path_style = self.project.read(cx).path_style(cx);
|
||||
let id = update.id.clone();
|
||||
|
||||
if let Some(ix) = self.index_for_tool_call(&id) {
|
||||
@@ -1352,13 +1316,7 @@ impl AcpThread {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
call.update_fields(
|
||||
update.fields,
|
||||
language_registry,
|
||||
path_style,
|
||||
&self.terminals,
|
||||
cx,
|
||||
)?;
|
||||
call.update_fields(update.fields, language_registry, &self.terminals, cx)?;
|
||||
call.status = status;
|
||||
|
||||
cx.emit(AcpThreadEvent::EntryUpdated(ix));
|
||||
@@ -1367,7 +1325,6 @@ impl AcpThread {
|
||||
update.try_into()?,
|
||||
status,
|
||||
language_registry,
|
||||
self.project.read(cx).path_style(cx),
|
||||
&self.terminals,
|
||||
cx,
|
||||
)?;
|
||||
@@ -1436,46 +1393,35 @@ impl AcpThread {
|
||||
let task = tool_call.resolve_locations(project, cx);
|
||||
cx.spawn(async move |this, cx| {
|
||||
let resolved_locations = task.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
let project = this.project.clone();
|
||||
|
||||
for location in resolved_locations.iter().flatten() {
|
||||
this.shared_buffers
|
||||
.insert(location.buffer.clone(), location.buffer.read(cx).snapshot());
|
||||
}
|
||||
let Some((ix, tool_call)) = this.tool_call_mut(&id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(Some(location)) = resolved_locations.last() {
|
||||
project.update(cx, |project, cx| {
|
||||
let should_ignore = if let Some(agent_location) = project
|
||||
.agent_location()
|
||||
.filter(|agent_location| agent_location.buffer == location.buffer)
|
||||
{
|
||||
let snapshot = location.buffer.read(cx).snapshot();
|
||||
let old_position = agent_location.position.to_point(&snapshot);
|
||||
let new_position = location.position.to_point(&snapshot);
|
||||
|
||||
// ignore this so that when we get updates from the edit tool
|
||||
// the position doesn't reset to the startof line
|
||||
old_position.row == new_position.row
|
||||
&& old_position.column > new_position.column
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !should_ignore {
|
||||
project.set_agent_location(Some(location.into()), cx);
|
||||
if let Some(agent_location) = project.agent_location() {
|
||||
let should_ignore = agent_location.buffer == location.buffer
|
||||
&& location
|
||||
.buffer
|
||||
.update(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
let old_position =
|
||||
agent_location.position.to_point(&snapshot);
|
||||
let new_position = location.position.to_point(&snapshot);
|
||||
// ignore this so that when we get updates from the edit tool
|
||||
// the position doesn't reset to the startof line
|
||||
old_position.row == new_position.row
|
||||
&& old_position.column > new_position.column
|
||||
})
|
||||
.ok()
|
||||
.unwrap_or_default();
|
||||
if !should_ignore {
|
||||
project.set_agent_location(Some(location.clone()), cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let resolved_locations = resolved_locations
|
||||
.iter()
|
||||
.map(|l| l.as_ref().map(|l| AgentLocation::from(l)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if tool_call.resolved_locations != resolved_locations {
|
||||
tool_call.resolved_locations = resolved_locations;
|
||||
cx.emit(AcpThreadEvent::EntryUpdated(ix));
|
||||
@@ -1647,7 +1593,6 @@ impl AcpThread {
|
||||
let block = ContentBlock::new_combined(
|
||||
message.clone(),
|
||||
self.project.read(cx).languages().clone(),
|
||||
self.project.read(cx).path_style(cx),
|
||||
cx,
|
||||
);
|
||||
let request = acp::PromptRequest {
|
||||
@@ -2616,19 +2561,17 @@ mod tests {
|
||||
thread.update(&mut cx, |thread, cx| {
|
||||
thread
|
||||
.handle_session_update(
|
||||
acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk {
|
||||
acp::SessionUpdate::AgentThoughtChunk {
|
||||
content: "Thinking ".into(),
|
||||
meta: None,
|
||||
}),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
thread
|
||||
.handle_session_update(
|
||||
acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk {
|
||||
acp::SessionUpdate::AgentThoughtChunk {
|
||||
content: "hard!".into(),
|
||||
meta: None,
|
||||
}),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -3127,10 +3070,9 @@ mod tests {
|
||||
thread.update(&mut cx, |thread, cx| {
|
||||
thread
|
||||
.handle_session_update(
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
|
||||
acp::SessionUpdate::AgentMessageChunk {
|
||||
content: content.text.to_uppercase().into(),
|
||||
meta: None,
|
||||
}),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -3487,10 +3429,9 @@ mod tests {
|
||||
thread.update(&mut cx, |thread, cx| {
|
||||
thread
|
||||
.handle_session_update(
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
|
||||
acp::SessionUpdate::AgentMessageChunk {
|
||||
content: content.text.to_uppercase().into(),
|
||||
meta: None,
|
||||
}),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -236,21 +236,21 @@ impl PendingDiff {
|
||||
fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
|
||||
let ranges = self.excerpt_ranges(cx);
|
||||
let base_text = self.base_text.clone();
|
||||
let new_buffer = self.new_buffer.read(cx);
|
||||
let language_registry = new_buffer.language_registry();
|
||||
let language_registry = self.new_buffer.read(cx).language_registry();
|
||||
|
||||
let path = new_buffer
|
||||
let path = self
|
||||
.new_buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.map(|file| file.path().display(file.path_style(cx)))
|
||||
.unwrap_or("untitled".into())
|
||||
.into();
|
||||
let replica_id = new_buffer.replica_id();
|
||||
|
||||
// Replace the buffer in the multibuffer with the snapshot
|
||||
let buffer = cx.new(|cx| {
|
||||
let language = self.new_buffer.read(cx).language().cloned();
|
||||
let buffer = TextBuffer::new_normalized(
|
||||
replica_id,
|
||||
0,
|
||||
cx.entity_id().as_non_zero_u64().into(),
|
||||
self.new_buffer.read(cx).line_ending(),
|
||||
self.new_buffer.read(cx).as_rope().clone(),
|
||||
|
||||
@@ -7,10 +7,10 @@ use std::{
|
||||
fmt,
|
||||
ops::RangeInclusive,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
use ui::{App, IconName, SharedString};
|
||||
use url::Url;
|
||||
use util::paths::PathStyle;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
pub enum MentionUri {
|
||||
@@ -49,7 +49,7 @@ pub enum MentionUri {
|
||||
}
|
||||
|
||||
impl MentionUri {
|
||||
pub fn parse(input: &str, path_style: PathStyle) -> Result<Self> {
|
||||
pub fn parse(input: &str) -> Result<Self> {
|
||||
fn parse_line_range(fragment: &str) -> Result<RangeInclusive<u32>> {
|
||||
let range = fragment
|
||||
.strip_prefix("L")
|
||||
@@ -74,34 +74,25 @@ impl MentionUri {
|
||||
let path = url.path();
|
||||
match url.scheme() {
|
||||
"file" => {
|
||||
let path = if path_style.is_windows() {
|
||||
path.trim_start_matches("/")
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
let path = url.to_file_path().ok().context("Extracting file path")?;
|
||||
if let Some(fragment) = url.fragment() {
|
||||
let line_range = parse_line_range(fragment)?;
|
||||
if let Some(name) = single_query_param(&url, "symbol")? {
|
||||
Ok(Self::Symbol {
|
||||
name,
|
||||
abs_path: path.into(),
|
||||
abs_path: path,
|
||||
line_range,
|
||||
})
|
||||
} else {
|
||||
Ok(Self::Selection {
|
||||
abs_path: Some(path.into()),
|
||||
abs_path: Some(path),
|
||||
line_range,
|
||||
})
|
||||
}
|
||||
} else if input.ends_with("/") {
|
||||
Ok(Self::Directory {
|
||||
abs_path: path.into(),
|
||||
})
|
||||
Ok(Self::Directory { abs_path: path })
|
||||
} else {
|
||||
Ok(Self::File {
|
||||
abs_path: path.into(),
|
||||
})
|
||||
Ok(Self::File { abs_path: path })
|
||||
}
|
||||
}
|
||||
"zed" => {
|
||||
@@ -222,14 +213,18 @@ impl MentionUri {
|
||||
pub fn to_uri(&self) -> Url {
|
||||
match self {
|
||||
MentionUri::File { abs_path } => {
|
||||
let mut url = Url::parse("file:///").unwrap();
|
||||
url.set_path(&abs_path.to_string_lossy());
|
||||
let mut url = Url::parse("zed:///").unwrap();
|
||||
url.set_path("/agent/file");
|
||||
url.query_pairs_mut()
|
||||
.append_pair("path", &abs_path.to_string_lossy());
|
||||
url
|
||||
}
|
||||
MentionUri::PastedImage => Url::parse("zed:///agent/pasted-image").unwrap(),
|
||||
MentionUri::Directory { abs_path } => {
|
||||
let mut url = Url::parse("file:///").unwrap();
|
||||
url.set_path(&abs_path.to_string_lossy());
|
||||
let mut url = Url::parse("zed:///").unwrap();
|
||||
url.set_path("/agent/directory");
|
||||
url.query_pairs_mut()
|
||||
.append_pair("path", &abs_path.to_string_lossy());
|
||||
url
|
||||
}
|
||||
MentionUri::Symbol {
|
||||
@@ -237,9 +232,10 @@ impl MentionUri {
|
||||
name,
|
||||
line_range,
|
||||
} => {
|
||||
let mut url = Url::parse("file:///").unwrap();
|
||||
url.set_path(&abs_path.to_string_lossy());
|
||||
url.query_pairs_mut().append_pair("symbol", name);
|
||||
let mut url = Url::parse("zed:///").unwrap();
|
||||
url.set_path(&format!("/agent/symbol/{name}"));
|
||||
url.query_pairs_mut()
|
||||
.append_pair("path", &abs_path.to_string_lossy());
|
||||
url.set_fragment(Some(&format!(
|
||||
"L{}:{}",
|
||||
line_range.start() + 1,
|
||||
@@ -251,14 +247,13 @@ impl MentionUri {
|
||||
abs_path,
|
||||
line_range,
|
||||
} => {
|
||||
let mut url = if let Some(path) = abs_path {
|
||||
let mut url = Url::parse("file:///").unwrap();
|
||||
url.set_path(&path.to_string_lossy());
|
||||
url
|
||||
let mut url = Url::parse("zed:///").unwrap();
|
||||
if let Some(abs_path) = abs_path {
|
||||
url.set_path("/agent/selection");
|
||||
url.query_pairs_mut()
|
||||
.append_pair("path", &abs_path.to_string_lossy());
|
||||
} else {
|
||||
let mut url = Url::parse("zed:///").unwrap();
|
||||
url.set_path("/agent/untitled-buffer");
|
||||
url
|
||||
};
|
||||
url.set_fragment(Some(&format!(
|
||||
"L{}:{}",
|
||||
@@ -293,6 +288,14 @@ impl MentionUri {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MentionUri {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||
Self::parse(s)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MentionLink<'a>(&'a MentionUri);
|
||||
|
||||
impl fmt::Display for MentionLink<'_> {
|
||||
@@ -335,81 +338,93 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_file_uri() {
|
||||
let file_uri = uri!("file:///path/to/file.rs");
|
||||
let parsed = MentionUri::parse(file_uri, PathStyle::local()).unwrap();
|
||||
let old_uri = uri!("file:///path/to/file.rs");
|
||||
let parsed = MentionUri::parse(old_uri).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::File { abs_path } => {
|
||||
assert_eq!(abs_path, Path::new(path!("/path/to/file.rs")));
|
||||
assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/file.rs"));
|
||||
}
|
||||
_ => panic!("Expected File variant"),
|
||||
}
|
||||
assert_eq!(parsed.to_uri().to_string(), file_uri);
|
||||
let new_uri = parsed.to_uri().to_string();
|
||||
assert!(new_uri.starts_with("zed:///agent/file"));
|
||||
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_directory_uri() {
|
||||
let file_uri = uri!("file:///path/to/dir/");
|
||||
let parsed = MentionUri::parse(file_uri, PathStyle::local()).unwrap();
|
||||
let old_uri = uri!("file:///path/to/dir/");
|
||||
let parsed = MentionUri::parse(old_uri).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::Directory { abs_path } => {
|
||||
assert_eq!(abs_path, Path::new(path!("/path/to/dir/")));
|
||||
assert_eq!(abs_path.to_str().unwrap(), path!("/path/to/dir/"));
|
||||
}
|
||||
_ => panic!("Expected Directory variant"),
|
||||
}
|
||||
assert_eq!(parsed.to_uri().to_string(), file_uri);
|
||||
let new_uri = parsed.to_uri().to_string();
|
||||
assert!(new_uri.starts_with("zed:///agent/directory"));
|
||||
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_directory_uri_without_slash() {
|
||||
let uri = MentionUri::Directory {
|
||||
abs_path: PathBuf::from(path!("/path/to/dir/")),
|
||||
abs_path: PathBuf::from(path!("/path/to/dir")),
|
||||
};
|
||||
let expected = uri!("file:///path/to/dir/");
|
||||
assert_eq!(uri.to_uri().to_string(), expected);
|
||||
let uri_string = uri.to_uri().to_string();
|
||||
assert!(uri_string.starts_with("zed:///agent/directory"));
|
||||
assert_eq!(MentionUri::parse(&uri_string).unwrap(), uri);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_symbol_uri() {
|
||||
let symbol_uri = uri!("file:///path/to/file.rs?symbol=MySymbol#L10:20");
|
||||
let parsed = MentionUri::parse(symbol_uri, PathStyle::local()).unwrap();
|
||||
let old_uri = uri!("file:///path/to/file.rs?symbol=MySymbol#L10:20");
|
||||
let parsed = MentionUri::parse(old_uri).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::Symbol {
|
||||
abs_path: path,
|
||||
name,
|
||||
line_range,
|
||||
} => {
|
||||
assert_eq!(path, Path::new(path!("/path/to/file.rs")));
|
||||
assert_eq!(path.to_str().unwrap(), path!("/path/to/file.rs"));
|
||||
assert_eq!(name, "MySymbol");
|
||||
assert_eq!(line_range.start(), &9);
|
||||
assert_eq!(line_range.end(), &19);
|
||||
}
|
||||
_ => panic!("Expected Symbol variant"),
|
||||
}
|
||||
assert_eq!(parsed.to_uri().to_string(), symbol_uri);
|
||||
let new_uri = parsed.to_uri().to_string();
|
||||
assert!(new_uri.starts_with("zed:///agent/symbol/MySymbol"));
|
||||
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_selection_uri() {
|
||||
let selection_uri = uri!("file:///path/to/file.rs#L5:15");
|
||||
let parsed = MentionUri::parse(selection_uri, PathStyle::local()).unwrap();
|
||||
let old_uri = uri!("file:///path/to/file.rs#L5:15");
|
||||
let parsed = MentionUri::parse(old_uri).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::Selection {
|
||||
abs_path: path,
|
||||
line_range,
|
||||
} => {
|
||||
assert_eq!(path.as_ref().unwrap(), Path::new(path!("/path/to/file.rs")));
|
||||
assert_eq!(
|
||||
path.as_ref().unwrap().to_str().unwrap(),
|
||||
path!("/path/to/file.rs")
|
||||
);
|
||||
assert_eq!(line_range.start(), &4);
|
||||
assert_eq!(line_range.end(), &14);
|
||||
}
|
||||
_ => panic!("Expected Selection variant"),
|
||||
}
|
||||
assert_eq!(parsed.to_uri().to_string(), selection_uri);
|
||||
let new_uri = parsed.to_uri().to_string();
|
||||
assert!(new_uri.starts_with("zed:///agent/selection"));
|
||||
assert_eq!(MentionUri::parse(&new_uri).unwrap(), parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_untitled_selection_uri() {
|
||||
let selection_uri = uri!("zed:///agent/untitled-buffer#L1:10");
|
||||
let parsed = MentionUri::parse(selection_uri, PathStyle::local()).unwrap();
|
||||
let parsed = MentionUri::parse(selection_uri).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::Selection {
|
||||
abs_path: None,
|
||||
@@ -426,7 +441,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_parse_thread_uri() {
|
||||
let thread_uri = "zed:///agent/thread/session123?name=Thread+name";
|
||||
let parsed = MentionUri::parse(thread_uri, PathStyle::local()).unwrap();
|
||||
let parsed = MentionUri::parse(thread_uri).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::Thread {
|
||||
id: thread_id,
|
||||
@@ -443,7 +458,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_parse_rule_uri() {
|
||||
let rule_uri = "zed:///agent/rule/d8694ff2-90d5-4b6f-be33-33c1763acd52?name=Some+rule";
|
||||
let parsed = MentionUri::parse(rule_uri, PathStyle::local()).unwrap();
|
||||
let parsed = MentionUri::parse(rule_uri).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::Rule { id, name } => {
|
||||
assert_eq!(id.to_string(), "d8694ff2-90d5-4b6f-be33-33c1763acd52");
|
||||
@@ -457,7 +472,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_parse_fetch_http_uri() {
|
||||
let http_uri = "http://example.com/path?query=value#fragment";
|
||||
let parsed = MentionUri::parse(http_uri, PathStyle::local()).unwrap();
|
||||
let parsed = MentionUri::parse(http_uri).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::Fetch { url } => {
|
||||
assert_eq!(url.to_string(), http_uri);
|
||||
@@ -470,7 +485,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_parse_fetch_https_uri() {
|
||||
let https_uri = "https://example.com/api/endpoint";
|
||||
let parsed = MentionUri::parse(https_uri, PathStyle::local()).unwrap();
|
||||
let parsed = MentionUri::parse(https_uri).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::Fetch { url } => {
|
||||
assert_eq!(url.to_string(), https_uri);
|
||||
@@ -482,55 +497,40 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_invalid_scheme() {
|
||||
assert!(MentionUri::parse("ftp://example.com", PathStyle::local()).is_err());
|
||||
assert!(MentionUri::parse("ssh://example.com", PathStyle::local()).is_err());
|
||||
assert!(MentionUri::parse("unknown://example.com", PathStyle::local()).is_err());
|
||||
assert!(MentionUri::parse("ftp://example.com").is_err());
|
||||
assert!(MentionUri::parse("ssh://example.com").is_err());
|
||||
assert!(MentionUri::parse("unknown://example.com").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_zed_path() {
|
||||
assert!(MentionUri::parse("zed:///invalid/path", PathStyle::local()).is_err());
|
||||
assert!(MentionUri::parse("zed:///agent/unknown/test", PathStyle::local()).is_err());
|
||||
assert!(MentionUri::parse("zed:///invalid/path").is_err());
|
||||
assert!(MentionUri::parse("zed:///agent/unknown/test").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_line_range_format() {
|
||||
// Missing L prefix
|
||||
assert!(
|
||||
MentionUri::parse(uri!("file:///path/to/file.rs#10:20"), PathStyle::local()).is_err()
|
||||
);
|
||||
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#10:20")).is_err());
|
||||
|
||||
// Missing colon separator
|
||||
assert!(
|
||||
MentionUri::parse(uri!("file:///path/to/file.rs#L1020"), PathStyle::local()).is_err()
|
||||
);
|
||||
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L1020")).is_err());
|
||||
|
||||
// Invalid numbers
|
||||
assert!(
|
||||
MentionUri::parse(uri!("file:///path/to/file.rs#L10:abc"), PathStyle::local()).is_err()
|
||||
);
|
||||
assert!(
|
||||
MentionUri::parse(uri!("file:///path/to/file.rs#Labc:20"), PathStyle::local()).is_err()
|
||||
);
|
||||
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L10:abc")).is_err());
|
||||
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#Labc:20")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_query_parameters() {
|
||||
// Invalid query parameter name
|
||||
assert!(
|
||||
MentionUri::parse(
|
||||
uri!("file:///path/to/file.rs#L10:20?invalid=test"),
|
||||
PathStyle::local()
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L10:20?invalid=test")).is_err());
|
||||
|
||||
// Too many query parameters
|
||||
assert!(
|
||||
MentionUri::parse(
|
||||
uri!("file:///path/to/file.rs#L10:20?symbol=test&another=param"),
|
||||
PathStyle::local()
|
||||
)
|
||||
MentionUri::parse(uri!(
|
||||
"file:///path/to/file.rs#L10:20?symbol=test&another=param"
|
||||
))
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
@@ -538,14 +538,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_zero_based_line_numbers() {
|
||||
// Test that 0-based line numbers are rejected (should be 1-based)
|
||||
assert!(
|
||||
MentionUri::parse(uri!("file:///path/to/file.rs#L0:10"), PathStyle::local()).is_err()
|
||||
);
|
||||
assert!(
|
||||
MentionUri::parse(uri!("file:///path/to/file.rs#L1:0"), PathStyle::local()).is_err()
|
||||
);
|
||||
assert!(
|
||||
MentionUri::parse(uri!("file:///path/to/file.rs#L0:0"), PathStyle::local()).is_err()
|
||||
);
|
||||
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L0:10")).is_err());
|
||||
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L1:0")).is_err());
|
||||
assert!(MentionUri::parse(uri!("file:///path/to/file.rs#L0:0")).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::Result;
|
||||
|
||||
use futures::{FutureExt as _, future::Shared};
|
||||
use gpui::{App, AppContext, AsyncApp, Context, Entity, Task};
|
||||
use gpui::{App, AppContext, Context, Entity, Task};
|
||||
use language::LanguageRegistry;
|
||||
use markdown::Markdown;
|
||||
use project::Project;
|
||||
use settings::{Settings as _, SettingsLocation};
|
||||
use std::{path::PathBuf, process::ExitStatus, sync::Arc, time::Instant};
|
||||
use task::Shell;
|
||||
use terminal::terminal_settings::TerminalSettings;
|
||||
use util::get_default_system_shell_preferring_bash;
|
||||
|
||||
pub struct Terminal {
|
||||
id: acp::TerminalId,
|
||||
@@ -175,68 +170,3 @@ impl Terminal {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_terminal_entity(
|
||||
command: String,
|
||||
args: &[String],
|
||||
env_vars: Vec<(String, String)>,
|
||||
cwd: Option<PathBuf>,
|
||||
project: &Entity<Project>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Entity<terminal::Terminal>> {
|
||||
let mut env = if let Some(dir) = &cwd {
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
let worktree = project.find_worktree(dir.as_path(), cx);
|
||||
let shell = TerminalSettings::get(
|
||||
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: &path,
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
.shell
|
||||
.clone();
|
||||
project.directory_environment(&shell, dir.clone().into(), cx)
|
||||
})?
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
// Disables paging for `git` and hopefully other commands
|
||||
env.insert("PAGER".into(), "".into());
|
||||
env.extend(env_vars);
|
||||
|
||||
// Use remote shell or default system shell, as appropriate
|
||||
let shell = project
|
||||
.update(cx, |project, cx| {
|
||||
project
|
||||
.remote_client()
|
||||
.and_then(|r| r.read(cx).default_system_shell())
|
||||
.map(Shell::Program)
|
||||
})?
|
||||
.unwrap_or_else(|| Shell::Program(get_default_system_shell_preferring_bash()));
|
||||
let is_windows = project
|
||||
.read_with(cx, |project, cx| project.path_style(cx).is_windows())
|
||||
.unwrap_or(cfg!(windows));
|
||||
let (task_command, task_args) = task::ShellBuilder::new(&shell, is_windows)
|
||||
.redirect_stdin_to_dev_null()
|
||||
.build(Some(command.clone()), &args);
|
||||
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_terminal_task(
|
||||
task::SpawnInTerminal {
|
||||
command: Some(task_command),
|
||||
args: task_args,
|
||||
cwd,
|
||||
env,
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -93,8 +93,8 @@ struct WatchedConnection {
|
||||
messages: Vec<WatchedConnectionMessage>,
|
||||
list_state: ListState,
|
||||
connection: Weak<acp::ClientSideConnection>,
|
||||
incoming_request_methods: HashMap<acp::RequestId, Arc<str>>,
|
||||
outgoing_request_methods: HashMap<acp::RequestId, Arc<str>>,
|
||||
incoming_request_methods: HashMap<i32, Arc<str>>,
|
||||
outgoing_request_methods: HashMap<i32, Arc<str>>,
|
||||
_task: Task<()>,
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ impl AcpTools {
|
||||
}
|
||||
};
|
||||
|
||||
method_map.insert(id.clone(), method.clone());
|
||||
method_map.insert(id, method.clone());
|
||||
(Some(id), method.into(), MessageType::Request, Ok(params))
|
||||
}
|
||||
acp::StreamMessageContent::Response { id, result } => {
|
||||
@@ -259,15 +259,6 @@ impl AcpTools {
|
||||
serde_json::to_string_pretty(&messages).ok()
|
||||
}
|
||||
|
||||
fn clear_messages(&mut self, cx: &mut Context<Self>) {
|
||||
if let Some(connection) = self.watched_connection.as_mut() {
|
||||
connection.messages.clear();
|
||||
connection.list_state.reset(0);
|
||||
self.expanded.clear();
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn render_message(
|
||||
&mut self,
|
||||
index: usize,
|
||||
@@ -347,7 +338,6 @@ impl AcpTools {
|
||||
.children(
|
||||
message
|
||||
.request_id
|
||||
.as_ref()
|
||||
.map(|req_id| div().child(ui::Chip::new(req_id.to_string()))),
|
||||
),
|
||||
)
|
||||
@@ -399,7 +389,7 @@ impl AcpTools {
|
||||
|
||||
struct WatchedConnectionMessage {
|
||||
name: SharedString,
|
||||
request_id: Option<acp::RequestId>,
|
||||
request_id: Option<i32>,
|
||||
direction: acp::StreamMessageDirection,
|
||||
message_type: MessageType,
|
||||
params: Result<Option<serde_json::Value>, acp::Error>,
|
||||
@@ -556,16 +546,10 @@ impl Render for AcpToolsToolbarItemView {
|
||||
};
|
||||
|
||||
let acp_tools = acp_tools.clone();
|
||||
let has_messages = acp_tools
|
||||
.read(cx)
|
||||
.watched_connection
|
||||
.as_ref()
|
||||
.is_some_and(|connection| !connection.messages.is_empty());
|
||||
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child({
|
||||
let acp_tools = acp_tools.clone();
|
||||
.child(
|
||||
IconButton::new(
|
||||
"copy_all_messages",
|
||||
if self.just_copied {
|
||||
@@ -580,7 +564,13 @@ impl Render for AcpToolsToolbarItemView {
|
||||
} else {
|
||||
"Copy All Messages"
|
||||
}))
|
||||
.disabled(!has_messages)
|
||||
.disabled(
|
||||
acp_tools
|
||||
.read(cx)
|
||||
.watched_connection
|
||||
.as_ref()
|
||||
.is_none_or(|connection| connection.messages.is_empty()),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _, _window, cx| {
|
||||
if let Some(content) = acp_tools.read(cx).serialize_observed_messages() {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(content));
|
||||
@@ -595,18 +585,7 @@ impl Render for AcpToolsToolbarItemView {
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}))
|
||||
})
|
||||
.child(
|
||||
IconButton::new("clear_messages", IconName::Trash)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Clear Messages"))
|
||||
.disabled(!has_messages)
|
||||
.on_click(cx.listener(move |_this, _, _window, cx| {
|
||||
acp_tools.update(cx, |acp_tools, cx| {
|
||||
acp_tools.clear_messages(cx);
|
||||
});
|
||||
})),
|
||||
})),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ use language::{
|
||||
LanguageServerStatusUpdate, ServerHealth,
|
||||
};
|
||||
use project::{
|
||||
LanguageServerProgress, LspStoreEvent, Project, ProjectEnvironmentEvent,
|
||||
EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project,
|
||||
ProjectEnvironmentEvent,
|
||||
git_store::{GitStoreEvent, Repository},
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
@@ -326,20 +327,20 @@ impl ActivityIndicator {
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a String> {
|
||||
fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a EnvironmentErrorMessage> {
|
||||
self.project.read(cx).peek_environment_error(cx)
|
||||
}
|
||||
|
||||
fn content_to_render(&mut self, cx: &mut Context<Self>) -> Option<Content> {
|
||||
// Show if any direnv calls failed
|
||||
if let Some(message) = self.pending_environment_error(cx) {
|
||||
if let Some(error) = self.pending_environment_error(cx) {
|
||||
return Some(Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: message.clone(),
|
||||
message: error.0.clone(),
|
||||
on_click: Some(Arc::new(move |this, window, cx| {
|
||||
this.project.update(cx, |project, cx| {
|
||||
project.pop_environment_error(cx);
|
||||
|
||||
@@ -10,8 +10,6 @@ path = "src/agent.rs"
|
||||
|
||||
[features]
|
||||
test-support = ["db/test-support"]
|
||||
eval = []
|
||||
unit-eval = []
|
||||
e2e = []
|
||||
|
||||
[lints]
|
||||
@@ -24,7 +22,7 @@ agent-client-protocol.workspace = true
|
||||
agent_servers.workspace = true
|
||||
agent_settings.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_text_thread.workspace = true
|
||||
assistant_context.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
cloud_llm_client.workspace = true
|
||||
@@ -76,7 +74,7 @@ zstd.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
agent_servers = { workspace = true, "features" = ["test-support"] }
|
||||
assistant_text_thread = { workspace = true, "features" = ["test-support"] }
|
||||
assistant_context = { workspace = true, "features" = ["test-support"] }
|
||||
client = { workspace = true, "features" = ["test-support"] }
|
||||
clock = { workspace = true, "features" = ["test-support"] }
|
||||
context_server = { workspace = true, "features" = ["test-support"] }
|
||||
|
||||
@@ -48,10 +48,24 @@ use util::rel_path::RelPath;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ProjectSnapshot {
|
||||
pub worktree_snapshots: Vec<project::telemetry_snapshot::TelemetryWorktreeSnapshot>,
|
||||
pub worktree_snapshots: Vec<WorktreeSnapshot>,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct WorktreeSnapshot {
|
||||
pub worktree_path: String,
|
||||
pub git_state: Option<GitState>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct GitState {
|
||||
pub remote_url: Option<String>,
|
||||
pub head_sha: Option<String>,
|
||||
pub current_branch: Option<String>,
|
||||
pub diff: Option<String>,
|
||||
}
|
||||
|
||||
const RULES_FILE_NAMES: [&str; 9] = [
|
||||
".rules",
|
||||
".cursorrules",
|
||||
@@ -1035,13 +1049,12 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
|
||||
let session_id = params.session_id.clone();
|
||||
log::info!("Received prompt request for session: {}", session_id);
|
||||
log::debug!("Prompt blocks count: {}", params.prompt.len());
|
||||
let path_style = self.0.read(cx).project.read(cx).path_style(cx);
|
||||
|
||||
self.run_turn(session_id, cx, move |thread, cx| {
|
||||
self.run_turn(session_id, cx, |thread, cx| {
|
||||
let content: Vec<UserMessageContent> = params
|
||||
.prompt
|
||||
.into_iter()
|
||||
.map(|block| UserMessageContent::from_content_block(block, path_style))
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
log::debug!("Converted prompt to message: {} chars", content.len());
|
||||
log::debug!("Message id: {:?}", id);
|
||||
@@ -1267,9 +1280,8 @@ mod internal_tests {
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [], cx).await;
|
||||
let text_thread_store =
|
||||
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||
let agent = NativeAgent::new(
|
||||
project.clone(),
|
||||
history_store,
|
||||
@@ -1329,9 +1341,8 @@ mod internal_tests {
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/", json!({ "a": {} })).await;
|
||||
let project = Project::test(fs.clone(), [], cx).await;
|
||||
let text_thread_store =
|
||||
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||
let connection = NativeAgentConnection(
|
||||
NativeAgent::new(
|
||||
project.clone(),
|
||||
@@ -1405,9 +1416,8 @@ mod internal_tests {
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [], cx).await;
|
||||
|
||||
let text_thread_store =
|
||||
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||
|
||||
// Create the agent and connection
|
||||
let agent = NativeAgent::new(
|
||||
@@ -1478,9 +1488,8 @@ mod internal_tests {
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/a").as_ref()], cx).await;
|
||||
let text_thread_store =
|
||||
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||
let agent = NativeAgent::new(
|
||||
project.clone(),
|
||||
history_store.clone(),
|
||||
|
||||
@@ -31,7 +31,7 @@ use std::{
|
||||
use util::path;
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
fn eval_extract_handle_command_output() {
|
||||
// Test how well agent generates multiple edit hunks.
|
||||
//
|
||||
@@ -108,7 +108,7 @@ fn eval_extract_handle_command_output() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
fn eval_delete_run_git_blame() {
|
||||
// Model | Pass rate
|
||||
// ----------------------------|----------
|
||||
@@ -171,7 +171,7 @@ fn eval_delete_run_git_blame() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
fn eval_translate_doc_comments() {
|
||||
// Model | Pass rate
|
||||
// ============================================
|
||||
@@ -234,7 +234,7 @@ fn eval_translate_doc_comments() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
fn eval_use_wasi_sdk_in_compile_parser_to_wasm() {
|
||||
// Model | Pass rate
|
||||
// ============================================
|
||||
@@ -360,7 +360,7 @@ fn eval_use_wasi_sdk_in_compile_parser_to_wasm() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
fn eval_disable_cursor_blinking() {
|
||||
// Model | Pass rate
|
||||
// ============================================
|
||||
@@ -446,7 +446,7 @@ fn eval_disable_cursor_blinking() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
fn eval_from_pixels_constructor() {
|
||||
// Results for 2025-06-13
|
||||
//
|
||||
@@ -656,7 +656,7 @@ fn eval_from_pixels_constructor() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
fn eval_zode() {
|
||||
// Model | Pass rate
|
||||
// ============================================
|
||||
@@ -763,7 +763,7 @@ fn eval_zode() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
fn eval_add_overwrite_test() {
|
||||
// Model | Pass rate
|
||||
// ============================================
|
||||
@@ -995,7 +995,7 @@ fn eval_add_overwrite_test() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
fn eval_create_empty_file() {
|
||||
// Check that Edit Agent can create a file without writing its
|
||||
// thoughts into it. This issue is not specific to empty files, but
|
||||
@@ -1483,27 +1483,16 @@ impl EditAgentTest {
|
||||
fs.insert_tree("/root", json!({})).await;
|
||||
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let agent_model = SelectedModel::from_str(
|
||||
&std::env::var("ZED_AGENT_MODEL").unwrap_or("anthropic/claude-sonnet-4-latest".into()),
|
||||
&std::env::var("ZED_AGENT_MODEL").unwrap_or("anthropic/claude-4-sonnet-latest".into()),
|
||||
)
|
||||
.unwrap();
|
||||
let judge_model = SelectedModel::from_str(
|
||||
&std::env::var("ZED_JUDGE_MODEL").unwrap_or("anthropic/claude-sonnet-4-latest".into()),
|
||||
&std::env::var("ZED_JUDGE_MODEL").unwrap_or("anthropic/claude-4-sonnet-latest".into()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let authenticate_provider_tasks = cx.update(|cx| {
|
||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||
registry
|
||||
.providers()
|
||||
.iter()
|
||||
.map(|p| p.authenticate(cx))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
});
|
||||
let (agent_model, judge_model) = cx
|
||||
.update(|cx| {
|
||||
cx.spawn(async move |cx| {
|
||||
futures::future::join_all(authenticate_provider_tasks).await;
|
||||
let agent_model = Self::load_model(&agent_model, cx).await;
|
||||
let judge_model = Self::load_model(&judge_model, cx).await;
|
||||
(agent_model.unwrap(), judge_model.unwrap())
|
||||
@@ -1547,7 +1536,7 @@ impl EditAgentTest {
|
||||
model.provider_id() == selected_model.provider
|
||||
&& model.id() == selected_model.model
|
||||
})
|
||||
.unwrap_or_else(|| panic!("Model {} not found", selected_model.model.0));
|
||||
.expect("Model not found");
|
||||
model
|
||||
})
|
||||
}
|
||||
|
||||
@@ -308,13 +308,12 @@ mod tests {
|
||||
use indoc::indoc;
|
||||
use language::{BufferId, TextBuffer};
|
||||
use rand::prelude::*;
|
||||
use text::ReplicaId;
|
||||
use util::test::{generate_marked_text, marked_text_ranges};
|
||||
|
||||
#[test]
|
||||
fn test_empty_query() {
|
||||
let buffer = TextBuffer::new(
|
||||
ReplicaId::LOCAL,
|
||||
0,
|
||||
BufferId::new(1).unwrap(),
|
||||
"Hello world\nThis is a test\nFoo bar baz",
|
||||
);
|
||||
@@ -328,7 +327,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_streaming_exact_match() {
|
||||
let buffer = TextBuffer::new(
|
||||
ReplicaId::LOCAL,
|
||||
0,
|
||||
BufferId::new(1).unwrap(),
|
||||
"Hello world\nThis is a test\nFoo bar baz",
|
||||
);
|
||||
@@ -352,7 +351,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_streaming_fuzzy_match() {
|
||||
let buffer = TextBuffer::new(
|
||||
ReplicaId::LOCAL,
|
||||
0,
|
||||
BufferId::new(1).unwrap(),
|
||||
indoc! {"
|
||||
function foo(a, b) {
|
||||
@@ -386,7 +385,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_incremental_improvement() {
|
||||
let buffer = TextBuffer::new(
|
||||
ReplicaId::LOCAL,
|
||||
0,
|
||||
BufferId::new(1).unwrap(),
|
||||
"Line 1\nLine 2\nLine 3\nLine 4\nLine 5",
|
||||
);
|
||||
@@ -411,7 +410,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_incomplete_lines_buffering() {
|
||||
let buffer = TextBuffer::new(
|
||||
ReplicaId::LOCAL,
|
||||
0,
|
||||
BufferId::new(1).unwrap(),
|
||||
indoc! {"
|
||||
The quick brown fox
|
||||
@@ -438,7 +437,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_multiline_fuzzy_match() {
|
||||
let buffer = TextBuffer::new(
|
||||
ReplicaId::LOCAL,
|
||||
0,
|
||||
BufferId::new(1).unwrap(),
|
||||
indoc! {r#"
|
||||
impl Display for User {
|
||||
@@ -692,11 +691,7 @@ mod tests {
|
||||
}
|
||||
"#};
|
||||
|
||||
let buffer = TextBuffer::new(
|
||||
ReplicaId::LOCAL,
|
||||
BufferId::new(1).unwrap(),
|
||||
text.to_string(),
|
||||
);
|
||||
let buffer = TextBuffer::new(0, BufferId::new(1).unwrap(), text.to_string());
|
||||
let snapshot = buffer.snapshot();
|
||||
let mut matcher = StreamingFuzzyMatcher::new(snapshot.clone());
|
||||
|
||||
@@ -729,7 +724,7 @@ mod tests {
|
||||
#[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);
|
||||
let buffer = TextBuffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), text.clone());
|
||||
let buffer = TextBuffer::new(0, BufferId::new(1).unwrap(), text.clone());
|
||||
let snapshot = buffer.snapshot();
|
||||
|
||||
let mut matcher = StreamingFuzzyMatcher::new(snapshot);
|
||||
|
||||
@@ -2,12 +2,12 @@ use crate::{DbThread, DbThreadMetadata, ThreadsDatabase};
|
||||
use acp_thread::MentionUri;
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_text_thread::{SavedTextThreadMetadata, TextThread};
|
||||
use assistant_context::{AssistantContext, SavedContextMetadata};
|
||||
use chrono::{DateTime, Utc};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
|
||||
use itertools::Itertools;
|
||||
use paths::text_threads_dir;
|
||||
use paths::contexts_dir;
|
||||
use project::Project;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::VecDeque, path::Path, rc::Rc, sync::Arc, time::Duration};
|
||||
@@ -50,23 +50,21 @@ pub fn load_agent_thread(
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HistoryEntry {
|
||||
AcpThread(DbThreadMetadata),
|
||||
TextThread(SavedTextThreadMetadata),
|
||||
TextThread(SavedContextMetadata),
|
||||
}
|
||||
|
||||
impl HistoryEntry {
|
||||
pub fn updated_at(&self) -> DateTime<Utc> {
|
||||
match self {
|
||||
HistoryEntry::AcpThread(thread) => thread.updated_at,
|
||||
HistoryEntry::TextThread(text_thread) => text_thread.mtime.to_utc(),
|
||||
HistoryEntry::TextThread(context) => context.mtime.to_utc(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> HistoryEntryId {
|
||||
match self {
|
||||
HistoryEntry::AcpThread(thread) => HistoryEntryId::AcpThread(thread.id.clone()),
|
||||
HistoryEntry::TextThread(text_thread) => {
|
||||
HistoryEntryId::TextThread(text_thread.path.clone())
|
||||
}
|
||||
HistoryEntry::TextThread(context) => HistoryEntryId::TextThread(context.path.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,9 +74,9 @@ impl HistoryEntry {
|
||||
id: thread.id.clone(),
|
||||
name: thread.title.to_string(),
|
||||
},
|
||||
HistoryEntry::TextThread(text_thread) => MentionUri::TextThread {
|
||||
path: text_thread.path.as_ref().to_owned(),
|
||||
name: text_thread.title.to_string(),
|
||||
HistoryEntry::TextThread(context) => MentionUri::TextThread {
|
||||
path: context.path.as_ref().to_owned(),
|
||||
name: context.title.to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -92,7 +90,7 @@ impl HistoryEntry {
|
||||
&thread.title
|
||||
}
|
||||
}
|
||||
HistoryEntry::TextThread(text_thread) => &text_thread.title,
|
||||
HistoryEntry::TextThread(context) => &context.title,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,7 +120,7 @@ enum SerializedRecentOpen {
|
||||
pub struct HistoryStore {
|
||||
threads: Vec<DbThreadMetadata>,
|
||||
entries: Vec<HistoryEntry>,
|
||||
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
|
||||
text_thread_store: Entity<assistant_context::ContextStore>,
|
||||
recently_opened_entries: VecDeque<HistoryEntryId>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
_save_recently_opened_entries_task: Task<()>,
|
||||
@@ -130,7 +128,7 @@ pub struct HistoryStore {
|
||||
|
||||
impl HistoryStore {
|
||||
pub fn new(
|
||||
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
|
||||
text_thread_store: Entity<assistant_context::ContextStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let subscriptions =
|
||||
@@ -194,16 +192,16 @@ impl HistoryStore {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.text_thread_store
|
||||
.update(cx, |store, cx| store.delete_local(path, cx))
|
||||
.update(cx, |store, cx| store.delete_local_context(path, cx))
|
||||
}
|
||||
|
||||
pub fn load_text_thread(
|
||||
&self,
|
||||
path: Arc<Path>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<TextThread>>> {
|
||||
) -> Task<Result<Entity<AssistantContext>>> {
|
||||
self.text_thread_store
|
||||
.update(cx, |store, cx| store.open_local(path, cx))
|
||||
.update(cx, |store, cx| store.open_local_context(path, cx))
|
||||
}
|
||||
|
||||
pub fn reload(&self, cx: &mut Context<Self>) {
|
||||
@@ -245,7 +243,7 @@ impl HistoryStore {
|
||||
history_entries.extend(
|
||||
self.text_thread_store
|
||||
.read(cx)
|
||||
.unordered_text_threads()
|
||||
.unordered_contexts()
|
||||
.cloned()
|
||||
.map(HistoryEntry::TextThread),
|
||||
);
|
||||
@@ -280,14 +278,14 @@ impl HistoryStore {
|
||||
let context_entries = self
|
||||
.text_thread_store
|
||||
.read(cx)
|
||||
.unordered_text_threads()
|
||||
.flat_map(|text_thread| {
|
||||
.unordered_contexts()
|
||||
.flat_map(|context| {
|
||||
self.recently_opened_entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(index, entry)| match entry {
|
||||
HistoryEntryId::TextThread(path) if &text_thread.path == path => {
|
||||
Some((index, HistoryEntry::TextThread(text_thread.clone())))
|
||||
HistoryEntryId::TextThread(path) if &context.path == path => {
|
||||
Some((index, HistoryEntry::TextThread(context.clone())))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
@@ -349,7 +347,7 @@ impl HistoryStore {
|
||||
acp::SessionId(id.as_str().into()),
|
||||
)),
|
||||
SerializedRecentOpen::TextThread(file_name) => Some(
|
||||
HistoryEntryId::TextThread(text_threads_dir().join(file_name).into()),
|
||||
HistoryEntryId::TextThread(contexts_dir().join(file_name).into()),
|
||||
),
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::{any::Any, path::Path, rc::Rc, sync::Arc};
|
||||
|
||||
use agent_servers::{AgentServer, AgentServerDelegate};
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use fs::Fs;
|
||||
use gpui::{App, Entity, SharedString, Task};
|
||||
use prompt_store::PromptStore;
|
||||
@@ -41,7 +42,7 @@ impl AgentServer for NativeAgentServer {
|
||||
) -> Task<
|
||||
Result<(
|
||||
Rc<dyn acp_thread::AgentConnection>,
|
||||
Option<task::SpawnInTerminal>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
)>,
|
||||
> {
|
||||
log::debug!(
|
||||
@@ -67,7 +68,7 @@ impl AgentServer for NativeAgentServer {
|
||||
|
||||
Ok((
|
||||
Rc::new(connection) as Rc<dyn acp_thread::AgentConnection>,
|
||||
None,
|
||||
HashMap::default(),
|
||||
))
|
||||
})
|
||||
}
|
||||
@@ -81,7 +82,7 @@ impl AgentServer for NativeAgentServer {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use assistant_context::ContextStore;
|
||||
use gpui::AppContext;
|
||||
|
||||
agent_servers::e2e_tests::common_e2e_tests!(
|
||||
@@ -116,9 +117,8 @@ mod tests {
|
||||
});
|
||||
|
||||
let history = cx.update(|cx| {
|
||||
let text_thread_store =
|
||||
cx.new(move |cx| TextThreadStore::fake(project.clone(), cx));
|
||||
cx.new(move |cx| HistoryStore::new(text_thread_store, cx))
|
||||
let context_store = cx.new(move |cx| ContextStore::fake(project.clone(), cx));
|
||||
cx.new(move |cx| HistoryStore::new(context_store, cx))
|
||||
});
|
||||
|
||||
NativeAgentServer::new(fs.clone(), history)
|
||||
|
||||
@@ -160,42 +160,6 @@ async fn test_system_prompt(cx: &mut TestAppContext) {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_system_prompt_without_tools(cx: &mut TestAppContext) {
|
||||
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
|
||||
let fake_model = model.as_fake();
|
||||
|
||||
thread
|
||||
.update(cx, |thread, cx| {
|
||||
thread.send(UserMessageId::new(), ["abc"], cx)
|
||||
})
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
let mut pending_completions = fake_model.pending_completions();
|
||||
assert_eq!(
|
||||
pending_completions.len(),
|
||||
1,
|
||||
"unexpected pending completions: {:?}",
|
||||
pending_completions
|
||||
);
|
||||
|
||||
let pending_completion = pending_completions.pop().unwrap();
|
||||
assert_eq!(pending_completion.messages[0].role, Role::System);
|
||||
|
||||
let system_message = &pending_completion.messages[0];
|
||||
let system_prompt = system_message.content[0].to_str().unwrap();
|
||||
assert!(
|
||||
!system_prompt.contains("## Tool Use"),
|
||||
"unexpected system message: {:?}",
|
||||
system_message
|
||||
);
|
||||
assert!(
|
||||
!system_prompt.contains("## Fixing Diagnostics"),
|
||||
"unexpected system message: {:?}",
|
||||
system_message
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_prompt_caching(cx: &mut TestAppContext) {
|
||||
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
|
||||
@@ -1870,9 +1834,8 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
|
||||
fake_fs.insert_tree(path!("/test"), json!({})).await;
|
||||
let project = Project::test(fake_fs.clone(), [Path::new("/test")], cx).await;
|
||||
let cwd = Path::new("/test");
|
||||
let text_thread_store =
|
||||
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||
|
||||
// Create agent and connection
|
||||
let agent = NativeAgent::new(
|
||||
@@ -2032,7 +1995,7 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) {
|
||||
locations: vec![],
|
||||
raw_input: Some(json!({})),
|
||||
raw_output: None,
|
||||
meta: Some(json!({ "tool_name": "thinking" })),
|
||||
meta: None,
|
||||
}
|
||||
);
|
||||
let update = expect_tool_call_update_fields(&mut events).await;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::{
|
||||
ContextServerRegistry, CopyPathTool, CreateDirectoryTool, DbLanguageModel, DbThread,
|
||||
DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GrepTool,
|
||||
DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GitState, GrepTool,
|
||||
ListDirectoryTool, MovePathTool, NowTool, OpenTool, ProjectSnapshot, ReadFileTool,
|
||||
SystemPromptTemplate, Template, Templates, TerminalTool, ThinkingTool, WebSearchTool,
|
||||
WorktreeSnapshot,
|
||||
};
|
||||
use acp_thread::{MentionUri, UserMessageId};
|
||||
use action_log::ActionLog;
|
||||
@@ -25,6 +26,7 @@ use futures::{
|
||||
future::Shared,
|
||||
stream::FuturesUnordered,
|
||||
};
|
||||
use git::repository::DiffType;
|
||||
use gpui::{
|
||||
App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,
|
||||
};
|
||||
@@ -35,7 +37,10 @@ use language_model::{
|
||||
LanguageModelToolResultContent, LanguageModelToolSchemaFormat, LanguageModelToolUse,
|
||||
LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage, ZED_CLOUD_PROVIDER_ID,
|
||||
};
|
||||
use project::Project;
|
||||
use project::{
|
||||
Project,
|
||||
git_store::{GitStore, RepositoryState},
|
||||
};
|
||||
use prompt_store::ProjectContext;
|
||||
use schemars::{JsonSchema, Schema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -50,7 +55,7 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::{fmt::Write, path::PathBuf};
|
||||
use util::{ResultExt, debug_panic, markdown::MarkdownCodeBlock, paths::PathStyle};
|
||||
use util::{ResultExt, debug_panic, markdown::MarkdownCodeBlock};
|
||||
use uuid::Uuid;
|
||||
|
||||
const TOOL_CANCELED_MESSAGE: &str = "Tool canceled by user";
|
||||
@@ -745,13 +750,7 @@ impl Thread {
|
||||
|
||||
let title = tool.initial_title(tool_use.input.clone(), cx);
|
||||
let kind = tool.kind();
|
||||
stream.send_tool_call(
|
||||
&tool_use.id,
|
||||
&tool_use.name,
|
||||
title,
|
||||
kind,
|
||||
tool_use.input.clone(),
|
||||
);
|
||||
stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone());
|
||||
|
||||
let output = tool_result
|
||||
.as_ref()
|
||||
@@ -881,17 +880,101 @@ impl Thread {
|
||||
project: Entity<Project>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Arc<ProjectSnapshot>> {
|
||||
let task = project::telemetry_snapshot::TelemetrySnapshot::new(&project, cx);
|
||||
let git_store = project.read(cx).git_store().clone();
|
||||
let worktree_snapshots: Vec<_> = project
|
||||
.read(cx)
|
||||
.visible_worktrees(cx)
|
||||
.map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
|
||||
.collect();
|
||||
|
||||
cx.spawn(async move |_, _| {
|
||||
let snapshot = task.await;
|
||||
let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
|
||||
|
||||
Arc::new(ProjectSnapshot {
|
||||
worktree_snapshots: snapshot.worktree_snapshots,
|
||||
worktree_snapshots,
|
||||
timestamp: Utc::now(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn worktree_snapshot(
|
||||
worktree: Entity<project::Worktree>,
|
||||
git_store: Entity<GitStore>,
|
||||
cx: &App,
|
||||
) -> Task<WorktreeSnapshot> {
|
||||
cx.spawn(async move |cx| {
|
||||
// Get worktree path and snapshot
|
||||
let worktree_info = cx.update(|app_cx| {
|
||||
let worktree = worktree.read(app_cx);
|
||||
let path = worktree.abs_path().to_string_lossy().into_owned();
|
||||
let snapshot = worktree.snapshot();
|
||||
(path, snapshot)
|
||||
});
|
||||
|
||||
let Ok((worktree_path, _snapshot)) = worktree_info else {
|
||||
return WorktreeSnapshot {
|
||||
worktree_path: String::new(),
|
||||
git_state: None,
|
||||
};
|
||||
};
|
||||
|
||||
let git_state = git_store
|
||||
.update(cx, |git_store, cx| {
|
||||
git_store
|
||||
.repositories()
|
||||
.values()
|
||||
.find(|repo| {
|
||||
repo.read(cx)
|
||||
.abs_path_to_repo_path(&worktree.read(cx).abs_path())
|
||||
.is_some()
|
||||
})
|
||||
.cloned()
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|repo| {
|
||||
repo.update(cx, |repo, _| {
|
||||
let current_branch =
|
||||
repo.branch.as_ref().map(|branch| branch.name().to_owned());
|
||||
repo.send_job(None, |state, _| async move {
|
||||
let RepositoryState::Local { backend, .. } = state else {
|
||||
return GitState {
|
||||
remote_url: None,
|
||||
head_sha: None,
|
||||
current_branch,
|
||||
diff: None,
|
||||
};
|
||||
};
|
||||
|
||||
let remote_url = backend.remote_url("origin");
|
||||
let head_sha = backend.head_sha().await;
|
||||
let diff = backend.diff(DiffType::HeadToWorktree).await.ok();
|
||||
|
||||
GitState {
|
||||
remote_url,
|
||||
head_sha,
|
||||
current_branch,
|
||||
diff,
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let git_state = match git_state {
|
||||
Some(git_state) => match git_state.ok() {
|
||||
Some(git_state) => git_state.await.ok(),
|
||||
None => None,
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
WorktreeSnapshot {
|
||||
worktree_path,
|
||||
git_state,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn project_context(&self) -> &Entity<ProjectContext> {
|
||||
&self.project_context
|
||||
}
|
||||
@@ -1050,18 +1133,14 @@ impl Thread {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn latest_request_token_usage(&self) -> Option<language_model::TokenUsage> {
|
||||
pub fn latest_token_usage(&self) -> Option<acp_thread::TokenUsage> {
|
||||
let last_user_message = self.last_user_message()?;
|
||||
let tokens = self.request_token_usage.get(&last_user_message.id)?;
|
||||
Some(*tokens)
|
||||
}
|
||||
|
||||
pub fn latest_token_usage(&self) -> Option<acp_thread::TokenUsage> {
|
||||
let usage = self.latest_request_token_usage()?;
|
||||
let model = self.model.clone()?;
|
||||
|
||||
Some(acp_thread::TokenUsage {
|
||||
max_tokens: model.max_token_count_for_mode(self.completion_mode.into()),
|
||||
used_tokens: usage.total_tokens(),
|
||||
used_tokens: tokens.total_tokens(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1104,14 +1183,6 @@ impl Thread {
|
||||
self.run_turn(cx)
|
||||
}
|
||||
|
||||
#[cfg(feature = "eval")]
|
||||
pub fn proceed(
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<mpsc::UnboundedReceiver<Result<ThreadEvent>>> {
|
||||
self.run_turn(cx)
|
||||
}
|
||||
|
||||
fn run_turn(
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -1479,13 +1550,7 @@ impl Thread {
|
||||
});
|
||||
|
||||
if push_new_tool_use {
|
||||
event_stream.send_tool_call(
|
||||
&tool_use.id,
|
||||
&tool_use.name,
|
||||
title,
|
||||
kind,
|
||||
tool_use.input.clone(),
|
||||
);
|
||||
event_stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone());
|
||||
last_message
|
||||
.content
|
||||
.push(AgentMessageContent::ToolUse(tool_use.clone()));
|
||||
@@ -1816,15 +1881,9 @@ impl Thread {
|
||||
log::debug!("Completion intent: {:?}", completion_intent);
|
||||
log::debug!("Completion mode: {:?}", self.completion_mode);
|
||||
|
||||
let available_tools: Vec<_> = self
|
||||
.running_turn
|
||||
.as_ref()
|
||||
.map(|turn| turn.tools.keys().cloned().collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
log::debug!("Request includes {} tools", available_tools.len());
|
||||
let messages = self.build_request_messages(available_tools, cx);
|
||||
let messages = self.build_request_messages(cx);
|
||||
log::debug!("Request will include {} messages", messages.len());
|
||||
log::debug!("Request includes {} tools", tools.len());
|
||||
|
||||
let request = LanguageModelRequest {
|
||||
thread_id: Some(self.id.to_string()),
|
||||
@@ -1863,7 +1922,7 @@ impl Thread {
|
||||
.tools
|
||||
.iter()
|
||||
.filter_map(|(tool_name, tool)| {
|
||||
if tool.supports_provider(&model.provider_id())
|
||||
if tool.supported_provider(&model.provider_id())
|
||||
&& profile.is_tool_enabled(tool_name)
|
||||
{
|
||||
Some((truncate(tool_name), tool.clone()))
|
||||
@@ -1915,11 +1974,7 @@ impl Thread {
|
||||
self.running_turn.as_ref()?.tools.get(name).cloned()
|
||||
}
|
||||
|
||||
fn build_request_messages(
|
||||
&self,
|
||||
available_tools: Vec<SharedString>,
|
||||
cx: &App,
|
||||
) -> Vec<LanguageModelRequestMessage> {
|
||||
fn build_request_messages(&self, cx: &App) -> Vec<LanguageModelRequestMessage> {
|
||||
log::trace!(
|
||||
"Building request messages from {} thread messages",
|
||||
self.messages.len()
|
||||
@@ -1927,7 +1982,7 @@ impl Thread {
|
||||
|
||||
let system_prompt = SystemPromptTemplate {
|
||||
project: self.project_context.read(cx),
|
||||
available_tools,
|
||||
available_tools: self.tools.keys().cloned().collect(),
|
||||
}
|
||||
.render(&self.templates)
|
||||
.context("failed to build system prompt")
|
||||
@@ -2143,7 +2198,7 @@ where
|
||||
|
||||
/// Some tools rely on a provider for the underlying billing or other reasons.
|
||||
/// Allow the tool to check if they are compatible, or should be filtered out.
|
||||
fn supports_provider(_provider: &LanguageModelProviderId) -> bool {
|
||||
fn supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -2184,7 +2239,7 @@ pub trait AnyAgentTool {
|
||||
fn kind(&self) -> acp::ToolKind;
|
||||
fn initial_title(&self, input: serde_json::Value, _cx: &mut App) -> SharedString;
|
||||
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value>;
|
||||
fn supports_provider(&self, _provider: &LanguageModelProviderId) -> bool {
|
||||
fn supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
|
||||
true
|
||||
}
|
||||
fn run(
|
||||
@@ -2229,8 +2284,8 @@ where
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
fn supports_provider(&self, provider: &LanguageModelProviderId) -> bool {
|
||||
T::supports_provider(provider)
|
||||
fn supported_provider(&self, provider: &LanguageModelProviderId) -> bool {
|
||||
self.0.supported_provider(provider)
|
||||
}
|
||||
|
||||
fn run(
|
||||
@@ -2290,7 +2345,6 @@ impl ThreadEventStream {
|
||||
fn send_tool_call(
|
||||
&self,
|
||||
id: &LanguageModelToolUseId,
|
||||
tool_name: &str,
|
||||
title: SharedString,
|
||||
kind: acp::ToolKind,
|
||||
input: serde_json::Value,
|
||||
@@ -2298,7 +2352,6 @@ impl ThreadEventStream {
|
||||
self.0
|
||||
.unbounded_send(Ok(ThreadEvent::ToolCall(Self::initial_tool_call(
|
||||
id,
|
||||
tool_name,
|
||||
title.to_string(),
|
||||
kind,
|
||||
input,
|
||||
@@ -2308,15 +2361,12 @@ impl ThreadEventStream {
|
||||
|
||||
fn initial_tool_call(
|
||||
id: &LanguageModelToolUseId,
|
||||
tool_name: &str,
|
||||
title: String,
|
||||
kind: acp::ToolKind,
|
||||
input: serde_json::Value,
|
||||
) -> acp::ToolCall {
|
||||
acp::ToolCall {
|
||||
meta: Some(serde_json::json!({
|
||||
"tool_name": tool_name
|
||||
})),
|
||||
meta: None,
|
||||
id: acp::ToolCallId(id.to_string().into()),
|
||||
title,
|
||||
kind,
|
||||
@@ -2548,8 +2598,8 @@ impl From<&str> for UserMessageContent {
|
||||
}
|
||||
}
|
||||
|
||||
impl UserMessageContent {
|
||||
pub fn from_content_block(value: acp::ContentBlock, path_style: PathStyle) -> Self {
|
||||
impl From<acp::ContentBlock> for UserMessageContent {
|
||||
fn from(value: acp::ContentBlock) -> Self {
|
||||
match value {
|
||||
acp::ContentBlock::Text(text_content) => Self::Text(text_content.text),
|
||||
acp::ContentBlock::Image(image_content) => Self::Image(convert_image(image_content)),
|
||||
@@ -2558,7 +2608,7 @@ impl UserMessageContent {
|
||||
Self::Text("[audio]".to_string())
|
||||
}
|
||||
acp::ContentBlock::ResourceLink(resource_link) => {
|
||||
match MentionUri::parse(&resource_link.uri, path_style) {
|
||||
match MentionUri::parse(&resource_link.uri) {
|
||||
Ok(uri) => Self::Mention {
|
||||
uri,
|
||||
content: String::new(),
|
||||
@@ -2571,7 +2621,7 @@ impl UserMessageContent {
|
||||
}
|
||||
acp::ContentBlock::Resource(resource) => match resource.resource {
|
||||
acp::EmbeddedResourceResource::TextResourceContents(resource) => {
|
||||
match MentionUri::parse(&resource.uri, path_style) {
|
||||
match MentionUri::parse(&resource.uri) {
|
||||
Ok(uri) => Self::Mention {
|
||||
uri,
|
||||
content: resource.text,
|
||||
|
||||
@@ -40,19 +40,13 @@ pub use web_search_tool::*;
|
||||
macro_rules! tools {
|
||||
($($tool:ty),* $(,)?) => {
|
||||
/// A list of all built-in tool names
|
||||
pub fn supported_built_in_tool_names(provider: Option<language_model::LanguageModelProviderId>) -> impl Iterator<Item = String> {
|
||||
pub fn built_in_tool_names() -> impl Iterator<Item = String> {
|
||||
[
|
||||
$(
|
||||
(if let Some(provider) = provider.as_ref() {
|
||||
<$tool>::supports_provider(provider)
|
||||
} else {
|
||||
true
|
||||
})
|
||||
.then(|| <$tool>::name().to_string()),
|
||||
<$tool>::name().to_string(),
|
||||
)*
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// A list of all built-in tools
|
||||
|
||||
@@ -57,7 +57,7 @@ impl AgentTool for WebSearchTool {
|
||||
}
|
||||
|
||||
/// We currently only support Zed Cloud as a provider.
|
||||
fn supports_provider(provider: &LanguageModelProviderId) -> bool {
|
||||
fn supported_provider(&self, provider: &LanguageModelProviderId) -> bool {
|
||||
provider == &ZED_CLOUD_PROVIDER_ID
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ language_model.workspace = true
|
||||
language_models.workspace = true
|
||||
log.workspace = true
|
||||
project.workspace = true
|
||||
release_channel.workspace = true
|
||||
reqwest_client = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -9,7 +9,9 @@ use futures::io::BufReader;
|
||||
use project::Project;
|
||||
use project::agent_server_store::AgentServerCommand;
|
||||
use serde::Deserialize;
|
||||
use util::ResultExt as _;
|
||||
use settings::{Settings as _, SettingsLocation};
|
||||
use task::Shell;
|
||||
use util::{ResultExt as _, get_default_system_shell_preferring_bash};
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{any::Any, cell::RefCell};
|
||||
@@ -21,7 +23,7 @@ use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString, Task, WeakEntit
|
||||
|
||||
use acp_thread::{AcpThread, AuthRequired, LoadError, TerminalProviderEvent};
|
||||
use terminal::TerminalBuilder;
|
||||
use terminal::terminal_settings::{AlternateScroll, CursorShape};
|
||||
use terminal::terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Unsupported version")]
|
||||
@@ -38,7 +40,7 @@ pub struct AcpConnection {
|
||||
// NB: Don't move this into the wait_task, since we need to ensure the process is
|
||||
// killed on drop (setting kill_on_drop on the command seems to not always work).
|
||||
child: smol::process::Child,
|
||||
_io_task: Task<Result<(), acp::Error>>,
|
||||
_io_task: Task<Result<()>>,
|
||||
_wait_task: Task<Result<()>>,
|
||||
_stderr_task: Task<Result<()>>,
|
||||
}
|
||||
@@ -105,14 +107,6 @@ impl AcpConnection {
|
||||
|
||||
let sessions = Rc::new(RefCell::new(HashMap::default()));
|
||||
|
||||
let (release_channel, version) = cx.update(|cx| {
|
||||
(
|
||||
release_channel::ReleaseChannel::try_global(cx)
|
||||
.map(|release_channel| release_channel.display_name()),
|
||||
release_channel::AppVersion::global(cx).to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let client = ClientDelegate {
|
||||
sessions: sessions.clone(),
|
||||
cx: cx.clone(),
|
||||
@@ -180,11 +174,6 @@ impl AcpConnection {
|
||||
"terminal_output": true,
|
||||
})),
|
||||
},
|
||||
client_info: Some(acp::Implementation {
|
||||
name: "zed".to_owned(),
|
||||
title: release_channel.map(|c| c.to_owned()),
|
||||
version,
|
||||
}),
|
||||
meta: None,
|
||||
})
|
||||
.await?;
|
||||
@@ -713,11 +702,7 @@ impl acp::Client for ClientDelegate {
|
||||
.get(¬ification.session_id)
|
||||
.context("Failed to get session")?;
|
||||
|
||||
if let acp::SessionUpdate::CurrentModeUpdate(acp::CurrentModeUpdate {
|
||||
current_mode_id,
|
||||
..
|
||||
}) = ¬ification.update
|
||||
{
|
||||
if let acp::SessionUpdate::CurrentModeUpdate { current_mode_id } = ¬ification.update {
|
||||
if let Some(session_modes) = &session.session_modes {
|
||||
session_modes.borrow_mut().current_mode_id = current_mode_id.clone();
|
||||
} else {
|
||||
@@ -831,18 +816,62 @@ impl acp::Client for ClientDelegate {
|
||||
let thread = self.session_thread(&args.session_id)?;
|
||||
let project = thread.read_with(&self.cx, |thread, _cx| thread.project().clone())?;
|
||||
|
||||
let terminal_entity = acp_thread::create_terminal_entity(
|
||||
args.command.clone(),
|
||||
&args.args,
|
||||
args.env
|
||||
.into_iter()
|
||||
.map(|env| (env.name, env.value))
|
||||
.collect(),
|
||||
args.cwd.clone(),
|
||||
&project,
|
||||
&mut self.cx.clone(),
|
||||
)
|
||||
.await?;
|
||||
let mut env = if let Some(dir) = &args.cwd {
|
||||
project
|
||||
.update(&mut self.cx.clone(), |project, cx| {
|
||||
let worktree = project.find_worktree(dir.as_path(), cx);
|
||||
let shell = TerminalSettings::get(
|
||||
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: &path,
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
.shell
|
||||
.clone();
|
||||
project.directory_environment(&shell, dir.clone().into(), cx)
|
||||
})?
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
// Disables paging for `git` and hopefully other commands
|
||||
env.insert("PAGER".into(), "".into());
|
||||
for var in args.env {
|
||||
env.insert(var.name, var.value);
|
||||
}
|
||||
|
||||
// Use remote shell or default system shell, as appropriate
|
||||
let shell = project
|
||||
.update(&mut self.cx.clone(), |project, cx| {
|
||||
project
|
||||
.remote_client()
|
||||
.and_then(|r| r.read(cx).default_system_shell())
|
||||
.map(Shell::Program)
|
||||
})?
|
||||
.unwrap_or_else(|| Shell::Program(get_default_system_shell_preferring_bash()));
|
||||
let is_windows = project
|
||||
.read_with(&self.cx, |project, cx| project.path_style(cx).is_windows())
|
||||
.unwrap_or(cfg!(windows));
|
||||
let (task_command, task_args) = task::ShellBuilder::new(&shell, is_windows)
|
||||
.redirect_stdin_to_dev_null()
|
||||
.build(Some(args.command.clone()), &args.args);
|
||||
|
||||
let terminal_entity = project
|
||||
.update(&mut self.cx.clone(), |project, cx| {
|
||||
project.create_terminal_task(
|
||||
task::SpawnInTerminal {
|
||||
command: Some(task_command),
|
||||
args: task_args,
|
||||
cwd: args.cwd.clone(),
|
||||
env,
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
// Register with renderer
|
||||
let terminal_entity = thread.update(&mut self.cx.clone(), |thread, cx| {
|
||||
|
||||
@@ -73,7 +73,12 @@ pub trait AgentServer: Send {
|
||||
root_dir: Option<&Path>,
|
||||
delegate: AgentServerDelegate,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>>;
|
||||
) -> Task<
|
||||
Result<(
|
||||
Rc<dyn AgentConnection>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
)>,
|
||||
>;
|
||||
|
||||
fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
|
||||
}
|
||||
@@ -84,6 +89,30 @@ impl dyn AgentServer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for ACP-specific agent capabilities.
|
||||
/// This trait is only implemented by agents that use the Agent Client Protocol (ACP).
|
||||
pub trait AcpAgentServer: AgentServer {
|
||||
/// Returns the list of slash commands that should trigger Zed's authentication UI
|
||||
/// when running locally (e.g., "/login").
|
||||
/// These commands will be intercepted by Zed to show the auth method selection UI.
|
||||
fn local_login_commands(&self) -> Vec<String>;
|
||||
|
||||
/// Returns the list of slash commands that should trigger Zed's authentication UI
|
||||
/// when running remotely (e.g., "/login").
|
||||
/// These commands will be intercepted by Zed to show the auth method selection UI.
|
||||
fn remote_login_commands(&self) -> Vec<String>;
|
||||
|
||||
/// Returns the list of logout-related slash commands that should be sent to the agent
|
||||
/// when running locally to let it reset internal state (e.g., "/logout").
|
||||
/// These commands will be added to available_commands and passed through to the agent.
|
||||
fn local_logout_commands(&self) -> Vec<String>;
|
||||
|
||||
/// Returns the list of logout-related slash commands that should be sent to the agent
|
||||
/// when running remotely to let it reset internal state (e.g., "/logout").
|
||||
/// These commands will be added to available_commands and passed through to the agent.
|
||||
fn remote_logout_commands(&self) -> Vec<String>;
|
||||
}
|
||||
|
||||
/// Load the default proxy environment variables to pass through to the agent
|
||||
pub fn load_proxy_env(cx: &mut App) -> HashMap<String, String> {
|
||||
let proxy_url = cx
|
||||
|
||||
@@ -7,10 +7,11 @@ use std::sync::Arc;
|
||||
use std::{any::Any, path::PathBuf};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::HashMap;
|
||||
use gpui::{App, AppContext as _, SharedString, Task};
|
||||
use project::agent_server_store::{AllAgentServersSettings, CLAUDE_CODE_NAME};
|
||||
|
||||
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use acp_thread::AgentConnection;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -60,7 +61,12 @@ impl AgentServer for ClaudeCode {
|
||||
root_dir: Option<&Path>,
|
||||
delegate: AgentServerDelegate,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||
) -> Task<
|
||||
Result<(
|
||||
Rc<dyn AgentConnection>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
)>,
|
||||
> {
|
||||
let name = self.name();
|
||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||
@@ -69,7 +75,7 @@ impl AgentServer for ClaudeCode {
|
||||
let default_mode = self.default_mode(cx);
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let (command, root_dir, login) = store
|
||||
let (command, root_dir, auth_commands) = store
|
||||
.update(cx, |store, cx| {
|
||||
let agent = store
|
||||
.get_external_agent(&CLAUDE_CODE_NAME.into())
|
||||
@@ -92,7 +98,7 @@ impl AgentServer for ClaudeCode {
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
Ok((connection, login))
|
||||
Ok((connection, auth_commands))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -100,3 +106,21 @@ impl AgentServer for ClaudeCode {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AcpAgentServer for ClaudeCode {
|
||||
fn local_login_commands(&self) -> Vec<String> {
|
||||
vec!["login".to_string()]
|
||||
}
|
||||
|
||||
fn remote_login_commands(&self) -> Vec<String> {
|
||||
vec!["login".to_string()]
|
||||
}
|
||||
|
||||
fn local_logout_commands(&self) -> Vec<String> {
|
||||
vec!["logout".to_string()]
|
||||
}
|
||||
|
||||
fn remote_logout_commands(&self) -> Vec<String> {
|
||||
vec!["logout".to_string()]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ use std::{any::Any, path::Path};
|
||||
use acp_thread::AgentConnection;
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::HashMap;
|
||||
use fs::Fs;
|
||||
use gpui::{App, AppContext as _, SharedString, Task};
|
||||
use project::agent_server_store::{AllAgentServersSettings, CODEX_NAME};
|
||||
use settings::{SettingsStore, update_settings_file};
|
||||
|
||||
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Codex;
|
||||
@@ -61,7 +62,12 @@ impl AgentServer for Codex {
|
||||
root_dir: Option<&Path>,
|
||||
delegate: AgentServerDelegate,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||
) -> Task<
|
||||
Result<(
|
||||
Rc<dyn AgentConnection>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
)>,
|
||||
> {
|
||||
let name = self.name();
|
||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||
@@ -70,7 +76,7 @@ impl AgentServer for Codex {
|
||||
let default_mode = self.default_mode(cx);
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let (command, root_dir, login) = store
|
||||
let (command, root_dir, auth_commands) = store
|
||||
.update(cx, |store, cx| {
|
||||
let agent = store
|
||||
.get_external_agent(&CODEX_NAME.into())
|
||||
@@ -79,6 +85,8 @@ impl AgentServer for Codex {
|
||||
root_dir.as_deref(),
|
||||
extra_env,
|
||||
delegate.status_tx,
|
||||
// For now, report that there are no updates.
|
||||
// (A future PR will use the GitHub Releases API to fetch them.)
|
||||
delegate.new_version_available,
|
||||
&mut cx.to_async(),
|
||||
))
|
||||
@@ -94,7 +102,7 @@ impl AgentServer for Codex {
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
Ok((connection, login))
|
||||
Ok((connection, auth_commands))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -102,3 +110,21 @@ impl AgentServer for Codex {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AcpAgentServer for Codex {
|
||||
fn local_login_commands(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn remote_login_commands(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn local_logout_commands(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn remote_logout_commands(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::{AgentServerDelegate, load_proxy_env};
|
||||
use crate::{AcpAgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use acp_thread::AgentConnection;
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::HashMap;
|
||||
use fs::Fs;
|
||||
use gpui::{App, AppContext as _, SharedString, Task};
|
||||
use project::agent_server_store::{AllAgentServersSettings, ExternalAgentServerName};
|
||||
use settings::{SettingsStore, update_settings_file};
|
||||
use std::{path::Path, rc::Rc, sync::Arc};
|
||||
use std::{any::Any, path::Path, rc::Rc, sync::Arc};
|
||||
use ui::IconName;
|
||||
|
||||
/// A generic agent server implementation for custom user-defined agents
|
||||
@@ -65,7 +66,12 @@ impl crate::AgentServer for CustomAgentServer {
|
||||
root_dir: Option<&Path>,
|
||||
delegate: AgentServerDelegate,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||
) -> Task<
|
||||
Result<(
|
||||
Rc<dyn AgentConnection>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
)>,
|
||||
> {
|
||||
let name = self.name();
|
||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||
@@ -74,7 +80,7 @@ impl crate::AgentServer for CustomAgentServer {
|
||||
let extra_env = load_proxy_env(cx);
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let (command, root_dir, login) = store
|
||||
let (command, root_dir, auth_commands) = store
|
||||
.update(cx, |store, cx| {
|
||||
let agent = store
|
||||
.get_external_agent(&ExternalAgentServerName(name.clone()))
|
||||
@@ -99,11 +105,29 @@ impl crate::AgentServer for CustomAgentServer {
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
Ok((connection, login))
|
||||
Ok((connection, auth_commands))
|
||||
})
|
||||
}
|
||||
|
||||
fn into_any(self: Rc<Self>) -> Rc<dyn std::any::Any> {
|
||||
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AcpAgentServer for CustomAgentServer {
|
||||
fn local_login_commands(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn remote_login_commands(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn local_logout_commands(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn remote_logout_commands(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::rc::Rc;
|
||||
use std::{any::Any, path::Path};
|
||||
|
||||
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use acp_thread::AgentConnection;
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::HashMap;
|
||||
use gpui::{App, SharedString, Task};
|
||||
use language_models::provider::google::GoogleLanguageModelProvider;
|
||||
use project::agent_server_store::GEMINI_NAME;
|
||||
@@ -29,7 +30,12 @@ impl AgentServer for Gemini {
|
||||
root_dir: Option<&Path>,
|
||||
delegate: AgentServerDelegate,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||
) -> Task<
|
||||
Result<(
|
||||
Rc<dyn AgentConnection>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
)>,
|
||||
> {
|
||||
let name = self.name();
|
||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||
@@ -47,7 +53,7 @@ impl AgentServer for Gemini {
|
||||
{
|
||||
extra_env.insert("GEMINI_API_KEY".into(), api_key);
|
||||
}
|
||||
let (command, root_dir, login) = store
|
||||
let (command, root_dir, auth_commands) = store
|
||||
.update(cx, |store, cx| {
|
||||
let agent = store
|
||||
.get_external_agent(&GEMINI_NAME.into())
|
||||
@@ -71,7 +77,7 @@ impl AgentServer for Gemini {
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
Ok((connection, login))
|
||||
Ok((connection, auth_commands))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,6 +86,26 @@ impl AgentServer for Gemini {
|
||||
}
|
||||
}
|
||||
|
||||
impl AcpAgentServer for Gemini {
|
||||
fn local_login_commands(&self) -> Vec<String> {
|
||||
vec!["login".to_string()]
|
||||
}
|
||||
|
||||
fn remote_login_commands(&self) -> Vec<String> {
|
||||
// When remote, OAuth doesn't work, so login is handled via the
|
||||
// auth_commands mapping (oauth-personal -> spawn-gemini-cli)
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn local_logout_commands(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn remote_logout_commands(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use project::agent_server_store::AgentServerCommand;
|
||||
|
||||
@@ -25,7 +25,7 @@ agent_settings.workspace = true
|
||||
ai_onboarding.workspace = true
|
||||
anyhow.workspace = true
|
||||
arrayvec.workspace = true
|
||||
assistant_text_thread.workspace = true
|
||||
assistant_context.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_slash_commands.workspace = true
|
||||
audio.workspace = true
|
||||
@@ -102,7 +102,7 @@ zed_actions.workspace = true
|
||||
[dev-dependencies]
|
||||
acp_thread = { workspace = true, features = ["test-support"] }
|
||||
agent = { workspace = true, features = ["test-support"] }
|
||||
assistant_text_thread = { workspace = true, features = ["test-support"] }
|
||||
assistant_context = { workspace = true, features = ["test-support"] }
|
||||
buffer_diff = { workspace = true, features = ["test-support"] }
|
||||
db = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -253,22 +253,17 @@ impl ContextPickerCompletionProvider {
|
||||
) -> Option<Completion> {
|
||||
let project = workspace.read(cx).project().clone();
|
||||
|
||||
let (abs_path, file_name) = match &symbol.path {
|
||||
SymbolLocation::InProject(project_path) => (
|
||||
project.read(cx).absolute_path(&project_path, cx)?,
|
||||
project_path.path.file_name()?.to_string().into(),
|
||||
),
|
||||
let label = CodeLabel::plain(symbol.name.clone(), None);
|
||||
|
||||
let abs_path = match &symbol.path {
|
||||
SymbolLocation::InProject(project_path) => {
|
||||
project.read(cx).absolute_path(&project_path, cx)?
|
||||
}
|
||||
SymbolLocation::OutsideProject {
|
||||
abs_path,
|
||||
signature: _,
|
||||
} => (
|
||||
PathBuf::from(abs_path.as_ref()),
|
||||
abs_path.file_name().map(|f| f.to_string_lossy())?,
|
||||
),
|
||||
} => PathBuf::from(abs_path.as_ref()),
|
||||
};
|
||||
|
||||
let label = build_symbol_label(&symbol.name, &file_name, symbol.range.start.0.row + 1, cx);
|
||||
|
||||
let uri = MentionUri::Symbol {
|
||||
abs_path,
|
||||
name: symbol.name.clone(),
|
||||
@@ -575,7 +570,6 @@ impl ContextPickerCompletionProvider {
|
||||
.unwrap_or_default();
|
||||
let workspace = workspace.read(cx);
|
||||
let project = workspace.project().read(cx);
|
||||
let include_root_name = workspace.visible_worktrees(cx).count() > 1;
|
||||
|
||||
if let Some(agent_panel) = workspace.panel::<AgentPanel>(cx)
|
||||
&& let Some(thread) = agent_panel.read(cx).active_agent_thread(cx)
|
||||
@@ -602,11 +596,7 @@ impl ContextPickerCompletionProvider {
|
||||
project
|
||||
.worktree_for_id(project_path.worktree_id, cx)
|
||||
.map(|worktree| {
|
||||
let path_prefix = if include_root_name {
|
||||
worktree.read(cx).root_name().into()
|
||||
} else {
|
||||
RelPath::empty().into()
|
||||
};
|
||||
let path_prefix = worktree.read(cx).root_name().into();
|
||||
Match::File(FileMatch {
|
||||
mat: fuzzy::PathMatch {
|
||||
score: 1.,
|
||||
@@ -684,17 +674,6 @@ impl ContextPickerCompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_symbol_label(symbol_name: &str, file_name: &str, line: u32, cx: &App) -> CodeLabel {
|
||||
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
||||
let mut label = CodeLabelBuilder::default();
|
||||
|
||||
label.push_str(symbol_name, None);
|
||||
label.push_str(" ", None);
|
||||
label.push_str(&format!("{} L{}", file_name, line), comment_id);
|
||||
|
||||
label.build()
|
||||
}
|
||||
|
||||
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
|
||||
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
||||
let mut label = CodeLabelBuilder::default();
|
||||
@@ -833,21 +812,9 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||
path: mat.path.clone(),
|
||||
};
|
||||
|
||||
// If path is empty, this means we're matching with the root directory itself
|
||||
// so we use the path_prefix as the name
|
||||
let path_prefix = if mat.path.is_empty() {
|
||||
project
|
||||
.read(cx)
|
||||
.worktree_for_id(project_path.worktree_id, cx)
|
||||
.map(|wt| wt.read(cx).root_name().into())
|
||||
.unwrap_or_else(|| mat.path_prefix.clone())
|
||||
} else {
|
||||
mat.path_prefix.clone()
|
||||
};
|
||||
|
||||
Self::completion_for_path(
|
||||
project_path,
|
||||
&path_prefix,
|
||||
&mat.path_prefix,
|
||||
is_recent,
|
||||
mat.is_dir,
|
||||
source_range.clone(),
|
||||
|
||||
@@ -402,7 +402,7 @@ mod tests {
|
||||
use agent::HistoryStore;
|
||||
use agent_client_protocol as acp;
|
||||
use agent_settings::AgentSettings;
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use assistant_context::ContextStore;
|
||||
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
|
||||
use editor::{EditorSettings, RowInfo};
|
||||
use fs::FakeFs;
|
||||
@@ -466,8 +466,8 @@ mod tests {
|
||||
connection.send_update(session_id, acp::SessionUpdate::ToolCall(tool_call), cx)
|
||||
});
|
||||
|
||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||
|
||||
let view_state = cx.new(|_cx| {
|
||||
EntryViewState::new(
|
||||
|
||||
@@ -11,10 +11,10 @@ use assistant_slash_commands::codeblock_fence_for_path;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
Addon, Anchor, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement,
|
||||
EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, Inlay,
|
||||
EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, InlayId,
|
||||
MultiBuffer, ToOffset,
|
||||
actions::Paste,
|
||||
display_map::{Crease, CreaseId, FoldId},
|
||||
display_map::{Crease, CreaseId, FoldId, Inlay},
|
||||
};
|
||||
use futures::{
|
||||
FutureExt as _,
|
||||
@@ -29,8 +29,7 @@ use language::{Buffer, Language, language_settings::InlayHintKind};
|
||||
use language_model::LanguageModelImage;
|
||||
use postage::stream::Stream as _;
|
||||
use project::{
|
||||
CompletionIntent, InlayHint, InlayHintLabel, InlayId, Project, ProjectItem, ProjectPath,
|
||||
Worktree,
|
||||
CompletionIntent, InlayHint, InlayHintLabel, Project, ProjectItem, ProjectPath, Worktree,
|
||||
};
|
||||
use prompt_store::{PromptId, PromptStore};
|
||||
use rope::Point;
|
||||
@@ -76,7 +75,7 @@ pub enum MessageEditorEvent {
|
||||
|
||||
impl EventEmitter<MessageEditorEvent> for MessageEditor {}
|
||||
|
||||
const COMMAND_HINT_INLAY_ID: InlayId = InlayId::Hint(0);
|
||||
const COMMAND_HINT_INLAY_ID: u32 = 0;
|
||||
|
||||
impl MessageEditor {
|
||||
pub fn new(
|
||||
@@ -152,7 +151,7 @@ impl MessageEditor {
|
||||
let has_new_hint = !new_hints.is_empty();
|
||||
editor.splice_inlays(
|
||||
if has_hint {
|
||||
&[COMMAND_HINT_INLAY_ID]
|
||||
&[InlayId::Hint(COMMAND_HINT_INLAY_ID)]
|
||||
} else {
|
||||
&[]
|
||||
},
|
||||
@@ -629,12 +628,12 @@ impl MessageEditor {
|
||||
path: PathBuf,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Mention>> {
|
||||
let text_thread_task = self.history_store.update(cx, |store, cx| {
|
||||
let context = self.history_store.update(cx, |store, cx| {
|
||||
store.load_text_thread(path.as_path().into(), cx)
|
||||
});
|
||||
cx.spawn(async move |_, cx| {
|
||||
let text_thread = text_thread_task.await?;
|
||||
let xml = text_thread.update(cx, |text_thread, cx| text_thread.to_xml(cx))?;
|
||||
let context = context.await?;
|
||||
let xml = context.update(cx, |context, cx| context.to_xml(cx))?;
|
||||
Ok(Mention::Text {
|
||||
content: xml,
|
||||
tracked_buffers: Vec::new(),
|
||||
@@ -702,7 +701,7 @@ impl MessageEditor {
|
||||
let mut all_tracked_buffers = Vec::new();
|
||||
|
||||
let result = editor.update(cx, |editor, cx| {
|
||||
let mut ix = text.chars().position(|c| !c.is_whitespace()).unwrap_or(0);
|
||||
let mut ix = 0;
|
||||
let mut chunks: Vec<acp::ContentBlock> = Vec::new();
|
||||
let text = editor.text(cx);
|
||||
editor.display_map.update(cx, |map, cx| {
|
||||
@@ -714,6 +713,15 @@ impl MessageEditor {
|
||||
|
||||
let crease_range = crease.range().to_offset(&snapshot.buffer_snapshot());
|
||||
if crease_range.start > ix {
|
||||
//todo(): Custom slash command ContentBlock?
|
||||
// let chunk = if prevent_slash_commands
|
||||
// && ix == 0
|
||||
// && parse_slash_command(&text[ix..]).is_some()
|
||||
// {
|
||||
// format!(" {}", &text[ix..crease_range.start]).into()
|
||||
// } else {
|
||||
// text[ix..crease_range.start].into()
|
||||
// };
|
||||
let chunk = text[ix..crease_range.start].into();
|
||||
chunks.push(chunk);
|
||||
}
|
||||
@@ -774,6 +782,15 @@ impl MessageEditor {
|
||||
}
|
||||
|
||||
if ix < text.len() {
|
||||
//todo(): Custom slash command ContentBlock?
|
||||
// let last_chunk = if prevent_slash_commands
|
||||
// && ix == 0
|
||||
// && parse_slash_command(&text[ix..]).is_some()
|
||||
// {
|
||||
// format!(" {}", text[ix..].trim_end())
|
||||
// } else {
|
||||
// text[ix..].trim_end().to_owned()
|
||||
// };
|
||||
let last_chunk = text[ix..].trim_end().to_owned();
|
||||
if !last_chunk.is_empty() {
|
||||
chunks.push(last_chunk.into());
|
||||
@@ -1044,7 +1061,6 @@ impl MessageEditor {
|
||||
) {
|
||||
self.clear(window, cx);
|
||||
|
||||
let path_style = self.project.read(cx).path_style(cx);
|
||||
let mut text = String::new();
|
||||
let mut mentions = Vec::new();
|
||||
|
||||
@@ -1057,8 +1073,7 @@ impl MessageEditor {
|
||||
resource: acp::EmbeddedResourceResource::TextResourceContents(resource),
|
||||
..
|
||||
}) => {
|
||||
let Some(mention_uri) = MentionUri::parse(&resource.uri, path_style).log_err()
|
||||
else {
|
||||
let Some(mention_uri) = MentionUri::parse(&resource.uri).log_err() else {
|
||||
continue;
|
||||
};
|
||||
let start = text.len();
|
||||
@@ -1074,9 +1089,7 @@ impl MessageEditor {
|
||||
));
|
||||
}
|
||||
acp::ContentBlock::ResourceLink(resource) => {
|
||||
if let Some(mention_uri) =
|
||||
MentionUri::parse(&resource.uri, path_style).log_err()
|
||||
{
|
||||
if let Some(mention_uri) = MentionUri::parse(&resource.uri).log_err() {
|
||||
let start = text.len();
|
||||
write!(&mut text, "{}", mention_uri.as_link()).ok();
|
||||
let end = text.len();
|
||||
@@ -1091,7 +1104,7 @@ impl MessageEditor {
|
||||
meta: _,
|
||||
}) => {
|
||||
let mention_uri = if let Some(uri) = uri {
|
||||
MentionUri::parse(&uri, path_style)
|
||||
MentionUri::parse(&uri)
|
||||
} else {
|
||||
Ok(MentionUri::PastedImage)
|
||||
};
|
||||
@@ -1577,7 +1590,7 @@ mod tests {
|
||||
use acp_thread::MentionUri;
|
||||
use agent::{HistoryStore, outline};
|
||||
use agent_client_protocol as acp;
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use assistant_context::ContextStore;
|
||||
use editor::{AnchorRangeExt as _, Editor, EditorMode};
|
||||
use fs::FakeFs;
|
||||
use futures::StreamExt as _;
|
||||
@@ -1608,8 +1621,8 @@ mod tests {
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||
|
||||
let message_editor = cx.update(|window, cx| {
|
||||
cx.new(|cx| {
|
||||
@@ -1713,8 +1726,8 @@ mod tests {
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
|
||||
// Start with no available commands - simulating Claude which doesn't support slash commands
|
||||
let available_commands = Rc::new(RefCell::new(vec![]));
|
||||
@@ -1877,8 +1890,8 @@ mod tests {
|
||||
|
||||
let mut cx = VisualTestContext::from_window(*window, cx);
|
||||
|
||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
|
||||
let available_commands = Rc::new(RefCell::new(vec![
|
||||
acp::AvailableCommand {
|
||||
@@ -2117,8 +2130,8 @@ mod tests {
|
||||
opened_editors.push(buffer);
|
||||
}
|
||||
|
||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
|
||||
|
||||
let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||
@@ -2165,10 +2178,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
current_completion_labels(editor),
|
||||
&[
|
||||
format!("eight.txt b{slash}"),
|
||||
format!("seven.txt b{slash}"),
|
||||
format!("six.txt b{slash}"),
|
||||
format!("five.txt b{slash}"),
|
||||
format!("eight.txt dir{slash}b{slash}"),
|
||||
format!("seven.txt dir{slash}b{slash}"),
|
||||
format!("six.txt dir{slash}b{slash}"),
|
||||
format!("five.txt dir{slash}b{slash}"),
|
||||
]
|
||||
);
|
||||
editor.set_text("", window, cx);
|
||||
@@ -2196,10 +2209,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
current_completion_labels(editor),
|
||||
&[
|
||||
format!("eight.txt b{slash}"),
|
||||
format!("seven.txt b{slash}"),
|
||||
format!("six.txt b{slash}"),
|
||||
format!("five.txt b{slash}"),
|
||||
format!("eight.txt dir{slash}b{slash}"),
|
||||
format!("seven.txt dir{slash}b{slash}"),
|
||||
format!("six.txt dir{slash}b{slash}"),
|
||||
format!("five.txt dir{slash}b{slash}"),
|
||||
"Files & Directories".into(),
|
||||
"Symbols".into(),
|
||||
"Threads".into(),
|
||||
@@ -2232,7 +2245,7 @@ mod tests {
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
current_completion_labels(editor),
|
||||
vec![format!("one.txt a{slash}")]
|
||||
vec![format!("one.txt dir{slash}a{slash}")]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2279,10 +2292,7 @@ mod tests {
|
||||
panic!("Unexpected mentions");
|
||||
};
|
||||
pretty_assertions::assert_eq!(content, "1");
|
||||
pretty_assertions::assert_eq!(
|
||||
uri,
|
||||
&MentionUri::parse(&url_one, PathStyle::local()).unwrap()
|
||||
);
|
||||
pretty_assertions::assert_eq!(uri, &url_one.parse::<MentionUri>().unwrap());
|
||||
}
|
||||
|
||||
let contents = message_editor
|
||||
@@ -2303,10 +2313,7 @@ mod tests {
|
||||
let [(uri, Mention::UriOnly)] = contents.as_slice() else {
|
||||
panic!("Unexpected mentions");
|
||||
};
|
||||
pretty_assertions::assert_eq!(
|
||||
uri,
|
||||
&MentionUri::parse(&url_one, PathStyle::local()).unwrap()
|
||||
);
|
||||
pretty_assertions::assert_eq!(uri, &url_one.parse::<MentionUri>().unwrap());
|
||||
}
|
||||
|
||||
cx.simulate_input(" ");
|
||||
@@ -2367,10 +2374,7 @@ mod tests {
|
||||
panic!("Unexpected mentions");
|
||||
};
|
||||
pretty_assertions::assert_eq!(content, "8");
|
||||
pretty_assertions::assert_eq!(
|
||||
uri,
|
||||
&MentionUri::parse(&url_eight, PathStyle::local()).unwrap()
|
||||
);
|
||||
pretty_assertions::assert_eq!(uri, &url_eight.parse::<MentionUri>().unwrap());
|
||||
}
|
||||
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
@@ -2455,7 +2459,7 @@ mod tests {
|
||||
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) @symbol ")
|
||||
);
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(current_completion_labels(editor), &["MySymbol one.txt L1"]);
|
||||
assert_eq!(current_completion_labels(editor), &["MySymbol"]);
|
||||
});
|
||||
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
@@ -2511,7 +2515,7 @@ mod tests {
|
||||
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) @file x.png", symbol.to_uri())
|
||||
);
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(current_completion_labels(editor), &["x.png "]);
|
||||
assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]);
|
||||
});
|
||||
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
@@ -2553,7 +2557,7 @@ mod tests {
|
||||
format!("Lorem [@one.txt]({url_one}) Ipsum [@eight.txt]({url_eight}) [@MySymbol]({}) @file x.png", symbol.to_uri())
|
||||
);
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(current_completion_labels(editor), &["x.png "]);
|
||||
assert_eq!(current_completion_labels(editor), &[format!("x.png dir{slash}")]);
|
||||
});
|
||||
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
@@ -2653,8 +2657,8 @@ mod tests {
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||
|
||||
let message_editor = cx.update(|window, cx| {
|
||||
cx.new(|cx| {
|
||||
@@ -2729,62 +2733,4 @@ mod tests {
|
||||
_ => panic!("Expected Text mention for small file"),
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_whitespace_trimming(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/project", json!({"file.rs": "fn main() {}"}))
|
||||
.await;
|
||||
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
|
||||
let message_editor = cx.update(|window, cx| {
|
||||
cx.new(|cx| {
|
||||
MessageEditor::new(
|
||||
workspace.downgrade(),
|
||||
project.clone(),
|
||||
history_store.clone(),
|
||||
None,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
"Test Agent".into(),
|
||||
"Test",
|
||||
EditorMode::AutoHeight {
|
||||
min_lines: 1,
|
||||
max_lines: None,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
let editor = message_editor.update(cx, |message_editor, _| message_editor.editor.clone());
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
editor.set_text(" hello world ", window, cx);
|
||||
});
|
||||
|
||||
let (content, _) = message_editor
|
||||
.update(cx, |message_editor, cx| message_editor.contents(false, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
content,
|
||||
vec![acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "hello world".into(),
|
||||
annotations: None,
|
||||
meta: None
|
||||
})]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ impl Render for ModeSelector {
|
||||
trigger_button,
|
||||
Tooltip::element({
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
move |_window, cx| {
|
||||
move |window, cx| {
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
@@ -205,9 +205,10 @@ impl Render for ModeSelector {
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(Label::new("Cycle Through Modes"))
|
||||
.child(KeyBinding::for_action_in(
|
||||
.children(KeyBinding::for_action_in(
|
||||
&CycleModeSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
@@ -216,9 +217,10 @@ impl Render for ModeSelector {
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(Label::new("Toggle Mode Menu"))
|
||||
.child(KeyBinding::for_action_in(
|
||||
.children(KeyBinding::for_action_in(
|
||||
&ToggleProfileSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
|
||||
@@ -77,8 +77,14 @@ impl Render for AcpModelSelectorPopover {
|
||||
.ml_0p5(),
|
||||
)
|
||||
.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
&ToggleModelSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::BottomRight,
|
||||
cx,
|
||||
|
||||
@@ -324,8 +324,8 @@ impl AcpThreadHistory {
|
||||
HistoryEntry::AcpThread(thread) => self
|
||||
.history_store
|
||||
.update(cx, |this, cx| this.delete_thread(thread.id.clone(), cx)),
|
||||
HistoryEntry::TextThread(text_thread) => self.history_store.update(cx, |this, cx| {
|
||||
this.delete_text_thread(text_thread.path.clone(), cx)
|
||||
HistoryEntry::TextThread(context) => self.history_store.update(cx, |this, cx| {
|
||||
this.delete_text_thread(context.path.clone(), cx)
|
||||
}),
|
||||
};
|
||||
task.detach_and_log_err(cx);
|
||||
@@ -423,8 +423,8 @@ impl AcpThreadHistory {
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(move |this, _, _, cx| this.remove_thread(ix, cx)),
|
||||
@@ -595,8 +595,8 @@ impl RenderOnce for AcpHistoryEntryElement {
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
|
||||
})
|
||||
.on_click({
|
||||
let thread_view = self.thread_view.clone();
|
||||
@@ -635,12 +635,12 @@ impl RenderOnce for AcpHistoryEntryElement {
|
||||
});
|
||||
}
|
||||
}
|
||||
HistoryEntry::TextThread(text_thread) => {
|
||||
HistoryEntry::TextThread(context) => {
|
||||
if let Some(panel) = workspace.read(cx).panel::<AgentPanel>(cx) {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel
|
||||
.open_saved_text_thread(
|
||||
text_thread.path.clone(),
|
||||
context.path.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -260,10 +260,11 @@ impl ThreadFeedbackState {
|
||||
|
||||
pub struct AcpThreadView {
|
||||
agent: Rc<dyn AgentServer>,
|
||||
acp_agent: Option<Rc<dyn agent_servers::AcpAgentServer>>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
thread_state: ThreadState,
|
||||
login: Option<task::SpawnInTerminal>,
|
||||
auth_commands: HashMap<String, task::SpawnInTerminal>,
|
||||
history_store: Entity<HistoryStore>,
|
||||
hovered_recent_history_item: Option<usize>,
|
||||
entry_view_state: Entity<EntryViewState>,
|
||||
@@ -403,8 +404,38 @@ impl AcpThreadView {
|
||||
let show_codex_windows_warning = crate::ExternalAgent::parse_built_in(agent.as_ref())
|
||||
== Some(crate::ExternalAgent::Codex);
|
||||
|
||||
// Try to downcast to AcpAgentServer for ACP-specific functionality
|
||||
let acp_agent = agent
|
||||
.clone()
|
||||
.into_any()
|
||||
.downcast::<agent_servers::Gemini>()
|
||||
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
|
||||
.or_else(|_| {
|
||||
agent
|
||||
.clone()
|
||||
.into_any()
|
||||
.downcast::<agent_servers::ClaudeCode>()
|
||||
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
|
||||
})
|
||||
.or_else(|_| {
|
||||
agent
|
||||
.clone()
|
||||
.into_any()
|
||||
.downcast::<agent_servers::Codex>()
|
||||
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
|
||||
})
|
||||
.or_else(|_| {
|
||||
agent
|
||||
.clone()
|
||||
.into_any()
|
||||
.downcast::<agent_servers::CustomAgentServer>()
|
||||
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
|
||||
})
|
||||
.ok();
|
||||
|
||||
Self {
|
||||
agent: agent.clone(),
|
||||
acp_agent,
|
||||
workspace: workspace.clone(),
|
||||
project: project.clone(),
|
||||
entry_view_state,
|
||||
@@ -416,7 +447,7 @@ impl AcpThreadView {
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
login: None,
|
||||
auth_commands: HashMap::default(),
|
||||
message_editor,
|
||||
model_selector: None,
|
||||
profile_selector: None,
|
||||
@@ -509,8 +540,9 @@ impl AcpThreadView {
|
||||
let connect_task = agent.connect(root_dir.as_deref(), delegate, cx);
|
||||
let load_task = cx.spawn_in(window, async move |this, cx| {
|
||||
let connection = match connect_task.await {
|
||||
Ok((connection, login)) => {
|
||||
this.update(cx, |this, _| this.login = login).ok();
|
||||
Ok((connection, auth_commands)) => {
|
||||
this.update(cx, |this, _| this.auth_commands = auth_commands)
|
||||
.ok();
|
||||
connection
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -1051,20 +1083,52 @@ impl AcpThreadView {
|
||||
|
||||
let text = self.message_editor.read(cx).text(cx);
|
||||
let text = text.trim();
|
||||
if text == "/login" || text == "/logout" {
|
||||
|
||||
// Check if this is a login or logout command (only for ACP agents)
|
||||
let command_name = text.strip_prefix('/');
|
||||
let is_remote = self.project.read(cx).is_via_remote_server();
|
||||
let (login_commands, logout_commands) = if let Some(acp_agent) = &self.acp_agent {
|
||||
let login = if is_remote {
|
||||
acp_agent.remote_login_commands()
|
||||
} else {
|
||||
acp_agent.local_login_commands()
|
||||
};
|
||||
let logout = if is_remote {
|
||||
acp_agent.remote_logout_commands()
|
||||
} else {
|
||||
acp_agent.local_logout_commands()
|
||||
};
|
||||
(login, logout)
|
||||
} else {
|
||||
(vec![], vec![])
|
||||
};
|
||||
let is_login_command = if let Some(cmd) = command_name {
|
||||
login_commands.iter().any(|c| c == cmd)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let is_logout_command = if let Some(cmd) = command_name {
|
||||
logout_commands.iter().any(|c| c == cmd)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if is_login_command || is_logout_command {
|
||||
let ThreadState::Ready { thread, .. } = &self.thread_state else {
|
||||
return;
|
||||
};
|
||||
|
||||
let connection = thread.read(cx).connection().clone();
|
||||
let can_login = !connection.auth_methods().is_empty() || self.login.is_some();
|
||||
let can_login = !connection.auth_methods().is_empty() || !self.auth_commands.is_empty();
|
||||
|
||||
// Does the agent have a specific logout command? Prefer that in case they need to reset internal state.
|
||||
let logout_supported = text == "/logout"
|
||||
let logout_supported = is_logout_command
|
||||
&& self
|
||||
.available_commands
|
||||
.borrow()
|
||||
.iter()
|
||||
.any(|command| command.name == "logout");
|
||||
.any(|command| command_name == Some(command.name.as_str()));
|
||||
|
||||
if can_login && !logout_supported {
|
||||
self.message_editor
|
||||
.update(cx, |editor, cx| editor.clear(window, cx));
|
||||
@@ -1259,7 +1323,6 @@ impl AcpThreadView {
|
||||
.await?;
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.send_impl(message_editor, window, cx);
|
||||
this.focus_handle(cx).focus(window);
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
@@ -1422,25 +1485,39 @@ impl AcpThreadView {
|
||||
AcpThreadEvent::AvailableCommandsUpdated(available_commands) => {
|
||||
let mut available_commands = available_commands.clone();
|
||||
|
||||
if thread
|
||||
.read(cx)
|
||||
.connection()
|
||||
.auth_methods()
|
||||
.iter()
|
||||
.any(|method| method.id.0.as_ref() == "claude-login")
|
||||
{
|
||||
available_commands.push(acp::AvailableCommand {
|
||||
name: "login".to_owned(),
|
||||
description: "Authenticate".to_owned(),
|
||||
input: None,
|
||||
meta: None,
|
||||
});
|
||||
available_commands.push(acp::AvailableCommand {
|
||||
name: "logout".to_owned(),
|
||||
description: "Authenticate".to_owned(),
|
||||
input: None,
|
||||
meta: None,
|
||||
});
|
||||
// Add auth commands only for ACP agents
|
||||
if let Some(acp_agent) = &self.acp_agent {
|
||||
let is_remote = self.project.read(cx).is_via_remote_server();
|
||||
let login_commands = if is_remote {
|
||||
acp_agent.remote_login_commands()
|
||||
} else {
|
||||
acp_agent.local_login_commands()
|
||||
};
|
||||
let logout_commands = if is_remote {
|
||||
acp_agent.remote_logout_commands()
|
||||
} else {
|
||||
acp_agent.local_logout_commands()
|
||||
};
|
||||
|
||||
// Add login commands from the agent
|
||||
for command_name in login_commands {
|
||||
available_commands.push(acp::AvailableCommand {
|
||||
name: command_name,
|
||||
description: "Authenticate".to_owned(),
|
||||
input: None,
|
||||
meta: None,
|
||||
});
|
||||
}
|
||||
|
||||
// Add logout commands from the agent
|
||||
for command_name in logout_commands {
|
||||
available_commands.push(acp::AvailableCommand {
|
||||
name: command_name,
|
||||
description: "Authenticate".to_owned(),
|
||||
input: None,
|
||||
meta: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.available_commands.replace(available_commands);
|
||||
@@ -1562,10 +1639,7 @@ impl AcpThreadView {
|
||||
self.thread_error.take();
|
||||
configuration_view.take();
|
||||
pending_auth_method.replace(method.clone());
|
||||
let authenticate = if (method.0.as_ref() == "claude-login"
|
||||
|| method.0.as_ref() == "spawn-gemini-cli")
|
||||
&& let Some(login) = self.login.clone()
|
||||
{
|
||||
let authenticate = if let Some(login) = self.auth_commands.get(method.0.as_ref()).cloned() {
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
Self::spawn_external_agent_login(login, workspace, false, window, cx)
|
||||
} else {
|
||||
@@ -2158,6 +2232,7 @@ impl AcpThreadView {
|
||||
options,
|
||||
entry_ix,
|
||||
tool_call.id.clone(),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.into_any(),
|
||||
@@ -2558,6 +2633,7 @@ impl AcpThreadView {
|
||||
options: &[acp::PermissionOption],
|
||||
entry_ix: usize,
|
||||
tool_call_id: acp::ToolCallId,
|
||||
window: &Window,
|
||||
cx: &Context<Self>,
|
||||
) -> Div {
|
||||
let is_first = self.thread().is_some_and(|thread| {
|
||||
@@ -2614,7 +2690,7 @@ impl AcpThreadView {
|
||||
seen_kinds.push(option.kind);
|
||||
|
||||
this.key_binding(
|
||||
KeyBinding::for_action_in(action, &self.focus_handle, cx)
|
||||
KeyBinding::for_action_in(action, &self.focus_handle, window, cx)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
})
|
||||
@@ -2795,11 +2871,12 @@ impl AcpThreadView {
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Error)
|
||||
.label_size(LabelSize::Small)
|
||||
.tooltip(move |_window, cx| {
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Stop This Command",
|
||||
None,
|
||||
"Also possible by placing your cursor inside the terminal and using regular terminal bindings.",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -3100,7 +3177,7 @@ impl AcpThreadView {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_recent_history(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
fn render_recent_history(&self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
let render_history = self
|
||||
.agent
|
||||
.clone()
|
||||
@@ -3129,6 +3206,7 @@ impl AcpThreadView {
|
||||
KeyBinding::for_action_in(
|
||||
&OpenHistory,
|
||||
&self.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
@@ -3267,48 +3345,34 @@ impl AcpThreadView {
|
||||
})
|
||||
.children(connection.auth_methods().iter().enumerate().rev().map(
|
||||
|(ix, method)| {
|
||||
let (method_id, name) = if self
|
||||
.project
|
||||
.read(cx)
|
||||
.is_via_remote_server()
|
||||
&& method.id.0.as_ref() == "oauth-personal"
|
||||
&& method.name == "Log in with Google"
|
||||
{
|
||||
("spawn-gemini-cli".into(), "Log in with Gemini CLI".into())
|
||||
} else {
|
||||
(method.id.0.clone(), method.name.clone())
|
||||
};
|
||||
let method_id = method.id.clone();
|
||||
let method_id_str = method.id.0.to_string();
|
||||
Button::new(
|
||||
SharedString::from(method.id.0.clone()),
|
||||
method.name.clone(),
|
||||
)
|
||||
.label_size(LabelSize::Small)
|
||||
.map(|this| {
|
||||
if ix == 0 {
|
||||
this.style(ButtonStyle::Tinted(TintColor::Warning))
|
||||
} else {
|
||||
this.style(ButtonStyle::Outlined)
|
||||
}
|
||||
})
|
||||
.when_some(method.description.clone(), |this, description| {
|
||||
this.tooltip(Tooltip::text(description))
|
||||
})
|
||||
.on_click({
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
telemetry::event!(
|
||||
"Authenticate Agent Started",
|
||||
agent = this.agent.telemetry_id(),
|
||||
method = method_id_str
|
||||
);
|
||||
|
||||
Button::new(SharedString::from(method_id.clone()), name)
|
||||
.label_size(LabelSize::Small)
|
||||
.map(|this| {
|
||||
if ix == 0 {
|
||||
this.style(ButtonStyle::Tinted(TintColor::Warning))
|
||||
} else {
|
||||
this.style(ButtonStyle::Outlined)
|
||||
}
|
||||
})
|
||||
.when_some(
|
||||
method.description.clone(),
|
||||
|this, description| {
|
||||
this.tooltip(Tooltip::text(description))
|
||||
},
|
||||
)
|
||||
.on_click({
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
telemetry::event!(
|
||||
"Authenticate Agent Started",
|
||||
agent = this.agent.telemetry_id(),
|
||||
method = method_id
|
||||
);
|
||||
|
||||
this.authenticate(
|
||||
acp::AuthMethodId(method_id.clone()),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
this.authenticate(method_id.clone(), window, cx)
|
||||
})
|
||||
})
|
||||
},
|
||||
)),
|
||||
)
|
||||
@@ -3456,6 +3520,7 @@ impl AcpThreadView {
|
||||
&changed_buffers,
|
||||
self.edits_expanded,
|
||||
pending_edits,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.when(self.edits_expanded, |parent| {
|
||||
@@ -3615,6 +3680,7 @@ impl AcpThreadView {
|
||||
changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
|
||||
expanded: bool,
|
||||
pending_edits: bool,
|
||||
window: &mut Window,
|
||||
cx: &Context<Self>,
|
||||
) -> Div {
|
||||
const EDIT_NOT_READY_TOOLTIP_LABEL: &str = "Wait until file edits are complete.";
|
||||
@@ -3690,11 +3756,12 @@ impl AcpThreadView {
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |_window, cx| {
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Review Changes",
|
||||
&OpenAgentDiff,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -3712,8 +3779,13 @@ impl AcpThreadView {
|
||||
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
|
||||
})
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&RejectAll, &focus_handle.clone(), cx)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
KeyBinding::for_action_in(
|
||||
&RejectAll,
|
||||
&focus_handle.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.reject_all(&RejectAll, window, cx);
|
||||
@@ -3727,7 +3799,7 @@ impl AcpThreadView {
|
||||
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
|
||||
})
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&KeepAll, &focus_handle, cx)
|
||||
KeyBinding::for_action_in(&KeepAll, &focus_handle, window, cx)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
@@ -3957,11 +4029,12 @@ impl AcpThreadView {
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip({
|
||||
move |_window, cx| {
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
expand_tooltip,
|
||||
&ExpandMessageEditor,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -4186,8 +4259,8 @@ impl AcpThreadView {
|
||||
IconButton::new("stop-generation", IconName::Stop)
|
||||
.icon_color(Color::Error)
|
||||
.style(ButtonStyle::Tinted(ui::TintColor::Error))
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::for_action("Stop Generation", &editor::actions::Cancel, cx)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("Stop Generation", &editor::actions::Cancel, window, cx)
|
||||
})
|
||||
.on_click(cx.listener(|this, _event, _, cx| this.cancel_generation(cx)))
|
||||
.into_any_element()
|
||||
@@ -4209,7 +4282,7 @@ impl AcpThreadView {
|
||||
this.icon_color(Color::Accent)
|
||||
}
|
||||
})
|
||||
.tooltip(move |_window, cx| Tooltip::for_action(send_btn_tooltip, &Chat, cx))
|
||||
.tooltip(move |window, cx| Tooltip::for_action(send_btn_tooltip, &Chat, window, cx))
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.send(window, cx);
|
||||
}))
|
||||
@@ -4270,14 +4343,15 @@ impl AcpThreadView {
|
||||
.icon_color(Color::Muted)
|
||||
.toggle_state(following)
|
||||
.selected_icon_color(Some(Color::Custom(cx.theme().players().agent().cursor)))
|
||||
.tooltip(move |_window, cx| {
|
||||
.tooltip(move |window, cx| {
|
||||
if following {
|
||||
Tooltip::for_action(tooltip_label.clone(), &Follow, cx)
|
||||
Tooltip::for_action(tooltip_label.clone(), &Follow, window, cx)
|
||||
} else {
|
||||
Tooltip::with_meta(
|
||||
tooltip_label.clone(),
|
||||
Some(&Follow),
|
||||
"Track the agent's location as it reads and edits files.",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -4305,8 +4379,7 @@ impl AcpThreadView {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(mention) = MentionUri::parse(&url, workspace.read(cx).path_style(cx)).log_err()
|
||||
{
|
||||
if let Some(mention) = MentionUri::parse(&url).log_err() {
|
||||
workspace.update(cx, |workspace, cx| match mention {
|
||||
MentionUri::File { abs_path } => {
|
||||
let project = workspace.project();
|
||||
@@ -5067,7 +5140,7 @@ impl AcpThreadView {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_thread_error(&self, cx: &mut Context<Self>) -> Option<Div> {
|
||||
fn render_thread_error(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
|
||||
let content = match self.thread_error.as_ref()? {
|
||||
ThreadError::Other(error) => self.render_any_thread_error(error.clone(), cx),
|
||||
ThreadError::Refusal => self.render_refusal_error(cx),
|
||||
@@ -5078,7 +5151,9 @@ impl AcpThreadView {
|
||||
ThreadError::ModelRequestLimitReached(plan) => {
|
||||
self.render_model_request_limit_reached_error(*plan, cx)
|
||||
}
|
||||
ThreadError::ToolUseLimitReached => self.render_tool_use_limit_reached_error(cx)?,
|
||||
ThreadError::ToolUseLimitReached => {
|
||||
self.render_tool_use_limit_reached_error(window, cx)?
|
||||
}
|
||||
};
|
||||
|
||||
Some(div().child(content))
|
||||
@@ -5269,7 +5344,11 @@ impl AcpThreadView {
|
||||
.dismiss_action(self.dismiss_error_button(cx))
|
||||
}
|
||||
|
||||
fn render_tool_use_limit_reached_error(&self, cx: &mut Context<Self>) -> Option<Callout> {
|
||||
fn render_tool_use_limit_reached_error(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Callout> {
|
||||
let thread = self.as_native_thread(cx)?;
|
||||
let supports_burn_mode = thread
|
||||
.read(cx)
|
||||
@@ -5296,6 +5375,7 @@ impl AcpThreadView {
|
||||
KeyBinding::for_action_in(
|
||||
&ContinueWithBurnMode,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
@@ -5319,8 +5399,13 @@ impl AcpThreadView {
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&ContinueThread, &focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
KeyBinding::for_action_in(
|
||||
&ContinueThread,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.resume_chat(cx);
|
||||
@@ -5415,11 +5500,9 @@ impl AcpThreadView {
|
||||
HistoryEntry::AcpThread(thread) => self.history_store.update(cx, |history, cx| {
|
||||
history.delete_thread(thread.id.clone(), cx)
|
||||
}),
|
||||
HistoryEntry::TextThread(text_thread) => {
|
||||
self.history_store.update(cx, |history, cx| {
|
||||
history.delete_text_thread(text_thread.path.clone(), cx)
|
||||
})
|
||||
}
|
||||
HistoryEntry::TextThread(context) => self.history_store.update(cx, |history, cx| {
|
||||
history.delete_text_thread(context.path.clone(), cx)
|
||||
}),
|
||||
};
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
@@ -5498,7 +5581,7 @@ impl Render for AcpThreadView {
|
||||
.into_any(),
|
||||
ThreadState::Loading { .. } => v_flex()
|
||||
.flex_1()
|
||||
.child(self.render_recent_history(cx))
|
||||
.child(self.render_recent_history(window, cx))
|
||||
.into_any(),
|
||||
ThreadState::LoadError(e) => v_flex()
|
||||
.flex_1()
|
||||
@@ -5529,7 +5612,8 @@ impl Render for AcpThreadView {
|
||||
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
|
||||
.into_any()
|
||||
} else {
|
||||
this.child(self.render_recent_history(cx)).into_any()
|
||||
this.child(self.render_recent_history(window, cx))
|
||||
.into_any()
|
||||
}
|
||||
}),
|
||||
})
|
||||
@@ -5553,7 +5637,7 @@ impl Render for AcpThreadView {
|
||||
Vec::<Empty>::new()
|
||||
}
|
||||
})
|
||||
.children(self.render_thread_error(cx))
|
||||
.children(self.render_thread_error(window, cx))
|
||||
.when_some(
|
||||
self.new_server_version_available.as_ref().filter(|_| {
|
||||
!has_messages || !matches!(self.thread_state, ThreadState::Ready { .. })
|
||||
@@ -5738,7 +5822,7 @@ fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
pub(crate) mod tests {
|
||||
use acp_thread::StubAgentConnection;
|
||||
use agent_client_protocol::SessionId;
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use assistant_context::ContextStore;
|
||||
use editor::EditorSettings;
|
||||
use fs::FakeFs;
|
||||
use gpui::{EventEmitter, SemanticVersion, TestAppContext, VisualTestContext};
|
||||
@@ -5901,10 +5985,10 @@ pub(crate) mod tests {
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let text_thread_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
|
||||
let context_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
|
||||
let history_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(text_thread_store, cx)));
|
||||
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx)));
|
||||
|
||||
let thread_view = cx.update(|window, cx| {
|
||||
cx.new(|cx| {
|
||||
@@ -5982,12 +6066,9 @@ pub(crate) mod tests {
|
||||
impl StubAgentServer<StubAgentConnection> {
|
||||
fn default_response() -> Self {
|
||||
let conn = StubAgentConnection::new();
|
||||
conn.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk {
|
||||
content: "Default response".into(),
|
||||
meta: None,
|
||||
},
|
||||
)]);
|
||||
conn.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
|
||||
content: "Default response".into(),
|
||||
}]);
|
||||
Self::new(conn)
|
||||
}
|
||||
}
|
||||
@@ -6013,8 +6094,13 @@ pub(crate) mod tests {
|
||||
_root_dir: Option<&Path>,
|
||||
_delegate: AgentServerDelegate,
|
||||
_cx: &mut App,
|
||||
) -> Task<gpui::Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||
Task::ready(Ok((Rc::new(self.connection.clone()), None)))
|
||||
) -> Task<
|
||||
gpui::Result<(
|
||||
Rc<dyn AgentConnection>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
)>,
|
||||
> {
|
||||
Task::ready(Ok((Rc::new(self.connection.clone()), HashMap::default())))
|
||||
}
|
||||
|
||||
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||
@@ -6176,10 +6262,10 @@ pub(crate) mod tests {
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let text_thread_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
|
||||
let context_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
|
||||
let history_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(text_thread_store, cx)));
|
||||
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx)));
|
||||
|
||||
let connection = Rc::new(StubAgentConnection::new());
|
||||
let thread_view = cx.update(|window, cx| {
|
||||
@@ -6338,16 +6424,13 @@ pub(crate) mod tests {
|
||||
|
||||
let connection = StubAgentConnection::new();
|
||||
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
}),
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
},
|
||||
)]);
|
||||
}),
|
||||
}]);
|
||||
|
||||
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
|
||||
add_to_workspace(thread_view.clone(), cx);
|
||||
@@ -6431,16 +6514,13 @@ pub(crate) mod tests {
|
||||
|
||||
let connection = StubAgentConnection::new();
|
||||
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
}),
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
},
|
||||
)]);
|
||||
}),
|
||||
}]);
|
||||
|
||||
let (thread_view, cx) =
|
||||
setup_thread_view(StubAgentServer::new(connection.clone()), cx).await;
|
||||
@@ -6478,16 +6558,13 @@ pub(crate) mod tests {
|
||||
});
|
||||
|
||||
// Send
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "New Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
}),
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "New Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
},
|
||||
)]);
|
||||
}),
|
||||
}]);
|
||||
|
||||
user_message_editor.update_in(cx, |_editor, window, cx| {
|
||||
window.dispatch_action(Box::new(Chat), cx);
|
||||
@@ -6574,14 +6651,13 @@ pub(crate) mod tests {
|
||||
cx.update(|_, cx| {
|
||||
connection.send_update(
|
||||
session_id.clone(),
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
|
||||
acp::SessionUpdate::AgentMessageChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
}),
|
||||
meta: None,
|
||||
}),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
connection.end_turn(session_id, acp::StopReason::EndTurn);
|
||||
@@ -6633,10 +6709,9 @@ pub(crate) mod tests {
|
||||
cx.update(|_, cx| {
|
||||
connection.send_update(
|
||||
session_id.clone(),
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
|
||||
acp::SessionUpdate::AgentMessageChunk {
|
||||
content: "Message 1 resp".into(),
|
||||
meta: None,
|
||||
}),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -6670,10 +6745,9 @@ pub(crate) mod tests {
|
||||
// Simulate a response sent after beginning to cancel
|
||||
connection.send_update(
|
||||
session_id.clone(),
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
|
||||
acp::SessionUpdate::AgentMessageChunk {
|
||||
content: "onse".into(),
|
||||
meta: None,
|
||||
}),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -6704,10 +6778,9 @@ pub(crate) mod tests {
|
||||
cx.update(|_, cx| {
|
||||
connection.send_update(
|
||||
session_id.clone(),
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
|
||||
acp::SessionUpdate::AgentMessageChunk {
|
||||
content: "Message 2 response".into(),
|
||||
meta: None,
|
||||
}),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
connection.end_turn(session_id.clone(), acp::StopReason::EndTurn);
|
||||
@@ -6745,16 +6818,13 @@ pub(crate) mod tests {
|
||||
init_test(cx);
|
||||
|
||||
let connection = StubAgentConnection::new();
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
}),
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
},
|
||||
)]);
|
||||
}),
|
||||
}]);
|
||||
|
||||
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
|
||||
add_to_workspace(thread_view.clone(), cx);
|
||||
@@ -6831,16 +6901,13 @@ pub(crate) mod tests {
|
||||
init_test(cx);
|
||||
|
||||
let connection = StubAgentConnection::new();
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
}),
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
},
|
||||
)]);
|
||||
}),
|
||||
}]);
|
||||
|
||||
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
|
||||
add_to_workspace(thread_view.clone(), cx);
|
||||
|
||||
@@ -10,7 +10,7 @@ use settings::{OpenAiCompatibleSettingsContent, update_settings_file};
|
||||
use ui::{
|
||||
Banner, Checkbox, KeyBinding, Modal, ModalFooter, ModalHeader, Section, ToggleState, prelude::*,
|
||||
};
|
||||
use ui_input::InputField;
|
||||
use ui_input::SingleLineInput;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -33,9 +33,9 @@ impl LlmCompatibleProvider {
|
||||
}
|
||||
|
||||
struct AddLlmProviderInput {
|
||||
provider_name: Entity<InputField>,
|
||||
api_url: Entity<InputField>,
|
||||
api_key: Entity<InputField>,
|
||||
provider_name: Entity<SingleLineInput>,
|
||||
api_url: Entity<SingleLineInput>,
|
||||
api_key: Entity<SingleLineInput>,
|
||||
models: Vec<ModelInput>,
|
||||
}
|
||||
|
||||
@@ -76,10 +76,10 @@ struct ModelCapabilityToggles {
|
||||
}
|
||||
|
||||
struct ModelInput {
|
||||
name: Entity<InputField>,
|
||||
max_completion_tokens: Entity<InputField>,
|
||||
max_output_tokens: Entity<InputField>,
|
||||
max_tokens: Entity<InputField>,
|
||||
name: Entity<SingleLineInput>,
|
||||
max_completion_tokens: Entity<SingleLineInput>,
|
||||
max_output_tokens: Entity<SingleLineInput>,
|
||||
max_tokens: Entity<SingleLineInput>,
|
||||
capabilities: ModelCapabilityToggles,
|
||||
}
|
||||
|
||||
@@ -171,9 +171,9 @@ fn single_line_input(
|
||||
text: Option<&str>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<InputField> {
|
||||
) -> Entity<SingleLineInput> {
|
||||
cx.new(|cx| {
|
||||
let input = InputField::new(window, cx, placeholder).label(label);
|
||||
let input = SingleLineInput::new(window, cx, placeholder).label(label);
|
||||
if let Some(text) = text {
|
||||
input
|
||||
.editor()
|
||||
@@ -431,7 +431,7 @@ impl Focusable for AddLlmProviderModal {
|
||||
impl ModalView for AddLlmProviderModal {}
|
||||
|
||||
impl Render for AddLlmProviderModal {
|
||||
fn render(&mut self, _window: &mut ui::Window, cx: &mut ui::Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut ui::Window, cx: &mut ui::Context<Self>) -> impl IntoElement {
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
div()
|
||||
@@ -484,6 +484,7 @@ impl Render for AddLlmProviderModal {
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
@@ -498,6 +499,7 @@ impl Render for AddLlmProviderModal {
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
@@ -755,7 +757,12 @@ mod tests {
|
||||
models: Vec<(&str, &str, &str, &str)>,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> Option<SharedString> {
|
||||
fn set_text(input: &Entity<InputField>, text: &str, window: &mut Window, cx: &mut App) {
|
||||
fn set_text(
|
||||
input: &Entity<SingleLineInput>,
|
||||
text: &str,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
input.update(cx, |input, cx| {
|
||||
input.editor().update(cx, |editor, cx| {
|
||||
editor.set_text(text, window, cx);
|
||||
|
||||
@@ -566,7 +566,7 @@ impl ConfigureContextServerModal {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_modal_footer(&self, cx: &mut Context<Self>) -> ModalFooter {
|
||||
fn render_modal_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> ModalFooter {
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
let is_connecting = matches!(self.state, State::Waiting);
|
||||
|
||||
@@ -584,11 +584,12 @@ impl ConfigureContextServerModal {
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let repository_url = repository_url.clone();
|
||||
move |_window, cx| {
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Open Repository",
|
||||
None,
|
||||
repository_url.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -615,7 +616,7 @@ impl ConfigureContextServerModal {
|
||||
},
|
||||
)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, cx)
|
||||
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, window, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(
|
||||
@@ -633,7 +634,7 @@ impl ConfigureContextServerModal {
|
||||
)
|
||||
.disabled(is_connecting)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
|
||||
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, window, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(
|
||||
@@ -708,7 +709,7 @@ impl Render for ConfigureContextServerModal {
|
||||
State::Error(error) => Self::render_modal_error(error.clone()),
|
||||
}),
|
||||
)
|
||||
.footer(self.render_modal_footer(cx)),
|
||||
.footer(self.render_modal_footer(window, cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profil
|
||||
use editor::Editor;
|
||||
use fs::Fs;
|
||||
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, prelude::*};
|
||||
use language_model::LanguageModel;
|
||||
use settings::Settings as _;
|
||||
use ui::{
|
||||
KeyBinding, ListItem, ListItemSpacing, ListSeparator, Navigable, NavigableEntry, prelude::*,
|
||||
@@ -97,7 +96,6 @@ pub struct NewProfileMode {
|
||||
pub struct ManageProfilesModal {
|
||||
fs: Arc<dyn Fs>,
|
||||
context_server_registry: Entity<ContextServerRegistry>,
|
||||
active_model: Option<Arc<dyn LanguageModel>>,
|
||||
focus_handle: FocusHandle,
|
||||
mode: Mode,
|
||||
}
|
||||
@@ -111,14 +109,9 @@ impl ManageProfilesModal {
|
||||
workspace.register_action(|workspace, action: &ManageProfiles, window, cx| {
|
||||
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let active_model = panel
|
||||
.read(cx)
|
||||
.active_native_agent_thread(cx)
|
||||
.and_then(|thread| thread.read(cx).model().cloned());
|
||||
|
||||
let context_server_registry = panel.read(cx).context_server_registry().clone();
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
let mut this = Self::new(fs, active_model, context_server_registry, window, cx);
|
||||
let mut this = Self::new(fs, context_server_registry, window, cx);
|
||||
|
||||
if let Some(profile_id) = action.customize_tools.clone() {
|
||||
this.configure_builtin_tools(profile_id, window, cx);
|
||||
@@ -132,7 +125,6 @@ impl ManageProfilesModal {
|
||||
|
||||
pub fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
active_model: Option<Arc<dyn LanguageModel>>,
|
||||
context_server_registry: Entity<ContextServerRegistry>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -141,7 +133,6 @@ impl ManageProfilesModal {
|
||||
|
||||
Self {
|
||||
fs,
|
||||
active_model,
|
||||
context_server_registry,
|
||||
focus_handle,
|
||||
mode: Mode::choose_profile(window, cx),
|
||||
@@ -237,11 +228,9 @@ impl ManageProfilesModal {
|
||||
let tool_picker = cx.new(|cx| {
|
||||
let delegate = ToolPickerDelegate::builtin_tools(
|
||||
//todo: This causes the web search tool to show up even it only works when using zed hosted models
|
||||
agent::supported_built_in_tool_names(
|
||||
self.active_model.as_ref().map(|model| model.provider_id()),
|
||||
)
|
||||
.map(|s| s.into())
|
||||
.collect::<Vec<_>>(),
|
||||
agent::built_in_tool_names()
|
||||
.map(|s| s.into())
|
||||
.collect::<Vec<_>>(),
|
||||
self.fs.clone(),
|
||||
profile_id.clone(),
|
||||
profile,
|
||||
@@ -352,9 +341,10 @@ impl ManageProfilesModal {
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(KeyBinding::for_action_in(
|
||||
.children(KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&self.focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
@@ -648,13 +638,14 @@ impl ManageProfilesModal {
|
||||
)
|
||||
.child(Label::new("Go Back"))
|
||||
.end_slot(
|
||||
div().child(
|
||||
div().children(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&self.focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.size(rems_from_px(12.)),
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
),
|
||||
)
|
||||
.on_click({
|
||||
@@ -698,9 +689,14 @@ impl Render for ManageProfilesModal {
|
||||
)
|
||||
.child(Label::new("Go Back"))
|
||||
.end_slot(
|
||||
div().child(
|
||||
KeyBinding::for_action_in(&menu::Cancel, &self.focus_handle, cx)
|
||||
.size(rems_from_px(12.)),
|
||||
div().children(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&self.focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
),
|
||||
)
|
||||
.on_click({
|
||||
|
||||
@@ -576,22 +576,16 @@ impl Item for AgentDiffPane {
|
||||
});
|
||||
}
|
||||
|
||||
fn can_split(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn clone_on_split(
|
||||
&self,
|
||||
_workspace_id: Option<workspace::WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| {
|
||||
Self::new(self.thread.clone(), self.workspace.clone(), window, cx)
|
||||
})))
|
||||
Some(cx.new(|cx| Self::new(self.thread.clone(), self.workspace.clone(), window, cx)))
|
||||
}
|
||||
|
||||
fn is_dirty(&self, cx: &App) -> bool {
|
||||
@@ -675,7 +669,7 @@ impl Item for AgentDiffPane {
|
||||
}
|
||||
|
||||
impl Render for AgentDiffPane {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let is_empty = self.multibuffer.read(cx).is_empty();
|
||||
let focus_handle = &self.focus_handle;
|
||||
|
||||
@@ -708,6 +702,7 @@ impl Render for AgentDiffPane {
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&ToggleFocus,
|
||||
&focus_handle.clone(),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.on_click(|_event, window, cx| {
|
||||
@@ -724,7 +719,14 @@ fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControl
|
||||
let thread = thread.clone();
|
||||
|
||||
Arc::new(
|
||||
move |row, status, hunk_range, is_created_file, line_height, editor, _, cx| {
|
||||
move |row,
|
||||
status: &DiffHunkStatus,
|
||||
hunk_range,
|
||||
is_created_file,
|
||||
line_height,
|
||||
editor: &Entity<Editor>,
|
||||
window: &mut Window,
|
||||
cx: &mut App| {
|
||||
{
|
||||
render_diff_hunk_controls(
|
||||
row,
|
||||
@@ -734,6 +736,7 @@ fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControl
|
||||
line_height,
|
||||
&thread,
|
||||
editor,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -749,6 +752,7 @@ fn render_diff_hunk_controls(
|
||||
line_height: Pixels,
|
||||
thread: &AgentDiffThread,
|
||||
editor: &Entity<Editor>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyElement {
|
||||
let editor = editor.clone();
|
||||
@@ -771,8 +775,13 @@ fn render_diff_hunk_controls(
|
||||
Button::new(("reject", row as u64), "Reject")
|
||||
.disabled(is_created_file)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&Reject, &editor.read(cx).focus_handle(cx), cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
KeyBinding::for_action_in(
|
||||
&Reject,
|
||||
&editor.read(cx).focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click({
|
||||
let editor = editor.clone();
|
||||
@@ -793,7 +802,7 @@ fn render_diff_hunk_controls(
|
||||
}),
|
||||
Button::new(("keep", row as u64), "Keep")
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), cx)
|
||||
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), window, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click({
|
||||
@@ -824,8 +833,14 @@ fn render_diff_hunk_controls(
|
||||
// .disabled(!has_multiple_hunks)
|
||||
.tooltip({
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Next Hunk",
|
||||
&GoToHunk,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click({
|
||||
@@ -854,11 +869,12 @@ fn render_diff_hunk_controls(
|
||||
// .disabled(!has_multiple_hunks)
|
||||
.tooltip({
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
move |_window, cx| {
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Previous Hunk",
|
||||
&GoToPreviousHunk,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -1023,7 +1039,7 @@ impl ToolbarItemView for AgentDiffToolbar {
|
||||
}
|
||||
|
||||
impl Render for AgentDiffToolbar {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let spinner_icon = div()
|
||||
.px_0p5()
|
||||
.id("generating")
|
||||
@@ -1098,6 +1114,7 @@ impl Render for AgentDiffToolbar {
|
||||
KeyBinding::for_action_in(
|
||||
&RejectAll,
|
||||
&editor_focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
@@ -1112,6 +1129,7 @@ impl Render for AgentDiffToolbar {
|
||||
KeyBinding::for_action_in(
|
||||
&KeepAll,
|
||||
&editor_focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
@@ -1188,8 +1206,13 @@ impl Render for AgentDiffToolbar {
|
||||
.child(
|
||||
Button::new("reject-all", "Reject All")
|
||||
.key_binding({
|
||||
KeyBinding::for_action_in(&RejectAll, &focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
KeyBinding::for_action_in(
|
||||
&RejectAll,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
})
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.dispatch_action(&RejectAll, window, cx)
|
||||
@@ -1198,8 +1221,13 @@ impl Render for AgentDiffToolbar {
|
||||
.child(
|
||||
Button::new("keep-all", "Keep All")
|
||||
.key_binding({
|
||||
KeyBinding::for_action_in(&KeepAll, &focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
KeyBinding::for_action_in(
|
||||
&KeepAll,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
})
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.dispatch_action(&KeepAll, window, cx)
|
||||
|
||||
@@ -96,8 +96,14 @@ impl Render for AgentModelSelector {
|
||||
.color(color)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
&ToggleModelSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::TopRight,
|
||||
cx,
|
||||
|
||||
@@ -6,11 +6,8 @@ use std::sync::Arc;
|
||||
use acp_thread::AcpThread;
|
||||
use agent::{ContextServerRegistry, DbThreadMetadata, HistoryEntry, HistoryStore};
|
||||
use db::kvp::{Dismissable, KEY_VALUE_STORE};
|
||||
use project::{
|
||||
ExternalAgentServerName,
|
||||
agent_server_store::{
|
||||
AgentServerCommand, AllAgentServersSettings, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME,
|
||||
},
|
||||
use project::agent_server_store::{
|
||||
AgentServerCommand, AllAgentServersSettings, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{
|
||||
@@ -39,13 +36,11 @@ use crate::{
|
||||
use agent_settings::AgentSettings;
|
||||
use ai_onboarding::AgentPanelOnboarding;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use assistant_text_thread::{TextThread, TextThreadEvent, TextThreadSummary};
|
||||
use client::{UserStore, zed_urls};
|
||||
use cloud_llm_client::{Plan, PlanV1, PlanV2, UsageLimit};
|
||||
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
|
||||
use extension::ExtensionEvents;
|
||||
use extension_host::ExtensionStore;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Action, AnyElement, App, AsyncWindowContext, Corner, DismissEvent, Entity, EventEmitter,
|
||||
@@ -72,9 +67,7 @@ use workspace::{
|
||||
};
|
||||
use zed_actions::{
|
||||
DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,
|
||||
agent::{
|
||||
OpenAcpOnboardingModal, OpenOnboardingModal, OpenSettings, ResetAgentZoom, ResetOnboarding,
|
||||
},
|
||||
agent::{OpenAcpOnboardingModal, OpenOnboardingModal, OpenSettings, ResetOnboarding},
|
||||
assistant::{OpenRulesLibrary, ToggleFocus},
|
||||
};
|
||||
|
||||
@@ -195,13 +188,6 @@ pub fn init(cx: &mut App) {
|
||||
})
|
||||
.register_action(|_workspace, _: &ResetTrialEndUpsell, _window, cx| {
|
||||
TrialEndUpsell::set_dismissed(false, cx);
|
||||
})
|
||||
.register_action(|workspace, _: &ResetAgentZoom, window, cx| {
|
||||
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.reset_agent_zoom(window, cx);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
@@ -213,7 +199,7 @@ enum ActiveView {
|
||||
thread_view: Entity<AcpThreadView>,
|
||||
},
|
||||
TextThread {
|
||||
text_thread_editor: Entity<TextThreadEditor>,
|
||||
context_editor: Entity<TextThreadEditor>,
|
||||
title_editor: Entity<Editor>,
|
||||
buffer_search_bar: Entity<BufferSearchBar>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
@@ -315,13 +301,13 @@ impl ActiveView {
|
||||
}
|
||||
|
||||
pub fn text_thread(
|
||||
text_thread_editor: Entity<TextThreadEditor>,
|
||||
context_editor: Entity<TextThreadEditor>,
|
||||
acp_history_store: Entity<agent::HistoryStore>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let title = text_thread_editor.read(cx).title(cx).to_string();
|
||||
let title = context_editor.read(cx).title(cx).to_string();
|
||||
|
||||
let editor = cx.new(|cx| {
|
||||
let mut editor = Editor::single_line(window, cx);
|
||||
@@ -337,7 +323,7 @@ impl ActiveView {
|
||||
let subscriptions = vec![
|
||||
window.subscribe(&editor, cx, {
|
||||
{
|
||||
let text_thread_editor = text_thread_editor.clone();
|
||||
let context_editor = context_editor.clone();
|
||||
move |editor, event, window, cx| match event {
|
||||
EditorEvent::BufferEdited => {
|
||||
if suppress_first_edit {
|
||||
@@ -346,19 +332,19 @@ impl ActiveView {
|
||||
}
|
||||
let new_summary = editor.read(cx).text(cx);
|
||||
|
||||
text_thread_editor.update(cx, |text_thread_editor, cx| {
|
||||
text_thread_editor
|
||||
.text_thread()
|
||||
.update(cx, |text_thread, cx| {
|
||||
text_thread.set_custom_summary(new_summary, cx);
|
||||
context_editor.update(cx, |context_editor, cx| {
|
||||
context_editor
|
||||
.context()
|
||||
.update(cx, |assistant_context, cx| {
|
||||
assistant_context.set_custom_summary(new_summary, cx);
|
||||
})
|
||||
})
|
||||
}
|
||||
EditorEvent::Blurred => {
|
||||
if editor.read(cx).text(cx).is_empty() {
|
||||
let summary = text_thread_editor
|
||||
let summary = context_editor
|
||||
.read(cx)
|
||||
.text_thread()
|
||||
.context()
|
||||
.read(cx)
|
||||
.summary()
|
||||
.or_default();
|
||||
@@ -372,17 +358,17 @@ impl ActiveView {
|
||||
}
|
||||
}
|
||||
}),
|
||||
window.subscribe(&text_thread_editor.read(cx).text_thread().clone(), cx, {
|
||||
window.subscribe(&context_editor.read(cx).context().clone(), cx, {
|
||||
let editor = editor.clone();
|
||||
move |text_thread, event, window, cx| match event {
|
||||
TextThreadEvent::SummaryGenerated => {
|
||||
let summary = text_thread.read(cx).summary().or_default();
|
||||
move |assistant_context, event, window, cx| match event {
|
||||
ContextEvent::SummaryGenerated => {
|
||||
let summary = assistant_context.read(cx).summary().or_default();
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.set_text(summary, window, cx);
|
||||
})
|
||||
}
|
||||
TextThreadEvent::PathChanged { old_path, new_path } => {
|
||||
ContextEvent::PathChanged { old_path, new_path } => {
|
||||
acp_history_store.update(cx, |history_store, cx| {
|
||||
if let Some(old_path) = old_path {
|
||||
history_store
|
||||
@@ -403,11 +389,11 @@ impl ActiveView {
|
||||
let buffer_search_bar =
|
||||
cx.new(|cx| BufferSearchBar::new(Some(language_registry), window, cx));
|
||||
buffer_search_bar.update(cx, |buffer_search_bar, cx| {
|
||||
buffer_search_bar.set_active_pane_item(Some(&text_thread_editor), window, cx)
|
||||
buffer_search_bar.set_active_pane_item(Some(&context_editor), window, cx)
|
||||
});
|
||||
|
||||
Self::TextThread {
|
||||
text_thread_editor,
|
||||
context_editor,
|
||||
title_editor: editor,
|
||||
buffer_search_bar,
|
||||
_subscriptions: subscriptions,
|
||||
@@ -424,7 +410,7 @@ pub struct AgentPanel {
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
acp_history: Entity<AcpThreadHistory>,
|
||||
history_store: Entity<agent::HistoryStore>,
|
||||
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
|
||||
text_thread_store: Entity<assistant_context::ContextStore>,
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
context_server_registry: Entity<ContextServerRegistry>,
|
||||
inline_assist_context_store: Entity<ContextStore>,
|
||||
@@ -436,7 +422,6 @@ pub struct AgentPanel {
|
||||
agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
agent_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
agent_navigation_menu: Option<Entity<ContextMenu>>,
|
||||
_extension_subscription: Option<Subscription>,
|
||||
width: Option<Pixels>,
|
||||
height: Option<Pixels>,
|
||||
zoomed: bool,
|
||||
@@ -489,7 +474,7 @@ impl AgentPanel {
|
||||
let text_thread_store = workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
assistant_text_thread::TextThreadStore::new(
|
||||
assistant_context::ContextStore::new(
|
||||
project,
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
@@ -527,7 +512,7 @@ impl AgentPanel {
|
||||
|
||||
fn new(
|
||||
workspace: &Workspace,
|
||||
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
|
||||
text_thread_store: Entity<assistant_context::ContextStore>,
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -580,8 +565,8 @@ impl AgentPanel {
|
||||
DefaultView::TextThread => {
|
||||
let context = text_thread_store.update(cx, |store, cx| store.create(cx));
|
||||
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project.clone(), cx).unwrap();
|
||||
let text_thread_editor = cx.new(|cx| {
|
||||
let mut editor = TextThreadEditor::for_text_thread(
|
||||
let context_editor = cx.new(|cx| {
|
||||
let mut editor = TextThreadEditor::for_context(
|
||||
context,
|
||||
fs.clone(),
|
||||
workspace.clone(),
|
||||
@@ -594,7 +579,7 @@ impl AgentPanel {
|
||||
editor
|
||||
});
|
||||
ActiveView::text_thread(
|
||||
text_thread_editor,
|
||||
context_editor,
|
||||
history_store.clone(),
|
||||
language_registry.clone(),
|
||||
window,
|
||||
@@ -647,24 +632,7 @@ impl AgentPanel {
|
||||
)
|
||||
});
|
||||
|
||||
// Subscribe to extension events to sync agent servers when extensions change
|
||||
let extension_subscription = if let Some(extension_events) = ExtensionEvents::try_global(cx)
|
||||
{
|
||||
Some(
|
||||
cx.subscribe(&extension_events, |this, _source, event, cx| match event {
|
||||
extension::Event::ExtensionInstalled(_)
|
||||
| extension::Event::ExtensionUninstalled(_)
|
||||
| extension::Event::ExtensionsInstalledChanged => {
|
||||
this.sync_agent_servers_from_extensions(cx);
|
||||
}
|
||||
_ => {}
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut panel = Self {
|
||||
Self {
|
||||
active_view,
|
||||
workspace,
|
||||
user_store,
|
||||
@@ -682,7 +650,6 @@ impl AgentPanel {
|
||||
agent_panel_menu_handle: PopoverMenuHandle::default(),
|
||||
agent_navigation_menu_handle: PopoverMenuHandle::default(),
|
||||
agent_navigation_menu: None,
|
||||
_extension_subscription: extension_subscription,
|
||||
width: None,
|
||||
height: None,
|
||||
zoomed: false,
|
||||
@@ -692,11 +659,7 @@ impl AgentPanel {
|
||||
history_store,
|
||||
selected_agent: AgentType::default(),
|
||||
loading: false,
|
||||
};
|
||||
|
||||
// Initial sync of agent servers from extensions
|
||||
panel.sync_agent_servers_from_extensions(cx);
|
||||
panel
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_focus(
|
||||
@@ -773,8 +736,8 @@ impl AgentPanel {
|
||||
.log_err()
|
||||
.flatten();
|
||||
|
||||
let text_thread_editor = cx.new(|cx| {
|
||||
let mut editor = TextThreadEditor::for_text_thread(
|
||||
let context_editor = cx.new(|cx| {
|
||||
let mut editor = TextThreadEditor::for_context(
|
||||
context,
|
||||
self.fs.clone(),
|
||||
self.workspace.clone(),
|
||||
@@ -794,7 +757,7 @@ impl AgentPanel {
|
||||
|
||||
self.set_active_view(
|
||||
ActiveView::text_thread(
|
||||
text_thread_editor.clone(),
|
||||
context_editor.clone(),
|
||||
self.history_store.clone(),
|
||||
self.language_registry.clone(),
|
||||
window,
|
||||
@@ -803,7 +766,7 @@ impl AgentPanel {
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
text_thread_editor.focus_handle(cx).focus(window);
|
||||
context_editor.focus_handle(cx).focus(window);
|
||||
}
|
||||
|
||||
fn external_thread(
|
||||
@@ -942,20 +905,20 @@ impl AgentPanel {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let text_thread_task = self
|
||||
let context = self
|
||||
.history_store
|
||||
.update(cx, |store, cx| store.load_text_thread(path, cx));
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let text_thread = text_thread_task.await?;
|
||||
let context = context.await?;
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.open_text_thread(text_thread, window, cx);
|
||||
this.open_text_thread(context, window, cx);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn open_text_thread(
|
||||
&mut self,
|
||||
text_thread: Entity<TextThread>,
|
||||
context: Entity<AssistantContext>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -963,8 +926,8 @@ impl AgentPanel {
|
||||
.log_err()
|
||||
.flatten();
|
||||
let editor = cx.new(|cx| {
|
||||
TextThreadEditor::for_text_thread(
|
||||
text_thread,
|
||||
TextThreadEditor::for_context(
|
||||
context,
|
||||
self.fs.clone(),
|
||||
self.workspace.clone(),
|
||||
self.project.clone(),
|
||||
@@ -1002,10 +965,8 @@ impl AgentPanel {
|
||||
ActiveView::ExternalAgentThread { thread_view } => {
|
||||
thread_view.focus_handle(cx).focus(window);
|
||||
}
|
||||
ActiveView::TextThread {
|
||||
text_thread_editor, ..
|
||||
} => {
|
||||
text_thread_editor.focus_handle(cx).focus(window);
|
||||
ActiveView::TextThread { context_editor, .. } => {
|
||||
context_editor.focus_handle(cx).focus(window);
|
||||
}
|
||||
ActiveView::History | ActiveView::Configuration => {}
|
||||
}
|
||||
@@ -1068,21 +1029,13 @@ impl AgentPanel {
|
||||
update_settings_file(self.fs.clone(), cx, move |settings, cx| {
|
||||
let agent_ui_font_size =
|
||||
ThemeSettings::get_global(cx).agent_ui_font_size(cx) + delta;
|
||||
let agent_buffer_font_size =
|
||||
ThemeSettings::get_global(cx).agent_buffer_font_size(cx) + delta;
|
||||
|
||||
let _ = settings
|
||||
.theme
|
||||
.agent_ui_font_size
|
||||
.insert(theme::clamp_font_size(agent_ui_font_size).into());
|
||||
let _ = settings
|
||||
.theme
|
||||
.agent_buffer_font_size
|
||||
.insert(theme::clamp_font_size(agent_buffer_font_size).into());
|
||||
});
|
||||
} else {
|
||||
theme::adjust_agent_ui_font_size(cx, |size| size + delta);
|
||||
theme::adjust_agent_buffer_font_size(cx, |size| size + delta);
|
||||
}
|
||||
}
|
||||
WhichFontSize::BufferFont => {
|
||||
@@ -1103,19 +1056,12 @@ impl AgentPanel {
|
||||
if action.persist {
|
||||
update_settings_file(self.fs.clone(), cx, move |settings, _| {
|
||||
settings.theme.agent_ui_font_size = None;
|
||||
settings.theme.agent_buffer_font_size = None;
|
||||
});
|
||||
} else {
|
||||
theme::reset_agent_ui_font_size(cx);
|
||||
theme::reset_agent_buffer_font_size(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_agent_zoom(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
theme::reset_agent_ui_font_size(cx);
|
||||
theme::reset_agent_buffer_font_size(cx);
|
||||
}
|
||||
|
||||
pub fn toggle_zoom(&mut self, _: &ToggleZoom, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.zoomed {
|
||||
cx.emit(PanelEvent::ZoomOut);
|
||||
@@ -1237,11 +1183,9 @@ impl AgentPanel {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn active_text_thread_editor(&self) -> Option<Entity<TextThreadEditor>> {
|
||||
pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
|
||||
match &self.active_view {
|
||||
ActiveView::TextThread {
|
||||
text_thread_editor, ..
|
||||
} => Some(text_thread_editor.clone()),
|
||||
ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1262,16 +1206,16 @@ impl AgentPanel {
|
||||
let new_is_special = new_is_history || new_is_config;
|
||||
|
||||
match &new_view {
|
||||
ActiveView::TextThread {
|
||||
text_thread_editor, ..
|
||||
} => self.history_store.update(cx, |store, cx| {
|
||||
if let Some(path) = text_thread_editor.read(cx).text_thread().read(cx).path() {
|
||||
store.push_recently_opened_entry(
|
||||
agent::HistoryEntryId::TextThread(path.clone()),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
ActiveView::TextThread { context_editor, .. } => {
|
||||
self.history_store.update(cx, |store, cx| {
|
||||
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
|
||||
store.push_recently_opened_entry(
|
||||
agent::HistoryEntryId::TextThread(path.clone()),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
ActiveView::ExternalAgentThread { .. } => {}
|
||||
ActiveView::History | ActiveView::Configuration => {}
|
||||
}
|
||||
@@ -1361,31 +1305,6 @@ impl AgentPanel {
|
||||
self.selected_agent.clone()
|
||||
}
|
||||
|
||||
fn sync_agent_servers_from_extensions(&mut self, cx: &mut Context<Self>) {
|
||||
if let Some(extension_store) = ExtensionStore::try_global(cx) {
|
||||
let (manifests, extensions_dir) = {
|
||||
let store = extension_store.read(cx);
|
||||
let installed = store.installed_extensions();
|
||||
let manifests: Vec<_> = installed
|
||||
.iter()
|
||||
.map(|(id, entry)| (id.clone(), entry.manifest.clone()))
|
||||
.collect();
|
||||
let extensions_dir = paths::extensions_dir().join("installed");
|
||||
(manifests, extensions_dir)
|
||||
};
|
||||
|
||||
self.project.update(cx, |project, cx| {
|
||||
project.agent_server_store().update(cx, |store, cx| {
|
||||
let manifest_refs: Vec<_> = manifests
|
||||
.iter()
|
||||
.map(|(id, manifest)| (id.as_ref(), manifest.as_ref()))
|
||||
.collect();
|
||||
store.sync_extension_agents(manifest_refs, extensions_dir, cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_agent_thread(
|
||||
&mut self,
|
||||
agent: AgentType,
|
||||
@@ -1453,9 +1372,7 @@ impl Focusable for AgentPanel {
|
||||
match &self.active_view {
|
||||
ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
|
||||
ActiveView::History => self.acp_history.focus_handle(cx),
|
||||
ActiveView::TextThread {
|
||||
text_thread_editor, ..
|
||||
} => text_thread_editor.focus_handle(cx),
|
||||
ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
|
||||
ActiveView::Configuration => {
|
||||
if let Some(configuration) = self.configuration.as_ref() {
|
||||
configuration.focus_handle(cx)
|
||||
@@ -1478,10 +1395,6 @@ impl Panel for AgentPanel {
|
||||
"AgentPanel"
|
||||
}
|
||||
|
||||
fn panel_key() -> &'static str {
|
||||
AGENT_PANEL_KEY
|
||||
}
|
||||
|
||||
fn position(&self, _window: &Window, cx: &App) -> DockPosition {
|
||||
agent_panel_dock_position(cx)
|
||||
}
|
||||
@@ -1590,17 +1503,17 @@ impl AgentPanel {
|
||||
}
|
||||
ActiveView::TextThread {
|
||||
title_editor,
|
||||
text_thread_editor,
|
||||
context_editor,
|
||||
..
|
||||
} => {
|
||||
let summary = text_thread_editor.read(cx).text_thread().read(cx).summary();
|
||||
let summary = context_editor.read(cx).context().read(cx).summary();
|
||||
|
||||
match summary {
|
||||
TextThreadSummary::Pending => Label::new(TextThreadSummary::DEFAULT)
|
||||
ContextSummary::Pending => Label::new(ContextSummary::DEFAULT)
|
||||
.color(Color::Muted)
|
||||
.truncate()
|
||||
.into_any_element(),
|
||||
TextThreadSummary::Content(summary) => {
|
||||
ContextSummary::Content(summary) => {
|
||||
if summary.done {
|
||||
div()
|
||||
.w_full()
|
||||
@@ -1613,17 +1526,17 @@ impl AgentPanel {
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
TextThreadSummary::Error => h_flex()
|
||||
ContextSummary::Error => h_flex()
|
||||
.w_full()
|
||||
.child(title_editor.clone())
|
||||
.child(
|
||||
IconButton::new("retry-summary-generation", IconName::RotateCcw)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
let text_thread_editor = text_thread_editor.clone();
|
||||
let context_editor = context_editor.clone();
|
||||
move |_, _window, cx| {
|
||||
text_thread_editor.update(cx, |text_thread_editor, cx| {
|
||||
text_thread_editor.regenerate_summary(cx);
|
||||
context_editor.update(cx, |context_editor, cx| {
|
||||
context_editor.regenerate_summary(cx);
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -1678,11 +1591,12 @@ impl AgentPanel {
|
||||
.icon_size(IconSize::Small),
|
||||
{
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |_window, cx| {
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Toggle Agent Menu",
|
||||
&ToggleOptionsMenu,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -1746,7 +1660,7 @@ impl AgentPanel {
|
||||
.separator();
|
||||
|
||||
menu = menu
|
||||
.action("Rules", Box::new(OpenRulesLibrary::default()))
|
||||
.action("Rules…", Box::new(OpenRulesLibrary::default()))
|
||||
.action("Settings", Box::new(OpenSettings))
|
||||
.separator()
|
||||
.action(full_screen_label, Box::new(ToggleZoom));
|
||||
@@ -1773,11 +1687,12 @@ impl AgentPanel {
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("agent-nav-menu", icon).icon_size(IconSize::Small),
|
||||
{
|
||||
move |_window, cx| {
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Toggle Recent Threads",
|
||||
&ToggleNavigationMenu,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -1811,8 +1726,8 @@ impl AgentPanel {
|
||||
this.go_back(&workspace::GoBack, window, cx);
|
||||
}))
|
||||
.tooltip({
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, cx)
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, window, cx)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1821,16 +1736,6 @@ impl AgentPanel {
|
||||
let agent_server_store = self.project.read(cx).agent_server_store().clone();
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
// Get custom icon path for selected agent before building menu (to avoid borrow issues)
|
||||
let selected_agent_custom_icon =
|
||||
if let AgentType::Custom { name, .. } = &self.selected_agent {
|
||||
agent_server_store
|
||||
.read(cx)
|
||||
.agent_icon(&ExternalAgentServerName(name.clone()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let active_thread = match &self.active_view {
|
||||
ActiveView::ExternalAgentThread { thread_view } => {
|
||||
thread_view.read(cx).as_native_thread(cx)
|
||||
@@ -1843,8 +1748,14 @@ impl AgentPanel {
|
||||
IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
|
||||
{
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in("New…", &ToggleNewThreadMenu, &focus_handle, cx)
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"New…",
|
||||
&ToggleNewThreadMenu,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -1863,7 +1774,8 @@ impl AgentPanel {
|
||||
|
||||
let active_thread = active_thread.clone();
|
||||
Some(ContextMenu::build(window, cx, |menu, _window, cx| {
|
||||
menu.context(focus_handle.clone())
|
||||
menu
|
||||
.context(focus_handle.clone())
|
||||
.header("Zed Agent")
|
||||
.when_some(active_thread, |this, active_thread| {
|
||||
let thread = active_thread.read(cx);
|
||||
@@ -2020,110 +1932,83 @@ impl AgentPanel {
|
||||
}),
|
||||
)
|
||||
.map(|mut menu| {
|
||||
let agent_server_store_read = agent_server_store.read(cx);
|
||||
let agent_names = agent_server_store_read
|
||||
let agent_names = agent_server_store
|
||||
.read(cx)
|
||||
.external_agents()
|
||||
.filter(|name| {
|
||||
name.0 != GEMINI_NAME
|
||||
&& name.0 != CLAUDE_CODE_NAME
|
||||
&& name.0 != CODEX_NAME
|
||||
name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME && name.0 != CODEX_NAME
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let custom_settings = cx
|
||||
.global::<SettingsStore>()
|
||||
.get::<AllAgentServersSettings>(None)
|
||||
.custom
|
||||
.clone();
|
||||
let custom_settings = cx.global::<SettingsStore>().get::<AllAgentServersSettings>(None).custom.clone();
|
||||
for agent_name in agent_names {
|
||||
let icon_path = agent_server_store_read.agent_icon(&agent_name);
|
||||
let mut entry =
|
||||
ContextMenuEntry::new(format!("New {} Thread", agent_name));
|
||||
if let Some(icon_path) = icon_path {
|
||||
entry = entry.custom_icon_path(icon_path);
|
||||
} else {
|
||||
entry = entry.icon(IconName::Terminal);
|
||||
}
|
||||
entry = entry
|
||||
.icon_color(Color::Muted)
|
||||
.disabled(is_via_collab)
|
||||
.handler({
|
||||
let workspace = workspace.clone();
|
||||
let agent_name = agent_name.clone();
|
||||
let custom_settings = custom_settings.clone();
|
||||
move |window, cx| {
|
||||
if let Some(workspace) = workspace.upgrade() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
if let Some(panel) =
|
||||
workspace.panel::<AgentPanel>(cx)
|
||||
{
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.new_agent_thread(
|
||||
AgentType::Custom {
|
||||
name: agent_name
|
||||
.clone()
|
||||
.into(),
|
||||
command: custom_settings
|
||||
.get(&agent_name.0)
|
||||
.map(|settings| {
|
||||
settings
|
||||
.command
|
||||
.clone()
|
||||
})
|
||||
.unwrap_or(
|
||||
placeholder_command(
|
||||
),
|
||||
),
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
menu = menu.item(
|
||||
ContextMenuEntry::new(format!("New {} Thread", agent_name))
|
||||
.icon(IconName::Terminal)
|
||||
.icon_color(Color::Muted)
|
||||
.disabled(is_via_collab)
|
||||
.handler({
|
||||
let workspace = workspace.clone();
|
||||
let agent_name = agent_name.clone();
|
||||
let custom_settings = custom_settings.clone();
|
||||
move |window, cx| {
|
||||
if let Some(workspace) = workspace.upgrade() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
if let Some(panel) =
|
||||
workspace.panel::<AgentPanel>(cx)
|
||||
{
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.new_agent_thread(
|
||||
AgentType::Custom {
|
||||
name: agent_name.clone().into(),
|
||||
command: custom_settings
|
||||
.get(&agent_name.0)
|
||||
.map(|settings| {
|
||||
settings.command.clone()
|
||||
})
|
||||
.unwrap_or(placeholder_command()),
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
menu = menu.item(entry);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
menu
|
||||
})
|
||||
.separator()
|
||||
.link(
|
||||
"Add Other Agents",
|
||||
OpenBrowser {
|
||||
url: zed_urls::external_agents_docs(cx),
|
||||
}
|
||||
.boxed_clone(),
|
||||
)
|
||||
.separator().link(
|
||||
"Add Other Agents",
|
||||
OpenBrowser {
|
||||
url: zed_urls::external_agents_docs(cx),
|
||||
}
|
||||
.boxed_clone(),
|
||||
)
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
let selected_agent_label = self.selected_agent.label();
|
||||
|
||||
let has_custom_icon = selected_agent_custom_icon.is_some();
|
||||
let selected_agent = div()
|
||||
.id("selected_agent_icon")
|
||||
.when_some(selected_agent_custom_icon, |this, icon_path| {
|
||||
let label = selected_agent_label.clone();
|
||||
.when_some(self.selected_agent.icon(), |this, icon| {
|
||||
this.px(DynamicSpacing::Base02.rems(cx))
|
||||
.child(Icon::from_path(icon_path).color(Color::Muted))
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
|
||||
.child(Icon::new(icon).color(Color::Muted))
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
selected_agent_label.clone(),
|
||||
None,
|
||||
"Selected Agent",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.when(!has_custom_icon, |this| {
|
||||
this.when_some(self.selected_agent.icon(), |this, icon| {
|
||||
let label = selected_agent_label.clone();
|
||||
this.px(DynamicSpacing::Base02.rems(cx))
|
||||
.child(Icon::new(icon).color(Color::Muted))
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
|
||||
})
|
||||
})
|
||||
})
|
||||
.into_any_element();
|
||||
|
||||
h_flex()
|
||||
@@ -2297,6 +2182,7 @@ impl AgentPanel {
|
||||
border_bottom: bool,
|
||||
configuration_error: &ConfigurationError,
|
||||
focus_handle: &FocusHandle,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> impl IntoElement {
|
||||
let zed_provider_configured = AgentSettings::get_global(cx)
|
||||
@@ -2345,7 +2231,7 @@ impl AgentPanel {
|
||||
.style(ButtonStyle::Tinted(ui::TintColor::Warning))
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&OpenSettings, focus_handle, cx)
|
||||
KeyBinding::for_action_in(&OpenSettings, focus_handle, window, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(|_event, window, cx| {
|
||||
@@ -2363,7 +2249,7 @@ impl AgentPanel {
|
||||
|
||||
fn render_text_thread(
|
||||
&self,
|
||||
text_thread_editor: &Entity<TextThreadEditor>,
|
||||
context_editor: &Entity<TextThreadEditor>,
|
||||
buffer_search_bar: &Entity<BufferSearchBar>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -2397,7 +2283,7 @@ impl AgentPanel {
|
||||
)
|
||||
})
|
||||
})
|
||||
.child(text_thread_editor.clone())
|
||||
.child(context_editor.clone())
|
||||
.child(self.render_drag_target(cx))
|
||||
}
|
||||
|
||||
@@ -2473,12 +2359,10 @@ impl AgentPanel {
|
||||
thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
|
||||
});
|
||||
}
|
||||
ActiveView::TextThread {
|
||||
text_thread_editor, ..
|
||||
} => {
|
||||
text_thread_editor.update(cx, |text_thread_editor, cx| {
|
||||
ActiveView::TextThread { context_editor, .. } => {
|
||||
context_editor.update(cx, |context_editor, cx| {
|
||||
TextThreadEditor::insert_dragged_files(
|
||||
text_thread_editor,
|
||||
context_editor,
|
||||
paths,
|
||||
added_worktrees,
|
||||
window,
|
||||
@@ -2549,7 +2433,7 @@ impl Render for AgentPanel {
|
||||
.child(self.render_drag_target(cx)),
|
||||
ActiveView::History => parent.child(self.acp_history.clone()),
|
||||
ActiveView::TextThread {
|
||||
text_thread_editor,
|
||||
context_editor,
|
||||
buffer_search_bar,
|
||||
..
|
||||
} => {
|
||||
@@ -2565,6 +2449,7 @@ impl Render for AgentPanel {
|
||||
true,
|
||||
err,
|
||||
&self.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
} else {
|
||||
@@ -2572,7 +2457,7 @@ impl Render for AgentPanel {
|
||||
}
|
||||
})
|
||||
.child(self.render_text_thread(
|
||||
text_thread_editor,
|
||||
context_editor,
|
||||
buffer_search_bar,
|
||||
window,
|
||||
cx,
|
||||
@@ -2650,17 +2535,17 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
|
||||
pub struct ConcreteAssistantPanelDelegate;
|
||||
|
||||
impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
|
||||
fn active_text_thread_editor(
|
||||
fn active_context_editor(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Option<Entity<TextThreadEditor>> {
|
||||
let panel = workspace.panel::<AgentPanel>(cx)?;
|
||||
panel.read(cx).active_text_thread_editor()
|
||||
panel.read(cx).active_context_editor()
|
||||
}
|
||||
|
||||
fn open_local_text_thread(
|
||||
fn open_saved_context(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
path: Arc<Path>,
|
||||
@@ -2676,10 +2561,10 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
fn open_remote_text_thread(
|
||||
fn open_remote_context(
|
||||
&self,
|
||||
_workspace: &mut Workspace,
|
||||
_text_thread_id: assistant_text_thread::TextThreadId,
|
||||
_context_id: assistant_context::ContextId,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Workspace>,
|
||||
) -> Task<Result<Entity<TextThreadEditor>>> {
|
||||
@@ -2710,15 +2595,15 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
|
||||
thread_view.update(cx, |thread_view, cx| {
|
||||
thread_view.insert_selections(window, cx);
|
||||
});
|
||||
} else if let Some(text_thread_editor) = panel.active_text_thread_editor() {
|
||||
} else if let Some(context_editor) = panel.active_context_editor() {
|
||||
let snapshot = buffer.read(cx).snapshot(cx);
|
||||
let selection_ranges = selection_ranges
|
||||
.into_iter()
|
||||
.map(|range| range.to_point(&snapshot))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
text_thread_editor.update(cx, |text_thread_editor, cx| {
|
||||
text_thread_editor.quote_ranges(selection_ranges, snapshot, window, cx)
|
||||
context_editor.update(cx, |context_editor, cx| {
|
||||
context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -130,6 +130,12 @@ actions!(
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Action)]
|
||||
#[action(namespace = agent)]
|
||||
#[action(deprecated_aliases = ["assistant::QuoteSelection"])]
|
||||
/// Quotes the current selection in the agent panel's message editor.
|
||||
pub struct QuoteSelection;
|
||||
|
||||
/// Creates a new conversation thread, optionally based on an existing thread.
|
||||
#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = agent)]
|
||||
@@ -250,7 +256,7 @@ pub fn init(
|
||||
) {
|
||||
AgentSettings::register(cx);
|
||||
|
||||
assistant_text_thread::init(client.clone(), cx);
|
||||
assistant_context::init(client.clone(), cx);
|
||||
rules_library::init(cx);
|
||||
if !is_eval {
|
||||
// Initializing the language model from the user settings messes with the eval, so we only initialize them when
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use agent::outline;
|
||||
use assistant_text_thread::TextThread;
|
||||
use assistant_context::AssistantContext;
|
||||
use futures::future;
|
||||
use futures::{FutureExt, future::Shared};
|
||||
use gpui::{App, AppContext as _, ElementId, Entity, SharedString, Task};
|
||||
@@ -581,7 +581,7 @@ impl Display for ThreadContext {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextThreadContextHandle {
|
||||
pub text_thread: Entity<TextThread>,
|
||||
pub context: Entity<AssistantContext>,
|
||||
pub context_id: ContextId,
|
||||
}
|
||||
|
||||
@@ -595,20 +595,20 @@ pub struct TextThreadContext {
|
||||
impl TextThreadContextHandle {
|
||||
// pub fn lookup_key() ->
|
||||
pub fn eq_for_key(&self, other: &Self) -> bool {
|
||||
self.text_thread == other.text_thread
|
||||
self.context == other.context
|
||||
}
|
||||
|
||||
pub fn hash_for_key<H: Hasher>(&self, state: &mut H) {
|
||||
self.text_thread.hash(state)
|
||||
self.context.hash(state)
|
||||
}
|
||||
|
||||
pub fn title(&self, cx: &App) -> SharedString {
|
||||
self.text_thread.read(cx).summary().or_default()
|
||||
self.context.read(cx).summary().or_default()
|
||||
}
|
||||
|
||||
fn load(self, cx: &App) -> Task<Option<AgentContext>> {
|
||||
let title = self.title(cx);
|
||||
let text = self.text_thread.read(cx).to_xml(cx);
|
||||
let text = self.context.read(cx).to_xml(cx);
|
||||
let context = AgentContext::TextThread(TextThreadContext {
|
||||
title,
|
||||
text: text.into(),
|
||||
|
||||
@@ -662,7 +662,6 @@ pub(crate) fn recent_context_picker_entries(
|
||||
let mut recent = Vec::with_capacity(6);
|
||||
let workspace = workspace.read(cx);
|
||||
let project = workspace.project().read(cx);
|
||||
let include_root_name = workspace.visible_worktrees(cx).count() > 1;
|
||||
|
||||
recent.extend(
|
||||
workspace
|
||||
@@ -676,16 +675,9 @@ pub(crate) fn recent_context_picker_entries(
|
||||
.filter_map(|(project_path, _)| {
|
||||
project
|
||||
.worktree_for_id(project_path.worktree_id, cx)
|
||||
.map(|worktree| {
|
||||
let path_prefix = if include_root_name {
|
||||
worktree.read(cx).root_name().into()
|
||||
} else {
|
||||
RelPath::empty().into()
|
||||
};
|
||||
RecentEntry::File {
|
||||
project_path,
|
||||
path_prefix,
|
||||
}
|
||||
.map(|worktree| RecentEntry::File {
|
||||
project_path,
|
||||
path_prefix: worktree.read(cx).root_name().into(),
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -655,12 +655,13 @@ impl ContextPickerCompletionProvider {
|
||||
let SymbolLocation::InProject(symbol_path) = &symbol.path else {
|
||||
return None;
|
||||
};
|
||||
let _path_prefix = workspace
|
||||
let path_prefix = workspace
|
||||
.read(cx)
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktree_for_id(symbol_path.worktree_id, cx)?;
|
||||
let path_prefix = RelPath::empty();
|
||||
.worktree_for_id(symbol_path.worktree_id, cx)?
|
||||
.read(cx)
|
||||
.root_name();
|
||||
|
||||
let (file_name, directory) = super::file_context_picker::extract_file_name_and_directory(
|
||||
&symbol_path.path,
|
||||
@@ -817,21 +818,9 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||
return None;
|
||||
}
|
||||
|
||||
// If path is empty, this means we're matching with the root directory itself
|
||||
// so we use the path_prefix as the name
|
||||
let path_prefix = if mat.path.is_empty() {
|
||||
project
|
||||
.read(cx)
|
||||
.worktree_for_id(project_path.worktree_id, cx)
|
||||
.map(|wt| wt.read(cx).root_name().into())
|
||||
.unwrap_or_else(|| mat.path_prefix.clone())
|
||||
} else {
|
||||
mat.path_prefix.clone()
|
||||
};
|
||||
|
||||
Some(Self::completion_for_path(
|
||||
project_path,
|
||||
&path_prefix,
|
||||
&mat.path_prefix,
|
||||
is_recent,
|
||||
mat.is_dir,
|
||||
excerpt_id,
|
||||
@@ -1320,10 +1309,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
current_completion_labels(editor),
|
||||
&[
|
||||
format!("seven.txt b{slash}"),
|
||||
format!("six.txt b{slash}"),
|
||||
format!("five.txt b{slash}"),
|
||||
format!("four.txt a{slash}"),
|
||||
format!("seven.txt dir{slash}b{slash}"),
|
||||
format!("six.txt dir{slash}b{slash}"),
|
||||
format!("five.txt dir{slash}b{slash}"),
|
||||
format!("four.txt dir{slash}a{slash}"),
|
||||
"Files & Directories".into(),
|
||||
"Symbols".into(),
|
||||
"Fetch".into()
|
||||
@@ -1355,7 +1344,7 @@ mod tests {
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
current_completion_labels(editor),
|
||||
vec![format!("one.txt a{slash}")]
|
||||
vec![format!("one.txt dir{slash}a{slash}")]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1367,12 +1356,12 @@ mod tests {
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
format!("Lorem [@one.txt](@file:a{slash}one.txt) ")
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) ")
|
||||
);
|
||||
assert!(!editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
fold_ranges(editor, cx),
|
||||
vec![Point::new(0, 6)..Point::new(0, 33)]
|
||||
vec![Point::new(0, 6)..Point::new(0, 37)]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1381,12 +1370,12 @@ mod tests {
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
format!("Lorem [@one.txt](@file:a{slash}one.txt) ")
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) ")
|
||||
);
|
||||
assert!(!editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
fold_ranges(editor, cx),
|
||||
vec![Point::new(0, 6)..Point::new(0, 33)]
|
||||
vec![Point::new(0, 6)..Point::new(0, 37)]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1395,12 +1384,12 @@ mod tests {
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
format!("Lorem [@one.txt](@file:a{slash}one.txt) Ipsum "),
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum "),
|
||||
);
|
||||
assert!(!editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
fold_ranges(editor, cx),
|
||||
vec![Point::new(0, 6)..Point::new(0, 33)]
|
||||
vec![Point::new(0, 6)..Point::new(0, 37)]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1409,12 +1398,12 @@ mod tests {
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
format!("Lorem [@one.txt](@file:a{slash}one.txt) Ipsum @file "),
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum @file "),
|
||||
);
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
fold_ranges(editor, cx),
|
||||
vec![Point::new(0, 6)..Point::new(0, 33)]
|
||||
vec![Point::new(0, 6)..Point::new(0, 37)]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1427,14 +1416,14 @@ mod tests {
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
format!("Lorem [@one.txt](@file:a{slash}one.txt) Ipsum [@seven.txt](@file:b{slash}seven.txt) ")
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) ")
|
||||
);
|
||||
assert!(!editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
fold_ranges(editor, cx),
|
||||
vec![
|
||||
Point::new(0, 6)..Point::new(0, 33),
|
||||
Point::new(0, 41)..Point::new(0, 72)
|
||||
Point::new(0, 6)..Point::new(0, 37),
|
||||
Point::new(0, 45)..Point::new(0, 80)
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -1444,14 +1433,14 @@ mod tests {
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
format!("Lorem [@one.txt](@file:a{slash}one.txt) Ipsum [@seven.txt](@file:b{slash}seven.txt) \n@")
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) \n@")
|
||||
);
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
fold_ranges(editor, cx),
|
||||
vec![
|
||||
Point::new(0, 6)..Point::new(0, 33),
|
||||
Point::new(0, 41)..Point::new(0, 72)
|
||||
Point::new(0, 6)..Point::new(0, 37),
|
||||
Point::new(0, 45)..Point::new(0, 80)
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -1465,203 +1454,20 @@ mod tests {
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
format!("Lorem [@one.txt](@file:a{slash}one.txt) Ipsum [@seven.txt](@file:b{slash}seven.txt) \n[@six.txt](@file:b{slash}six.txt) ")
|
||||
format!("Lorem [@one.txt](@file:dir{slash}a{slash}one.txt) Ipsum [@seven.txt](@file:dir{slash}b{slash}seven.txt) \n[@six.txt](@file:dir{slash}b{slash}six.txt) ")
|
||||
);
|
||||
assert!(!editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
fold_ranges(editor, cx),
|
||||
vec![
|
||||
Point::new(0, 6)..Point::new(0, 33),
|
||||
Point::new(0, 41)..Point::new(0, 72),
|
||||
Point::new(1, 0)..Point::new(1, 27)
|
||||
Point::new(0, 6)..Point::new(0, 37),
|
||||
Point::new(0, 45)..Point::new(0, 80),
|
||||
Point::new(1, 0)..Point::new(1, 31)
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_context_completion_provider_multiple_worktrees(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let app_state = cx.update(AppState::test);
|
||||
|
||||
cx.update(|cx| {
|
||||
language::init(cx);
|
||||
editor::init(cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
Project::init_settings(cx);
|
||||
});
|
||||
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
path!("/project1"),
|
||||
json!({
|
||||
"a": {
|
||||
"one.txt": "",
|
||||
"two.txt": "",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
path!("/project2"),
|
||||
json!({
|
||||
"b": {
|
||||
"three.txt": "",
|
||||
"four.txt": "",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(
|
||||
app_state.fs.clone(),
|
||||
[path!("/project1").as_ref(), path!("/project2").as_ref()],
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
|
||||
let worktrees = project.update(cx, |project, cx| {
|
||||
let worktrees = project.worktrees(cx).collect::<Vec<_>>();
|
||||
assert_eq!(worktrees.len(), 2);
|
||||
worktrees
|
||||
});
|
||||
|
||||
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||
let slash = PathStyle::local().separator();
|
||||
|
||||
for (worktree_idx, paths) in [
|
||||
vec![rel_path("a/one.txt"), rel_path("a/two.txt")],
|
||||
vec![rel_path("b/three.txt"), rel_path("b/four.txt")],
|
||||
]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let worktree_id = worktrees[worktree_idx].read_with(&cx, |wt, _| wt.id());
|
||||
for path in paths {
|
||||
workspace
|
||||
.update_in(&mut cx, |workspace, window, cx| {
|
||||
workspace.open_path(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: (*path).into(),
|
||||
},
|
||||
None,
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let editor = workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||
let editor = cx.new(|cx| {
|
||||
Editor::new(
|
||||
editor::EditorMode::full(),
|
||||
multi_buffer::MultiBuffer::build_simple("", cx),
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.add_item(
|
||||
Box::new(cx.new(|_| AtMentionEditor(editor.clone()))),
|
||||
true,
|
||||
true,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
editor
|
||||
});
|
||||
|
||||
let context_store = cx.new(|_| ContextStore::new(project.downgrade()));
|
||||
|
||||
let editor_entity = editor.downgrade();
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
window.focus(&editor.focus_handle(cx));
|
||||
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
|
||||
workspace.downgrade(),
|
||||
context_store.downgrade(),
|
||||
None,
|
||||
None,
|
||||
editor_entity,
|
||||
None,
|
||||
))));
|
||||
});
|
||||
|
||||
cx.simulate_input("@");
|
||||
|
||||
// With multiple worktrees, we should see the project name as prefix
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "@");
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
let labels = current_completion_labels(editor);
|
||||
|
||||
assert!(
|
||||
labels.contains(&format!("four.txt project2{slash}b{slash}")),
|
||||
"Expected 'four.txt project2{slash}b{slash}' in labels: {:?}",
|
||||
labels
|
||||
);
|
||||
assert!(
|
||||
labels.contains(&format!("three.txt project2{slash}b{slash}")),
|
||||
"Expected 'three.txt project2{slash}b{slash}' in labels: {:?}",
|
||||
labels
|
||||
);
|
||||
});
|
||||
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
|
||||
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
|
||||
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
|
||||
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
|
||||
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "@file ");
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
});
|
||||
|
||||
cx.simulate_input("one");
|
||||
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "@file one");
|
||||
assert!(editor.has_visible_completions_menu());
|
||||
assert_eq!(
|
||||
current_completion_labels(editor),
|
||||
vec![format!("one.txt project1{slash}a{slash}")]
|
||||
);
|
||||
});
|
||||
|
||||
editor.update_in(&mut cx, |editor, window, cx| {
|
||||
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
|
||||
});
|
||||
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
format!("[@one.txt](@file:project1{slash}a{slash}one.txt) ")
|
||||
);
|
||||
assert!(!editor.has_visible_completions_menu());
|
||||
});
|
||||
}
|
||||
|
||||
fn fold_ranges(editor: &Editor, cx: &mut App) -> Vec<Range<Point>> {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
|
||||
@@ -197,50 +197,34 @@ pub(crate) fn search_files(
|
||||
if query.is_empty() {
|
||||
let workspace = workspace.read(cx);
|
||||
let project = workspace.project().read(cx);
|
||||
let visible_worktrees = workspace.visible_worktrees(cx).collect::<Vec<_>>();
|
||||
let include_root_name = visible_worktrees.len() > 1;
|
||||
|
||||
let recent_matches = workspace
|
||||
.recent_navigation_history(Some(10), cx)
|
||||
.into_iter()
|
||||
.map(|(project_path, _)| {
|
||||
let path_prefix = if include_root_name {
|
||||
project
|
||||
.worktree_for_id(project_path.worktree_id, cx)
|
||||
.map(|wt| wt.read(cx).root_name().into())
|
||||
.unwrap_or_else(|| RelPath::empty().into())
|
||||
} else {
|
||||
RelPath::empty().into()
|
||||
};
|
||||
|
||||
FileMatch {
|
||||
.filter_map(|(project_path, _)| {
|
||||
let worktree = project.worktree_for_id(project_path.worktree_id, cx)?;
|
||||
Some(FileMatch {
|
||||
mat: PathMatch {
|
||||
score: 0.,
|
||||
positions: Vec::new(),
|
||||
worktree_id: project_path.worktree_id.to_usize(),
|
||||
path: project_path.path,
|
||||
path_prefix,
|
||||
path_prefix: worktree.read(cx).root_name().into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
},
|
||||
is_recent: true,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let file_matches = visible_worktrees.into_iter().flat_map(|worktree| {
|
||||
let file_matches = project.worktrees(cx).flat_map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
let path_prefix: Arc<RelPath> = if include_root_name {
|
||||
worktree.root_name().into()
|
||||
} else {
|
||||
RelPath::empty().into()
|
||||
};
|
||||
worktree.entries(false, 0).map(move |entry| FileMatch {
|
||||
mat: PathMatch {
|
||||
score: 0.,
|
||||
positions: Vec::new(),
|
||||
worktree_id: worktree.id().to_usize(),
|
||||
path: entry.path.clone(),
|
||||
path_prefix: path_prefix.clone(),
|
||||
path_prefix: worktree.root_name().into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: entry.is_dir(),
|
||||
},
|
||||
@@ -251,7 +235,6 @@ pub(crate) fn search_files(
|
||||
Task::ready(recent_matches.chain(file_matches).collect())
|
||||
} else {
|
||||
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
|
||||
let include_root_name = worktrees.len() > 1;
|
||||
let candidate_sets = worktrees
|
||||
.into_iter()
|
||||
.map(|worktree| {
|
||||
@@ -260,7 +243,7 @@ pub(crate) fn search_files(
|
||||
PathMatchCandidateSet {
|
||||
snapshot: worktree.snapshot(),
|
||||
include_ignored: worktree.root_entry().is_some_and(|entry| entry.is_ignored),
|
||||
include_root_name,
|
||||
include_root_name: true,
|
||||
candidates: project::Candidates::Entries,
|
||||
}
|
||||
})
|
||||
@@ -293,12 +276,6 @@ pub fn extract_file_name_and_directory(
|
||||
path_prefix: &RelPath,
|
||||
path_style: PathStyle,
|
||||
) -> (SharedString, Option<SharedString>) {
|
||||
// If path is empty, this means we're matching with the root directory itself
|
||||
// so we use the path_prefix as the name
|
||||
if path.is_empty() && !path_prefix.is_empty() {
|
||||
return (path_prefix.display(path_style).to_string().into(), None);
|
||||
}
|
||||
|
||||
let full_path = path_prefix.join(path);
|
||||
let file_name = full_path.file_name().unwrap_or_default();
|
||||
let display_path = full_path.display(path_style);
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::context::{
|
||||
};
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_text_thread::TextThread;
|
||||
use assistant_context::AssistantContext;
|
||||
use collections::{HashSet, IndexSet};
|
||||
use futures::{self, FutureExt};
|
||||
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
|
||||
@@ -200,13 +200,13 @@ impl ContextStore {
|
||||
|
||||
pub fn add_text_thread(
|
||||
&mut self,
|
||||
text_thread: Entity<TextThread>,
|
||||
context: Entity<AssistantContext>,
|
||||
remove_if_exists: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<AgentContextHandle> {
|
||||
let context_id = self.next_context_id.post_inc();
|
||||
let context = AgentContextHandle::TextThread(TextThreadContextHandle {
|
||||
text_thread,
|
||||
context,
|
||||
context_id,
|
||||
});
|
||||
|
||||
@@ -353,15 +353,21 @@ impl ContextStore {
|
||||
);
|
||||
};
|
||||
}
|
||||
SuggestedContext::TextThread {
|
||||
text_thread,
|
||||
name: _,
|
||||
} => {
|
||||
if let Some(text_thread) = text_thread.upgrade() {
|
||||
// SuggestedContext::Thread { thread, name: _ } => {
|
||||
// if let Some(thread) = thread.upgrade() {
|
||||
// let context_id = self.next_context_id.post_inc();
|
||||
// self.insert_context(
|
||||
// AgentContextHandle::Thread(ThreadContextHandle { thread, context_id }),
|
||||
// cx,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
SuggestedContext::TextThread { context, name: _ } => {
|
||||
if let Some(context) = context.upgrade() {
|
||||
let context_id = self.next_context_id.post_inc();
|
||||
self.insert_context(
|
||||
AgentContextHandle::TextThread(TextThreadContextHandle {
|
||||
text_thread,
|
||||
context,
|
||||
context_id,
|
||||
}),
|
||||
cx,
|
||||
@@ -386,7 +392,7 @@ impl ContextStore {
|
||||
// }
|
||||
AgentContextHandle::TextThread(text_thread_context) => {
|
||||
self.context_text_thread_paths
|
||||
.extend(text_thread_context.text_thread.read(cx).path().cloned());
|
||||
.extend(text_thread_context.context.read(cx).path().cloned());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -408,7 +414,7 @@ impl ContextStore {
|
||||
.remove(thread_context.thread.read(cx).id());
|
||||
}
|
||||
AgentContextHandle::TextThread(text_thread_context) => {
|
||||
if let Some(path) = text_thread_context.text_thread.read(cx).path() {
|
||||
if let Some(path) = text_thread_context.context.read(cx).path() {
|
||||
self.context_text_thread_paths.remove(path);
|
||||
}
|
||||
}
|
||||
@@ -532,9 +538,13 @@ pub enum SuggestedContext {
|
||||
icon_path: Option<SharedString>,
|
||||
buffer: WeakEntity<Buffer>,
|
||||
},
|
||||
// Thread {
|
||||
// name: SharedString,
|
||||
// thread: WeakEntity<Thread>,
|
||||
// },
|
||||
TextThread {
|
||||
name: SharedString,
|
||||
text_thread: WeakEntity<TextThread>,
|
||||
context: WeakEntity<AssistantContext>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -542,6 +552,7 @@ impl SuggestedContext {
|
||||
pub fn name(&self) -> &SharedString {
|
||||
match self {
|
||||
Self::File { name, .. } => name,
|
||||
// Self::Thread { name, .. } => name,
|
||||
Self::TextThread { name, .. } => name,
|
||||
}
|
||||
}
|
||||
@@ -549,6 +560,7 @@ impl SuggestedContext {
|
||||
pub fn icon_path(&self) -> Option<SharedString> {
|
||||
match self {
|
||||
Self::File { icon_path, .. } => icon_path.clone(),
|
||||
// Self::Thread { .. } => None,
|
||||
Self::TextThread { .. } => None,
|
||||
}
|
||||
}
|
||||
@@ -556,6 +568,7 @@ impl SuggestedContext {
|
||||
pub fn kind(&self) -> ContextKind {
|
||||
match self {
|
||||
Self::File { .. } => ContextKind::File,
|
||||
// Self::Thread { .. } => ContextKind::Thread,
|
||||
Self::TextThread { .. } => ContextKind::TextThread,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,19 +132,19 @@ impl ContextStrip {
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let panel = workspace.read(cx).panel::<AgentPanel>(cx)?.read(cx);
|
||||
|
||||
if let Some(active_text_thread_editor) = panel.active_text_thread_editor() {
|
||||
let text_thread = active_text_thread_editor.read(cx).text_thread();
|
||||
let weak_text_thread = text_thread.downgrade();
|
||||
let text_thread = text_thread.read(cx);
|
||||
let path = text_thread.path()?;
|
||||
if let Some(active_context_editor) = panel.active_context_editor() {
|
||||
let context = active_context_editor.read(cx).context();
|
||||
let weak_context = context.downgrade();
|
||||
let context = context.read(cx);
|
||||
let path = context.path()?;
|
||||
|
||||
if self.context_store.read(cx).includes_text_thread(path) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SuggestedContext::TextThread {
|
||||
name: text_thread.summary().or_default(),
|
||||
text_thread: weak_text_thread,
|
||||
name: context.summary().or_default(),
|
||||
context: weak_context,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -332,7 +332,7 @@ impl ContextStrip {
|
||||
AgentContextHandle::TextThread(text_thread_context) => {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
||||
let context = text_thread_context.text_thread.clone();
|
||||
let context = text_thread_context.context.clone();
|
||||
window.defer(cx, move |window, cx| {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.open_text_thread(context, window, cx)
|
||||
@@ -483,11 +483,12 @@ impl Render for ContextStrip {
|
||||
.style(ui::ButtonStyle::Filled),
|
||||
{
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |_window, cx| {
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Add Context",
|
||||
&ToggleContextPicker,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -557,11 +558,12 @@ impl Render for ContextStrip {
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |_window, cx| {
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Remove All Context",
|
||||
&RemoveAllContext,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1508,8 +1508,8 @@ impl InlineAssistant {
|
||||
return Some(InlineAssistTarget::Terminal(terminal_view));
|
||||
}
|
||||
|
||||
let text_thread_editor = agent_panel
|
||||
.and_then(|panel| panel.read(cx).active_text_thread_editor())
|
||||
let context_editor = agent_panel
|
||||
.and_then(|panel| panel.read(cx).active_context_editor())
|
||||
.and_then(|editor| {
|
||||
let editor = &editor.read(cx).editor().clone();
|
||||
if editor.read(cx).is_focused(window) {
|
||||
@@ -1519,8 +1519,8 @@ impl InlineAssistant {
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(text_thread_editor) = text_thread_editor {
|
||||
Some(InlineAssistTarget::Editor(text_thread_editor))
|
||||
if let Some(context_editor) = context_editor {
|
||||
Some(InlineAssistTarget::Editor(context_editor))
|
||||
} else if let Some(workspace_editor) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
|
||||
@@ -468,11 +468,12 @@ impl<T: 'static> PromptEditor<T> {
|
||||
IconButton::new("stop", IconName::Stop)
|
||||
.icon_color(Color::Error)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |_window, cx| {
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
mode.tooltip_interrupt(),
|
||||
Some(&menu::Cancel),
|
||||
"Changes won't be discarded",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -486,11 +487,12 @@ impl<T: 'static> PromptEditor<T> {
|
||||
IconButton::new("restart", IconName::RotateCw)
|
||||
.icon_color(Color::Info)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |_window, cx| {
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
mode.tooltip_restart(),
|
||||
Some(&menu::Confirm),
|
||||
"Changes will be discarded",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -503,8 +505,8 @@ impl<T: 'static> PromptEditor<T> {
|
||||
let accept = IconButton::new("accept", IconName::Check)
|
||||
.icon_color(Color::Info)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, cx)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, window, cx)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, _, cx| {
|
||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
|
||||
@@ -517,10 +519,11 @@ impl<T: 'static> PromptEditor<T> {
|
||||
IconButton::new("confirm", IconName::PlayFilled)
|
||||
.icon_color(Color::Info)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|_window, cx| {
|
||||
.tooltip(|window, cx| {
|
||||
Tooltip::for_action(
|
||||
"Execute Generated Command",
|
||||
&menu::SecondaryConfirm,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -612,12 +615,13 @@ impl<T: 'static> PromptEditor<T> {
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip({
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
move |_window, cx| {
|
||||
move |window, cx| {
|
||||
cx.new(|cx| {
|
||||
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&CyclePreviousInlineAssist,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
);
|
||||
@@ -653,12 +657,13 @@ impl<T: 'static> PromptEditor<T> {
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip({
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
move |_window, cx| {
|
||||
move |window, cx| {
|
||||
cx.new(|cx| {
|
||||
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&CycleNextInlineAssist,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -162,11 +162,12 @@ impl Render for ProfileSelector {
|
||||
PickerPopoverMenu::new(
|
||||
picker,
|
||||
trigger_button,
|
||||
move |_window, cx| {
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Toggle Profile Menu",
|
||||
&ToggleProfileSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
|
||||
@@ -155,8 +155,8 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
match command {
|
||||
SlashCommandEntry::Info(info) => {
|
||||
self.active_context_editor
|
||||
.update(cx, |text_thread_editor, cx| {
|
||||
text_thread_editor.insert_command(&info.name, window, cx)
|
||||
.update(cx, |context_editor, cx| {
|
||||
context_editor.insert_command(&info.name, window, cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{
|
||||
QuoteSelection,
|
||||
language_model_selector::{LanguageModelSelector, language_model_selector},
|
||||
ui::BurnModeTooltip,
|
||||
};
|
||||
@@ -71,13 +72,13 @@ use workspace::{
|
||||
pane,
|
||||
searchable::{SearchEvent, SearchableItem},
|
||||
};
|
||||
use zed_actions::agent::{AddSelectionToThread, ToggleModelSelector};
|
||||
use zed_actions::agent::ToggleModelSelector;
|
||||
|
||||
use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};
|
||||
use assistant_text_thread::{
|
||||
CacheStatus, Content, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId,
|
||||
MessageMetadata, MessageStatus, PendingSlashCommandStatus, TextThread, TextThreadEvent,
|
||||
TextThreadId, ThoughtProcessOutputSection,
|
||||
use assistant_context::{
|
||||
AssistantContext, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId,
|
||||
InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus,
|
||||
PendingSlashCommandStatus, ThoughtProcessOutputSection,
|
||||
};
|
||||
|
||||
actions!(
|
||||
@@ -126,14 +127,14 @@ pub enum ThoughtProcessStatus {
|
||||
}
|
||||
|
||||
pub trait AgentPanelDelegate {
|
||||
fn active_text_thread_editor(
|
||||
fn active_context_editor(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Option<Entity<TextThreadEditor>>;
|
||||
|
||||
fn open_local_text_thread(
|
||||
fn open_saved_context(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
path: Arc<Path>,
|
||||
@@ -141,10 +142,10 @@ pub trait AgentPanelDelegate {
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Task<Result<()>>;
|
||||
|
||||
fn open_remote_text_thread(
|
||||
fn open_remote_context(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
text_thread_id: TextThreadId,
|
||||
context_id: ContextId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Task<Result<Entity<TextThreadEditor>>>;
|
||||
@@ -177,7 +178,7 @@ struct GlobalAssistantPanelDelegate(Arc<dyn AgentPanelDelegate>);
|
||||
impl Global for GlobalAssistantPanelDelegate {}
|
||||
|
||||
pub struct TextThreadEditor {
|
||||
text_thread: Entity<TextThread>,
|
||||
context: Entity<AssistantContext>,
|
||||
fs: Arc<dyn Fs>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
@@ -223,8 +224,8 @@ impl TextThreadEditor {
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn for_text_thread(
|
||||
text_thread: Entity<TextThread>,
|
||||
pub fn for_context(
|
||||
context: Entity<AssistantContext>,
|
||||
fs: Arc<dyn Fs>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
@@ -233,14 +234,14 @@ impl TextThreadEditor {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let completion_provider = SlashCommandCompletionProvider::new(
|
||||
text_thread.read(cx).slash_commands().clone(),
|
||||
context.read(cx).slash_commands().clone(),
|
||||
Some(cx.entity().downgrade()),
|
||||
Some(workspace.clone()),
|
||||
);
|
||||
|
||||
let editor = cx.new(|cx| {
|
||||
let mut editor =
|
||||
Editor::for_buffer(text_thread.read(cx).buffer().clone(), None, window, cx);
|
||||
Editor::for_buffer(context.read(cx).buffer().clone(), None, window, cx);
|
||||
editor.disable_scrollbars_and_minimap(window, cx);
|
||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
||||
editor.set_show_line_numbers(false, cx);
|
||||
@@ -264,24 +265,18 @@ impl TextThreadEditor {
|
||||
});
|
||||
|
||||
let _subscriptions = vec![
|
||||
cx.observe(&text_thread, |_, _, cx| cx.notify()),
|
||||
cx.subscribe_in(&text_thread, window, Self::handle_text_thread_event),
|
||||
cx.observe(&context, |_, _, cx| cx.notify()),
|
||||
cx.subscribe_in(&context, window, Self::handle_context_event),
|
||||
cx.subscribe_in(&editor, window, Self::handle_editor_event),
|
||||
cx.subscribe_in(&editor, window, Self::handle_editor_search_event),
|
||||
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
|
||||
];
|
||||
|
||||
let slash_command_sections = text_thread
|
||||
.read(cx)
|
||||
.slash_command_output_sections()
|
||||
.to_vec();
|
||||
let thought_process_sections = text_thread
|
||||
.read(cx)
|
||||
.thought_process_output_sections()
|
||||
.to_vec();
|
||||
let slash_commands = text_thread.read(cx).slash_commands().clone();
|
||||
let slash_command_sections = context.read(cx).slash_command_output_sections().to_vec();
|
||||
let thought_process_sections = context.read(cx).thought_process_output_sections().to_vec();
|
||||
let slash_commands = context.read(cx).slash_commands().clone();
|
||||
let mut this = Self {
|
||||
text_thread,
|
||||
context,
|
||||
slash_commands,
|
||||
editor,
|
||||
lsp_adapter_delegate,
|
||||
@@ -343,8 +338,8 @@ impl TextThreadEditor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn text_thread(&self) -> &Entity<TextThread> {
|
||||
&self.text_thread
|
||||
pub fn context(&self) -> &Entity<AssistantContext> {
|
||||
&self.context
|
||||
}
|
||||
|
||||
pub fn editor(&self) -> &Entity<Editor> {
|
||||
@@ -356,9 +351,9 @@ impl TextThreadEditor {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.insert(&format!("/{command_name}\n\n"), window, cx)
|
||||
});
|
||||
let command = self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.reparse(cx);
|
||||
text_thread.parsed_slash_commands()[0].clone()
|
||||
let command = self.context.update(cx, |context, cx| {
|
||||
context.reparse(cx);
|
||||
context.parsed_slash_commands()[0].clone()
|
||||
});
|
||||
self.run_command(
|
||||
command.source_range,
|
||||
@@ -381,14 +376,11 @@ impl TextThreadEditor {
|
||||
|
||||
fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.last_error = None;
|
||||
if let Some(user_message) = self
|
||||
.text_thread
|
||||
.update(cx, |text_thread, cx| text_thread.assist(cx))
|
||||
{
|
||||
if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) {
|
||||
let new_selection = {
|
||||
let cursor = user_message
|
||||
.start
|
||||
.to_offset(self.text_thread.read(cx).buffer().read(cx));
|
||||
.to_offset(self.context.read(cx).buffer().read(cx));
|
||||
cursor..cursor
|
||||
};
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
@@ -412,8 +404,8 @@ impl TextThreadEditor {
|
||||
self.last_error = None;
|
||||
|
||||
if self
|
||||
.text_thread
|
||||
.update(cx, |text_thread, cx| text_thread.cancel_last_assist(cx))
|
||||
.context
|
||||
.update(cx, |context, cx| context.cancel_last_assist(cx))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -428,13 +420,13 @@ impl TextThreadEditor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let cursors = self.cursors(cx);
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
let messages = text_thread
|
||||
self.context.update(cx, |context, cx| {
|
||||
let messages = context
|
||||
.messages_for_offsets(cursors, cx)
|
||||
.into_iter()
|
||||
.map(|message| message.id)
|
||||
.collect();
|
||||
text_thread.cycle_message_roles(messages, cx)
|
||||
context.cycle_message_roles(messages, cx)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -500,11 +492,11 @@ impl TextThreadEditor {
|
||||
let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
|
||||
let mut commands_by_range = HashMap::default();
|
||||
let workspace = self.workspace.clone();
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.reparse(cx);
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.reparse(cx);
|
||||
for selection in selections.iter() {
|
||||
if let Some(command) =
|
||||
text_thread.pending_command_for_position(selection.head().text_anchor, cx)
|
||||
context.pending_command_for_position(selection.head().text_anchor, cx)
|
||||
{
|
||||
commands_by_range
|
||||
.entry(command.source_range.clone())
|
||||
@@ -542,14 +534,14 @@ impl TextThreadEditor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(command) = self.slash_commands.command(name, cx) {
|
||||
let text_thread = self.text_thread.read(cx);
|
||||
let sections = text_thread
|
||||
let context = self.context.read(cx);
|
||||
let sections = context
|
||||
.slash_command_output_sections()
|
||||
.iter()
|
||||
.filter(|section| section.is_valid(text_thread.buffer().read(cx)))
|
||||
.filter(|section| section.is_valid(context.buffer().read(cx)))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let snapshot = text_thread.buffer().read(cx).snapshot();
|
||||
let snapshot = context.buffer().read(cx).snapshot();
|
||||
let output = command.run(
|
||||
arguments,
|
||||
§ions,
|
||||
@@ -559,8 +551,8 @@ impl TextThreadEditor {
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.insert_command_output(
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.insert_command_output(
|
||||
command_range,
|
||||
name,
|
||||
output,
|
||||
@@ -571,32 +563,32 @@ impl TextThreadEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_text_thread_event(
|
||||
fn handle_context_event(
|
||||
&mut self,
|
||||
_: &Entity<TextThread>,
|
||||
event: &TextThreadEvent,
|
||||
_: &Entity<AssistantContext>,
|
||||
event: &ContextEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let text_thread_editor = cx.entity().downgrade();
|
||||
let context_editor = cx.entity().downgrade();
|
||||
|
||||
match event {
|
||||
TextThreadEvent::MessagesEdited => {
|
||||
ContextEvent::MessagesEdited => {
|
||||
self.update_message_headers(cx);
|
||||
self.update_image_blocks(cx);
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
|
||||
});
|
||||
}
|
||||
TextThreadEvent::SummaryChanged => {
|
||||
ContextEvent::SummaryChanged => {
|
||||
cx.emit(EditorEvent::TitleChanged);
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
|
||||
});
|
||||
}
|
||||
TextThreadEvent::SummaryGenerated => {}
|
||||
TextThreadEvent::PathChanged { .. } => {}
|
||||
TextThreadEvent::StartedThoughtProcess(range) => {
|
||||
ContextEvent::SummaryGenerated => {}
|
||||
ContextEvent::PathChanged { .. } => {}
|
||||
ContextEvent::StartedThoughtProcess(range) => {
|
||||
let creases = self.insert_thought_process_output_sections(
|
||||
[(
|
||||
ThoughtProcessOutputSection {
|
||||
@@ -609,7 +601,7 @@ impl TextThreadEditor {
|
||||
);
|
||||
self.pending_thought_process = Some((creases[0], range.start));
|
||||
}
|
||||
TextThreadEvent::EndedThoughtProcess(end) => {
|
||||
ContextEvent::EndedThoughtProcess(end) => {
|
||||
if let Some((crease_id, start)) = self.pending_thought_process.take() {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
@@ -635,7 +627,7 @@ impl TextThreadEditor {
|
||||
);
|
||||
}
|
||||
}
|
||||
TextThreadEvent::StreamedCompletion => {
|
||||
ContextEvent::StreamedCompletion => {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
if let Some(scroll_position) = self.scroll_position {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
@@ -650,7 +642,7 @@ impl TextThreadEditor {
|
||||
}
|
||||
});
|
||||
}
|
||||
TextThreadEvent::ParsedSlashCommandsUpdated { removed, updated } => {
|
||||
ContextEvent::ParsedSlashCommandsUpdated { removed, updated } => {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let (&excerpt_id, _, _) = buffer.as_singleton().unwrap();
|
||||
@@ -666,12 +658,12 @@ impl TextThreadEditor {
|
||||
updated.iter().map(|command| {
|
||||
let workspace = self.workspace.clone();
|
||||
let confirm_command = Arc::new({
|
||||
let text_thread_editor = text_thread_editor.clone();
|
||||
let context_editor = context_editor.clone();
|
||||
let command = command.clone();
|
||||
move |window: &mut Window, cx: &mut App| {
|
||||
text_thread_editor
|
||||
.update(cx, |text_thread_editor, cx| {
|
||||
text_thread_editor.run_command(
|
||||
context_editor
|
||||
.update(cx, |context_editor, cx| {
|
||||
context_editor.run_command(
|
||||
command.source_range.clone(),
|
||||
&command.name,
|
||||
&command.arguments,
|
||||
@@ -721,17 +713,17 @@ impl TextThreadEditor {
|
||||
);
|
||||
})
|
||||
}
|
||||
TextThreadEvent::InvokedSlashCommandChanged { command_id } => {
|
||||
ContextEvent::InvokedSlashCommandChanged { command_id } => {
|
||||
self.update_invoked_slash_command(*command_id, window, cx);
|
||||
}
|
||||
TextThreadEvent::SlashCommandOutputSectionAdded { section } => {
|
||||
ContextEvent::SlashCommandOutputSectionAdded { section } => {
|
||||
self.insert_slash_command_output_sections([section.clone()], false, window, cx);
|
||||
}
|
||||
TextThreadEvent::Operation(_) => {}
|
||||
TextThreadEvent::ShowAssistError(error_message) => {
|
||||
ContextEvent::Operation(_) => {}
|
||||
ContextEvent::ShowAssistError(error_message) => {
|
||||
self.last_error = Some(AssistError::Message(error_message.clone()));
|
||||
}
|
||||
TextThreadEvent::ShowPaymentRequiredError => {
|
||||
ContextEvent::ShowPaymentRequiredError => {
|
||||
self.last_error = Some(AssistError::PaymentRequired);
|
||||
}
|
||||
}
|
||||
@@ -744,14 +736,14 @@ impl TextThreadEditor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(invoked_slash_command) =
|
||||
self.text_thread.read(cx).invoked_slash_command(&command_id)
|
||||
self.context.read(cx).invoked_slash_command(&command_id)
|
||||
&& let InvokedSlashCommandStatus::Finished = invoked_slash_command.status
|
||||
{
|
||||
let run_commands_in_ranges = invoked_slash_command.run_commands_in_ranges.clone();
|
||||
for range in run_commands_in_ranges {
|
||||
let commands = self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.reparse(cx);
|
||||
text_thread
|
||||
let commands = self.context.update(cx, |context, cx| {
|
||||
context.reparse(cx);
|
||||
context
|
||||
.pending_commands_for_range(range.clone(), cx)
|
||||
.to_vec()
|
||||
});
|
||||
@@ -772,7 +764,7 @@ impl TextThreadEditor {
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
if let Some(invoked_slash_command) =
|
||||
self.text_thread.read(cx).invoked_slash_command(&command_id)
|
||||
self.context.read(cx).invoked_slash_command(&command_id)
|
||||
{
|
||||
if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
@@ -799,7 +791,7 @@ impl TextThreadEditor {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let (&excerpt_id, _buffer_id, _buffer_snapshot) =
|
||||
buffer.as_singleton().unwrap();
|
||||
let context = self.text_thread.downgrade();
|
||||
let context = self.context.downgrade();
|
||||
let range = buffer
|
||||
.anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
|
||||
.unwrap();
|
||||
@@ -1029,7 +1021,7 @@ impl TextThreadEditor {
|
||||
|
||||
let render_block = |message: MessageMetadata| -> RenderBlock {
|
||||
Arc::new({
|
||||
let text_thread = self.text_thread.clone();
|
||||
let context = self.context.clone();
|
||||
|
||||
move |cx| {
|
||||
let message_id = MessageId(message.timestamp);
|
||||
@@ -1093,19 +1085,20 @@ impl TextThreadEditor {
|
||||
.child(label)
|
||||
.children(spinner),
|
||||
)
|
||||
.tooltip(|_window, cx| {
|
||||
.tooltip(|window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Toggle message role",
|
||||
None,
|
||||
"Available roles: You (User), Agent, System",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click({
|
||||
let text_thread = text_thread.clone();
|
||||
let context = context.clone();
|
||||
move |_, _window, cx| {
|
||||
text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.cycle_message_roles(
|
||||
context.update(cx, |context, cx| {
|
||||
context.cycle_message_roles(
|
||||
HashSet::from_iter(Some(message_id)),
|
||||
cx,
|
||||
)
|
||||
@@ -1133,11 +1126,12 @@ impl TextThreadEditor {
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Hint),
|
||||
)
|
||||
.tooltip(|_window, cx| {
|
||||
.tooltip(|window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Context Cached",
|
||||
None,
|
||||
"Large messages cached to optimize performance",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -1167,11 +1161,11 @@ impl TextThreadEditor {
|
||||
.icon_position(IconPosition::Start)
|
||||
.tooltip(Tooltip::text("View Details"))
|
||||
.on_click({
|
||||
let text_thread = text_thread.clone();
|
||||
let context = context.clone();
|
||||
let error = error.clone();
|
||||
move |_, _window, cx| {
|
||||
text_thread.update(cx, |_, cx| {
|
||||
cx.emit(TextThreadEvent::ShowAssistError(
|
||||
context.update(cx, |_, cx| {
|
||||
cx.emit(ContextEvent::ShowAssistError(
|
||||
error.clone(),
|
||||
));
|
||||
});
|
||||
@@ -1214,7 +1208,7 @@ impl TextThreadEditor {
|
||||
};
|
||||
let mut new_blocks = vec![];
|
||||
let mut block_index_to_message = vec![];
|
||||
for message in self.text_thread.read(cx).messages(cx) {
|
||||
for message in self.context.read(cx).messages(cx) {
|
||||
if blocks_to_remove.remove(&message.id).is_some() {
|
||||
// This is an old message that we might modify.
|
||||
let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
|
||||
@@ -1255,18 +1249,18 @@ impl TextThreadEditor {
|
||||
) -> Option<(String, bool)> {
|
||||
const CODE_FENCE_DELIMITER: &str = "```";
|
||||
|
||||
let text_thread_editor = context_editor_view.read(cx).editor.clone();
|
||||
text_thread_editor.update(cx, |text_thread_editor, cx| {
|
||||
let display_map = text_thread_editor.display_snapshot(cx);
|
||||
if text_thread_editor
|
||||
let context_editor = context_editor_view.read(cx).editor.clone();
|
||||
context_editor.update(cx, |context_editor, cx| {
|
||||
let display_map = context_editor.display_snapshot(cx);
|
||||
if context_editor
|
||||
.selections
|
||||
.newest::<Point>(&display_map)
|
||||
.is_empty()
|
||||
{
|
||||
let snapshot = text_thread_editor.buffer().read(cx).snapshot(cx);
|
||||
let snapshot = context_editor.buffer().read(cx).snapshot(cx);
|
||||
let (_, _, snapshot) = snapshot.as_singleton()?;
|
||||
|
||||
let head = text_thread_editor
|
||||
let head = context_editor
|
||||
.selections
|
||||
.newest::<Point>(&display_map)
|
||||
.head();
|
||||
@@ -1286,8 +1280,8 @@ impl TextThreadEditor {
|
||||
|
||||
(!text.is_empty()).then_some((text, true))
|
||||
} else {
|
||||
let selection = text_thread_editor.selections.newest_adjusted(&display_map);
|
||||
let buffer = text_thread_editor.buffer().read(cx).snapshot(cx);
|
||||
let selection = context_editor.selections.newest_adjusted(&display_map);
|
||||
let buffer = context_editor.buffer().read(cx).snapshot(cx);
|
||||
let selected_text = buffer.text_for_range(selection.range()).collect::<String>();
|
||||
|
||||
(!selected_text.is_empty()).then_some((selected_text, false))
|
||||
@@ -1305,7 +1299,7 @@ impl TextThreadEditor {
|
||||
return;
|
||||
};
|
||||
let Some(context_editor_view) =
|
||||
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
|
||||
agent_panel_delegate.active_context_editor(workspace, window, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -1333,7 +1327,7 @@ impl TextThreadEditor {
|
||||
let result = maybe!({
|
||||
let agent_panel_delegate = <dyn AgentPanelDelegate>::try_global(cx)?;
|
||||
let context_editor_view =
|
||||
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)?;
|
||||
agent_panel_delegate.active_context_editor(workspace, window, cx)?;
|
||||
Self::get_selection_or_code_block(&context_editor_view, cx)
|
||||
});
|
||||
let Some((text, is_code_block)) = result else {
|
||||
@@ -1370,7 +1364,7 @@ impl TextThreadEditor {
|
||||
return;
|
||||
};
|
||||
let Some(context_editor_view) =
|
||||
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
|
||||
agent_panel_delegate.active_context_editor(workspace, window, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -1456,7 +1450,7 @@ impl TextThreadEditor {
|
||||
|
||||
pub fn quote_selection(
|
||||
workspace: &mut Workspace,
|
||||
_: &AddSelectionToThread,
|
||||
_: &QuoteSelection,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
@@ -1631,33 +1625,29 @@ impl TextThreadEditor {
|
||||
)
|
||||
});
|
||||
|
||||
let text_thread = self.text_thread.read(cx);
|
||||
let context = self.context.read(cx);
|
||||
|
||||
let mut text = String::new();
|
||||
|
||||
// If selection is empty, we want to copy the entire line
|
||||
if selection.range().is_empty() {
|
||||
let snapshot = text_thread.buffer().read(cx).snapshot();
|
||||
let snapshot = context.buffer().read(cx).snapshot();
|
||||
let point = snapshot.offset_to_point(selection.range().start);
|
||||
selection.start = snapshot.point_to_offset(Point::new(point.row, 0));
|
||||
selection.end = snapshot
|
||||
.point_to_offset(cmp::min(Point::new(point.row + 1, 0), snapshot.max_point()));
|
||||
for chunk in text_thread
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.text_for_range(selection.range())
|
||||
{
|
||||
for chunk in context.buffer().read(cx).text_for_range(selection.range()) {
|
||||
text.push_str(chunk);
|
||||
}
|
||||
} else {
|
||||
for message in text_thread.messages(cx) {
|
||||
for message in context.messages(cx) {
|
||||
if message.offset_range.start >= selection.range().end {
|
||||
break;
|
||||
} else if message.offset_range.end >= selection.range().start {
|
||||
let range = cmp::max(message.offset_range.start, selection.range().start)
|
||||
..cmp::min(message.offset_range.end, selection.range().end);
|
||||
if !range.is_empty() {
|
||||
for chunk in text_thread.buffer().read(cx).text_for_range(range) {
|
||||
for chunk in context.buffer().read(cx).text_for_range(range) {
|
||||
text.push_str(chunk);
|
||||
}
|
||||
if message.offset_range.end < selection.range().end {
|
||||
@@ -1768,7 +1758,7 @@ impl TextThreadEditor {
|
||||
});
|
||||
});
|
||||
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
self.context.update(cx, |context, cx| {
|
||||
for image in images {
|
||||
let Some(render_image) = image.to_image_data(cx.svg_renderer()).log_err()
|
||||
else {
|
||||
@@ -1778,7 +1768,7 @@ impl TextThreadEditor {
|
||||
let image_task = LanguageModelImage::from_image(Arc::new(image), cx).shared();
|
||||
|
||||
for image_position in image_positions.iter() {
|
||||
text_thread.insert_content(
|
||||
context.insert_content(
|
||||
Content::Image {
|
||||
anchor: image_position.text_anchor,
|
||||
image_id,
|
||||
@@ -1799,7 +1789,7 @@ impl TextThreadEditor {
|
||||
let excerpt_id = *buffer.as_singleton().unwrap().0;
|
||||
let old_blocks = std::mem::take(&mut self.image_blocks);
|
||||
let new_blocks = self
|
||||
.text_thread
|
||||
.context
|
||||
.read(cx)
|
||||
.contents(cx)
|
||||
.map(
|
||||
@@ -1847,36 +1837,36 @@ impl TextThreadEditor {
|
||||
}
|
||||
|
||||
fn split(&mut self, _: &Split, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
self.context.update(cx, |context, cx| {
|
||||
let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
|
||||
for selection in selections.as_ref() {
|
||||
let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||
let range = selection
|
||||
.map(|endpoint| endpoint.to_offset(&buffer))
|
||||
.range();
|
||||
text_thread.split_message(range, cx);
|
||||
context.split_message(range, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn title(&self, cx: &App) -> SharedString {
|
||||
self.text_thread.read(cx).summary().or_default()
|
||||
self.context.read(cx).summary().or_default()
|
||||
}
|
||||
|
||||
pub fn regenerate_summary(&mut self, cx: &mut Context<Self>) {
|
||||
self.text_thread
|
||||
.update(cx, |text_thread, cx| text_thread.summarize(true, cx));
|
||||
self.context
|
||||
.update(cx, |context, cx| context.summarize(true, cx));
|
||||
}
|
||||
|
||||
fn render_remaining_tokens(&self, cx: &App) -> Option<impl IntoElement + use<>> {
|
||||
let (token_count_color, token_count, max_token_count, tooltip) =
|
||||
match token_state(&self.text_thread, cx)? {
|
||||
match token_state(&self.context, cx)? {
|
||||
TokenState::NoTokensLeft {
|
||||
max_token_count,
|
||||
token_count,
|
||||
@@ -1924,7 +1914,7 @@ impl TextThreadEditor {
|
||||
fn render_send_button(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
let (style, tooltip) = match token_state(&self.text_thread, cx) {
|
||||
let (style, tooltip) = match token_state(&self.context, cx) {
|
||||
Some(TokenState::NoTokensLeft { .. }) => (
|
||||
ButtonStyle::Tinted(TintColor::Error),
|
||||
Some(Tooltip::text("Token limit reached")(window, cx)),
|
||||
@@ -1957,7 +1947,7 @@ impl TextThreadEditor {
|
||||
})
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&Assist, &focus_handle, cx)
|
||||
KeyBinding::for_action_in(&Assist, &focus_handle, window, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(move |_event, window, cx| {
|
||||
@@ -1992,14 +1982,20 @@ impl TextThreadEditor {
|
||||
.icon_color(Color::Muted)
|
||||
.selected_icon_color(Color::Accent)
|
||||
.selected_style(ButtonStyle::Filled),
|
||||
move |_window, cx| {
|
||||
Tooltip::with_meta("Add Context", None, "Type / to insert via keyboard", cx)
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Add Context",
|
||||
None,
|
||||
"Type / to insert via keyboard",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
let text_thread = self.text_thread().read(cx);
|
||||
let context = self.context().read(cx);
|
||||
let active_model = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()
|
||||
.map(|default| default.model)?;
|
||||
@@ -2007,7 +2003,7 @@ impl TextThreadEditor {
|
||||
return None;
|
||||
}
|
||||
|
||||
let active_completion_mode = text_thread.completion_mode();
|
||||
let active_completion_mode = context.completion_mode();
|
||||
let burn_mode_enabled = active_completion_mode == CompletionMode::Burn;
|
||||
let icon = if burn_mode_enabled {
|
||||
IconName::ZedBurnModeOn
|
||||
@@ -2022,8 +2018,8 @@ impl TextThreadEditor {
|
||||
.toggle_state(burn_mode_enabled)
|
||||
.selected_icon_color(Color::Error)
|
||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||
this.text_thread().update(cx, |text_thread, _cx| {
|
||||
text_thread.set_completion_mode(match active_completion_mode {
|
||||
this.context().update(cx, |context, _cx| {
|
||||
context.set_completion_mode(match active_completion_mode {
|
||||
CompletionMode::Burn => CompletionMode::Normal,
|
||||
CompletionMode::Normal => CompletionMode::Burn,
|
||||
});
|
||||
@@ -2082,8 +2078,14 @@ impl TextThreadEditor {
|
||||
)
|
||||
.child(Icon::new(icon).color(color).size(IconSize::XSmall)),
|
||||
),
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
&ToggleModelSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::BottomRight,
|
||||
cx,
|
||||
@@ -2650,10 +2652,10 @@ impl FollowableItem for TextThreadEditor {
|
||||
}
|
||||
|
||||
fn to_state_proto(&self, window: &Window, cx: &App) -> Option<proto::view::Variant> {
|
||||
let text_thread = self.text_thread.read(cx);
|
||||
let context = self.context.read(cx);
|
||||
Some(proto::view::Variant::ContextEditor(
|
||||
proto::view::ContextEditor {
|
||||
context_id: text_thread.id().to_proto(),
|
||||
context_id: context.id().to_proto(),
|
||||
editor: if let Some(proto::view::Variant::Editor(proto)) =
|
||||
self.editor.read(cx).to_state_proto(window, cx)
|
||||
{
|
||||
@@ -2679,22 +2681,22 @@ impl FollowableItem for TextThreadEditor {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let text_thread_id = TextThreadId::from_proto(state.context_id);
|
||||
let context_id = ContextId::from_proto(state.context_id);
|
||||
let editor_state = state.editor?;
|
||||
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let agent_panel_delegate = <dyn AgentPanelDelegate>::try_global(cx)?;
|
||||
|
||||
let text_thread_editor_task = workspace.update(cx, |workspace, cx| {
|
||||
agent_panel_delegate.open_remote_text_thread(workspace, text_thread_id, window, cx)
|
||||
let context_editor_task = workspace.update(cx, |workspace, cx| {
|
||||
agent_panel_delegate.open_remote_context(workspace, context_id, window, cx)
|
||||
});
|
||||
|
||||
Some(window.spawn(cx, async move |cx| {
|
||||
let text_thread_editor = text_thread_editor_task.await?;
|
||||
text_thread_editor
|
||||
.update_in(cx, |text_thread_editor, window, cx| {
|
||||
text_thread_editor.remote_id = Some(id);
|
||||
text_thread_editor.editor.update(cx, |editor, cx| {
|
||||
let context_editor = context_editor_task.await?;
|
||||
context_editor
|
||||
.update_in(cx, |context_editor, window, cx| {
|
||||
context_editor.remote_id = Some(id);
|
||||
context_editor.editor.update(cx, |editor, cx| {
|
||||
editor.apply_update_proto(
|
||||
&project,
|
||||
proto::update_view::Variant::Editor(proto::update_view::Editor {
|
||||
@@ -2711,7 +2713,7 @@ impl FollowableItem for TextThreadEditor {
|
||||
})
|
||||
})?
|
||||
.await?;
|
||||
Ok(text_thread_editor)
|
||||
Ok(context_editor)
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -2758,7 +2760,7 @@ impl FollowableItem for TextThreadEditor {
|
||||
}
|
||||
|
||||
fn dedup(&self, existing: &Self, _window: &Window, cx: &App) -> Option<item::Dedup> {
|
||||
if existing.text_thread.read(cx).id() == self.text_thread.read(cx).id() {
|
||||
if existing.context.read(cx).id() == self.context.read(cx).id() {
|
||||
Some(item::Dedup::KeepExisting)
|
||||
} else {
|
||||
None
|
||||
@@ -2770,17 +2772,17 @@ enum PendingSlashCommand {}
|
||||
|
||||
fn invoked_slash_command_fold_placeholder(
|
||||
command_id: InvokedSlashCommandId,
|
||||
text_thread: WeakEntity<TextThread>,
|
||||
context: WeakEntity<AssistantContext>,
|
||||
) -> FoldPlaceholder {
|
||||
FoldPlaceholder {
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
render: Arc::new(move |fold_id, _, cx| {
|
||||
let Some(text_thread) = text_thread.upgrade() else {
|
||||
let Some(context) = context.upgrade() else {
|
||||
return Empty.into_any();
|
||||
};
|
||||
|
||||
let Some(command) = text_thread.read(cx).invoked_slash_command(&command_id) else {
|
||||
let Some(command) = context.read(cx).invoked_slash_command(&command_id) else {
|
||||
return Empty.into_any();
|
||||
};
|
||||
|
||||
@@ -2821,15 +2823,14 @@ enum TokenState {
|
||||
},
|
||||
}
|
||||
|
||||
fn token_state(text_thread: &Entity<TextThread>, cx: &App) -> Option<TokenState> {
|
||||
fn token_state(context: &Entity<AssistantContext>, cx: &App) -> Option<TokenState> {
|
||||
const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
|
||||
|
||||
let model = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()?
|
||||
.model;
|
||||
let token_count = text_thread.read(cx).token_count()?;
|
||||
let max_token_count =
|
||||
model.max_token_count_for_mode(text_thread.read(cx).completion_mode().into());
|
||||
let token_count = context.read(cx).token_count()?;
|
||||
let max_token_count = model.max_token_count_for_mode(context.read(cx).completion_mode().into());
|
||||
let token_state = if max_token_count.saturating_sub(token_count) == 0 {
|
||||
TokenState::NoTokensLeft {
|
||||
max_token_count,
|
||||
@@ -2941,7 +2942,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_copy_paste_whole_message(cx: &mut TestAppContext) {
|
||||
let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(vec![
|
||||
let (context, context_editor, mut cx) = setup_context_editor_text(vec![
|
||||
(Role::User, "What is the Zed editor?"),
|
||||
(
|
||||
Role::Assistant,
|
||||
@@ -2951,8 +2952,8 @@ mod tests {
|
||||
],cx).await;
|
||||
|
||||
// Select & Copy whole user message
|
||||
assert_copy_paste_text_thread_editor(
|
||||
&text_thread_editor,
|
||||
assert_copy_paste_context_editor(
|
||||
&context_editor,
|
||||
message_range(&context, 0, &mut cx),
|
||||
indoc! {"
|
||||
What is the Zed editor?
|
||||
@@ -2963,8 +2964,8 @@ mod tests {
|
||||
);
|
||||
|
||||
// Select & Copy whole assistant message
|
||||
assert_copy_paste_text_thread_editor(
|
||||
&text_thread_editor,
|
||||
assert_copy_paste_context_editor(
|
||||
&context_editor,
|
||||
message_range(&context, 1, &mut cx),
|
||||
indoc! {"
|
||||
What is the Zed editor?
|
||||
@@ -2978,7 +2979,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_copy_paste_no_selection(cx: &mut TestAppContext) {
|
||||
let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(
|
||||
let (context, context_editor, mut cx) = setup_context_editor_text(
|
||||
vec![
|
||||
(Role::User, "user1"),
|
||||
(Role::Assistant, "assistant1"),
|
||||
@@ -2991,8 +2992,8 @@ mod tests {
|
||||
|
||||
// Copy and paste first assistant message
|
||||
let message_2_range = message_range(&context, 1, &mut cx);
|
||||
assert_copy_paste_text_thread_editor(
|
||||
&text_thread_editor,
|
||||
assert_copy_paste_context_editor(
|
||||
&context_editor,
|
||||
message_2_range.start..message_2_range.start,
|
||||
indoc! {"
|
||||
user1
|
||||
@@ -3005,8 +3006,8 @@ mod tests {
|
||||
|
||||
// Copy and cut second assistant message
|
||||
let message_3_range = message_range(&context, 2, &mut cx);
|
||||
assert_copy_paste_text_thread_editor(
|
||||
&text_thread_editor,
|
||||
assert_copy_paste_context_editor(
|
||||
&context_editor,
|
||||
message_3_range.start..message_3_range.start,
|
||||
indoc! {"
|
||||
user1
|
||||
@@ -3093,29 +3094,29 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
async fn setup_text_thread_editor_text(
|
||||
async fn setup_context_editor_text(
|
||||
messages: Vec<(Role, &str)>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> (
|
||||
Entity<TextThread>,
|
||||
Entity<AssistantContext>,
|
||||
Entity<TextThreadEditor>,
|
||||
VisualTestContext,
|
||||
) {
|
||||
cx.update(init_test);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let text_thread = create_text_thread_with_messages(messages, cx);
|
||||
let context = create_context_with_messages(messages, cx);
|
||||
|
||||
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let mut cx = VisualTestContext::from_window(*window, cx);
|
||||
|
||||
let text_thread_editor = window
|
||||
let context_editor = window
|
||||
.update(&mut cx, |_, window, cx| {
|
||||
cx.new(|cx| {
|
||||
TextThreadEditor::for_text_thread(
|
||||
text_thread.clone(),
|
||||
TextThreadEditor::for_context(
|
||||
context.clone(),
|
||||
fs,
|
||||
workspace.downgrade(),
|
||||
project,
|
||||
@@ -3127,59 +3128,59 @@ mod tests {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
(text_thread, text_thread_editor, cx)
|
||||
(context, context_editor, cx)
|
||||
}
|
||||
|
||||
fn message_range(
|
||||
text_thread: &Entity<TextThread>,
|
||||
context: &Entity<AssistantContext>,
|
||||
message_ix: usize,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Range<usize> {
|
||||
text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread
|
||||
context.update(cx, |context, cx| {
|
||||
context
|
||||
.messages(cx)
|
||||
.nth(message_ix)
|
||||
.unwrap()
|
||||
.anchor_range
|
||||
.to_offset(&text_thread.buffer().read(cx).snapshot())
|
||||
.to_offset(&context.buffer().read(cx).snapshot())
|
||||
})
|
||||
}
|
||||
|
||||
fn assert_copy_paste_text_thread_editor<T: editor::ToOffset>(
|
||||
text_thread_editor: &Entity<TextThreadEditor>,
|
||||
fn assert_copy_paste_context_editor<T: editor::ToOffset>(
|
||||
context_editor: &Entity<TextThreadEditor>,
|
||||
range: Range<T>,
|
||||
expected_text: &str,
|
||||
cx: &mut VisualTestContext,
|
||||
) {
|
||||
text_thread_editor.update_in(cx, |text_thread_editor, window, cx| {
|
||||
text_thread_editor.editor.update(cx, |editor, cx| {
|
||||
context_editor.update_in(cx, |context_editor, window, cx| {
|
||||
context_editor.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([range])
|
||||
});
|
||||
});
|
||||
|
||||
text_thread_editor.copy(&Default::default(), window, cx);
|
||||
context_editor.copy(&Default::default(), window, cx);
|
||||
|
||||
text_thread_editor.editor.update(cx, |editor, cx| {
|
||||
context_editor.editor.update(cx, |editor, cx| {
|
||||
editor.move_to_end(&Default::default(), window, cx);
|
||||
});
|
||||
|
||||
text_thread_editor.paste(&Default::default(), window, cx);
|
||||
context_editor.paste(&Default::default(), window, cx);
|
||||
|
||||
text_thread_editor.editor.update(cx, |editor, cx| {
|
||||
context_editor.editor.update(cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), expected_text);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn create_text_thread_with_messages(
|
||||
fn create_context_with_messages(
|
||||
mut messages: Vec<(Role, &str)>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Entity<TextThread> {
|
||||
) -> Entity<AssistantContext> {
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
cx.new(|cx| {
|
||||
let mut text_thread = TextThread::local(
|
||||
let mut context = AssistantContext::local(
|
||||
registry,
|
||||
None,
|
||||
None,
|
||||
@@ -3187,33 +3188,33 @@ mod tests {
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
cx,
|
||||
);
|
||||
let mut message_1 = text_thread.messages(cx).next().unwrap();
|
||||
let mut message_1 = context.messages(cx).next().unwrap();
|
||||
let (role, text) = messages.remove(0);
|
||||
|
||||
loop {
|
||||
if role == message_1.role {
|
||||
text_thread.buffer().update(cx, |buffer, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(message_1.offset_range, text)], None, cx);
|
||||
});
|
||||
break;
|
||||
}
|
||||
let mut ids = HashSet::default();
|
||||
ids.insert(message_1.id);
|
||||
text_thread.cycle_message_roles(ids, cx);
|
||||
message_1 = text_thread.messages(cx).next().unwrap();
|
||||
context.cycle_message_roles(ids, cx);
|
||||
message_1 = context.messages(cx).next().unwrap();
|
||||
}
|
||||
|
||||
let mut last_message_id = message_1.id;
|
||||
for (role, text) in messages {
|
||||
text_thread.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
|
||||
let message = text_thread.messages(cx).last().unwrap();
|
||||
context.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
|
||||
let message = context.messages(cx).last().unwrap();
|
||||
last_message_id = message.id;
|
||||
text_thread.buffer().update(cx, |buffer, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(message.offset_range, text)], None, cx);
|
||||
})
|
||||
}
|
||||
|
||||
text_thread
|
||||
context
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ impl BurnModeTooltip {
|
||||
}
|
||||
|
||||
impl Render for BurnModeTooltip {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (icon, color) = if self.selected {
|
||||
(IconName::ZedBurnModeOn, Color::Error)
|
||||
} else {
|
||||
@@ -45,7 +45,8 @@ impl Render for BurnModeTooltip {
|
||||
.child(Label::new("Burn Mode"))
|
||||
.when(self.selected, |title| title.child(turned_on));
|
||||
|
||||
let keybinding = KeyBinding::for_action(&ToggleBurnMode, cx).size(rems_from_px(12.));
|
||||
let keybinding = KeyBinding::for_action(&ToggleBurnMode, window, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.)));
|
||||
|
||||
tooltip_container(cx, |this, _| {
|
||||
this
|
||||
@@ -53,7 +54,7 @@ impl Render for BurnModeTooltip {
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(title)
|
||||
.child(keybinding)
|
||||
.children(keybinding)
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
|
||||
@@ -244,8 +244,8 @@ impl RenderOnce for ContextPill {
|
||||
.truncate(),
|
||||
),
|
||||
)
|
||||
.tooltip(|_window, cx| {
|
||||
Tooltip::with_meta("Suggested Context", None, "Click to add it", cx)
|
||||
.tooltip(|window, cx| {
|
||||
Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)
|
||||
})
|
||||
.when_some(on_click.as_ref(), |element, on_click| {
|
||||
let on_click = on_click.clone();
|
||||
@@ -497,9 +497,9 @@ impl AddedContext {
|
||||
icon_path: None,
|
||||
status: ContextStatus::Ready,
|
||||
render_hover: {
|
||||
let text_thread = handle.text_thread.clone();
|
||||
let context = handle.context.clone();
|
||||
Some(Rc::new(move |_, cx| {
|
||||
let text = text_thread.read(cx).to_xml(cx);
|
||||
let text = context.read(cx).to_xml(cx);
|
||||
ContextPillHover::new_text(text.into(), cx).into()
|
||||
}))
|
||||
},
|
||||
|
||||
@@ -84,32 +84,10 @@ impl ZedAiOnboarding {
|
||||
self
|
||||
}
|
||||
|
||||
fn render_dismiss_button(&self) -> Option<AnyElement> {
|
||||
self.dismiss_onboarding.as_ref().map(|dismiss_callback| {
|
||||
let callback = dismiss_callback.clone();
|
||||
|
||||
h_flex()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.right_0()
|
||||
.child(
|
||||
IconButton::new("dismiss_onboarding", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Dismiss"))
|
||||
.on_click(move |_, window, cx| {
|
||||
telemetry::event!("Banner Dismissed", source = "AI Onboarding",);
|
||||
callback(window, cx)
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
}
|
||||
|
||||
fn render_sign_in_disclaimer(&self, _cx: &mut App) -> AnyElement {
|
||||
let signing_in = matches!(self.sign_in_status, SignInStatus::SigningIn);
|
||||
|
||||
v_flex()
|
||||
.relative()
|
||||
.gap_1()
|
||||
.child(Headline::new("Welcome to Zed AI"))
|
||||
.child(
|
||||
@@ -131,7 +109,6 @@ impl ZedAiOnboarding {
|
||||
}
|
||||
}),
|
||||
)
|
||||
.children(self.render_dismiss_button())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -203,7 +180,27 @@ impl ZedAiOnboarding {
|
||||
)
|
||||
.child(PlanDefinitions.free_plan(is_v2)),
|
||||
)
|
||||
.children(self.render_dismiss_button())
|
||||
.when_some(
|
||||
self.dismiss_onboarding.as_ref(),
|
||||
|this, dismiss_callback| {
|
||||
let callback = dismiss_callback.clone();
|
||||
|
||||
this.child(
|
||||
h_flex().absolute().top_0().right_0().child(
|
||||
IconButton::new("dismiss_onboarding", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Dismiss"))
|
||||
.on_click(move |_, window, cx| {
|
||||
telemetry::event!(
|
||||
"Banner Dismissed",
|
||||
source = "AI Onboarding",
|
||||
);
|
||||
callback(window, cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.mt_2()
|
||||
@@ -248,7 +245,26 @@ impl ZedAiOnboarding {
|
||||
.mb_2(),
|
||||
)
|
||||
.child(PlanDefinitions.pro_trial(is_v2, false))
|
||||
.children(self.render_dismiss_button())
|
||||
.when_some(
|
||||
self.dismiss_onboarding.as_ref(),
|
||||
|this, dismiss_callback| {
|
||||
let callback = dismiss_callback.clone();
|
||||
this.child(
|
||||
h_flex().absolute().top_0().right_0().child(
|
||||
IconButton::new("dismiss_onboarding", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Dismiss"))
|
||||
.on_click(move |_, window, cx| {
|
||||
telemetry::event!(
|
||||
"Banner Dismissed",
|
||||
source = "AI Onboarding",
|
||||
);
|
||||
callback(window, cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -262,7 +278,26 @@ impl ZedAiOnboarding {
|
||||
.mb_2(),
|
||||
)
|
||||
.child(PlanDefinitions.pro_plan(is_v2, false))
|
||||
.children(self.render_dismiss_button())
|
||||
.when_some(
|
||||
self.dismiss_onboarding.as_ref(),
|
||||
|this, dismiss_callback| {
|
||||
let callback = dismiss_callback.clone();
|
||||
this.child(
|
||||
h_flex().absolute().top_0().right_0().child(
|
||||
IconButton::new("dismiss_onboarding", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Dismiss"))
|
||||
.on_click(move |_, window, cx| {
|
||||
telemetry::event!(
|
||||
"Banner Dismissed",
|
||||
source = "AI Onboarding",
|
||||
);
|
||||
callback(window, cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use futures::{
|
||||
};
|
||||
use gpui::{AsyncApp, BackgroundExecutor, Task};
|
||||
use smol::fs;
|
||||
use util::{ResultExt as _, debug_panic, maybe, paths::PathExt, shell::ShellKind};
|
||||
use util::{ResultExt as _, debug_panic, maybe, paths::PathExt};
|
||||
|
||||
/// Path to the program used for askpass
|
||||
///
|
||||
@@ -199,15 +199,9 @@ impl PasswordProxy {
|
||||
let current_exec =
|
||||
std::env::current_exe().context("Failed to determine current zed executable path.")?;
|
||||
|
||||
// TODO: inferred from the use of powershell.exe in askpass_helper_script
|
||||
let shell_kind = if cfg!(windows) {
|
||||
ShellKind::PowerShell
|
||||
} else {
|
||||
ShellKind::Posix
|
||||
};
|
||||
let askpass_program = ASKPASS_PROGRAM
|
||||
.get_or_init(|| current_exec)
|
||||
.try_shell_safe(shell_kind)
|
||||
.try_shell_safe()
|
||||
.context("Failed to shell-escape Askpass program path.")?
|
||||
.to_string();
|
||||
// Create an askpass script that communicates back to this process.
|
||||
@@ -349,7 +343,7 @@ fn generate_askpass_script(askpass_program: &str, askpass_socket: &std::path::Pa
|
||||
format!(
|
||||
r#"
|
||||
$ErrorActionPreference = 'Stop';
|
||||
($args -join [char]0) | & {askpass_program} --askpass={askpass_socket} 2> $null
|
||||
($args -join [char]0) | & "{askpass_program}" --askpass={askpass_socket} 2> $null
|
||||
"#,
|
||||
askpass_socket = askpass_socket.display(),
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "assistant_text_thread"
|
||||
name = "assistant_context"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
@@ -9,7 +9,7 @@ license = "GPL-3.0-or-later"
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/assistant_text_thread.rs"
|
||||
path = "src/assistant_context.rs"
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
@@ -1,3 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod assistant_context_tests;
|
||||
mod context_store;
|
||||
|
||||
use agent_settings::{AgentSettings, SUMMARIZE_THREAD_PROMPT};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use assistant_slash_command::{
|
||||
@@ -5,7 +9,7 @@ use assistant_slash_command::{
|
||||
SlashCommandResult, SlashCommandWorkingSet,
|
||||
};
|
||||
use assistant_slash_commands::FileCommandMetadata;
|
||||
use client::{self, ModelRequestUsage, RequestUsage, proto, telemetry::Telemetry};
|
||||
use client::{self, Client, ModelRequestUsage, RequestUsage, proto, telemetry::Telemetry};
|
||||
use clock::ReplicaId;
|
||||
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
|
||||
use collections::{HashMap, HashSet};
|
||||
@@ -23,7 +27,7 @@ use language_model::{
|
||||
report_assistant_event,
|
||||
};
|
||||
use open_ai::Model as OpenAiModel;
|
||||
use paths::text_threads_dir;
|
||||
use paths::contexts_dir;
|
||||
use project::Project;
|
||||
use prompt_store::PromptBuilder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -44,10 +48,16 @@ use ui::IconName;
|
||||
use util::{ResultExt, TryFutureExt, post_inc};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct TextThreadId(String);
|
||||
pub use crate::context_store::*;
|
||||
|
||||
impl TextThreadId {
|
||||
pub fn init(client: Arc<Client>, _: &mut App) {
|
||||
context_store::init(&client.into());
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct ContextId(String);
|
||||
|
||||
impl ContextId {
|
||||
pub fn new() -> Self {
|
||||
Self(Uuid::new_v4().to_string())
|
||||
}
|
||||
@@ -120,7 +130,7 @@ impl MessageStatus {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TextThreadOperation {
|
||||
pub enum ContextOperation {
|
||||
InsertMessage {
|
||||
anchor: MessageAnchor,
|
||||
metadata: MessageMetadata,
|
||||
@@ -132,7 +142,7 @@ pub enum TextThreadOperation {
|
||||
version: clock::Global,
|
||||
},
|
||||
UpdateSummary {
|
||||
summary: TextThreadSummaryContent,
|
||||
summary: ContextSummaryContent,
|
||||
version: clock::Global,
|
||||
},
|
||||
SlashCommandStarted {
|
||||
@@ -160,7 +170,7 @@ pub enum TextThreadOperation {
|
||||
BufferOperation(language::Operation),
|
||||
}
|
||||
|
||||
impl TextThreadOperation {
|
||||
impl ContextOperation {
|
||||
pub fn from_proto(op: proto::ContextOperation) -> Result<Self> {
|
||||
match op.variant.context("invalid variant")? {
|
||||
proto::context_operation::Variant::InsertMessage(insert) => {
|
||||
@@ -202,7 +212,7 @@ impl TextThreadOperation {
|
||||
version: language::proto::deserialize_version(&update.version),
|
||||
}),
|
||||
proto::context_operation::Variant::UpdateSummary(update) => Ok(Self::UpdateSummary {
|
||||
summary: TextThreadSummaryContent {
|
||||
summary: ContextSummaryContent {
|
||||
text: update.summary,
|
||||
done: update.done,
|
||||
timestamp: language::proto::deserialize_timestamp(
|
||||
@@ -443,7 +453,7 @@ impl TextThreadOperation {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TextThreadEvent {
|
||||
pub enum ContextEvent {
|
||||
ShowAssistError(SharedString),
|
||||
ShowPaymentRequiredError,
|
||||
MessagesEdited,
|
||||
@@ -466,24 +476,24 @@ pub enum TextThreadEvent {
|
||||
SlashCommandOutputSectionAdded {
|
||||
section: SlashCommandOutputSection<language::Anchor>,
|
||||
},
|
||||
Operation(TextThreadOperation),
|
||||
Operation(ContextOperation),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum TextThreadSummary {
|
||||
pub enum ContextSummary {
|
||||
Pending,
|
||||
Content(TextThreadSummaryContent),
|
||||
Content(ContextSummaryContent),
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct TextThreadSummaryContent {
|
||||
#[derive(Default, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ContextSummaryContent {
|
||||
pub text: String,
|
||||
pub done: bool,
|
||||
pub timestamp: clock::Lamport,
|
||||
}
|
||||
|
||||
impl TextThreadSummary {
|
||||
impl ContextSummary {
|
||||
pub const DEFAULT: &str = "New Text Thread";
|
||||
|
||||
pub fn or_default(&self) -> SharedString {
|
||||
@@ -495,48 +505,44 @@ impl TextThreadSummary {
|
||||
.map_or_else(|| message.into(), |content| content.text.clone().into())
|
||||
}
|
||||
|
||||
pub fn content(&self) -> Option<&TextThreadSummaryContent> {
|
||||
pub fn content(&self) -> Option<&ContextSummaryContent> {
|
||||
match self {
|
||||
TextThreadSummary::Content(content) => Some(content),
|
||||
TextThreadSummary::Pending | TextThreadSummary::Error => None,
|
||||
ContextSummary::Content(content) => Some(content),
|
||||
ContextSummary::Pending | ContextSummary::Error => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn content_as_mut(&mut self) -> Option<&mut TextThreadSummaryContent> {
|
||||
fn content_as_mut(&mut self) -> Option<&mut ContextSummaryContent> {
|
||||
match self {
|
||||
TextThreadSummary::Content(content) => Some(content),
|
||||
TextThreadSummary::Pending | TextThreadSummary::Error => None,
|
||||
ContextSummary::Content(content) => Some(content),
|
||||
ContextSummary::Pending | ContextSummary::Error => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn content_or_set_empty(&mut self) -> &mut TextThreadSummaryContent {
|
||||
fn content_or_set_empty(&mut self) -> &mut ContextSummaryContent {
|
||||
match self {
|
||||
TextThreadSummary::Content(content) => content,
|
||||
TextThreadSummary::Pending | TextThreadSummary::Error => {
|
||||
let content = TextThreadSummaryContent {
|
||||
text: "".to_string(),
|
||||
done: false,
|
||||
timestamp: clock::Lamport::MIN,
|
||||
};
|
||||
*self = TextThreadSummary::Content(content);
|
||||
ContextSummary::Content(content) => content,
|
||||
ContextSummary::Pending | ContextSummary::Error => {
|
||||
let content = ContextSummaryContent::default();
|
||||
*self = ContextSummary::Content(content);
|
||||
self.content_as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_pending(&self) -> bool {
|
||||
matches!(self, TextThreadSummary::Pending)
|
||||
matches!(self, ContextSummary::Pending)
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> Option<clock::Lamport> {
|
||||
match self {
|
||||
TextThreadSummary::Content(content) => Some(content.timestamp),
|
||||
TextThreadSummary::Pending | TextThreadSummary::Error => None,
|
||||
ContextSummary::Content(content) => Some(content.timestamp),
|
||||
ContextSummary::Pending | ContextSummary::Error => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for TextThreadSummary {
|
||||
impl PartialOrd for ContextSummary {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.timestamp().partial_cmp(&other.timestamp())
|
||||
}
|
||||
@@ -658,27 +664,27 @@ struct PendingCompletion {
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct InvokedSlashCommandId(clock::Lamport);
|
||||
|
||||
pub struct TextThread {
|
||||
id: TextThreadId,
|
||||
pub struct AssistantContext {
|
||||
id: ContextId,
|
||||
timestamp: clock::Lamport,
|
||||
version: clock::Global,
|
||||
pub(crate) pending_ops: Vec<TextThreadOperation>,
|
||||
operations: Vec<TextThreadOperation>,
|
||||
pending_ops: Vec<ContextOperation>,
|
||||
operations: Vec<ContextOperation>,
|
||||
buffer: Entity<Buffer>,
|
||||
pub(crate) parsed_slash_commands: Vec<ParsedSlashCommand>,
|
||||
parsed_slash_commands: Vec<ParsedSlashCommand>,
|
||||
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
|
||||
edits_since_last_parse: language::Subscription,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
pub(crate) slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||
thought_process_output_sections: Vec<ThoughtProcessOutputSection<language::Anchor>>,
|
||||
pub(crate) message_anchors: Vec<MessageAnchor>,
|
||||
message_anchors: Vec<MessageAnchor>,
|
||||
contents: Vec<Content>,
|
||||
pub(crate) messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
summary: TextThreadSummary,
|
||||
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
summary: ContextSummary,
|
||||
summary_task: Task<Option<()>>,
|
||||
completion_count: usize,
|
||||
pending_completions: Vec<PendingCompletion>,
|
||||
pub(crate) token_count: Option<u64>,
|
||||
token_count: Option<u64>,
|
||||
pending_token_count: Task<Option<()>>,
|
||||
pending_save: Task<Result<()>>,
|
||||
pending_cache_warming_task: Task<Option<()>>,
|
||||
@@ -701,9 +707,9 @@ impl ContextAnnotation for ParsedSlashCommand {
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<TextThreadEvent> for TextThread {}
|
||||
impl EventEmitter<ContextEvent> for AssistantContext {}
|
||||
|
||||
impl TextThread {
|
||||
impl AssistantContext {
|
||||
pub fn local(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
project: Option<Entity<Project>>,
|
||||
@@ -713,7 +719,7 @@ impl TextThread {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
TextThreadId::new(),
|
||||
ContextId::new(),
|
||||
ReplicaId::default(),
|
||||
language::Capability::ReadWrite,
|
||||
language_registry,
|
||||
@@ -734,7 +740,7 @@ impl TextThread {
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
id: TextThreadId,
|
||||
id: ContextId,
|
||||
replica_id: ReplicaId,
|
||||
capability: language::Capability,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
@@ -770,7 +776,7 @@ impl TextThread {
|
||||
slash_command_output_sections: Vec::new(),
|
||||
thought_process_output_sections: Vec::new(),
|
||||
edits_since_last_parse: edits_since_last_slash_command_parse,
|
||||
summary: TextThreadSummary::Pending,
|
||||
summary: ContextSummary::Pending,
|
||||
summary_task: Task::ready(None),
|
||||
completion_count: Default::default(),
|
||||
pending_completions: Default::default(),
|
||||
@@ -790,7 +796,7 @@ impl TextThread {
|
||||
};
|
||||
|
||||
let first_message_id = MessageId(clock::Lamport {
|
||||
replica_id: ReplicaId::LOCAL,
|
||||
replica_id: 0,
|
||||
value: 0,
|
||||
});
|
||||
let message = MessageAnchor {
|
||||
@@ -813,12 +819,12 @@ impl TextThread {
|
||||
this
|
||||
}
|
||||
|
||||
pub(crate) fn serialize(&self, cx: &App) -> SavedTextThread {
|
||||
pub(crate) fn serialize(&self, cx: &App) -> SavedContext {
|
||||
let buffer = self.buffer.read(cx);
|
||||
SavedTextThread {
|
||||
SavedContext {
|
||||
id: Some(self.id.clone()),
|
||||
zed: "context".into(),
|
||||
version: SavedTextThread::VERSION.into(),
|
||||
version: SavedContext::VERSION.into(),
|
||||
text: buffer.text(),
|
||||
messages: self
|
||||
.messages(cx)
|
||||
@@ -866,7 +872,7 @@ impl TextThread {
|
||||
}
|
||||
|
||||
pub fn deserialize(
|
||||
saved_context: SavedTextThread,
|
||||
saved_context: SavedContext,
|
||||
path: Arc<Path>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
@@ -875,7 +881,7 @@ impl TextThread {
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let id = saved_context.id.clone().unwrap_or_else(TextThreadId::new);
|
||||
let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
|
||||
let mut this = Self::new(
|
||||
id,
|
||||
ReplicaId::default(),
|
||||
@@ -896,7 +902,7 @@ impl TextThread {
|
||||
this
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &TextThreadId {
|
||||
pub fn id(&self) -> &ContextId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
@@ -904,9 +910,9 @@ impl TextThread {
|
||||
self.timestamp.replica_id
|
||||
}
|
||||
|
||||
pub fn version(&self, cx: &App) -> TextThreadVersion {
|
||||
TextThreadVersion {
|
||||
text_thread: self.version.clone(),
|
||||
pub fn version(&self, cx: &App) -> ContextVersion {
|
||||
ContextVersion {
|
||||
context: self.version.clone(),
|
||||
buffer: self.buffer.read(cx).version(),
|
||||
}
|
||||
}
|
||||
@@ -928,7 +934,7 @@ impl TextThread {
|
||||
|
||||
pub fn serialize_ops(
|
||||
&self,
|
||||
since: &TextThreadVersion,
|
||||
since: &ContextVersion,
|
||||
cx: &App,
|
||||
) -> Task<Vec<proto::ContextOperation>> {
|
||||
let buffer_ops = self
|
||||
@@ -939,7 +945,7 @@ impl TextThread {
|
||||
let mut context_ops = self
|
||||
.operations
|
||||
.iter()
|
||||
.filter(|op| !since.text_thread.observed(op.timestamp()))
|
||||
.filter(|op| !since.context.observed(op.timestamp()))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
context_ops.extend(self.pending_ops.iter().cloned());
|
||||
@@ -963,13 +969,13 @@ impl TextThread {
|
||||
|
||||
pub fn apply_ops(
|
||||
&mut self,
|
||||
ops: impl IntoIterator<Item = TextThreadOperation>,
|
||||
ops: impl IntoIterator<Item = ContextOperation>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mut buffer_ops = Vec::new();
|
||||
for op in ops {
|
||||
match op {
|
||||
TextThreadOperation::BufferOperation(buffer_op) => buffer_ops.push(buffer_op),
|
||||
ContextOperation::BufferOperation(buffer_op) => buffer_ops.push(buffer_op),
|
||||
op @ _ => self.pending_ops.push(op),
|
||||
}
|
||||
}
|
||||
@@ -978,7 +984,7 @@ impl TextThread {
|
||||
self.flush_ops(cx);
|
||||
}
|
||||
|
||||
fn flush_ops(&mut self, cx: &mut Context<TextThread>) {
|
||||
fn flush_ops(&mut self, cx: &mut Context<AssistantContext>) {
|
||||
let mut changed_messages = HashSet::default();
|
||||
let mut summary_generated = false;
|
||||
|
||||
@@ -991,7 +997,7 @@ impl TextThread {
|
||||
|
||||
let timestamp = op.timestamp();
|
||||
match op.clone() {
|
||||
TextThreadOperation::InsertMessage {
|
||||
ContextOperation::InsertMessage {
|
||||
anchor, metadata, ..
|
||||
} => {
|
||||
if self.messages_metadata.contains_key(&anchor.id) {
|
||||
@@ -1001,7 +1007,7 @@ impl TextThread {
|
||||
self.insert_message(anchor, metadata, cx);
|
||||
}
|
||||
}
|
||||
TextThreadOperation::UpdateMessage {
|
||||
ContextOperation::UpdateMessage {
|
||||
message_id,
|
||||
metadata: new_metadata,
|
||||
..
|
||||
@@ -1012,7 +1018,7 @@ impl TextThread {
|
||||
changed_messages.insert(message_id);
|
||||
}
|
||||
}
|
||||
TextThreadOperation::UpdateSummary {
|
||||
ContextOperation::UpdateSummary {
|
||||
summary: new_summary,
|
||||
..
|
||||
} => {
|
||||
@@ -1021,11 +1027,11 @@ impl TextThread {
|
||||
.timestamp()
|
||||
.is_none_or(|current_timestamp| new_summary.timestamp > current_timestamp)
|
||||
{
|
||||
self.summary = TextThreadSummary::Content(new_summary);
|
||||
self.summary = ContextSummary::Content(new_summary);
|
||||
summary_generated = true;
|
||||
}
|
||||
}
|
||||
TextThreadOperation::SlashCommandStarted {
|
||||
ContextOperation::SlashCommandStarted {
|
||||
id,
|
||||
output_range,
|
||||
name,
|
||||
@@ -1042,9 +1048,9 @@ impl TextThread {
|
||||
timestamp: id.0,
|
||||
},
|
||||
);
|
||||
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id: id });
|
||||
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id: id });
|
||||
}
|
||||
TextThreadOperation::SlashCommandOutputSectionAdded { section, .. } => {
|
||||
ContextOperation::SlashCommandOutputSectionAdded { section, .. } => {
|
||||
let buffer = self.buffer.read(cx);
|
||||
if let Err(ix) = self
|
||||
.slash_command_output_sections
|
||||
@@ -1052,10 +1058,10 @@ impl TextThread {
|
||||
{
|
||||
self.slash_command_output_sections
|
||||
.insert(ix, section.clone());
|
||||
cx.emit(TextThreadEvent::SlashCommandOutputSectionAdded { section });
|
||||
cx.emit(ContextEvent::SlashCommandOutputSectionAdded { section });
|
||||
}
|
||||
}
|
||||
TextThreadOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
|
||||
ContextOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
|
||||
let buffer = self.buffer.read(cx);
|
||||
if let Err(ix) = self
|
||||
.thought_process_output_sections
|
||||
@@ -1065,7 +1071,7 @@ impl TextThread {
|
||||
.insert(ix, section.clone());
|
||||
}
|
||||
}
|
||||
TextThreadOperation::SlashCommandFinished {
|
||||
ContextOperation::SlashCommandFinished {
|
||||
id,
|
||||
error_message,
|
||||
timestamp,
|
||||
@@ -1084,10 +1090,10 @@ impl TextThread {
|
||||
slash_command.status = InvokedSlashCommandStatus::Finished;
|
||||
}
|
||||
}
|
||||
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id: id });
|
||||
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id: id });
|
||||
}
|
||||
}
|
||||
TextThreadOperation::BufferOperation(_) => unreachable!(),
|
||||
ContextOperation::BufferOperation(_) => unreachable!(),
|
||||
}
|
||||
|
||||
self.version.observe(timestamp);
|
||||
@@ -1097,43 +1103,43 @@ impl TextThread {
|
||||
|
||||
if !changed_messages.is_empty() {
|
||||
self.message_roles_updated(changed_messages, cx);
|
||||
cx.emit(TextThreadEvent::MessagesEdited);
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
if summary_generated {
|
||||
cx.emit(TextThreadEvent::SummaryChanged);
|
||||
cx.emit(TextThreadEvent::SummaryGenerated);
|
||||
cx.emit(ContextEvent::SummaryChanged);
|
||||
cx.emit(ContextEvent::SummaryGenerated);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn can_apply_op(&self, op: &TextThreadOperation, cx: &App) -> bool {
|
||||
fn can_apply_op(&self, op: &ContextOperation, cx: &App) -> bool {
|
||||
if !self.version.observed_all(op.version()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match op {
|
||||
TextThreadOperation::InsertMessage { anchor, .. } => self
|
||||
ContextOperation::InsertMessage { anchor, .. } => self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.version
|
||||
.observed(anchor.start.timestamp),
|
||||
TextThreadOperation::UpdateMessage { message_id, .. } => {
|
||||
ContextOperation::UpdateMessage { message_id, .. } => {
|
||||
self.messages_metadata.contains_key(message_id)
|
||||
}
|
||||
TextThreadOperation::UpdateSummary { .. } => true,
|
||||
TextThreadOperation::SlashCommandStarted { output_range, .. } => {
|
||||
ContextOperation::UpdateSummary { .. } => true,
|
||||
ContextOperation::SlashCommandStarted { output_range, .. } => {
|
||||
self.has_received_operations_for_anchor_range(output_range.clone(), cx)
|
||||
}
|
||||
TextThreadOperation::SlashCommandOutputSectionAdded { section, .. } => {
|
||||
ContextOperation::SlashCommandOutputSectionAdded { section, .. } => {
|
||||
self.has_received_operations_for_anchor_range(section.range.clone(), cx)
|
||||
}
|
||||
TextThreadOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
|
||||
ContextOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
|
||||
self.has_received_operations_for_anchor_range(section.range.clone(), cx)
|
||||
}
|
||||
TextThreadOperation::SlashCommandFinished { .. } => true,
|
||||
TextThreadOperation::BufferOperation(_) => {
|
||||
ContextOperation::SlashCommandFinished { .. } => true,
|
||||
ContextOperation::BufferOperation(_) => {
|
||||
panic!("buffer operations should always be applied")
|
||||
}
|
||||
}
|
||||
@@ -1154,9 +1160,9 @@ impl TextThread {
|
||||
observed_start && observed_end
|
||||
}
|
||||
|
||||
fn push_op(&mut self, op: TextThreadOperation, cx: &mut Context<Self>) {
|
||||
fn push_op(&mut self, op: ContextOperation, cx: &mut Context<Self>) {
|
||||
self.operations.push(op.clone());
|
||||
cx.emit(TextThreadEvent::Operation(op));
|
||||
cx.emit(ContextEvent::Operation(op));
|
||||
}
|
||||
|
||||
pub fn buffer(&self) -> &Entity<Buffer> {
|
||||
@@ -1179,7 +1185,7 @@ impl TextThread {
|
||||
self.path.as_ref()
|
||||
}
|
||||
|
||||
pub fn summary(&self) -> &TextThreadSummary {
|
||||
pub fn summary(&self) -> &ContextSummary {
|
||||
&self.summary
|
||||
}
|
||||
|
||||
@@ -1240,13 +1246,13 @@ impl TextThread {
|
||||
language::BufferEvent::Operation {
|
||||
operation,
|
||||
is_local: true,
|
||||
} => cx.emit(TextThreadEvent::Operation(
|
||||
TextThreadOperation::BufferOperation(operation.clone()),
|
||||
)),
|
||||
} => cx.emit(ContextEvent::Operation(ContextOperation::BufferOperation(
|
||||
operation.clone(),
|
||||
))),
|
||||
language::BufferEvent::Edited => {
|
||||
self.count_remaining_tokens(cx);
|
||||
self.reparse(cx);
|
||||
cx.emit(TextThreadEvent::MessagesEdited);
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -1512,7 +1518,7 @@ impl TextThread {
|
||||
if !updated_parsed_slash_commands.is_empty()
|
||||
|| !removed_parsed_slash_command_ranges.is_empty()
|
||||
{
|
||||
cx.emit(TextThreadEvent::ParsedSlashCommandsUpdated {
|
||||
cx.emit(ContextEvent::ParsedSlashCommandsUpdated {
|
||||
removed: removed_parsed_slash_command_ranges,
|
||||
updated: updated_parsed_slash_commands,
|
||||
});
|
||||
@@ -1586,7 +1592,7 @@ impl TextThread {
|
||||
&& (!command.range.start.is_valid(buffer) || !command.range.end.is_valid(buffer))
|
||||
{
|
||||
command.status = InvokedSlashCommandStatus::Finished;
|
||||
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
|
||||
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
|
||||
invalidated_command_ids.push(command_id);
|
||||
}
|
||||
}
|
||||
@@ -1595,7 +1601,7 @@ impl TextThread {
|
||||
let version = self.version.clone();
|
||||
let timestamp = self.next_timestamp();
|
||||
self.push_op(
|
||||
TextThreadOperation::SlashCommandFinished {
|
||||
ContextOperation::SlashCommandFinished {
|
||||
id: command_id,
|
||||
timestamp,
|
||||
error_message: None,
|
||||
@@ -1900,9 +1906,9 @@ impl TextThread {
|
||||
}
|
||||
}
|
||||
|
||||
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
|
||||
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
|
||||
this.push_op(
|
||||
TextThreadOperation::SlashCommandFinished {
|
||||
ContextOperation::SlashCommandFinished {
|
||||
id: command_id,
|
||||
timestamp,
|
||||
error_message,
|
||||
@@ -1925,9 +1931,9 @@ impl TextThread {
|
||||
timestamp: command_id.0,
|
||||
},
|
||||
);
|
||||
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
|
||||
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
|
||||
self.push_op(
|
||||
TextThreadOperation::SlashCommandStarted {
|
||||
ContextOperation::SlashCommandStarted {
|
||||
id: command_id,
|
||||
output_range: command_range,
|
||||
name: name.to_string(),
|
||||
@@ -1951,13 +1957,13 @@ impl TextThread {
|
||||
};
|
||||
self.slash_command_output_sections
|
||||
.insert(insertion_ix, section.clone());
|
||||
cx.emit(TextThreadEvent::SlashCommandOutputSectionAdded {
|
||||
cx.emit(ContextEvent::SlashCommandOutputSectionAdded {
|
||||
section: section.clone(),
|
||||
});
|
||||
let version = self.version.clone();
|
||||
let timestamp = self.next_timestamp();
|
||||
self.push_op(
|
||||
TextThreadOperation::SlashCommandOutputSectionAdded {
|
||||
ContextOperation::SlashCommandOutputSectionAdded {
|
||||
timestamp,
|
||||
section,
|
||||
version,
|
||||
@@ -1986,7 +1992,7 @@ impl TextThread {
|
||||
let version = self.version.clone();
|
||||
let timestamp = self.next_timestamp();
|
||||
self.push_op(
|
||||
TextThreadOperation::ThoughtProcessOutputSectionAdded {
|
||||
ContextOperation::ThoughtProcessOutputSectionAdded {
|
||||
timestamp,
|
||||
section,
|
||||
version,
|
||||
@@ -2105,7 +2111,7 @@ impl TextThread {
|
||||
let end = buffer
|
||||
.anchor_before(message_old_end_offset + chunk_len);
|
||||
context_event = Some(
|
||||
TextThreadEvent::StartedThoughtProcess(start..end),
|
||||
ContextEvent::StartedThoughtProcess(start..end),
|
||||
);
|
||||
} else {
|
||||
// This ensures that all the thinking chunks are inserted inside the thinking tag
|
||||
@@ -2123,7 +2129,7 @@ impl TextThread {
|
||||
if let Some(start) = thought_process_stack.pop() {
|
||||
let end = buffer.anchor_before(message_old_end_offset);
|
||||
context_event =
|
||||
Some(TextThreadEvent::EndedThoughtProcess(end));
|
||||
Some(ContextEvent::EndedThoughtProcess(end));
|
||||
thought_process_output_section =
|
||||
Some(ThoughtProcessOutputSection {
|
||||
range: start..end,
|
||||
@@ -2153,7 +2159,7 @@ impl TextThread {
|
||||
cx.emit(context_event);
|
||||
}
|
||||
|
||||
cx.emit(TextThreadEvent::StreamedCompletion);
|
||||
cx.emit(ContextEvent::StreamedCompletion);
|
||||
|
||||
Some(())
|
||||
})?;
|
||||
@@ -2174,7 +2180,7 @@ impl TextThread {
|
||||
this.update(cx, |this, cx| {
|
||||
let error_message = if let Some(error) = result.as_ref().err() {
|
||||
if error.is::<PaymentRequiredError>() {
|
||||
cx.emit(TextThreadEvent::ShowPaymentRequiredError);
|
||||
cx.emit(ContextEvent::ShowPaymentRequiredError);
|
||||
this.update_metadata(assistant_message_id, cx, |metadata| {
|
||||
metadata.status = MessageStatus::Canceled;
|
||||
});
|
||||
@@ -2185,7 +2191,7 @@ impl TextThread {
|
||||
.map(|err| err.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
cx.emit(TextThreadEvent::ShowAssistError(SharedString::from(
|
||||
cx.emit(ContextEvent::ShowAssistError(SharedString::from(
|
||||
error_message.clone(),
|
||||
)));
|
||||
this.update_metadata(assistant_message_id, cx, |metadata| {
|
||||
@@ -2402,13 +2408,13 @@ impl TextThread {
|
||||
if let Some(metadata) = self.messages_metadata.get_mut(&id) {
|
||||
f(metadata);
|
||||
metadata.timestamp = timestamp;
|
||||
let operation = TextThreadOperation::UpdateMessage {
|
||||
let operation = ContextOperation::UpdateMessage {
|
||||
message_id: id,
|
||||
metadata: metadata.clone(),
|
||||
version,
|
||||
};
|
||||
self.push_op(operation, cx);
|
||||
cx.emit(TextThreadEvent::MessagesEdited);
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -2472,7 +2478,7 @@ impl TextThread {
|
||||
};
|
||||
self.insert_message(anchor.clone(), metadata.clone(), cx);
|
||||
self.push_op(
|
||||
TextThreadOperation::InsertMessage {
|
||||
ContextOperation::InsertMessage {
|
||||
anchor: anchor.clone(),
|
||||
metadata,
|
||||
version,
|
||||
@@ -2495,7 +2501,7 @@ impl TextThread {
|
||||
Err(ix) => ix,
|
||||
};
|
||||
self.contents.insert(insertion_ix, content);
|
||||
cx.emit(TextThreadEvent::MessagesEdited);
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
}
|
||||
|
||||
pub fn contents<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = Content> {
|
||||
@@ -2570,7 +2576,7 @@ impl TextThread {
|
||||
};
|
||||
self.insert_message(suffix.clone(), suffix_metadata.clone(), cx);
|
||||
self.push_op(
|
||||
TextThreadOperation::InsertMessage {
|
||||
ContextOperation::InsertMessage {
|
||||
anchor: suffix.clone(),
|
||||
metadata: suffix_metadata,
|
||||
version,
|
||||
@@ -2620,7 +2626,7 @@ impl TextThread {
|
||||
};
|
||||
self.insert_message(selection.clone(), selection_metadata.clone(), cx);
|
||||
self.push_op(
|
||||
TextThreadOperation::InsertMessage {
|
||||
ContextOperation::InsertMessage {
|
||||
anchor: selection.clone(),
|
||||
metadata: selection_metadata,
|
||||
version,
|
||||
@@ -2632,7 +2638,7 @@ impl TextThread {
|
||||
};
|
||||
|
||||
if !edited_buffer {
|
||||
cx.emit(TextThreadEvent::MessagesEdited);
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
}
|
||||
new_messages
|
||||
} else {
|
||||
@@ -2646,7 +2652,7 @@ impl TextThread {
|
||||
new_metadata: MessageMetadata,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
cx.emit(TextThreadEvent::MessagesEdited);
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
|
||||
self.messages_metadata.insert(new_anchor.id, new_metadata);
|
||||
|
||||
@@ -2682,15 +2688,15 @@ impl TextThread {
|
||||
// If there is no summary, it is set with `done: false` so that "Loading Summary…" can
|
||||
// be displayed.
|
||||
match self.summary {
|
||||
TextThreadSummary::Pending | TextThreadSummary::Error => {
|
||||
self.summary = TextThreadSummary::Content(TextThreadSummaryContent {
|
||||
ContextSummary::Pending | ContextSummary::Error => {
|
||||
self.summary = ContextSummary::Content(ContextSummaryContent {
|
||||
text: "".to_string(),
|
||||
done: false,
|
||||
timestamp: clock::Lamport::MIN,
|
||||
timestamp: clock::Lamport::default(),
|
||||
});
|
||||
replace_old = true;
|
||||
}
|
||||
TextThreadSummary::Content(_) => {}
|
||||
ContextSummary::Content(_) => {}
|
||||
}
|
||||
|
||||
self.summary_task = cx.spawn(async move |this, cx| {
|
||||
@@ -2712,13 +2718,13 @@ impl TextThread {
|
||||
}
|
||||
summary.text.extend(lines.next());
|
||||
summary.timestamp = timestamp;
|
||||
let operation = TextThreadOperation::UpdateSummary {
|
||||
let operation = ContextOperation::UpdateSummary {
|
||||
summary: summary.clone(),
|
||||
version,
|
||||
};
|
||||
this.push_op(operation, cx);
|
||||
cx.emit(TextThreadEvent::SummaryChanged);
|
||||
cx.emit(TextThreadEvent::SummaryGenerated);
|
||||
cx.emit(ContextEvent::SummaryChanged);
|
||||
cx.emit(ContextEvent::SummaryGenerated);
|
||||
})?;
|
||||
|
||||
// Stop if the LLM generated multiple lines.
|
||||
@@ -2742,13 +2748,13 @@ impl TextThread {
|
||||
if let Some(summary) = this.summary.content_as_mut() {
|
||||
summary.done = true;
|
||||
summary.timestamp = timestamp;
|
||||
let operation = TextThreadOperation::UpdateSummary {
|
||||
let operation = ContextOperation::UpdateSummary {
|
||||
summary: summary.clone(),
|
||||
version,
|
||||
};
|
||||
this.push_op(operation, cx);
|
||||
cx.emit(TextThreadEvent::SummaryChanged);
|
||||
cx.emit(TextThreadEvent::SummaryGenerated);
|
||||
cx.emit(ContextEvent::SummaryChanged);
|
||||
cx.emit(ContextEvent::SummaryGenerated);
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -2758,8 +2764,8 @@ impl TextThread {
|
||||
|
||||
if let Err(err) = result {
|
||||
this.update(cx, |this, cx| {
|
||||
this.summary = TextThreadSummary::Error;
|
||||
cx.emit(TextThreadEvent::SummaryChanged);
|
||||
this.summary = ContextSummary::Error;
|
||||
cx.emit(ContextEvent::SummaryChanged);
|
||||
})
|
||||
.log_err();
|
||||
log::error!("Error generating context summary: {}", err);
|
||||
@@ -2865,7 +2871,7 @@ impl TextThread {
|
||||
&mut self,
|
||||
debounce: Option<Duration>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut Context<TextThread>,
|
||||
cx: &mut Context<AssistantContext>,
|
||||
) {
|
||||
if self.replica_id() != ReplicaId::default() {
|
||||
// Prevent saving a remote context for now.
|
||||
@@ -2896,7 +2902,7 @@ impl TextThread {
|
||||
let mut discriminant = 1;
|
||||
let mut new_path;
|
||||
loop {
|
||||
new_path = text_threads_dir().join(&format!(
|
||||
new_path = contexts_dir().join(&format!(
|
||||
"{} - {}.zed.json",
|
||||
summary.trim(),
|
||||
discriminant
|
||||
@@ -2908,7 +2914,7 @@ impl TextThread {
|
||||
}
|
||||
}
|
||||
|
||||
fs.create_dir(text_threads_dir().as_ref()).await?;
|
||||
fs.create_dir(contexts_dir().as_ref()).await?;
|
||||
|
||||
// rename before write ensures that only one file exists
|
||||
if let Some(old_path) = old_path.as_ref()
|
||||
@@ -2930,7 +2936,7 @@ impl TextThread {
|
||||
let new_path: Arc<Path> = new_path.clone().into();
|
||||
move |this, cx| {
|
||||
this.path = Some(new_path.clone());
|
||||
cx.emit(TextThreadEvent::PathChanged { old_path, new_path });
|
||||
cx.emit(ContextEvent::PathChanged { old_path, new_path });
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
@@ -2949,7 +2955,7 @@ impl TextThread {
|
||||
summary.timestamp = timestamp;
|
||||
summary.done = true;
|
||||
summary.text = custom_summary;
|
||||
cx.emit(TextThreadEvent::SummaryChanged);
|
||||
cx.emit(ContextEvent::SummaryChanged);
|
||||
}
|
||||
|
||||
fn update_model_request_usage(&self, amount: u32, limit: UsageLimit, cx: &mut App) {
|
||||
@@ -2969,23 +2975,23 @@ impl TextThread {
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TextThreadVersion {
|
||||
text_thread: clock::Global,
|
||||
pub struct ContextVersion {
|
||||
context: clock::Global,
|
||||
buffer: clock::Global,
|
||||
}
|
||||
|
||||
impl TextThreadVersion {
|
||||
impl ContextVersion {
|
||||
pub fn from_proto(proto: &proto::ContextVersion) -> Self {
|
||||
Self {
|
||||
text_thread: language::proto::deserialize_version(&proto.context_version),
|
||||
context: language::proto::deserialize_version(&proto.context_version),
|
||||
buffer: language::proto::deserialize_version(&proto.buffer_version),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_proto(&self, context_id: TextThreadId) -> proto::ContextVersion {
|
||||
pub fn to_proto(&self, context_id: ContextId) -> proto::ContextVersion {
|
||||
proto::ContextVersion {
|
||||
context_id: context_id.to_proto(),
|
||||
context_version: language::proto::serialize_version(&self.text_thread),
|
||||
context_version: language::proto::serialize_version(&self.context),
|
||||
buffer_version: language::proto::serialize_version(&self.buffer),
|
||||
}
|
||||
}
|
||||
@@ -3053,8 +3059,8 @@ pub struct SavedMessage {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SavedTextThread {
|
||||
pub id: Option<TextThreadId>,
|
||||
pub struct SavedContext {
|
||||
pub id: Option<ContextId>,
|
||||
pub zed: String,
|
||||
pub version: String,
|
||||
pub text: String,
|
||||
@@ -3066,7 +3072,7 @@ pub struct SavedTextThread {
|
||||
pub thought_process_output_sections: Vec<ThoughtProcessOutputSection<usize>>,
|
||||
}
|
||||
|
||||
impl SavedTextThread {
|
||||
impl SavedContext {
|
||||
pub const VERSION: &'static str = "0.4.0";
|
||||
|
||||
pub fn from_json(json: &str) -> Result<Self> {
|
||||
@@ -3076,9 +3082,9 @@ impl SavedTextThread {
|
||||
.context("version not found")?
|
||||
{
|
||||
serde_json::Value::String(version) => match version.as_str() {
|
||||
SavedTextThread::VERSION => Ok(serde_json::from_value::<SavedTextThread>(
|
||||
saved_context_json,
|
||||
)?),
|
||||
SavedContext::VERSION => {
|
||||
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
|
||||
}
|
||||
SavedContextV0_3_0::VERSION => {
|
||||
let saved_context =
|
||||
serde_json::from_value::<SavedContextV0_3_0>(saved_context_json)?;
|
||||
@@ -3103,18 +3109,18 @@ impl SavedTextThread {
|
||||
fn into_ops(
|
||||
self,
|
||||
buffer: &Entity<Buffer>,
|
||||
cx: &mut Context<TextThread>,
|
||||
) -> Vec<TextThreadOperation> {
|
||||
cx: &mut Context<AssistantContext>,
|
||||
) -> Vec<ContextOperation> {
|
||||
let mut operations = Vec::new();
|
||||
let mut version = clock::Global::new();
|
||||
let mut next_timestamp = clock::Lamport::new(ReplicaId::default());
|
||||
|
||||
let mut first_message_metadata = None;
|
||||
for message in self.messages {
|
||||
if message.id == MessageId(clock::Lamport::MIN) {
|
||||
if message.id == MessageId(clock::Lamport::default()) {
|
||||
first_message_metadata = Some(message.metadata);
|
||||
} else {
|
||||
operations.push(TextThreadOperation::InsertMessage {
|
||||
operations.push(ContextOperation::InsertMessage {
|
||||
anchor: MessageAnchor {
|
||||
id: message.id,
|
||||
start: buffer.read(cx).anchor_before(message.start),
|
||||
@@ -3134,8 +3140,8 @@ impl SavedTextThread {
|
||||
|
||||
if let Some(metadata) = first_message_metadata {
|
||||
let timestamp = next_timestamp.tick();
|
||||
operations.push(TextThreadOperation::UpdateMessage {
|
||||
message_id: MessageId(clock::Lamport::MIN),
|
||||
operations.push(ContextOperation::UpdateMessage {
|
||||
message_id: MessageId(clock::Lamport::default()),
|
||||
metadata: MessageMetadata {
|
||||
role: metadata.role,
|
||||
status: metadata.status,
|
||||
@@ -3150,7 +3156,7 @@ impl SavedTextThread {
|
||||
let buffer = buffer.read(cx);
|
||||
for section in self.slash_command_output_sections {
|
||||
let timestamp = next_timestamp.tick();
|
||||
operations.push(TextThreadOperation::SlashCommandOutputSectionAdded {
|
||||
operations.push(ContextOperation::SlashCommandOutputSectionAdded {
|
||||
timestamp,
|
||||
section: SlashCommandOutputSection {
|
||||
range: buffer.anchor_after(section.range.start)
|
||||
@@ -3167,7 +3173,7 @@ impl SavedTextThread {
|
||||
|
||||
for section in self.thought_process_output_sections {
|
||||
let timestamp = next_timestamp.tick();
|
||||
operations.push(TextThreadOperation::ThoughtProcessOutputSectionAdded {
|
||||
operations.push(ContextOperation::ThoughtProcessOutputSectionAdded {
|
||||
timestamp,
|
||||
section: ThoughtProcessOutputSection {
|
||||
range: buffer.anchor_after(section.range.start)
|
||||
@@ -3180,8 +3186,8 @@ impl SavedTextThread {
|
||||
}
|
||||
|
||||
let timestamp = next_timestamp.tick();
|
||||
operations.push(TextThreadOperation::UpdateSummary {
|
||||
summary: TextThreadSummaryContent {
|
||||
operations.push(ContextOperation::UpdateSummary {
|
||||
summary: ContextSummaryContent {
|
||||
text: self.summary,
|
||||
done: true,
|
||||
timestamp,
|
||||
@@ -3211,7 +3217,7 @@ struct SavedMessageMetadataPreV0_4_0 {
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SavedContextV0_3_0 {
|
||||
id: Option<TextThreadId>,
|
||||
id: Option<ContextId>,
|
||||
zed: String,
|
||||
version: String,
|
||||
text: String,
|
||||
@@ -3224,11 +3230,11 @@ struct SavedContextV0_3_0 {
|
||||
impl SavedContextV0_3_0 {
|
||||
const VERSION: &'static str = "0.3.0";
|
||||
|
||||
fn upgrade(self) -> SavedTextThread {
|
||||
SavedTextThread {
|
||||
fn upgrade(self) -> SavedContext {
|
||||
SavedContext {
|
||||
id: self.id,
|
||||
zed: self.zed,
|
||||
version: SavedTextThread::VERSION.into(),
|
||||
version: SavedContext::VERSION.into(),
|
||||
text: self.text,
|
||||
messages: self
|
||||
.messages
|
||||
@@ -3260,7 +3266,7 @@ impl SavedContextV0_3_0 {
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SavedContextV0_2_0 {
|
||||
id: Option<TextThreadId>,
|
||||
id: Option<ContextId>,
|
||||
zed: String,
|
||||
version: String,
|
||||
text: String,
|
||||
@@ -3272,7 +3278,7 @@ struct SavedContextV0_2_0 {
|
||||
impl SavedContextV0_2_0 {
|
||||
const VERSION: &'static str = "0.2.0";
|
||||
|
||||
fn upgrade(self) -> SavedTextThread {
|
||||
fn upgrade(self) -> SavedContext {
|
||||
SavedContextV0_3_0 {
|
||||
id: self.id,
|
||||
zed: self.zed,
|
||||
@@ -3289,7 +3295,7 @@ impl SavedContextV0_2_0 {
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SavedContextV0_1_0 {
|
||||
id: Option<TextThreadId>,
|
||||
id: Option<ContextId>,
|
||||
zed: String,
|
||||
version: String,
|
||||
text: String,
|
||||
@@ -3303,7 +3309,7 @@ struct SavedContextV0_1_0 {
|
||||
impl SavedContextV0_1_0 {
|
||||
const VERSION: &'static str = "0.1.0";
|
||||
|
||||
fn upgrade(self) -> SavedTextThread {
|
||||
fn upgrade(self) -> SavedContext {
|
||||
SavedContextV0_2_0 {
|
||||
id: self.id,
|
||||
zed: self.zed,
|
||||
@@ -3318,7 +3324,7 @@ impl SavedContextV0_1_0 {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SavedTextThreadMetadata {
|
||||
pub struct SavedContextMetadata {
|
||||
pub title: SharedString,
|
||||
pub path: Arc<Path>,
|
||||
pub mtime: chrono::DateTime<chrono::Local>,
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
SavedTextThread, SavedTextThreadMetadata, TextThread, TextThreadEvent, TextThreadId,
|
||||
TextThreadOperation, TextThreadVersion,
|
||||
AssistantContext, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
|
||||
SavedContextMetadata,
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
|
||||
@@ -11,9 +11,9 @@ use context_server::ContextServerId;
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use futures::StreamExt;
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, Task, WeakEntity};
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
|
||||
use language::LanguageRegistry;
|
||||
use paths::text_threads_dir;
|
||||
use paths::contexts_dir;
|
||||
use project::{
|
||||
Project,
|
||||
context_server_store::{ContextServerStatus, ContextServerStore},
|
||||
@@ -27,24 +27,24 @@ use util::{ResultExt, TryFutureExt};
|
||||
use zed_env_vars::ZED_STATELESS;
|
||||
|
||||
pub(crate) fn init(client: &AnyProtoClient) {
|
||||
client.add_entity_message_handler(TextThreadStore::handle_advertise_contexts);
|
||||
client.add_entity_request_handler(TextThreadStore::handle_open_context);
|
||||
client.add_entity_request_handler(TextThreadStore::handle_create_context);
|
||||
client.add_entity_message_handler(TextThreadStore::handle_update_context);
|
||||
client.add_entity_request_handler(TextThreadStore::handle_synchronize_contexts);
|
||||
client.add_entity_message_handler(ContextStore::handle_advertise_contexts);
|
||||
client.add_entity_request_handler(ContextStore::handle_open_context);
|
||||
client.add_entity_request_handler(ContextStore::handle_create_context);
|
||||
client.add_entity_message_handler(ContextStore::handle_update_context);
|
||||
client.add_entity_request_handler(ContextStore::handle_synchronize_contexts);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RemoteTextThreadMetadata {
|
||||
pub id: TextThreadId,
|
||||
pub struct RemoteContextMetadata {
|
||||
pub id: ContextId,
|
||||
pub summary: Option<String>,
|
||||
}
|
||||
|
||||
pub struct TextThreadStore {
|
||||
text_threads: Vec<TextThreadHandle>,
|
||||
text_threads_metadata: Vec<SavedTextThreadMetadata>,
|
||||
pub struct ContextStore {
|
||||
contexts: Vec<ContextHandle>,
|
||||
contexts_metadata: Vec<SavedContextMetadata>,
|
||||
context_server_slash_command_ids: HashMap<ContextServerId, Vec<SlashCommandId>>,
|
||||
host_text_threads: Vec<RemoteTextThreadMetadata>,
|
||||
host_contexts: Vec<RemoteContextMetadata>,
|
||||
fs: Arc<dyn Fs>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
@@ -58,28 +58,34 @@ pub struct TextThreadStore {
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
}
|
||||
|
||||
enum TextThreadHandle {
|
||||
Weak(WeakEntity<TextThread>),
|
||||
Strong(Entity<TextThread>),
|
||||
pub enum ContextStoreEvent {
|
||||
ContextCreated(ContextId),
|
||||
}
|
||||
|
||||
impl TextThreadHandle {
|
||||
fn upgrade(&self) -> Option<Entity<TextThread>> {
|
||||
impl EventEmitter<ContextStoreEvent> for ContextStore {}
|
||||
|
||||
enum ContextHandle {
|
||||
Weak(WeakEntity<AssistantContext>),
|
||||
Strong(Entity<AssistantContext>),
|
||||
}
|
||||
|
||||
impl ContextHandle {
|
||||
fn upgrade(&self) -> Option<Entity<AssistantContext>> {
|
||||
match self {
|
||||
TextThreadHandle::Weak(weak) => weak.upgrade(),
|
||||
TextThreadHandle::Strong(strong) => Some(strong.clone()),
|
||||
ContextHandle::Weak(weak) => weak.upgrade(),
|
||||
ContextHandle::Strong(strong) => Some(strong.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn downgrade(&self) -> WeakEntity<TextThread> {
|
||||
fn downgrade(&self) -> WeakEntity<AssistantContext> {
|
||||
match self {
|
||||
TextThreadHandle::Weak(weak) => weak.clone(),
|
||||
TextThreadHandle::Strong(strong) => strong.downgrade(),
|
||||
ContextHandle::Weak(weak) => weak.clone(),
|
||||
ContextHandle::Strong(strong) => strong.downgrade(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextThreadStore {
|
||||
impl ContextStore {
|
||||
pub fn new(
|
||||
project: Entity<Project>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
@@ -91,14 +97,14 @@ impl TextThreadStore {
|
||||
let telemetry = project.read(cx).client().telemetry().clone();
|
||||
cx.spawn(async move |cx| {
|
||||
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
|
||||
let (mut events, _) = fs.watch(text_threads_dir(), CONTEXT_WATCH_DURATION).await;
|
||||
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
|
||||
|
||||
let this = cx.new(|cx: &mut Context<Self>| {
|
||||
let mut this = Self {
|
||||
text_threads: Vec::new(),
|
||||
text_threads_metadata: Vec::new(),
|
||||
contexts: Vec::new(),
|
||||
contexts_metadata: Vec::new(),
|
||||
context_server_slash_command_ids: HashMap::default(),
|
||||
host_text_threads: Vec::new(),
|
||||
host_contexts: Vec::new(),
|
||||
fs,
|
||||
languages,
|
||||
slash_commands,
|
||||
@@ -136,10 +142,10 @@ impl TextThreadStore {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn fake(project: Entity<Project>, cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
text_threads: Default::default(),
|
||||
text_threads_metadata: Default::default(),
|
||||
contexts: Default::default(),
|
||||
contexts_metadata: Default::default(),
|
||||
context_server_slash_command_ids: Default::default(),
|
||||
host_text_threads: Default::default(),
|
||||
host_contexts: Default::default(),
|
||||
fs: project.read(cx).fs().clone(),
|
||||
languages: project.read(cx).languages().clone(),
|
||||
slash_commands: Arc::default(),
|
||||
@@ -160,13 +166,13 @@ impl TextThreadStore {
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.host_text_threads = envelope
|
||||
this.host_contexts = envelope
|
||||
.payload
|
||||
.contexts
|
||||
.into_iter()
|
||||
.map(|text_thread| RemoteTextThreadMetadata {
|
||||
id: TextThreadId::from_proto(text_thread.context_id),
|
||||
summary: text_thread.summary,
|
||||
.map(|context| RemoteContextMetadata {
|
||||
id: ContextId::from_proto(context.context_id),
|
||||
summary: context.summary,
|
||||
})
|
||||
.collect();
|
||||
cx.notify();
|
||||
@@ -178,25 +184,25 @@ impl TextThreadStore {
|
||||
envelope: TypedEnvelope<proto::OpenContext>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::OpenContextResponse> {
|
||||
let context_id = TextThreadId::from_proto(envelope.payload.context_id);
|
||||
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
||||
let operations = this.update(&mut cx, |this, cx| {
|
||||
anyhow::ensure!(
|
||||
!this.project.read(cx).is_via_collab(),
|
||||
"only the host contexts can be opened"
|
||||
);
|
||||
|
||||
let text_thread = this
|
||||
.loaded_text_thread_for_id(&context_id, cx)
|
||||
let context = this
|
||||
.loaded_context_for_id(&context_id, cx)
|
||||
.context("context not found")?;
|
||||
anyhow::ensure!(
|
||||
text_thread.read(cx).replica_id() == ReplicaId::default(),
|
||||
context.read(cx).replica_id() == ReplicaId::default(),
|
||||
"context must be opened via the host"
|
||||
);
|
||||
|
||||
anyhow::Ok(
|
||||
text_thread
|
||||
context
|
||||
.read(cx)
|
||||
.serialize_ops(&TextThreadVersion::default(), cx),
|
||||
.serialize_ops(&ContextVersion::default(), cx),
|
||||
)
|
||||
})??;
|
||||
let operations = operations.await;
|
||||
@@ -216,14 +222,15 @@ impl TextThreadStore {
|
||||
"can only create contexts as the host"
|
||||
);
|
||||
|
||||
let text_thread = this.create(cx);
|
||||
let context_id = text_thread.read(cx).id().clone();
|
||||
let context = this.create(cx);
|
||||
let context_id = context.read(cx).id().clone();
|
||||
cx.emit(ContextStoreEvent::ContextCreated(context_id.clone()));
|
||||
|
||||
anyhow::Ok((
|
||||
context_id,
|
||||
text_thread
|
||||
context
|
||||
.read(cx)
|
||||
.serialize_ops(&TextThreadVersion::default(), cx),
|
||||
.serialize_ops(&ContextVersion::default(), cx),
|
||||
))
|
||||
})??;
|
||||
let operations = operations.await;
|
||||
@@ -239,11 +246,11 @@ impl TextThreadStore {
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let context_id = TextThreadId::from_proto(envelope.payload.context_id);
|
||||
if let Some(text_thread) = this.loaded_text_thread_for_id(&context_id, cx) {
|
||||
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
||||
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
let operation_proto = envelope.payload.operation.context("invalid operation")?;
|
||||
let operation = TextThreadOperation::from_proto(operation_proto)?;
|
||||
text_thread.update(cx, |text_thread, cx| text_thread.apply_ops([operation], cx));
|
||||
let operation = ContextOperation::from_proto(operation_proto)?;
|
||||
context.update(cx, |context, cx| context.apply_ops([operation], cx));
|
||||
}
|
||||
Ok(())
|
||||
})?
|
||||
@@ -262,12 +269,12 @@ impl TextThreadStore {
|
||||
|
||||
let mut local_versions = Vec::new();
|
||||
for remote_version_proto in envelope.payload.contexts {
|
||||
let remote_version = TextThreadVersion::from_proto(&remote_version_proto);
|
||||
let context_id = TextThreadId::from_proto(remote_version_proto.context_id);
|
||||
if let Some(text_thread) = this.loaded_text_thread_for_id(&context_id, cx) {
|
||||
let text_thread = text_thread.read(cx);
|
||||
let operations = text_thread.serialize_ops(&remote_version, cx);
|
||||
local_versions.push(text_thread.version(cx).to_proto(context_id.clone()));
|
||||
let remote_version = ContextVersion::from_proto(&remote_version_proto);
|
||||
let context_id = ContextId::from_proto(remote_version_proto.context_id);
|
||||
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
let context = context.read(cx);
|
||||
let operations = context.serialize_ops(&remote_version, cx);
|
||||
local_versions.push(context.version(cx).to_proto(context_id.clone()));
|
||||
let client = this.client.clone();
|
||||
let project_id = envelope.payload.project_id;
|
||||
cx.background_spawn(async move {
|
||||
@@ -301,9 +308,9 @@ impl TextThreadStore {
|
||||
}
|
||||
|
||||
if is_shared {
|
||||
self.text_threads.retain_mut(|text_thread| {
|
||||
if let Some(strong_context) = text_thread.upgrade() {
|
||||
*text_thread = TextThreadHandle::Strong(strong_context);
|
||||
self.contexts.retain_mut(|context| {
|
||||
if let Some(strong_context) = context.upgrade() {
|
||||
*context = ContextHandle::Strong(strong_context);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -338,12 +345,12 @@ impl TextThreadStore {
|
||||
self.synchronize_contexts(cx);
|
||||
}
|
||||
project::Event::DisconnectedFromHost => {
|
||||
self.text_threads.retain_mut(|text_thread| {
|
||||
if let Some(strong_context) = text_thread.upgrade() {
|
||||
*text_thread = TextThreadHandle::Weak(text_thread.downgrade());
|
||||
strong_context.update(cx, |text_thread, cx| {
|
||||
if text_thread.replica_id() != ReplicaId::default() {
|
||||
text_thread.set_capability(language::Capability::ReadOnly, cx);
|
||||
self.contexts.retain_mut(|context| {
|
||||
if let Some(strong_context) = context.upgrade() {
|
||||
*context = ContextHandle::Weak(context.downgrade());
|
||||
strong_context.update(cx, |context, cx| {
|
||||
if context.replica_id() != ReplicaId::default() {
|
||||
context.set_capability(language::Capability::ReadOnly, cx);
|
||||
}
|
||||
});
|
||||
true
|
||||
@@ -351,24 +358,20 @@ impl TextThreadStore {
|
||||
false
|
||||
}
|
||||
});
|
||||
self.host_text_threads.clear();
|
||||
self.host_contexts.clear();
|
||||
cx.notify();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unordered_text_threads(&self) -> impl Iterator<Item = &SavedTextThreadMetadata> {
|
||||
self.text_threads_metadata.iter()
|
||||
pub fn unordered_contexts(&self) -> impl Iterator<Item = &SavedContextMetadata> {
|
||||
self.contexts_metadata.iter()
|
||||
}
|
||||
|
||||
pub fn host_text_threads(&self) -> impl Iterator<Item = &RemoteTextThreadMetadata> {
|
||||
self.host_text_threads.iter()
|
||||
}
|
||||
|
||||
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<TextThread> {
|
||||
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
|
||||
let context = cx.new(|cx| {
|
||||
TextThread::local(
|
||||
AssistantContext::local(
|
||||
self.languages.clone(),
|
||||
Some(self.project.clone()),
|
||||
Some(self.telemetry.clone()),
|
||||
@@ -377,11 +380,14 @@ impl TextThreadStore {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
self.register_text_thread(&context, cx);
|
||||
self.register_context(&context, cx);
|
||||
context
|
||||
}
|
||||
|
||||
pub fn create_remote(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<TextThread>>> {
|
||||
pub fn create_remote_context(
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<AssistantContext>>> {
|
||||
let project = self.project.read(cx);
|
||||
let Some(project_id) = project.remote_id() else {
|
||||
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
|
||||
@@ -397,10 +403,10 @@ impl TextThreadStore {
|
||||
let request = self.client.request(proto::CreateContext { project_id });
|
||||
cx.spawn(async move |this, cx| {
|
||||
let response = request.await?;
|
||||
let context_id = TextThreadId::from_proto(response.context_id);
|
||||
let context_id = ContextId::from_proto(response.context_id);
|
||||
let context_proto = response.context.context("invalid context")?;
|
||||
let text_thread = cx.new(|cx| {
|
||||
TextThread::new(
|
||||
let context = cx.new(|cx| {
|
||||
AssistantContext::new(
|
||||
context_id.clone(),
|
||||
replica_id,
|
||||
capability,
|
||||
@@ -417,29 +423,29 @@ impl TextThreadStore {
|
||||
context_proto
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(TextThreadOperation::from_proto)
|
||||
.map(ContextOperation::from_proto)
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})
|
||||
.await?;
|
||||
text_thread.update(cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||
context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||
this.update(cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_text_thread_for_id(&context_id, cx) {
|
||||
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
existing_context
|
||||
} else {
|
||||
this.register_text_thread(&text_thread, cx);
|
||||
this.register_context(&context, cx);
|
||||
this.synchronize_contexts(cx);
|
||||
text_thread
|
||||
context
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_local(
|
||||
pub fn open_local_context(
|
||||
&mut self,
|
||||
path: Arc<Path>,
|
||||
cx: &Context<Self>,
|
||||
) -> Task<Result<Entity<TextThread>>> {
|
||||
if let Some(existing_context) = self.loaded_text_thread_for_path(&path, cx) {
|
||||
) -> Task<Result<Entity<AssistantContext>>> {
|
||||
if let Some(existing_context) = self.loaded_context_for_path(&path, cx) {
|
||||
return Task::ready(Ok(existing_context));
|
||||
}
|
||||
|
||||
@@ -451,7 +457,7 @@ impl TextThreadStore {
|
||||
let path = path.clone();
|
||||
async move {
|
||||
let saved_context = fs.load(&path).await?;
|
||||
SavedTextThread::from_json(&saved_context)
|
||||
SavedContext::from_json(&saved_context)
|
||||
}
|
||||
});
|
||||
let prompt_builder = self.prompt_builder.clone();
|
||||
@@ -460,7 +466,7 @@ impl TextThreadStore {
|
||||
cx.spawn(async move |this, cx| {
|
||||
let saved_context = load.await?;
|
||||
let context = cx.new(|cx| {
|
||||
TextThread::deserialize(
|
||||
AssistantContext::deserialize(
|
||||
saved_context,
|
||||
path.clone(),
|
||||
languages,
|
||||
@@ -472,17 +478,21 @@ impl TextThreadStore {
|
||||
)
|
||||
})?;
|
||||
this.update(cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_text_thread_for_path(&path, cx) {
|
||||
if let Some(existing_context) = this.loaded_context_for_path(&path, cx) {
|
||||
existing_context
|
||||
} else {
|
||||
this.register_text_thread(&context, cx);
|
||||
this.register_context(&context, cx);
|
||||
context
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_local(&mut self, path: Arc<Path>, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
pub fn delete_local_context(
|
||||
&mut self,
|
||||
path: Arc<Path>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let fs = self.fs.clone();
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
@@ -496,57 +506,57 @@ impl TextThreadStore {
|
||||
.await?;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.text_threads.retain(|text_thread| {
|
||||
text_thread
|
||||
this.contexts.retain(|context| {
|
||||
context
|
||||
.upgrade()
|
||||
.and_then(|text_thread| text_thread.read(cx).path())
|
||||
.and_then(|context| context.read(cx).path())
|
||||
!= Some(&path)
|
||||
});
|
||||
this.text_threads_metadata
|
||||
.retain(|text_thread| text_thread.path.as_ref() != path.as_ref());
|
||||
this.contexts_metadata
|
||||
.retain(|context| context.path.as_ref() != path.as_ref());
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn loaded_text_thread_for_path(&self, path: &Path, cx: &App) -> Option<Entity<TextThread>> {
|
||||
self.text_threads.iter().find_map(|text_thread| {
|
||||
let text_thread = text_thread.upgrade()?;
|
||||
if text_thread.read(cx).path().map(Arc::as_ref) == Some(path) {
|
||||
Some(text_thread)
|
||||
fn loaded_context_for_path(&self, path: &Path, cx: &App) -> Option<Entity<AssistantContext>> {
|
||||
self.contexts.iter().find_map(|context| {
|
||||
let context = context.upgrade()?;
|
||||
if context.read(cx).path().map(Arc::as_ref) == Some(path) {
|
||||
Some(context)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn loaded_text_thread_for_id(
|
||||
pub fn loaded_context_for_id(
|
||||
&self,
|
||||
id: &TextThreadId,
|
||||
id: &ContextId,
|
||||
cx: &App,
|
||||
) -> Option<Entity<TextThread>> {
|
||||
self.text_threads.iter().find_map(|text_thread| {
|
||||
let text_thread = text_thread.upgrade()?;
|
||||
if text_thread.read(cx).id() == id {
|
||||
Some(text_thread)
|
||||
) -> Option<Entity<AssistantContext>> {
|
||||
self.contexts.iter().find_map(|context| {
|
||||
let context = context.upgrade()?;
|
||||
if context.read(cx).id() == id {
|
||||
Some(context)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_remote(
|
||||
pub fn open_remote_context(
|
||||
&mut self,
|
||||
text_thread_id: TextThreadId,
|
||||
context_id: ContextId,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<TextThread>>> {
|
||||
) -> Task<Result<Entity<AssistantContext>>> {
|
||||
let project = self.project.read(cx);
|
||||
let Some(project_id) = project.remote_id() else {
|
||||
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
|
||||
};
|
||||
|
||||
if let Some(context) = self.loaded_text_thread_for_id(&text_thread_id, cx) {
|
||||
if let Some(context) = self.loaded_context_for_id(&context_id, cx) {
|
||||
return Task::ready(Ok(context));
|
||||
}
|
||||
|
||||
@@ -557,16 +567,16 @@ impl TextThreadStore {
|
||||
let telemetry = self.telemetry.clone();
|
||||
let request = self.client.request(proto::OpenContext {
|
||||
project_id,
|
||||
context_id: text_thread_id.to_proto(),
|
||||
context_id: context_id.to_proto(),
|
||||
});
|
||||
let prompt_builder = self.prompt_builder.clone();
|
||||
let slash_commands = self.slash_commands.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
let response = request.await?;
|
||||
let context_proto = response.context.context("invalid context")?;
|
||||
let text_thread = cx.new(|cx| {
|
||||
TextThread::new(
|
||||
text_thread_id.clone(),
|
||||
let context = cx.new(|cx| {
|
||||
AssistantContext::new(
|
||||
context_id.clone(),
|
||||
replica_id,
|
||||
capability,
|
||||
language_registry,
|
||||
@@ -582,40 +592,38 @@ impl TextThreadStore {
|
||||
context_proto
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(TextThreadOperation::from_proto)
|
||||
.map(ContextOperation::from_proto)
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})
|
||||
.await?;
|
||||
text_thread.update(cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||
context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||
this.update(cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_text_thread_for_id(&text_thread_id, cx)
|
||||
{
|
||||
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
existing_context
|
||||
} else {
|
||||
this.register_text_thread(&text_thread, cx);
|
||||
this.register_context(&context, cx);
|
||||
this.synchronize_contexts(cx);
|
||||
text_thread
|
||||
context
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn register_text_thread(&mut self, text_thread: &Entity<TextThread>, cx: &mut Context<Self>) {
|
||||
fn register_context(&mut self, context: &Entity<AssistantContext>, cx: &mut Context<Self>) {
|
||||
let handle = if self.project_is_shared {
|
||||
TextThreadHandle::Strong(text_thread.clone())
|
||||
ContextHandle::Strong(context.clone())
|
||||
} else {
|
||||
TextThreadHandle::Weak(text_thread.downgrade())
|
||||
ContextHandle::Weak(context.downgrade())
|
||||
};
|
||||
self.text_threads.push(handle);
|
||||
self.contexts.push(handle);
|
||||
self.advertise_contexts(cx);
|
||||
cx.subscribe(text_thread, Self::handle_context_event)
|
||||
.detach();
|
||||
cx.subscribe(context, Self::handle_context_event).detach();
|
||||
}
|
||||
|
||||
fn handle_context_event(
|
||||
&mut self,
|
||||
text_thread: Entity<TextThread>,
|
||||
event: &TextThreadEvent,
|
||||
context: Entity<AssistantContext>,
|
||||
event: &ContextEvent,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(project_id) = self.project.read(cx).remote_id() else {
|
||||
@@ -623,12 +631,12 @@ impl TextThreadStore {
|
||||
};
|
||||
|
||||
match event {
|
||||
TextThreadEvent::SummaryChanged => {
|
||||
ContextEvent::SummaryChanged => {
|
||||
self.advertise_contexts(cx);
|
||||
}
|
||||
TextThreadEvent::PathChanged { old_path, new_path } => {
|
||||
ContextEvent::PathChanged { old_path, new_path } => {
|
||||
if let Some(old_path) = old_path.as_ref() {
|
||||
for metadata in &mut self.text_threads_metadata {
|
||||
for metadata in &mut self.contexts_metadata {
|
||||
if &metadata.path == old_path {
|
||||
metadata.path = new_path.clone();
|
||||
break;
|
||||
@@ -636,8 +644,8 @@ impl TextThreadStore {
|
||||
}
|
||||
}
|
||||
}
|
||||
TextThreadEvent::Operation(operation) => {
|
||||
let context_id = text_thread.read(cx).id().to_proto();
|
||||
ContextEvent::Operation(operation) => {
|
||||
let context_id = context.read(cx).id().to_proto();
|
||||
let operation = operation.to_proto();
|
||||
self.client
|
||||
.send(proto::UpdateContext {
|
||||
@@ -662,15 +670,15 @@ impl TextThreadStore {
|
||||
}
|
||||
|
||||
let contexts = self
|
||||
.text_threads
|
||||
.contexts
|
||||
.iter()
|
||||
.rev()
|
||||
.filter_map(|text_thread| {
|
||||
let text_thread = text_thread.upgrade()?.read(cx);
|
||||
if text_thread.replica_id() == ReplicaId::default() {
|
||||
.filter_map(|context| {
|
||||
let context = context.upgrade()?.read(cx);
|
||||
if context.replica_id() == ReplicaId::default() {
|
||||
Some(proto::ContextMetadata {
|
||||
context_id: text_thread.id().to_proto(),
|
||||
summary: text_thread
|
||||
context_id: context.id().to_proto(),
|
||||
summary: context
|
||||
.summary()
|
||||
.content()
|
||||
.map(|summary| summary.text.clone()),
|
||||
@@ -693,13 +701,13 @@ impl TextThreadStore {
|
||||
return;
|
||||
};
|
||||
|
||||
let text_threads = self
|
||||
.text_threads
|
||||
let contexts = self
|
||||
.contexts
|
||||
.iter()
|
||||
.filter_map(|text_thread| {
|
||||
let text_thread = text_thread.upgrade()?.read(cx);
|
||||
if text_thread.replica_id() != ReplicaId::default() {
|
||||
Some(text_thread.version(cx).to_proto(text_thread.id().clone()))
|
||||
.filter_map(|context| {
|
||||
let context = context.upgrade()?.read(cx);
|
||||
if context.replica_id() != ReplicaId::default() {
|
||||
Some(context.version(cx).to_proto(context.id().clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -709,27 +717,26 @@ impl TextThreadStore {
|
||||
let client = self.client.clone();
|
||||
let request = self.client.request(proto::SynchronizeContexts {
|
||||
project_id,
|
||||
contexts: text_threads,
|
||||
contexts,
|
||||
});
|
||||
cx.spawn(async move |this, cx| {
|
||||
let response = request.await?;
|
||||
|
||||
let mut text_thread_ids = Vec::new();
|
||||
let mut context_ids = Vec::new();
|
||||
let mut operations = Vec::new();
|
||||
this.read_with(cx, |this, cx| {
|
||||
for context_version_proto in response.contexts {
|
||||
let text_thread_version = TextThreadVersion::from_proto(&context_version_proto);
|
||||
let text_thread_id = TextThreadId::from_proto(context_version_proto.context_id);
|
||||
if let Some(text_thread) = this.loaded_text_thread_for_id(&text_thread_id, cx) {
|
||||
text_thread_ids.push(text_thread_id);
|
||||
operations
|
||||
.push(text_thread.read(cx).serialize_ops(&text_thread_version, cx));
|
||||
let context_version = ContextVersion::from_proto(&context_version_proto);
|
||||
let context_id = ContextId::from_proto(context_version_proto.context_id);
|
||||
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
context_ids.push(context_id);
|
||||
operations.push(context.read(cx).serialize_ops(&context_version, cx));
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
let operations = futures::future::join_all(operations).await;
|
||||
for (context_id, operations) in text_thread_ids.into_iter().zip(operations) {
|
||||
for (context_id, operations) in context_ids.into_iter().zip(operations) {
|
||||
for operation in operations {
|
||||
client.send(proto::UpdateContext {
|
||||
project_id,
|
||||
@@ -744,8 +751,8 @@ impl TextThreadStore {
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn search(&self, query: String, cx: &App) -> Task<Vec<SavedTextThreadMetadata>> {
|
||||
let metadata = self.text_threads_metadata.clone();
|
||||
pub fn search(&self, query: String, cx: &App) -> Task<Vec<SavedContextMetadata>> {
|
||||
let metadata = self.contexts_metadata.clone();
|
||||
let executor = cx.background_executor().clone();
|
||||
cx.background_spawn(async move {
|
||||
if query.is_empty() {
|
||||
@@ -775,16 +782,20 @@ impl TextThreadStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn host_contexts(&self) -> &[RemoteContextMetadata] {
|
||||
&self.host_contexts
|
||||
}
|
||||
|
||||
fn reload(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
let fs = self.fs.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
if *ZED_STATELESS {
|
||||
return Ok(());
|
||||
}
|
||||
fs.create_dir(text_threads_dir()).await?;
|
||||
fs.create_dir(contexts_dir()).await?;
|
||||
|
||||
let mut paths = fs.read_dir(text_threads_dir()).await?;
|
||||
let mut contexts = Vec::<SavedTextThreadMetadata>::new();
|
||||
let mut paths = fs.read_dir(contexts_dir()).await?;
|
||||
let mut contexts = Vec::<SavedContextMetadata>::new();
|
||||
while let Some(path) = paths.next().await {
|
||||
let path = path?;
|
||||
if path.extension() != Some(OsStr::new("json")) {
|
||||
@@ -810,7 +821,7 @@ impl TextThreadStore {
|
||||
.lines()
|
||||
.next()
|
||||
{
|
||||
contexts.push(SavedTextThreadMetadata {
|
||||
contexts.push(SavedContextMetadata {
|
||||
title: title.to_string().into(),
|
||||
path: path.into(),
|
||||
mtime: metadata.mtime.timestamp_for_user().into(),
|
||||
@@ -818,10 +829,10 @@ impl TextThreadStore {
|
||||
}
|
||||
}
|
||||
}
|
||||
contexts.sort_unstable_by_key(|text_thread| Reverse(text_thread.mtime));
|
||||
contexts.sort_unstable_by_key(|context| Reverse(context.mtime));
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.text_threads_metadata = contexts;
|
||||
this.contexts_metadata = contexts;
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
@@ -1,15 +0,0 @@
|
||||
#[cfg(test)]
|
||||
mod assistant_text_thread_tests;
|
||||
mod text_thread;
|
||||
mod text_thread_store;
|
||||
|
||||
pub use crate::text_thread::*;
|
||||
pub use crate::text_thread_store::*;
|
||||
|
||||
use client::Client;
|
||||
use gpui::App;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn init(client: Arc<Client>, _: &mut App) {
|
||||
text_thread_store::init(&client.into());
|
||||
}
|
||||
@@ -433,7 +433,7 @@ where
|
||||
/// Stores already emitted samples, once its full we call the callback.
|
||||
buffer: [Sample; N],
|
||||
/// Next free element in buffer. If this is equal to the buffer length
|
||||
/// we have no more free elements.
|
||||
/// we have no more free lements.
|
||||
free: usize,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,16 @@
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type='win32'
|
||||
<assemblyIdentity type='win32'
|
||||
name='Microsoft.Windows.Common-Controls'
|
||||
version='6.0.0.0'
|
||||
processorArchitecture='*'
|
||||
publicKeyToken='6595b64144ccf1df'
|
||||
/>
|
||||
version='6.0.0.0' processorArchitecture='*'
|
||||
publicKeyToken='6595b64144ccf1df' />
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
|
||||
@@ -36,31 +36,13 @@ pub(crate) const JOBS: &[Job] = &[
|
||||
std::fs::remove_file(&zed_wsl)
|
||||
.context(format!("Failed to remove old file {}", zed_wsl.display()))
|
||||
},
|
||||
// TODO: remove after a few weeks once everyone is on the new version and this file never exists
|
||||
|app_dir| {
|
||||
let open_console = app_dir.join("OpenConsole.exe");
|
||||
if open_console.exists() {
|
||||
log::info!("Removing old file: {}", open_console.display());
|
||||
std::fs::remove_file(&open_console).context(format!(
|
||||
"Failed to remove old file {}",
|
||||
open_console.display()
|
||||
))?
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
|app_dir| {
|
||||
let archs = ["x64", "arm64"];
|
||||
for arch in archs {
|
||||
let open_console = app_dir.join(format!("{arch}\\OpenConsole.exe"));
|
||||
if open_console.exists() {
|
||||
log::info!("Removing old file: {}", open_console.display());
|
||||
std::fs::remove_file(&open_console).context(format!(
|
||||
"Failed to remove old file {}",
|
||||
open_console.display()
|
||||
))?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
log::info!("Removing old file: {}", open_console.display());
|
||||
std::fs::remove_file(&open_console).context(format!(
|
||||
"Failed to remove old file {}",
|
||||
open_console.display()
|
||||
))
|
||||
},
|
||||
|app_dir| {
|
||||
let conpty = app_dir.join("conpty.dll");
|
||||
@@ -118,32 +100,20 @@ pub(crate) const JOBS: &[Job] = &[
|
||||
))
|
||||
},
|
||||
|app_dir| {
|
||||
let archs = ["x64", "arm64"];
|
||||
for arch in archs {
|
||||
let open_console_source = app_dir.join(format!("install\\{arch}\\OpenConsole.exe"));
|
||||
let open_console_dest = app_dir.join(format!("{arch}\\OpenConsole.exe"));
|
||||
if open_console_source.exists() {
|
||||
log::info!(
|
||||
"Copying new file {} to {}",
|
||||
open_console_source.display(),
|
||||
open_console_dest.display()
|
||||
);
|
||||
let parent = open_console_dest.parent().context(format!(
|
||||
"Failed to get parent directory of {}",
|
||||
open_console_dest.display()
|
||||
))?;
|
||||
std::fs::create_dir_all(parent)
|
||||
.context(format!("Failed to create directory {}", parent.display()))?;
|
||||
std::fs::copy(&open_console_source, &open_console_dest)
|
||||
.map(|_| ())
|
||||
.context(format!(
|
||||
"Failed to copy new file {} to {}",
|
||||
open_console_source.display(),
|
||||
open_console_dest.display()
|
||||
))?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
let open_console_source = app_dir.join("install\\OpenConsole.exe");
|
||||
let open_console_dest = app_dir.join("OpenConsole.exe");
|
||||
log::info!(
|
||||
"Copying new file {} to {}",
|
||||
open_console_source.display(),
|
||||
open_console_dest.display()
|
||||
);
|
||||
std::fs::copy(&open_console_source, &open_console_dest)
|
||||
.map(|_| ())
|
||||
.context(format!(
|
||||
"Failed to copy new file {} to {}",
|
||||
open_console_source.display(),
|
||||
open_console_dest.display()
|
||||
))
|
||||
},
|
||||
|app_dir| {
|
||||
let conpty_source = app_dir.join("install\\conpty.dll");
|
||||
@@ -227,7 +197,7 @@ pub(crate) fn perform_update(app_dir: &Path, hwnd: Option<isize>, launch: bool)
|
||||
break;
|
||||
}
|
||||
|
||||
log::error!("Operation failed: {} ({:?})", err, io_err.kind());
|
||||
log::error!("Operation failed: {}", err);
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,8 +66,6 @@ pub enum Model {
|
||||
Claude3Sonnet,
|
||||
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
|
||||
Claude3_5Haiku,
|
||||
#[serde(rename = "claude-haiku-4-5", alias = "claude-haiku-4-5-latest")]
|
||||
ClaudeHaiku4_5,
|
||||
Claude3_5Sonnet,
|
||||
Claude3Haiku,
|
||||
// Amazon Nova Models
|
||||
@@ -149,8 +147,6 @@ impl Model {
|
||||
Ok(Self::Claude3Sonnet)
|
||||
} else if id.starts_with("claude-3-5-haiku") {
|
||||
Ok(Self::Claude3_5Haiku)
|
||||
} else if id.starts_with("claude-haiku-4-5") {
|
||||
Ok(Self::ClaudeHaiku4_5)
|
||||
} else if id.starts_with("claude-3-7-sonnet") {
|
||||
Ok(Self::Claude3_7Sonnet)
|
||||
} else if id.starts_with("claude-3-7-sonnet-thinking") {
|
||||
@@ -184,7 +180,6 @@ impl Model {
|
||||
Model::Claude3Sonnet => "claude-3-sonnet",
|
||||
Model::Claude3Haiku => "claude-3-haiku",
|
||||
Model::Claude3_5Haiku => "claude-3-5-haiku",
|
||||
Model::ClaudeHaiku4_5 => "claude-haiku-4-5",
|
||||
Model::Claude3_7Sonnet => "claude-3-7-sonnet",
|
||||
Model::Claude3_7SonnetThinking => "claude-3-7-sonnet-thinking",
|
||||
Model::AmazonNovaLite => "amazon-nova-lite",
|
||||
@@ -251,7 +246,6 @@ impl Model {
|
||||
Model::Claude3Sonnet => "anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
Model::Claude3Haiku => "anthropic.claude-3-haiku-20240307-v1:0",
|
||||
Model::Claude3_5Haiku => "anthropic.claude-3-5-haiku-20241022-v1:0",
|
||||
Model::ClaudeHaiku4_5 => "anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
Model::Claude3_7Sonnet | Model::Claude3_7SonnetThinking => {
|
||||
"anthropic.claude-3-7-sonnet-20250219-v1:0"
|
||||
}
|
||||
@@ -315,7 +309,6 @@ impl Model {
|
||||
Self::Claude3Sonnet => "Claude 3 Sonnet",
|
||||
Self::Claude3Haiku => "Claude 3 Haiku",
|
||||
Self::Claude3_5Haiku => "Claude 3.5 Haiku",
|
||||
Self::ClaudeHaiku4_5 => "Claude Haiku 4.5",
|
||||
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
|
||||
Self::Claude3_7SonnetThinking => "Claude 3.7 Sonnet Thinking",
|
||||
Self::AmazonNovaLite => "Amazon Nova Lite",
|
||||
@@ -370,7 +363,6 @@ impl Model {
|
||||
| Self::Claude3Opus
|
||||
| Self::Claude3Sonnet
|
||||
| Self::Claude3_5Haiku
|
||||
| Self::ClaudeHaiku4_5
|
||||
| Self::Claude3_7Sonnet
|
||||
| Self::ClaudeSonnet4
|
||||
| Self::ClaudeOpus4
|
||||
@@ -393,7 +385,7 @@ impl Model {
|
||||
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3_5Haiku => 4_096,
|
||||
Self::Claude3_7Sonnet | Self::Claude3_7SonnetThinking => 128_000,
|
||||
Self::ClaudeSonnet4 | Self::ClaudeSonnet4Thinking => 64_000,
|
||||
Self::ClaudeSonnet4_5 | Self::ClaudeSonnet4_5Thinking | Self::ClaudeHaiku4_5 => 64_000,
|
||||
Self::ClaudeSonnet4_5 | Self::ClaudeSonnet4_5Thinking => 64_000,
|
||||
Self::ClaudeOpus4
|
||||
| Self::ClaudeOpus4Thinking
|
||||
| Self::ClaudeOpus4_1
|
||||
@@ -412,7 +404,6 @@ impl Model {
|
||||
| Self::Claude3Opus
|
||||
| Self::Claude3Sonnet
|
||||
| Self::Claude3_5Haiku
|
||||
| Self::ClaudeHaiku4_5
|
||||
| Self::Claude3_7Sonnet
|
||||
| Self::ClaudeOpus4
|
||||
| Self::ClaudeOpus4Thinking
|
||||
@@ -447,8 +438,7 @@ impl Model {
|
||||
| Self::ClaudeSonnet4Thinking
|
||||
| Self::ClaudeSonnet4_5
|
||||
| Self::ClaudeSonnet4_5Thinking
|
||||
| Self::Claude3_5Haiku
|
||||
| Self::ClaudeHaiku4_5 => true,
|
||||
| Self::Claude3_5Haiku => true,
|
||||
|
||||
// Amazon Nova models (all support tool use)
|
||||
Self::AmazonNovaPremier
|
||||
@@ -474,7 +464,6 @@ impl Model {
|
||||
// Nova models support only text caching
|
||||
// https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html#prompt-caching-models
|
||||
Self::Claude3_5Haiku
|
||||
| Self::ClaudeHaiku4_5
|
||||
| Self::Claude3_7Sonnet
|
||||
| Self::Claude3_7SonnetThinking
|
||||
| Self::ClaudeSonnet4
|
||||
@@ -511,7 +500,7 @@ impl Model {
|
||||
min_total_token: 1024,
|
||||
}),
|
||||
|
||||
Self::Claude3_5Haiku | Self::ClaudeHaiku4_5 => Some(BedrockModelCacheConfiguration {
|
||||
Self::Claude3_5Haiku => Some(BedrockModelCacheConfiguration {
|
||||
max_cache_anchors: 4,
|
||||
min_total_token: 2048,
|
||||
}),
|
||||
@@ -580,7 +569,6 @@ impl Model {
|
||||
(
|
||||
Model::AmazonNovaPremier
|
||||
| Model::Claude3_5Haiku
|
||||
| Model::ClaudeHaiku4_5
|
||||
| Model::Claude3_5Sonnet
|
||||
| Model::Claude3_5SonnetV2
|
||||
| Model::Claude3_7Sonnet
|
||||
@@ -618,7 +606,6 @@ impl Model {
|
||||
// Models available in EU
|
||||
(
|
||||
Model::Claude3_5Sonnet
|
||||
| Model::ClaudeHaiku4_5
|
||||
| Model::Claude3_7Sonnet
|
||||
| Model::Claude3_7SonnetThinking
|
||||
| Model::ClaudeSonnet4
|
||||
@@ -637,7 +624,6 @@ impl Model {
|
||||
(
|
||||
Model::Claude3_5Sonnet
|
||||
| Model::Claude3_5SonnetV2
|
||||
| Model::ClaudeHaiku4_5
|
||||
| Model::Claude3Haiku
|
||||
| Model::Claude3Sonnet
|
||||
| Model::Claude3_7Sonnet
|
||||
|
||||
@@ -119,19 +119,21 @@ impl Render for Breadcrumbs {
|
||||
}
|
||||
}
|
||||
})
|
||||
.tooltip(move |_window, cx| {
|
||||
.tooltip(move |window, cx| {
|
||||
if let Some(editor) = editor.upgrade() {
|
||||
let focus_handle = editor.read(cx).focus_handle(cx);
|
||||
Tooltip::for_action_in(
|
||||
"Show Symbol Outline",
|
||||
&zed_actions::outline::ToggleOutline,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::for_action(
|
||||
"Show Symbol Outline",
|
||||
&zed_actions::outline::ToggleOutline,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ struct PendingHunk {
|
||||
new_status: DiffHunkSecondaryStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DiffHunkSummary {
|
||||
buffer_range: Range<Anchor>,
|
||||
}
|
||||
@@ -114,9 +114,7 @@ impl sum_tree::Summary for DiffHunkSummary {
|
||||
type Context<'a> = &'a text::BufferSnapshot;
|
||||
|
||||
fn zero(_cx: Self::Context<'_>) -> Self {
|
||||
DiffHunkSummary {
|
||||
buffer_range: Anchor::MIN..Anchor::MIN,
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
|
||||
@@ -939,9 +937,7 @@ impl BufferDiff {
|
||||
|
||||
pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
|
||||
if self.secondary_diff.is_some() {
|
||||
self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
|
||||
buffer_range: Anchor::MIN..Anchor::MIN,
|
||||
});
|
||||
self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary::default());
|
||||
cx.emit(BufferDiffEvent::DiffChanged {
|
||||
changed_range: Some(Anchor::MIN..Anchor::MAX),
|
||||
});
|
||||
@@ -1162,22 +1158,34 @@ impl BufferDiff {
|
||||
self.hunks_intersecting_range(start..end, buffer, cx)
|
||||
}
|
||||
|
||||
pub fn set_base_text_buffer(
|
||||
&mut self,
|
||||
base_buffer: Entity<language::Buffer>,
|
||||
buffer: text::BufferSnapshot,
|
||||
cx: &mut Context<Self>,
|
||||
) -> oneshot::Receiver<()> {
|
||||
let base_buffer = base_buffer.read(cx);
|
||||
let language_registry = base_buffer.language_registry();
|
||||
let base_buffer = base_buffer.snapshot();
|
||||
self.set_base_text(base_buffer, language_registry, buffer, cx)
|
||||
}
|
||||
|
||||
/// Used in cases where the change set isn't derived from git.
|
||||
pub fn set_base_text(
|
||||
&mut self,
|
||||
base_text: Option<Arc<String>>,
|
||||
language: Option<Arc<Language>>,
|
||||
base_buffer: language::BufferSnapshot,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
buffer: text::BufferSnapshot,
|
||||
cx: &mut Context<Self>,
|
||||
) -> oneshot::Receiver<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let this = cx.weak_entity();
|
||||
let base_text = Arc::new(base_buffer.text());
|
||||
|
||||
let snapshot = BufferDiffSnapshot::new_with_base_text(
|
||||
buffer.clone(),
|
||||
base_text,
|
||||
language,
|
||||
Some(base_text),
|
||||
base_buffer.language().cloned(),
|
||||
language_registry,
|
||||
cx,
|
||||
);
|
||||
@@ -1360,7 +1368,7 @@ mod tests {
|
||||
use gpui::TestAppContext;
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
use rand::{Rng as _, rngs::StdRng};
|
||||
use text::{Buffer, BufferId, ReplicaId, Rope};
|
||||
use text::{Buffer, BufferId, Rope};
|
||||
use unindent::Unindent as _;
|
||||
use util::test::marked_text_ranges;
|
||||
|
||||
@@ -1385,7 +1393,7 @@ mod tests {
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
|
||||
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
|
||||
let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
|
||||
assert_hunks(
|
||||
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
|
||||
@@ -1459,7 +1467,7 @@ mod tests {
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
|
||||
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
|
||||
let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
|
||||
let mut uncommitted_diff =
|
||||
BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
|
||||
@@ -1528,7 +1536,7 @@ mod tests {
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
|
||||
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
|
||||
let diff = cx
|
||||
.update(|cx| {
|
||||
BufferDiffSnapshot::new_with_base_text(
|
||||
@@ -1791,7 +1799,7 @@ mod tests {
|
||||
|
||||
for example in table {
|
||||
let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
|
||||
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
|
||||
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
|
||||
let hunk_range =
|
||||
buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
|
||||
|
||||
@@ -1864,11 +1872,7 @@ mod tests {
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer = Buffer::new(
|
||||
ReplicaId::LOCAL,
|
||||
BufferId::new(1).unwrap(),
|
||||
buffer_text.clone(),
|
||||
);
|
||||
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text.clone());
|
||||
let unstaged = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
|
||||
let uncommitted = BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
|
||||
let unstaged_diff = cx.new(|cx| {
|
||||
@@ -1941,7 +1945,7 @@ mod tests {
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
|
||||
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1);
|
||||
|
||||
let empty_diff = cx.update(|cx| BufferDiffSnapshot::empty(&buffer, cx));
|
||||
let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
|
||||
|
||||
@@ -9,7 +9,7 @@ use rpc::{
|
||||
proto::{self, PeerId},
|
||||
};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use text::{BufferId, ReplicaId};
|
||||
use text::BufferId;
|
||||
use util::ResultExt;
|
||||
|
||||
pub const ACKNOWLEDGE_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(250);
|
||||
@@ -65,12 +65,7 @@ impl ChannelBuffer {
|
||||
|
||||
let buffer = cx.new(|cx| {
|
||||
let capability = channel_store.read(cx).channel_capability(channel.id);
|
||||
language::Buffer::remote(
|
||||
buffer_id,
|
||||
ReplicaId::new(response.replica_id as u16),
|
||||
capability,
|
||||
base_text,
|
||||
)
|
||||
language::Buffer::remote(buffer_id, response.replica_id as u16, capability, base_text)
|
||||
})?;
|
||||
buffer.update(cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
|
||||
|
||||
@@ -277,7 +272,7 @@ impl ChannelBuffer {
|
||||
self.connected
|
||||
}
|
||||
|
||||
pub fn replica_id(&self, cx: &App) -> ReplicaId {
|
||||
pub fn replica_id(&self, cx: &App) -> u16 {
|
||||
self.buffer.read(cx).replica_id()
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user