Compare commits
189 Commits
multi-auth
...
linux-ci
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a89156cbbe | ||
|
|
892e8cddcc | ||
|
|
4ed39540e7 | ||
|
|
91c509ea5e | ||
|
|
54ca5e7c91 | ||
|
|
38db09157e | ||
|
|
8a4d85eb82 | ||
|
|
6a983f657a | ||
|
|
6439ea7fb8 | ||
|
|
59d8458a24 | ||
|
|
be7c18234d | ||
|
|
f11a3dcc97 | ||
|
|
5aa82887ec | ||
|
|
fe730e9129 | ||
|
|
59a98bae3a | ||
|
|
1cf765e126 | ||
|
|
79eff1fe05 | ||
|
|
af0d2ad491 | ||
|
|
f0ac54e8a5 | ||
|
|
68707ffc74 | ||
|
|
1ce9a85a1a | ||
|
|
1966d4c818 | ||
|
|
b6a18671dc | ||
|
|
c0ff8ef8e9 | ||
|
|
66ec0fc0e1 | ||
|
|
435eab6896 | ||
|
|
55bc679c19 | ||
|
|
6aaf19f276 | ||
|
|
d83ed4e03e | ||
|
|
11eba64e68 | ||
|
|
63fe1eae59 | ||
|
|
8b6f3ec647 | ||
|
|
a66098b485 | ||
|
|
b519ab2758 | ||
|
|
023ac1b649 | ||
|
|
738e248109 | ||
|
|
4f0a44896a | ||
|
|
9a6397fb17 | ||
|
|
93ef1947b5 | ||
|
|
8c1b4cb1cd | ||
|
|
3bb4c94ed4 | ||
|
|
16f7bd0a2e | ||
|
|
c529a066bf | ||
|
|
278032c6b8 | ||
|
|
5a05986479 | ||
|
|
05c2cc0254 | ||
|
|
1edb1b3896 | ||
|
|
8f4646d6c3 | ||
|
|
18daa9a839 | ||
|
|
bf63ff2b91 | ||
|
|
f9e0642a72 | ||
|
|
bada88c5b3 | ||
|
|
6b8f8592ea | ||
|
|
4fd4cbbfb7 | ||
|
|
044701e3a5 | ||
|
|
a96bf504e0 | ||
|
|
c16f2a1a29 | ||
|
|
ca4103246f | ||
|
|
731237222e | ||
|
|
2096f256f2 | ||
|
|
7880e2b961 | ||
|
|
ab22478ed4 | ||
|
|
6ed9c0271d | ||
|
|
93136a9aaa | ||
|
|
f393138711 | ||
|
|
ed5b9a4705 | ||
|
|
5738bde3ce | ||
|
|
6622902964 | ||
|
|
cb7881ec0b | ||
|
|
c60343af71 | ||
|
|
4a93719b6b | ||
|
|
d558005058 | ||
|
|
96a0db24d9 | ||
|
|
23e9e32d65 | ||
|
|
b207da5a71 | ||
|
|
a24601903a | ||
|
|
3a12122d1b | ||
|
|
d0398da099 | ||
|
|
69b2ee7bf0 | ||
|
|
88bac4d5fc | ||
|
|
d53efe4e91 | ||
|
|
14b41b122f | ||
|
|
98c7e018ae | ||
|
|
c81ffaffb6 | ||
|
|
bd69124f2b | ||
|
|
77dbe08d9d | ||
|
|
252b75e493 | ||
|
|
bc0bace81f | ||
|
|
8ceb2f2c61 | ||
|
|
25de2acfdb | ||
|
|
8bad2cbd83 | ||
|
|
762af0982c | ||
|
|
3d3e9130a8 | ||
|
|
fd9c2e32f3 | ||
|
|
08d95ad9d3 | ||
|
|
d096132888 | ||
|
|
d7c855550c | ||
|
|
9c71a7f43c | ||
|
|
221637ea82 | ||
|
|
e468edd389 | ||
|
|
2903a06e5c | ||
|
|
2338164c10 | ||
|
|
2deafd8706 | ||
|
|
0c13403fa5 | ||
|
|
8f3da5c5cd | ||
|
|
b519f53b3e | ||
|
|
50d184b6a6 | ||
|
|
2bba3358b8 | ||
|
|
fcecf379dc | ||
|
|
256fe6e45c | ||
|
|
e49edfac74 | ||
|
|
fd10017837 | ||
|
|
4b429033e7 | ||
|
|
a398f80ba6 | ||
|
|
a71cc6a1e7 | ||
|
|
2764c51af1 | ||
|
|
641ae90cd6 | ||
|
|
fa550de922 | ||
|
|
ce5d597efa | ||
|
|
cf8422f7fd | ||
|
|
d7ffc37b14 | ||
|
|
b79837695e | ||
|
|
69025f3bf4 | ||
|
|
981fa288eb | ||
|
|
854d1ec4dc | ||
|
|
10b9ae5e44 | ||
|
|
cad06011c5 | ||
|
|
0eccdfe61f | ||
|
|
0be70e24d6 | ||
|
|
3d4abde55a | ||
|
|
2bfbe031c6 | ||
|
|
12d912114f | ||
|
|
b487d2cfe0 | ||
|
|
977887b65f | ||
|
|
0721c7873a | ||
|
|
0aa7b7c773 | ||
|
|
1b544b9e19 | ||
|
|
ea6e6dbda1 | ||
|
|
a56122e144 | ||
|
|
04a45e3501 | ||
|
|
4b489f4ce9 | ||
|
|
71ea133d72 | ||
|
|
917f22f884 | ||
|
|
36c006828e | ||
|
|
a2c42813c4 | ||
|
|
267052f891 | ||
|
|
62516e8f1f | ||
|
|
b479d1ef49 | ||
|
|
684f4dced9 | ||
|
|
de6750d3f4 | ||
|
|
4f5f299265 | ||
|
|
32a442d522 | ||
|
|
f7a0971d2b | ||
|
|
ed82233030 | ||
|
|
ec0efc9360 | ||
|
|
1c639da8a8 | ||
|
|
ebaefa8cbc | ||
|
|
33bc586ed1 | ||
|
|
853d7c3f92 | ||
|
|
79ada634ac | ||
|
|
057c3c1206 | ||
|
|
13fe9938c2 | ||
|
|
78bfda5045 | ||
|
|
d8f4293ac3 | ||
|
|
b6fb1d0a19 | ||
|
|
0a17f91923 | ||
|
|
7aa0626098 | ||
|
|
ea5f3e6086 | ||
|
|
db404fc2e3 | ||
|
|
eda7a49f01 | ||
|
|
8bef4800f0 | ||
|
|
67b9d480b4 | ||
|
|
30f3152e65 | ||
|
|
7c4fb5a899 | ||
|
|
85c2aa7325 | ||
|
|
08ecaa3931 | ||
|
|
96415e2d19 | ||
|
|
9a3c7945a9 | ||
|
|
cc21089736 | ||
|
|
bdb7c642a1 | ||
|
|
b827d8cfc0 | ||
|
|
9a72453a2b | ||
|
|
43a9368dff | ||
|
|
37e264ab99 | ||
|
|
94c28ba14a | ||
|
|
36210e72af | ||
|
|
e3297cdcae | ||
|
|
92ff29fa7d | ||
|
|
1d3bf9789e |
@@ -5,12 +5,16 @@
|
||||
# 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 incovenient.
|
||||
# would be inconvenient.
|
||||
# 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,3 +4,17 @@ 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
Normal file
35
.github/ISSUE_TEMPLATE/06_bug_git.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
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,9 +33,10 @@ body:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||
label: If applicable, attach your `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,8 +15,11 @@ runs:
|
||||
node-version: "18"
|
||||
|
||||
- name: Limit target directory size
|
||||
env:
|
||||
MAX_SIZE: ${{ runner.os == 'macOS' && 300 || 100 }}
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
# Use the variable in the run command
|
||||
run: script/clear-target-dir-if-larger-than ${{ env.MAX_SIZE }}
|
||||
|
||||
- name: Run tests
|
||||
shell: bash -euxo pipefail {0}
|
||||
|
||||
51
.github/workflows/ci.yml
vendored
51
.github/workflows/ci.yml
vendored
@@ -177,7 +177,7 @@ jobs:
|
||||
uses: ./.github/actions/check_style
|
||||
|
||||
- name: Check for typos
|
||||
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6
|
||||
uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1 # v1.38.1
|
||||
with:
|
||||
config: ./typos.toml
|
||||
|
||||
@@ -296,49 +296,53 @@ jobs:
|
||||
github.repository_owner == 'zed-industries' &&
|
||||
needs.job_spec.outputs.run_tests == 'true'
|
||||
runs-on:
|
||||
- namespace-profile-16x32-ubuntu-2204
|
||||
- namespace-profile-ubuntu22-x86-16x32-custom
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
run: |
|
||||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
echo "$HOME/.cargo-nextest/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||
- name: Configure Go and Rust cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
# cache-provider: "buildjet"
|
||||
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux
|
||||
path: |
|
||||
/home/runner/.cargo-nextest
|
||||
/home/runner/.rustup
|
||||
./target
|
||||
|
||||
- name: Configure CI
|
||||
run: |
|
||||
mkdir -p ./../.cargo
|
||||
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
|
||||
|
||||
- name: cargo clippy
|
||||
run: ./script/clippy
|
||||
- name: Install cargo nextest
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
cargo install cargo-nextest --locked --root ~/.cargo-nextest
|
||||
|
||||
- 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 }}
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
|
||||
|
||||
- name: Build other binaries and features
|
||||
run: |
|
||||
cargo build -p zed
|
||||
cargo check -p workspace
|
||||
cargo check -p gpui --examples
|
||||
|
||||
# Even the Linux runner is not stateful, in theory there is no need to do this cleanup.
|
||||
# But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code
|
||||
# to clean up the config file, I’ve included the cleanup code here as a precaution.
|
||||
# While it’s not strictly necessary at this moment, I believe it’s better to err on the side of caution.
|
||||
- name: Clean CI config file
|
||||
if: always()
|
||||
run: rm -rf ./../.cargo
|
||||
- name: cargo clippy
|
||||
run: ./script/clippy
|
||||
|
||||
doctests:
|
||||
# Nextest currently doesn't support doctests, so run them separately and in parallel.
|
||||
@@ -847,7 +851,8 @@ jobs:
|
||||
auto-release-preview:
|
||||
name: Auto release preview
|
||||
if: |
|
||||
startsWith(github.ref, 'refs/tags/v')
|
||||
false
|
||||
&& startsWith(github.ref, 'refs/tags/v')
|
||||
&& endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
|
||||
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, bundle-windows-x64]
|
||||
runs-on:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -25,6 +25,7 @@
|
||||
/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
|
||||
|
||||
415
Cargo.lock
generated
415
Cargo.lock
generated
@@ -142,7 +142,7 @@ dependencies = [
|
||||
"agent_servers",
|
||||
"agent_settings",
|
||||
"anyhow",
|
||||
"assistant_context",
|
||||
"assistant_text_thread",
|
||||
"chrono",
|
||||
"client",
|
||||
"clock",
|
||||
@@ -151,7 +151,7 @@ dependencies = [
|
||||
"context_server",
|
||||
"ctor",
|
||||
"db",
|
||||
"derive_more",
|
||||
"derive_more 0.99.20",
|
||||
"editor",
|
||||
"env_logger 0.11.8",
|
||||
"fs",
|
||||
@@ -210,16 +210,30 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "agent-client-protocol"
|
||||
version = "0.4.3"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3aaa2bd05a2401887945f8bfd70026e90bc3cf96c62ab9eba2779835bf21dc60"
|
||||
checksum = "2f655394a107cd601bd2e5375c2d909ea83adc65678a0e0e8d77613d3c848a7d"
|
||||
dependencies = [
|
||||
"agent-client-protocol-schema",
|
||||
"anyhow",
|
||||
"async-broadcast",
|
||||
"async-trait",
|
||||
"derive_more 2.0.1",
|
||||
"futures 0.3.31",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agent-client-protocol-schema"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61be4454304d7df1a5b44c4ae55e707ffe72eac4dfb1ef8762510ce8d8f6d924"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"derive_more 2.0.1",
|
||||
"schemars 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -301,9 +315,9 @@ dependencies = [
|
||||
"ai_onboarding",
|
||||
"anyhow",
|
||||
"arrayvec",
|
||||
"assistant_context",
|
||||
"assistant_slash_command",
|
||||
"assistant_slash_commands",
|
||||
"assistant_text_thread",
|
||||
"audio",
|
||||
"buffer_diff",
|
||||
"chrono",
|
||||
@@ -788,53 +802,6 @@ dependencies = [
|
||||
"rust-embed",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assistant_context"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent_settings",
|
||||
"anyhow",
|
||||
"assistant_slash_command",
|
||||
"assistant_slash_commands",
|
||||
"chrono",
|
||||
"client",
|
||||
"clock",
|
||||
"cloud_llm_client",
|
||||
"collections",
|
||||
"context_server",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"language",
|
||||
"language_model",
|
||||
"log",
|
||||
"open_ai",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"prompt_store",
|
||||
"proto",
|
||||
"rand 0.9.2",
|
||||
"regex",
|
||||
"rpc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"telemetry_events",
|
||||
"text",
|
||||
"ui",
|
||||
"unindent",
|
||||
"util",
|
||||
"uuid",
|
||||
"workspace",
|
||||
"zed_env_vars",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assistant_slash_command"
|
||||
version = "0.1.0"
|
||||
@@ -842,7 +809,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"collections",
|
||||
"derive_more",
|
||||
"derive_more 0.99.20",
|
||||
"extension",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
@@ -892,6 +859,53 @@ dependencies = [
|
||||
"zlog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assistant_text_thread"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent_settings",
|
||||
"anyhow",
|
||||
"assistant_slash_command",
|
||||
"assistant_slash_commands",
|
||||
"chrono",
|
||||
"client",
|
||||
"clock",
|
||||
"cloud_llm_client",
|
||||
"collections",
|
||||
"context_server",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"language",
|
||||
"language_model",
|
||||
"log",
|
||||
"open_ai",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"prompt_store",
|
||||
"proto",
|
||||
"rand 0.9.2",
|
||||
"regex",
|
||||
"rpc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"telemetry_events",
|
||||
"text",
|
||||
"ui",
|
||||
"unindent",
|
||||
"util",
|
||||
"uuid",
|
||||
"workspace",
|
||||
"zed_env_vars",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-attributes"
|
||||
version = "1.1.2"
|
||||
@@ -1170,9 +1184,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-tar"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a42f905d4f623faf634bbd1e001e84e0efc24694afa64be9ad239bf6ca49e1f8"
|
||||
checksum = "d1937db2d56578aa3919b9bdb0e5100693fd7d1c0f145c53eb81fbb03e217550"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"filetime",
|
||||
@@ -2064,7 +2078,7 @@ dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
@@ -2084,7 +2098,7 @@ dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
@@ -3079,7 +3093,7 @@ dependencies = [
|
||||
"cloud_llm_client",
|
||||
"collections",
|
||||
"credentials_provider",
|
||||
"derive_more",
|
||||
"derive_more 0.99.20",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
@@ -3310,8 +3324,8 @@ version = "0.44.0"
|
||||
dependencies = [
|
||||
"agent_settings",
|
||||
"anyhow",
|
||||
"assistant_context",
|
||||
"assistant_slash_command",
|
||||
"assistant_text_thread",
|
||||
"async-trait",
|
||||
"async-tungstenite",
|
||||
"audio",
|
||||
@@ -3379,6 +3393,7 @@ dependencies = [
|
||||
"rpc",
|
||||
"scrypt",
|
||||
"sea-orm",
|
||||
"sea-orm-macros",
|
||||
"semantic_version",
|
||||
"semver",
|
||||
"serde",
|
||||
@@ -3539,7 +3554,7 @@ name = "command_palette_hooks"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"collections",
|
||||
"derive_more",
|
||||
"derive_more 0.99.20",
|
||||
"gpui",
|
||||
"workspace",
|
||||
]
|
||||
@@ -4117,6 +4132,7 @@ dependencies = [
|
||||
"bincode 1.3.3",
|
||||
"cfg-if",
|
||||
"crash-handler",
|
||||
"extension_host",
|
||||
"log",
|
||||
"mach2 0.5.0",
|
||||
"minidumper",
|
||||
@@ -4856,6 +4872,27 @@ dependencies = [
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
|
||||
dependencies = [
|
||||
"derive_more-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more-impl"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_refineable"
|
||||
version = "0.1.0"
|
||||
@@ -4865,6 +4902,18 @@ dependencies = [
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_setters"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae5c625eda104c228c06ecaf988d1c60e542176bd7a490e60eeda3493244c0c9"
|
||||
dependencies = [
|
||||
"darling 0.20.11",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deunicode"
|
||||
version = "1.6.2"
|
||||
@@ -5002,7 +5051,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.5.2",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5332,6 +5381,7 @@ dependencies = [
|
||||
"rand 0.9.2",
|
||||
"regex",
|
||||
"release_channel",
|
||||
"rope",
|
||||
"rpc",
|
||||
"schemars 1.0.4",
|
||||
"serde",
|
||||
@@ -5606,7 +5656,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5655,6 +5705,61 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eval"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"acp_thread",
|
||||
"agent",
|
||||
"agent-client-protocol",
|
||||
"agent_settings",
|
||||
"agent_ui",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"buffer_diff",
|
||||
"chrono",
|
||||
"clap",
|
||||
"client",
|
||||
"collections",
|
||||
"debug_adapter_extension",
|
||||
"dirs 4.0.0",
|
||||
"dotenvy",
|
||||
"env_logger 0.11.8",
|
||||
"extension",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"gpui_tokio",
|
||||
"handlebars 4.5.0",
|
||||
"language",
|
||||
"language_extension",
|
||||
"language_model",
|
||||
"language_models",
|
||||
"languages",
|
||||
"markdown",
|
||||
"node_runtime",
|
||||
"pathdiff",
|
||||
"paths",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"prompt_store",
|
||||
"rand 0.9.2",
|
||||
"regex",
|
||||
"release_channel",
|
||||
"reqwest_client",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"shellexpand 2.1.2",
|
||||
"telemetry",
|
||||
"terminal_view",
|
||||
"toml 0.8.23",
|
||||
"unindent",
|
||||
"util",
|
||||
"uuid",
|
||||
"watch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
@@ -5793,6 +5898,7 @@ dependencies = [
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"gpui_tokio",
|
||||
"http_client",
|
||||
"language",
|
||||
"language_extension",
|
||||
@@ -6341,6 +6447,14 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_benchmarks"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fs",
|
||||
"gpui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
@@ -6840,6 +6954,35 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gh-workflow"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fffeec7bd9dfa73ffe1db38979cca5716ec6ffd944f03fae65fee81f16082ae"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"derive_more 2.0.1",
|
||||
"derive_setters",
|
||||
"gh-workflow-macros",
|
||||
"indexmap 2.11.4",
|
||||
"merge",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"strum_macros 0.27.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gh-workflow-macros"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eafb4d2a1005d4ac6d041ce929af10be1de1e1eae478795d9d634b84ccf8191"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.13.3"
|
||||
@@ -6875,7 +7018,7 @@ dependencies = [
|
||||
"askpass",
|
||||
"async-trait",
|
||||
"collections",
|
||||
"derive_more",
|
||||
"derive_more 0.99.20",
|
||||
"futures 0.3.31",
|
||||
"git2",
|
||||
"gpui",
|
||||
@@ -7115,7 +7258,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gpui"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"as-raw-xcb-connection",
|
||||
@@ -7141,7 +7284,7 @@ dependencies = [
|
||||
"core-video",
|
||||
"cosmic-text",
|
||||
"ctor",
|
||||
"derive_more",
|
||||
"derive_more 0.99.20",
|
||||
"embed-resource",
|
||||
"env_logger 0.11.8",
|
||||
"etagere",
|
||||
@@ -7640,7 +7783,7 @@ dependencies = [
|
||||
"async-fs",
|
||||
"async-tar",
|
||||
"bytes 1.10.1",
|
||||
"derive_more",
|
||||
"derive_more 0.99.20",
|
||||
"futures 0.3.31",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
@@ -9710,6 +9853,28 @@ dependencies = [
|
||||
"gpui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "merge"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9"
|
||||
dependencies = [
|
||||
"merge_derive",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "merge_derive"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "metal"
|
||||
version = "0.29.0"
|
||||
@@ -10294,7 +10459,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10746,7 +10911,6 @@ dependencies = [
|
||||
"telemetry",
|
||||
"theme",
|
||||
"ui",
|
||||
"ui_input",
|
||||
"util",
|
||||
"vim_mode_setting",
|
||||
"workspace",
|
||||
@@ -11344,7 +11508,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"env_logger 0.10.2",
|
||||
@@ -11381,7 +11545,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-conda"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"lazy_static",
|
||||
@@ -11400,7 +11564,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-core"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"lazy_static",
|
||||
@@ -11415,7 +11579,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-env-var-path"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -11431,7 +11595,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-fs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11440,7 +11604,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-global-virtualenvs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11453,7 +11617,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-homebrew"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -11471,7 +11635,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-jsonrpc"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"log",
|
||||
@@ -11484,7 +11648,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-linux-global-python"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11497,7 +11661,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-mac-commandlinetools"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11510,7 +11674,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-mac-python-org"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11523,7 +11687,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-mac-xcode"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11536,7 +11700,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-pipenv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11549,7 +11713,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-pixi"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11561,7 +11725,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-poetry"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"lazy_static",
|
||||
@@ -11582,7 +11746,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-pyenv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -11600,7 +11764,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-python-utils"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"lazy_static",
|
||||
@@ -11617,7 +11781,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-reporter"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"log",
|
||||
@@ -11631,7 +11795,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-telemetry"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"env_logger 0.10.2",
|
||||
"lazy_static",
|
||||
@@ -11646,7 +11810,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-venv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11658,7 +11822,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-virtualenv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11670,7 +11834,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-virtualenvwrapper"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"msvc_spectre_libs",
|
||||
@@ -11683,7 +11847,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-windows-registry"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -11701,7 +11865,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pet-windows-store"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
|
||||
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -12700,6 +12864,30 @@ dependencies = [
|
||||
"toml_edit 0.23.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
@@ -13283,7 +13471,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2 0.6.1",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -14126,6 +14314,7 @@ dependencies = [
|
||||
"log",
|
||||
"rand 0.9.2",
|
||||
"rayon",
|
||||
"regex",
|
||||
"smallvec",
|
||||
"sum_tree",
|
||||
"unicode-segmentation",
|
||||
@@ -14366,7 +14555,7 @@ dependencies = [
|
||||
"errno 0.3.14",
|
||||
"libc",
|
||||
"linux-raw-sys 0.11.0",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -14856,6 +15045,7 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"language",
|
||||
"lsp",
|
||||
"menu",
|
||||
"project",
|
||||
"schemars 1.0.4",
|
||||
@@ -15123,6 +15313,19 @@ dependencies = [
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap 2.11.4",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial2"
|
||||
version = "0.2.33"
|
||||
@@ -15151,7 +15354,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"derive_more",
|
||||
"derive_more 0.99.20",
|
||||
"ec4rs",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
@@ -15229,8 +15432,10 @@ dependencies = [
|
||||
"menu",
|
||||
"node_runtime",
|
||||
"paths",
|
||||
"picker",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"release_channel",
|
||||
"schemars 1.0.4",
|
||||
"search",
|
||||
"serde",
|
||||
@@ -16816,7 +17021,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix 1.1.2",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -16930,7 +17135,6 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"postage",
|
||||
"rand 0.9.2",
|
||||
"regex",
|
||||
"rope",
|
||||
"smallvec",
|
||||
"sum_tree",
|
||||
@@ -16944,7 +17148,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"derive_more",
|
||||
"derive_more 0.99.20",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
@@ -18149,10 +18353,8 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"component",
|
||||
"editor",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"menu",
|
||||
"picker",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
@@ -18295,6 +18497,12 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -19498,7 +19706,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -20704,6 +20912,7 @@ dependencies = [
|
||||
"cargo_metadata",
|
||||
"cargo_toml",
|
||||
"clap",
|
||||
"gh-workflow",
|
||||
"indoc",
|
||||
"toml 0.8.23",
|
||||
"toml_edit 0.22.27",
|
||||
@@ -20889,7 +21098,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.210.0"
|
||||
version = "0.211.0"
|
||||
dependencies = [
|
||||
"acp_tools",
|
||||
"activity_indicator",
|
||||
@@ -20935,6 +21144,7 @@ dependencies = [
|
||||
"file_finder",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"gh-workflow",
|
||||
"git",
|
||||
"git_hosting_providers",
|
||||
"git_ui",
|
||||
@@ -21463,6 +21673,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"telemetry",
|
||||
"text",
|
||||
"ui",
|
||||
"ui_input",
|
||||
|
||||
29
Cargo.toml
29
Cargo.toml
@@ -13,7 +13,7 @@ members = [
|
||||
"crates/anthropic",
|
||||
"crates/askpass",
|
||||
"crates/assets",
|
||||
"crates/assistant_context",
|
||||
"crates/assistant_text_thread",
|
||||
"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,6 +70,7 @@ members = [
|
||||
"crates/file_finder",
|
||||
"crates/file_icons",
|
||||
"crates/fs",
|
||||
"crates/fs_benchmarks",
|
||||
"crates/fsevent",
|
||||
"crates/fuzzy",
|
||||
"crates/git",
|
||||
@@ -245,7 +246,7 @@ ai_onboarding = { path = "crates/ai_onboarding" }
|
||||
anthropic = { path = "crates/anthropic" }
|
||||
askpass = { path = "crates/askpass" }
|
||||
assets = { path = "crates/assets" }
|
||||
assistant_context = { path = "crates/assistant_context" }
|
||||
assistant_text_thread = { path = "crates/assistant_text_thread" }
|
||||
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
||||
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
|
||||
audio = { path = "crates/audio" }
|
||||
@@ -437,7 +438,7 @@ zlog_settings = { path = "crates/zlog_settings" }
|
||||
# External crates
|
||||
#
|
||||
|
||||
agent-client-protocol = { version = "=0.4.3", features = ["unstable"] }
|
||||
agent-client-protocol = { version = "0.5.0", features = ["unstable"] }
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = "0.25.1-rc1"
|
||||
any_vec = "0.14"
|
||||
@@ -451,7 +452,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.0"
|
||||
async-tar = "0.5.1"
|
||||
async-task = "4.7"
|
||||
async-trait = "0.1"
|
||||
async-tungstenite = "0.31.0"
|
||||
@@ -505,6 +506,7 @@ fork = "0.2.0"
|
||||
futures = "0.3"
|
||||
futures-batch = "0.6.1"
|
||||
futures-lite = "1.13"
|
||||
gh-workflow = "0.8.0"
|
||||
git2 = { version = "0.20.1", default-features = false }
|
||||
globset = "0.4"
|
||||
handlebars = "4.3"
|
||||
@@ -579,14 +581,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 = "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" }
|
||||
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" }
|
||||
portable-pty = "0.9.0"
|
||||
postage = { version = "0.5", features = ["futures-traits"] }
|
||||
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
|
||||
@@ -899,4 +901,5 @@ ignored = [
|
||||
"serde",
|
||||
"component",
|
||||
"documented",
|
||||
"sea-orm-macros",
|
||||
]
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
"find": "buffer_search::Deploy",
|
||||
"ctrl-f": "buffer_search::Deploy",
|
||||
"ctrl-h": "buffer_search::DeployReplace",
|
||||
"ctrl->": "agent::QuoteSelection",
|
||||
"ctrl->": "agent::AddSelectionToThread",
|
||||
"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::QuoteSelection",
|
||||
"ctrl->": "agent::AddSelectionToThread",
|
||||
"ctrl-alt-e": "agent::RemoveAllContext",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-enter": "agent::ContinueThread",
|
||||
@@ -366,7 +366,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "PromptLibrary",
|
||||
"context": "RulesLibrary",
|
||||
"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::ShowSignatureHelp",
|
||||
"ctrl-shift-space": "editor::ShowWordCompletions",
|
||||
"ctrl-.": "editor::ToggleCodeActions",
|
||||
"ctrl-k r": "editor::RevealInFileManager",
|
||||
"ctrl-k p": "editor::CopyPath",
|
||||
@@ -799,7 +799,7 @@
|
||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||
"ctrl-f8": "editor::GoToHunk",
|
||||
"ctrl-shift-f8": "editor::GoToPreviousHunk",
|
||||
"ctrl-i": "assistant::InlineAssist",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
@@ -1080,7 +1080,8 @@
|
||||
{
|
||||
"context": "StashList || (StashList > Picker > Editor)",
|
||||
"bindings": {
|
||||
"ctrl-shift-backspace": "stash_picker::DropStashItem"
|
||||
"ctrl-shift-backspace": "stash_picker::DropStashItem",
|
||||
"ctrl-shift-v": "stash_picker::ShowStashItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1093,7 +1094,7 @@
|
||||
"paste": "terminal::Paste",
|
||||
"shift-insert": "terminal::Paste",
|
||||
"ctrl-shift-v": "terminal::Paste",
|
||||
"ctrl-i": "assistant::InlineAssist",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||
"alt-.": ["terminal::SendText", "\u001b."],
|
||||
@@ -1266,12 +1267,22 @@
|
||||
"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",
|
||||
@@ -1279,5 +1290,13 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
"cmd-\"": "editor::ExpandAllDiffHunks",
|
||||
"cmd-alt-g b": "git::Blame",
|
||||
"cmd-alt-g m": "git::OpenModifiedFiles",
|
||||
"cmd-shift-space": "editor::ShowSignatureHelp",
|
||||
"cmd-i": "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::QuoteSelection",
|
||||
"cmd->": "agent::AddSelectionToThread",
|
||||
"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::QuoteSelection",
|
||||
"cmd->": "agent::AddSelectionToThread",
|
||||
"cmd-alt-e": "agent::RemoveAllContext",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-ctrl-b": "agent::ToggleBurnMode",
|
||||
@@ -423,7 +423,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "PromptLibrary",
|
||||
"context": "RulesLibrary",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-n": "rules_library::NewRule",
|
||||
@@ -864,7 +864,7 @@
|
||||
"cmd-shift-e": "pane::RevealInProjectPanel",
|
||||
"cmd-f8": "editor::GoToHunk",
|
||||
"cmd-shift-f8": "editor::GoToPreviousHunk",
|
||||
"cmd-i": "assistant::InlineAssist",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
@@ -1153,7 +1153,8 @@
|
||||
"context": "StashList || (StashList > Picker > Editor)",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-shift-backspace": "stash_picker::DropStashItem"
|
||||
"ctrl-shift-backspace": "stash_picker::DropStashItem",
|
||||
"ctrl-shift-v": "stash_picker::ShowStashItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1167,7 +1168,7 @@
|
||||
"cmd-a": "editor::SelectAll",
|
||||
"cmd-k": "terminal::Clear",
|
||||
"cmd-n": "workspace::NewTerminal",
|
||||
"cmd-i": "assistant::InlineAssist",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-_": null, // emacs undo
|
||||
// Some nice conveniences
|
||||
"cmd-backspace": ["terminal::SendText", "\u0015"], // ctrl-u: clear line
|
||||
@@ -1371,12 +1372,23 @@
|
||||
"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",
|
||||
@@ -1384,5 +1396,13 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
"ctrl-k z": "editor::ToggleSoftWrap",
|
||||
"ctrl-f": "buffer_search::Deploy",
|
||||
"ctrl-h": "buffer_search::DeployReplace",
|
||||
"ctrl-shift-.": "agent::QuoteSelection",
|
||||
"ctrl-shift-.": "agent::AddSelectionToThread",
|
||||
"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::QuoteSelection",
|
||||
"ctrl-shift-.": "agent::AddSelectionToThread",
|
||||
"shift-alt-e": "agent::RemoveAllContext",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-enter": "agent::ContinueThread",
|
||||
@@ -375,7 +375,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "PromptLibrary",
|
||||
"context": "RulesLibrary",
|
||||
"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::ShowSignatureHelp",
|
||||
"ctrl-shift-space": "editor::ShowWordCompletions",
|
||||
"ctrl-.": "editor::ToggleCodeActions",
|
||||
"ctrl-k r": "editor::RevealInFileManager",
|
||||
"ctrl-k p": "editor::CopyPath",
|
||||
@@ -812,7 +812,7 @@
|
||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||
"ctrl-f8": "editor::GoToHunk",
|
||||
"ctrl-shift-f8": "editor::GoToPreviousHunk",
|
||||
"ctrl-i": "assistant::InlineAssist",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-shift-;": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
@@ -1106,7 +1106,8 @@
|
||||
"context": "StashList || (StashList > Picker > Editor)",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-shift-backspace": "stash_picker::DropStashItem"
|
||||
"ctrl-shift-backspace": "stash_picker::DropStashItem",
|
||||
"ctrl-shift-v": "stash_picker::ShowStashItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1119,7 +1120,7 @@
|
||||
"shift-insert": "terminal::Paste",
|
||||
"ctrl-v": "terminal::Paste",
|
||||
"ctrl-shift-v": "terminal::Paste",
|
||||
"ctrl-i": "assistant::InlineAssist",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||
"alt-.": ["terminal::SendText", "\u001b."],
|
||||
@@ -1294,12 +1295,23 @@
|
||||
"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",
|
||||
@@ -1307,5 +1319,13 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
"bindings": {
|
||||
"ctrl-i": "agent::ToggleFocus",
|
||||
"ctrl-shift-i": "agent::ToggleFocus",
|
||||
"ctrl-shift-l": "agent::QuoteSelection", // In cursor uses "Ask" mode
|
||||
"ctrl-l": "agent::QuoteSelection", // In cursor uses "Agent" mode
|
||||
"ctrl-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
|
||||
"ctrl-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
|
||||
"ctrl-k": "assistant::InlineAssist",
|
||||
"ctrl-shift-k": "assistant::InsertIntoEditor"
|
||||
}
|
||||
|
||||
@@ -8,13 +8,23 @@
|
||||
"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
|
||||
@@ -33,8 +43,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::MoveToNextSubwordEnd", // forward-word
|
||||
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
|
||||
"alt-f": "editor::MoveToNextWordEnd", // forward-word
|
||||
"alt-b": "editor::MoveToPreviousWordStart", // backward-word
|
||||
"alt-u": "editor::ConvertToUpperCase", // upcase-word
|
||||
"alt-l": "editor::ConvertToLowerCase", // downcase-word
|
||||
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
|
||||
@@ -98,7 +108,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::SelectToPreviousSubwordStart",
|
||||
"alt-b": "editor::SelectToPreviousWordStart",
|
||||
"alt-{": "editor::SelectToStartOfParagraph",
|
||||
"alt-}": "editor::SelectToEndOfParagraph",
|
||||
"ctrl-up": "editor::SelectToStartOfParagraph",
|
||||
@@ -126,15 +136,28 @@
|
||||
"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
|
||||
@@ -145,10 +168,19 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
// Workaround to enable using emacs in the Zed terminal.
|
||||
// Workaround to enable using native emacs from 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
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
"bindings": {
|
||||
"cmd-i": "agent::ToggleFocus",
|
||||
"cmd-shift-i": "agent::ToggleFocus",
|
||||
"cmd-shift-l": "agent::QuoteSelection", // In cursor uses "Ask" mode
|
||||
"cmd-l": "agent::QuoteSelection", // In cursor uses "Agent" mode
|
||||
"cmd-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
|
||||
"cmd-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
|
||||
"cmd-k": "assistant::InlineAssist",
|
||||
"cmd-shift-k": "assistant::InsertIntoEditor"
|
||||
}
|
||||
|
||||
@@ -9,13 +9,19 @@
|
||||
"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
|
||||
@@ -34,8 +40,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::MoveToNextSubwordEnd", // forward-word
|
||||
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
|
||||
"alt-f": "editor::MoveToNextWordEnd", // forward-word
|
||||
"alt-b": "editor::MoveToPreviousWordStart", // backward-word
|
||||
"alt-u": "editor::ConvertToUpperCase", // upcase-word
|
||||
"alt-l": "editor::ConvertToLowerCase", // downcase-word
|
||||
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
|
||||
@@ -99,7 +105,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::SelectToPreviousSubwordStart",
|
||||
"alt-b": "editor::SelectToPreviousWordStart",
|
||||
"alt-{": "editor::SelectToStartOfParagraph",
|
||||
"alt-}": "editor::SelectToEndOfParagraph",
|
||||
"ctrl-up": "editor::SelectToStartOfParagraph",
|
||||
@@ -127,15 +133,28 @@
|
||||
"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
|
||||
@@ -146,10 +165,19 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
// Workaround to enable using emacs in the Zed terminal.
|
||||
// Workaround to enable using native emacs from 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
|
||||
|
||||
@@ -422,56 +422,66 @@
|
||||
{
|
||||
"context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
|
||||
"bindings": {
|
||||
";": "vim::HelixCollapseSelection",
|
||||
":": "command_palette::Toggle",
|
||||
"m": "vim::PushHelixMatch",
|
||||
"s": "vim::HelixSelectRegex",
|
||||
"]": ["vim::PushHelixNext", { "around": true }],
|
||||
"[": ["vim::PushHelixPrevious", { "around": true }],
|
||||
"left": "vim::WrappingLeft",
|
||||
"right": "vim::WrappingRight",
|
||||
// Movement
|
||||
"h": "vim::WrappingLeft",
|
||||
"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",
|
||||
"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 }],
|
||||
"shift-p": ["vim::HelixPaste", { "before": true }],
|
||||
">": "vim::Indent",
|
||||
"<": "vim::Outdent",
|
||||
"=": "vim::AutoIndent",
|
||||
"`": "vim::ConvertToLowerCase",
|
||||
"alt-`": "vim::ConvertToUpperCase",
|
||||
"g q": "vim::PushRewrap",
|
||||
"g w": "vim::PushRewrap",
|
||||
"insert": "vim::InsertBefore",
|
||||
"alt-.": "vim::RepeatFind",
|
||||
"d": "vim::HelixDelete",
|
||||
"c": "vim::HelixSubstitute",
|
||||
"alt-c": "vim::HelixSubstituteNoYank",
|
||||
|
||||
// Selection manipulation
|
||||
"s": "vim::HelixSelectRegex",
|
||||
"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 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 h": "vim::StartOfLine",
|
||||
"g l": "vim::EndOfLine",
|
||||
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
|
||||
"g t": "vim::WindowTop",
|
||||
"g c": "vim::WindowMiddle",
|
||||
"g b": "vim::WindowBottom",
|
||||
|
||||
"shift-r": "editor::Paste",
|
||||
"x": "vim::HelixSelectLine",
|
||||
"shift-x": "editor::SelectLine",
|
||||
"%": "editor::SelectAll",
|
||||
"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
|
||||
|
||||
// Window mode
|
||||
"space w h": "workspace::ActivatePaneLeft",
|
||||
"space w l": "workspace::ActivatePaneRight",
|
||||
@@ -482,6 +492,7 @@
|
||||
"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",
|
||||
@@ -492,16 +503,18 @@
|
||||
"space a": "editor::ToggleCodeActions",
|
||||
"space h": "editor::SelectAllMatches",
|
||||
"space c": "editor::ToggleComments",
|
||||
"space y": "editor::Copy",
|
||||
"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"
|
||||
"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",
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -970,7 +983,9 @@
|
||||
"bindings": {
|
||||
"ctrl-h": "editor::Backspace",
|
||||
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
||||
"ctrl-w": "editor::DeleteToPreviousWordStart"
|
||||
"ctrl-w": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-p": "menu::SelectPrevious",
|
||||
"ctrl-n": "menu::SelectNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"$schema": "zed://schemas/settings",
|
||||
/// The displayed name of this project. If not set or empty, the root directory name
|
||||
/// The displayed name of this project. If not set or null, the root directory name
|
||||
/// will be displayed.
|
||||
"project_name": "",
|
||||
"project_name": null,
|
||||
// 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:
|
||||
// * `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
|
||||
// * "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"
|
||||
},
|
||||
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||
// before saving it.
|
||||
@@ -1350,7 +1350,9 @@
|
||||
// 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
|
||||
"cursor_position_button": true,
|
||||
// Whether to show active line endings button in the status bar.
|
||||
"line_endings_button": false
|
||||
},
|
||||
// Settings specific to the terminal
|
||||
"terminal": {
|
||||
@@ -1739,7 +1741,7 @@
|
||||
}
|
||||
},
|
||||
"Kotlin": {
|
||||
"language_servers": ["kotlin-language-server", "!kotlin-lsp", "..."]
|
||||
"language_servers": ["!kotlin-language-server", "kotlin-lsp", "..."]
|
||||
},
|
||||
"LaTeX": {
|
||||
"formatter": "language_server",
|
||||
@@ -1818,10 +1820,11 @@
|
||||
},
|
||||
"SystemVerilog": {
|
||||
"format_on_save": "off",
|
||||
"language_servers": ["!slang", "..."],
|
||||
"use_on_type_format": false
|
||||
},
|
||||
"Vue.js": {
|
||||
"language_servers": ["vue-language-server", "..."],
|
||||
"language_servers": ["vue-language-server", "vtsls", "..."],
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
|
||||
@@ -49,8 +49,9 @@
|
||||
"panel.background": "#3a3735ff",
|
||||
"panel.focused_border": "#83a598ff",
|
||||
"pane.focused_border": null,
|
||||
"scrollbar.thumb.background": "#fbf1c74c",
|
||||
"scrollbar.thumb.hover_background": "#494340ff",
|
||||
"scrollbar.thumb.active_background": "#83a598ac",
|
||||
"scrollbar.thumb.hover_background": "#fbf1c74c",
|
||||
"scrollbar.thumb.background": "#a899844c",
|
||||
"scrollbar.thumb.border": "#494340ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#373432ff",
|
||||
@@ -454,8 +455,9 @@
|
||||
"panel.background": "#393634ff",
|
||||
"panel.focused_border": "#83a598ff",
|
||||
"pane.focused_border": null,
|
||||
"scrollbar.thumb.background": "#fbf1c74c",
|
||||
"scrollbar.thumb.hover_background": "#494340ff",
|
||||
"scrollbar.thumb.active_background": "#83a598ac",
|
||||
"scrollbar.thumb.hover_background": "#fbf1c74c",
|
||||
"scrollbar.thumb.background": "#a899844c",
|
||||
"scrollbar.thumb.border": "#494340ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#343130ff",
|
||||
@@ -859,8 +861,9 @@
|
||||
"panel.background": "#3b3735ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar.thumb.background": "#fbf1c74c",
|
||||
"scrollbar.thumb.hover_background": "#494340ff",
|
||||
"scrollbar.thumb.active_background": "#83a598ac",
|
||||
"scrollbar.thumb.hover_background": "#fbf1c74c",
|
||||
"scrollbar.thumb.background": "#a899844c",
|
||||
"scrollbar.thumb.border": "#494340ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#393634ff",
|
||||
@@ -1264,8 +1267,9 @@
|
||||
"panel.background": "#ecddb4ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar.thumb.background": "#2828284c",
|
||||
"scrollbar.thumb.hover_background": "#ddcca7ff",
|
||||
"scrollbar.thumb.active_background": "#458588ac",
|
||||
"scrollbar.thumb.hover_background": "#2828284c",
|
||||
"scrollbar.thumb.background": "#7c6f644c",
|
||||
"scrollbar.thumb.border": "#ddcca7ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#eee0b7ff",
|
||||
@@ -1669,8 +1673,9 @@
|
||||
"panel.background": "#ecddb5ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar.thumb.background": "#2828284c",
|
||||
"scrollbar.thumb.hover_background": "#ddcca7ff",
|
||||
"scrollbar.thumb.active_background": "#458588ac",
|
||||
"scrollbar.thumb.hover_background": "#2828284c",
|
||||
"scrollbar.thumb.background": "#7c6f644c",
|
||||
"scrollbar.thumb.border": "#ddcca7ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#eee1bbff",
|
||||
@@ -2074,8 +2079,9 @@
|
||||
"panel.background": "#ecdcb3ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar.thumb.background": "#2828284c",
|
||||
"scrollbar.thumb.hover_background": "#ddcca7ff",
|
||||
"scrollbar.thumb.active_background": "#458588ac",
|
||||
"scrollbar.thumb.hover_background": "#2828284c",
|
||||
"scrollbar.thumb.background": "#7c6f644c",
|
||||
"scrollbar.thumb.border": "#ddcca7ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#eddeb5ff",
|
||||
|
||||
@@ -328,7 +328,7 @@ impl ToolCall {
|
||||
location: acp::ToolCallLocation,
|
||||
project: WeakEntity<Project>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Option<AgentLocation> {
|
||||
) -> Option<ResolvedLocation> {
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project
|
||||
@@ -350,17 +350,14 @@ impl ToolCall {
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position,
|
||||
})
|
||||
Some(ResolvedLocation { buffer, position })
|
||||
}
|
||||
|
||||
fn resolve_locations(
|
||||
&self,
|
||||
project: Entity<Project>,
|
||||
cx: &mut App,
|
||||
) -> Task<Vec<Option<AgentLocation>>> {
|
||||
) -> Task<Vec<Option<ResolvedLocation>>> {
|
||||
let locations = self.locations.clone();
|
||||
project.update(cx, |_, cx| {
|
||||
cx.spawn(async move |project, cx| {
|
||||
@@ -374,6 +371,23 @@ 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
|
||||
@@ -1393,35 +1407,46 @@ 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| {
|
||||
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 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
@@ -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 language_registry = self.new_buffer.read(cx).language_registry();
|
||||
let new_buffer = self.new_buffer.read(cx);
|
||||
let language_registry = new_buffer.language_registry();
|
||||
|
||||
let path = self
|
||||
.new_buffer
|
||||
.read(cx)
|
||||
let path = new_buffer
|
||||
.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(
|
||||
0,
|
||||
replica_id,
|
||||
cx.entity_id().as_non_zero_u64().into(),
|
||||
self.new_buffer.read(cx).line_ending(),
|
||||
self.new_buffer.read(cx).as_rope().clone(),
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
use agent_client_protocol as acp;
|
||||
|
||||
use anyhow::Result;
|
||||
use futures::{FutureExt as _, future::Shared};
|
||||
use gpui::{App, AppContext, Context, Entity, Task};
|
||||
use gpui::{App, AppContext, AsyncApp, Context, Entity, Task};
|
||||
use language::LanguageRegistry;
|
||||
use markdown::Markdown;
|
||||
use project::Project;
|
||||
use settings::{Settings as _, SettingsLocation};
|
||||
use std::{path::PathBuf, process::ExitStatus, sync::Arc, time::Instant};
|
||||
use task::Shell;
|
||||
use terminal::terminal_settings::TerminalSettings;
|
||||
use util::get_default_system_shell_preferring_bash;
|
||||
|
||||
pub struct Terminal {
|
||||
id: acp::TerminalId,
|
||||
@@ -170,3 +175,68 @@ 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<i32, Arc<str>>,
|
||||
outgoing_request_methods: HashMap<i32, Arc<str>>,
|
||||
incoming_request_methods: HashMap<acp::RequestId, Arc<str>>,
|
||||
outgoing_request_methods: HashMap<acp::RequestId, Arc<str>>,
|
||||
_task: Task<()>,
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ impl AcpTools {
|
||||
}
|
||||
};
|
||||
|
||||
method_map.insert(id, method.clone());
|
||||
method_map.insert(id.clone(), method.clone());
|
||||
(Some(id), method.into(), MessageType::Request, Ok(params))
|
||||
}
|
||||
acp::StreamMessageContent::Response { id, result } => {
|
||||
@@ -338,6 +338,7 @@ impl AcpTools {
|
||||
.children(
|
||||
message
|
||||
.request_id
|
||||
.as_ref()
|
||||
.map(|req_id| div().child(ui::Chip::new(req_id.to_string()))),
|
||||
),
|
||||
)
|
||||
@@ -389,7 +390,7 @@ impl AcpTools {
|
||||
|
||||
struct WatchedConnectionMessage {
|
||||
name: SharedString,
|
||||
request_id: Option<i32>,
|
||||
request_id: Option<acp::RequestId>,
|
||||
direction: acp::StreamMessageDirection,
|
||||
message_type: MessageType,
|
||||
params: Result<Option<serde_json::Value>, acp::Error>,
|
||||
|
||||
@@ -11,8 +11,7 @@ use language::{
|
||||
LanguageServerStatusUpdate, ServerHealth,
|
||||
};
|
||||
use project::{
|
||||
EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project,
|
||||
ProjectEnvironmentEvent,
|
||||
LanguageServerProgress, LspStoreEvent, Project, ProjectEnvironmentEvent,
|
||||
git_store::{GitStoreEvent, Repository},
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
@@ -327,20 +326,20 @@ impl ActivityIndicator {
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a EnvironmentErrorMessage> {
|
||||
fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a String> {
|
||||
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(error) = self.pending_environment_error(cx) {
|
||||
if let Some(message) = self.pending_environment_error(cx) {
|
||||
return Some(Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::Warning)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: error.0.clone(),
|
||||
message: message.clone(),
|
||||
on_click: Some(Arc::new(move |this, window, cx| {
|
||||
this.project.update(cx, |project, cx| {
|
||||
project.pop_environment_error(cx);
|
||||
|
||||
@@ -10,6 +10,8 @@ path = "src/agent.rs"
|
||||
|
||||
[features]
|
||||
test-support = ["db/test-support"]
|
||||
eval = []
|
||||
edit-agent-eval = []
|
||||
e2e = []
|
||||
|
||||
[lints]
|
||||
@@ -22,7 +24,7 @@ agent-client-protocol.workspace = true
|
||||
agent_servers.workspace = true
|
||||
agent_settings.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_context.workspace = true
|
||||
assistant_text_thread.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
cloud_llm_client.workspace = true
|
||||
@@ -74,7 +76,7 @@ zstd.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
agent_servers = { workspace = true, "features" = ["test-support"] }
|
||||
assistant_context = { workspace = true, "features" = ["test-support"] }
|
||||
assistant_text_thread = { 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,24 +48,10 @@ use util::rel_path::RelPath;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ProjectSnapshot {
|
||||
pub worktree_snapshots: Vec<WorktreeSnapshot>,
|
||||
pub worktree_snapshots: Vec<project::telemetry_snapshot::TelemetryWorktreeSnapshot>,
|
||||
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",
|
||||
@@ -1280,8 +1266,9 @@ mod internal_tests {
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [], cx).await;
|
||||
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 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 agent = NativeAgent::new(
|
||||
project.clone(),
|
||||
history_store,
|
||||
@@ -1341,8 +1328,9 @@ mod internal_tests {
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/", json!({ "a": {} })).await;
|
||||
let project = Project::test(fs.clone(), [], cx).await;
|
||||
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 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 connection = NativeAgentConnection(
|
||||
NativeAgent::new(
|
||||
project.clone(),
|
||||
@@ -1416,8 +1404,9 @@ mod internal_tests {
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [], cx).await;
|
||||
|
||||
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 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));
|
||||
|
||||
// Create the agent and connection
|
||||
let agent = NativeAgent::new(
|
||||
@@ -1488,8 +1477,9 @@ mod internal_tests {
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/a").as_ref()], cx).await;
|
||||
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 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 agent = NativeAgent::new(
|
||||
project.clone(),
|
||||
history_store.clone(),
|
||||
|
||||
@@ -31,7 +31,7 @@ use std::{
|
||||
use util::path;
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "edit-agent-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 = "eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "edit-agent-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 = "eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
|
||||
fn eval_translate_doc_comments() {
|
||||
// Model | Pass rate
|
||||
// ============================================
|
||||
@@ -234,7 +234,7 @@ fn eval_translate_doc_comments() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "edit-agent-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 = "eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
|
||||
fn eval_disable_cursor_blinking() {
|
||||
// Model | Pass rate
|
||||
// ============================================
|
||||
@@ -446,7 +446,7 @@ fn eval_disable_cursor_blinking() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "edit-agent-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 = "eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
|
||||
fn eval_zode() {
|
||||
// Model | Pass rate
|
||||
// ============================================
|
||||
@@ -763,7 +763,7 @@ fn eval_zode() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
|
||||
fn eval_add_overwrite_test() {
|
||||
// Model | Pass rate
|
||||
// ============================================
|
||||
@@ -995,7 +995,7 @@ fn eval_add_overwrite_test() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "eval"), ignore)]
|
||||
#[cfg_attr(not(feature = "edit-agent-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
|
||||
@@ -1490,9 +1490,20 @@ impl EditAgentTest {
|
||||
&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())
|
||||
|
||||
@@ -308,12 +308,13 @@ 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(
|
||||
0,
|
||||
ReplicaId::LOCAL,
|
||||
BufferId::new(1).unwrap(),
|
||||
"Hello world\nThis is a test\nFoo bar baz",
|
||||
);
|
||||
@@ -327,7 +328,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_streaming_exact_match() {
|
||||
let buffer = TextBuffer::new(
|
||||
0,
|
||||
ReplicaId::LOCAL,
|
||||
BufferId::new(1).unwrap(),
|
||||
"Hello world\nThis is a test\nFoo bar baz",
|
||||
);
|
||||
@@ -351,7 +352,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_streaming_fuzzy_match() {
|
||||
let buffer = TextBuffer::new(
|
||||
0,
|
||||
ReplicaId::LOCAL,
|
||||
BufferId::new(1).unwrap(),
|
||||
indoc! {"
|
||||
function foo(a, b) {
|
||||
@@ -385,7 +386,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_incremental_improvement() {
|
||||
let buffer = TextBuffer::new(
|
||||
0,
|
||||
ReplicaId::LOCAL,
|
||||
BufferId::new(1).unwrap(),
|
||||
"Line 1\nLine 2\nLine 3\nLine 4\nLine 5",
|
||||
);
|
||||
@@ -410,7 +411,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_incomplete_lines_buffering() {
|
||||
let buffer = TextBuffer::new(
|
||||
0,
|
||||
ReplicaId::LOCAL,
|
||||
BufferId::new(1).unwrap(),
|
||||
indoc! {"
|
||||
The quick brown fox
|
||||
@@ -437,7 +438,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_multiline_fuzzy_match() {
|
||||
let buffer = TextBuffer::new(
|
||||
0,
|
||||
ReplicaId::LOCAL,
|
||||
BufferId::new(1).unwrap(),
|
||||
indoc! {r#"
|
||||
impl Display for User {
|
||||
@@ -691,7 +692,11 @@ mod tests {
|
||||
}
|
||||
"#};
|
||||
|
||||
let buffer = TextBuffer::new(0, BufferId::new(1).unwrap(), text.to_string());
|
||||
let buffer = TextBuffer::new(
|
||||
ReplicaId::LOCAL,
|
||||
BufferId::new(1).unwrap(),
|
||||
text.to_string(),
|
||||
);
|
||||
let snapshot = buffer.snapshot();
|
||||
let mut matcher = StreamingFuzzyMatcher::new(snapshot.clone());
|
||||
|
||||
@@ -724,7 +729,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(0, BufferId::new(1).unwrap(), text.clone());
|
||||
let buffer = TextBuffer::new(ReplicaId::LOCAL, 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_context::{AssistantContext, SavedContextMetadata};
|
||||
use assistant_text_thread::{SavedTextThreadMetadata, TextThread};
|
||||
use chrono::{DateTime, Utc};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
|
||||
use itertools::Itertools;
|
||||
use paths::contexts_dir;
|
||||
use paths::text_threads_dir;
|
||||
use project::Project;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::VecDeque, path::Path, rc::Rc, sync::Arc, time::Duration};
|
||||
@@ -50,21 +50,23 @@ pub fn load_agent_thread(
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HistoryEntry {
|
||||
AcpThread(DbThreadMetadata),
|
||||
TextThread(SavedContextMetadata),
|
||||
TextThread(SavedTextThreadMetadata),
|
||||
}
|
||||
|
||||
impl HistoryEntry {
|
||||
pub fn updated_at(&self) -> DateTime<Utc> {
|
||||
match self {
|
||||
HistoryEntry::AcpThread(thread) => thread.updated_at,
|
||||
HistoryEntry::TextThread(context) => context.mtime.to_utc(),
|
||||
HistoryEntry::TextThread(text_thread) => text_thread.mtime.to_utc(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> HistoryEntryId {
|
||||
match self {
|
||||
HistoryEntry::AcpThread(thread) => HistoryEntryId::AcpThread(thread.id.clone()),
|
||||
HistoryEntry::TextThread(context) => HistoryEntryId::TextThread(context.path.clone()),
|
||||
HistoryEntry::TextThread(text_thread) => {
|
||||
HistoryEntryId::TextThread(text_thread.path.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,9 +76,9 @@ impl HistoryEntry {
|
||||
id: thread.id.clone(),
|
||||
name: thread.title.to_string(),
|
||||
},
|
||||
HistoryEntry::TextThread(context) => MentionUri::TextThread {
|
||||
path: context.path.as_ref().to_owned(),
|
||||
name: context.title.to_string(),
|
||||
HistoryEntry::TextThread(text_thread) => MentionUri::TextThread {
|
||||
path: text_thread.path.as_ref().to_owned(),
|
||||
name: text_thread.title.to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -90,7 +92,7 @@ impl HistoryEntry {
|
||||
&thread.title
|
||||
}
|
||||
}
|
||||
HistoryEntry::TextThread(context) => &context.title,
|
||||
HistoryEntry::TextThread(text_thread) => &text_thread.title,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,7 +122,7 @@ enum SerializedRecentOpen {
|
||||
pub struct HistoryStore {
|
||||
threads: Vec<DbThreadMetadata>,
|
||||
entries: Vec<HistoryEntry>,
|
||||
text_thread_store: Entity<assistant_context::ContextStore>,
|
||||
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
|
||||
recently_opened_entries: VecDeque<HistoryEntryId>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
_save_recently_opened_entries_task: Task<()>,
|
||||
@@ -128,7 +130,7 @@ pub struct HistoryStore {
|
||||
|
||||
impl HistoryStore {
|
||||
pub fn new(
|
||||
text_thread_store: Entity<assistant_context::ContextStore>,
|
||||
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let subscriptions =
|
||||
@@ -192,16 +194,16 @@ impl HistoryStore {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.text_thread_store
|
||||
.update(cx, |store, cx| store.delete_local_context(path, cx))
|
||||
.update(cx, |store, cx| store.delete_local(path, cx))
|
||||
}
|
||||
|
||||
pub fn load_text_thread(
|
||||
&self,
|
||||
path: Arc<Path>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<AssistantContext>>> {
|
||||
) -> Task<Result<Entity<TextThread>>> {
|
||||
self.text_thread_store
|
||||
.update(cx, |store, cx| store.open_local_context(path, cx))
|
||||
.update(cx, |store, cx| store.open_local(path, cx))
|
||||
}
|
||||
|
||||
pub fn reload(&self, cx: &mut Context<Self>) {
|
||||
@@ -243,7 +245,7 @@ impl HistoryStore {
|
||||
history_entries.extend(
|
||||
self.text_thread_store
|
||||
.read(cx)
|
||||
.unordered_contexts()
|
||||
.unordered_text_threads()
|
||||
.cloned()
|
||||
.map(HistoryEntry::TextThread),
|
||||
);
|
||||
@@ -278,14 +280,14 @@ impl HistoryStore {
|
||||
let context_entries = self
|
||||
.text_thread_store
|
||||
.read(cx)
|
||||
.unordered_contexts()
|
||||
.flat_map(|context| {
|
||||
.unordered_text_threads()
|
||||
.flat_map(|text_thread| {
|
||||
self.recently_opened_entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(index, entry)| match entry {
|
||||
HistoryEntryId::TextThread(path) if &context.path == path => {
|
||||
Some((index, HistoryEntry::TextThread(context.clone())))
|
||||
HistoryEntryId::TextThread(path) if &text_thread.path == path => {
|
||||
Some((index, HistoryEntry::TextThread(text_thread.clone())))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
@@ -347,7 +349,7 @@ impl HistoryStore {
|
||||
acp::SessionId(id.as_str().into()),
|
||||
)),
|
||||
SerializedRecentOpen::TextThread(file_name) => Some(
|
||||
HistoryEntryId::TextThread(contexts_dir().join(file_name).into()),
|
||||
HistoryEntryId::TextThread(text_threads_dir().join(file_name).into()),
|
||||
),
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -2,7 +2,6 @@ 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;
|
||||
@@ -42,7 +41,7 @@ impl AgentServer for NativeAgentServer {
|
||||
) -> Task<
|
||||
Result<(
|
||||
Rc<dyn acp_thread::AgentConnection>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
Option<task::SpawnInTerminal>,
|
||||
)>,
|
||||
> {
|
||||
log::debug!(
|
||||
@@ -68,7 +67,7 @@ impl AgentServer for NativeAgentServer {
|
||||
|
||||
Ok((
|
||||
Rc::new(connection) as Rc<dyn acp_thread::AgentConnection>,
|
||||
HashMap::default(),
|
||||
None,
|
||||
))
|
||||
})
|
||||
}
|
||||
@@ -82,7 +81,7 @@ impl AgentServer for NativeAgentServer {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use assistant_context::ContextStore;
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use gpui::AppContext;
|
||||
|
||||
agent_servers::e2e_tests::common_e2e_tests!(
|
||||
@@ -117,8 +116,9 @@ mod tests {
|
||||
});
|
||||
|
||||
let history = cx.update(|cx| {
|
||||
let context_store = cx.new(move |cx| ContextStore::fake(project.clone(), cx));
|
||||
cx.new(move |cx| HistoryStore::new(context_store, cx))
|
||||
let text_thread_store =
|
||||
cx.new(move |cx| TextThreadStore::fake(project.clone(), cx));
|
||||
cx.new(move |cx| HistoryStore::new(text_thread_store, cx))
|
||||
});
|
||||
|
||||
NativeAgentServer::new(fs.clone(), history)
|
||||
|
||||
@@ -1834,8 +1834,9 @@ 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 context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
|
||||
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));
|
||||
|
||||
// Create agent and connection
|
||||
let agent = NativeAgent::new(
|
||||
@@ -1995,7 +1996,7 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) {
|
||||
locations: vec![],
|
||||
raw_input: Some(json!({})),
|
||||
raw_output: None,
|
||||
meta: None,
|
||||
meta: Some(json!({ "tool_name": "thinking" })),
|
||||
}
|
||||
);
|
||||
let update = expect_tool_call_update_fields(&mut events).await;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use crate::{
|
||||
ContextServerRegistry, CopyPathTool, CreateDirectoryTool, DbLanguageModel, DbThread,
|
||||
DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GitState, GrepTool,
|
||||
DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GrepTool,
|
||||
ListDirectoryTool, MovePathTool, NowTool, OpenTool, ProjectSnapshot, ReadFileTool,
|
||||
SystemPromptTemplate, Template, Templates, TerminalTool, ThinkingTool, WebSearchTool,
|
||||
WorktreeSnapshot,
|
||||
};
|
||||
use acp_thread::{MentionUri, UserMessageId};
|
||||
use action_log::ActionLog;
|
||||
@@ -26,7 +25,6 @@ use futures::{
|
||||
future::Shared,
|
||||
stream::FuturesUnordered,
|
||||
};
|
||||
use git::repository::DiffType;
|
||||
use gpui::{
|
||||
App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,
|
||||
};
|
||||
@@ -37,10 +35,7 @@ use language_model::{
|
||||
LanguageModelToolResultContent, LanguageModelToolSchemaFormat, LanguageModelToolUse,
|
||||
LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage, ZED_CLOUD_PROVIDER_ID,
|
||||
};
|
||||
use project::{
|
||||
Project,
|
||||
git_store::{GitStore, RepositoryState},
|
||||
};
|
||||
use project::Project;
|
||||
use prompt_store::ProjectContext;
|
||||
use schemars::{JsonSchema, Schema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -750,7 +745,13 @@ impl Thread {
|
||||
|
||||
let title = tool.initial_title(tool_use.input.clone(), cx);
|
||||
let kind = tool.kind();
|
||||
stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone());
|
||||
stream.send_tool_call(
|
||||
&tool_use.id,
|
||||
&tool_use.name,
|
||||
title,
|
||||
kind,
|
||||
tool_use.input.clone(),
|
||||
);
|
||||
|
||||
let output = tool_result
|
||||
.as_ref()
|
||||
@@ -880,101 +881,17 @@ impl Thread {
|
||||
project: Entity<Project>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Arc<ProjectSnapshot>> {
|
||||
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();
|
||||
|
||||
let task = project::telemetry_snapshot::TelemetrySnapshot::new(&project, cx);
|
||||
cx.spawn(async move |_, _| {
|
||||
let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
|
||||
let snapshot = task.await;
|
||||
|
||||
Arc::new(ProjectSnapshot {
|
||||
worktree_snapshots,
|
||||
worktree_snapshots: snapshot.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
|
||||
}
|
||||
@@ -1133,14 +1050,18 @@ impl Thread {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn latest_token_usage(&self) -> Option<acp_thread::TokenUsage> {
|
||||
pub fn latest_request_token_usage(&self) -> Option<language_model::TokenUsage> {
|
||||
let last_user_message = self.last_user_message()?;
|
||||
let tokens = self.request_token_usage.get(&last_user_message.id)?;
|
||||
let model = self.model.clone()?;
|
||||
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: tokens.total_tokens(),
|
||||
used_tokens: usage.total_tokens(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1183,6 +1104,14 @@ 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>,
|
||||
@@ -1550,7 +1479,13 @@ impl Thread {
|
||||
});
|
||||
|
||||
if push_new_tool_use {
|
||||
event_stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone());
|
||||
event_stream.send_tool_call(
|
||||
&tool_use.id,
|
||||
&tool_use.name,
|
||||
title,
|
||||
kind,
|
||||
tool_use.input.clone(),
|
||||
);
|
||||
last_message
|
||||
.content
|
||||
.push(AgentMessageContent::ToolUse(tool_use.clone()));
|
||||
@@ -1922,7 +1857,7 @@ impl Thread {
|
||||
.tools
|
||||
.iter()
|
||||
.filter_map(|(tool_name, tool)| {
|
||||
if tool.supported_provider(&model.provider_id())
|
||||
if tool.supports_provider(&model.provider_id())
|
||||
&& profile.is_tool_enabled(tool_name)
|
||||
{
|
||||
Some((truncate(tool_name), tool.clone()))
|
||||
@@ -2198,7 +2133,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 supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
|
||||
fn supports_provider(_provider: &LanguageModelProviderId) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -2239,7 +2174,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 supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
|
||||
fn supports_provider(&self, _provider: &LanguageModelProviderId) -> bool {
|
||||
true
|
||||
}
|
||||
fn run(
|
||||
@@ -2284,8 +2219,8 @@ where
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
fn supported_provider(&self, provider: &LanguageModelProviderId) -> bool {
|
||||
self.0.supported_provider(provider)
|
||||
fn supports_provider(&self, provider: &LanguageModelProviderId) -> bool {
|
||||
T::supports_provider(provider)
|
||||
}
|
||||
|
||||
fn run(
|
||||
@@ -2345,6 +2280,7 @@ impl ThreadEventStream {
|
||||
fn send_tool_call(
|
||||
&self,
|
||||
id: &LanguageModelToolUseId,
|
||||
tool_name: &str,
|
||||
title: SharedString,
|
||||
kind: acp::ToolKind,
|
||||
input: serde_json::Value,
|
||||
@@ -2352,6 +2288,7 @@ impl ThreadEventStream {
|
||||
self.0
|
||||
.unbounded_send(Ok(ThreadEvent::ToolCall(Self::initial_tool_call(
|
||||
id,
|
||||
tool_name,
|
||||
title.to_string(),
|
||||
kind,
|
||||
input,
|
||||
@@ -2361,12 +2298,15 @@ 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: None,
|
||||
meta: Some(serde_json::json!({
|
||||
"tool_name": tool_name
|
||||
})),
|
||||
id: acp::ToolCallId(id.to_string().into()),
|
||||
title,
|
||||
kind,
|
||||
|
||||
@@ -40,13 +40,19 @@ pub use web_search_tool::*;
|
||||
macro_rules! tools {
|
||||
($($tool:ty),* $(,)?) => {
|
||||
/// A list of all built-in tool names
|
||||
pub fn built_in_tool_names() -> impl Iterator<Item = String> {
|
||||
pub fn supported_built_in_tool_names(provider: Option<language_model::LanguageModelProviderId>) -> impl Iterator<Item = String> {
|
||||
[
|
||||
$(
|
||||
<$tool>::name().to_string(),
|
||||
(if let Some(provider) = provider.as_ref() {
|
||||
<$tool>::supports_provider(provider)
|
||||
} else {
|
||||
true
|
||||
})
|
||||
.then(|| <$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 supported_provider(&self, provider: &LanguageModelProviderId) -> bool {
|
||||
fn supports_provider(provider: &LanguageModelProviderId) -> bool {
|
||||
provider == &ZED_CLOUD_PROVIDER_ID
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,7 @@ use futures::io::BufReader;
|
||||
use project::Project;
|
||||
use project::agent_server_store::AgentServerCommand;
|
||||
use serde::Deserialize;
|
||||
use settings::{Settings as _, SettingsLocation};
|
||||
use task::Shell;
|
||||
use util::{ResultExt as _, get_default_system_shell_preferring_bash};
|
||||
use util::ResultExt as _;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{any::Any, cell::RefCell};
|
||||
@@ -23,7 +21,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, TerminalSettings};
|
||||
use terminal::terminal_settings::{AlternateScroll, CursorShape};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Unsupported version")]
|
||||
@@ -40,7 +38,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<()>>,
|
||||
_io_task: Task<Result<(), acp::Error>>,
|
||||
_wait_task: Task<Result<()>>,
|
||||
_stderr_task: Task<Result<()>>,
|
||||
}
|
||||
@@ -816,62 +814,18 @@ 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 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?;
|
||||
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?;
|
||||
|
||||
// Register with renderer
|
||||
let terminal_entity = thread.update(&mut self.cx.clone(), |thread, cx| {
|
||||
|
||||
@@ -73,12 +73,7 @@ pub trait AgentServer: Send {
|
||||
root_dir: Option<&Path>,
|
||||
delegate: AgentServerDelegate,
|
||||
cx: &mut App,
|
||||
) -> Task<
|
||||
Result<(
|
||||
Rc<dyn AgentConnection>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
)>,
|
||||
>;
|
||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>>;
|
||||
|
||||
fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
|
||||
}
|
||||
@@ -89,30 +84,6 @@ 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,11 +7,10 @@ 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::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use acp_thread::AgentConnection;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -61,12 +60,7 @@ impl AgentServer for ClaudeCode {
|
||||
root_dir: Option<&Path>,
|
||||
delegate: AgentServerDelegate,
|
||||
cx: &mut App,
|
||||
) -> Task<
|
||||
Result<(
|
||||
Rc<dyn AgentConnection>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
)>,
|
||||
> {
|
||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<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();
|
||||
@@ -75,7 +69,7 @@ impl AgentServer for ClaudeCode {
|
||||
let default_mode = self.default_mode(cx);
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let (command, root_dir, auth_commands) = store
|
||||
let (command, root_dir, login) = store
|
||||
.update(cx, |store, cx| {
|
||||
let agent = store
|
||||
.get_external_agent(&CLAUDE_CODE_NAME.into())
|
||||
@@ -98,7 +92,7 @@ impl AgentServer for ClaudeCode {
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
Ok((connection, auth_commands))
|
||||
Ok((connection, login))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -106,21 +100,3 @@ 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,13 +5,12 @@ 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::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Codex;
|
||||
@@ -62,12 +61,7 @@ impl AgentServer for Codex {
|
||||
root_dir: Option<&Path>,
|
||||
delegate: AgentServerDelegate,
|
||||
cx: &mut App,
|
||||
) -> Task<
|
||||
Result<(
|
||||
Rc<dyn AgentConnection>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
)>,
|
||||
> {
|
||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<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();
|
||||
@@ -76,7 +70,7 @@ impl AgentServer for Codex {
|
||||
let default_mode = self.default_mode(cx);
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let (command, root_dir, auth_commands) = store
|
||||
let (command, root_dir, login) = store
|
||||
.update(cx, |store, cx| {
|
||||
let agent = store
|
||||
.get_external_agent(&CODEX_NAME.into())
|
||||
@@ -102,7 +96,7 @@ impl AgentServer for Codex {
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
Ok((connection, auth_commands))
|
||||
Ok((connection, login))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -110,21 +104,3 @@ 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,13 +1,12 @@
|
||||
use crate::{AcpAgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use crate::{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::{any::Any, path::Path, rc::Rc, sync::Arc};
|
||||
use std::{path::Path, rc::Rc, sync::Arc};
|
||||
use ui::IconName;
|
||||
|
||||
/// A generic agent server implementation for custom user-defined agents
|
||||
@@ -66,12 +65,7 @@ impl crate::AgentServer for CustomAgentServer {
|
||||
root_dir: Option<&Path>,
|
||||
delegate: AgentServerDelegate,
|
||||
cx: &mut App,
|
||||
) -> Task<
|
||||
Result<(
|
||||
Rc<dyn AgentConnection>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
)>,
|
||||
> {
|
||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<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();
|
||||
@@ -80,7 +74,7 @@ impl crate::AgentServer for CustomAgentServer {
|
||||
let extra_env = load_proxy_env(cx);
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let (command, root_dir, auth_commands) = store
|
||||
let (command, root_dir, login) = store
|
||||
.update(cx, |store, cx| {
|
||||
let agent = store
|
||||
.get_external_agent(&ExternalAgentServerName(name.clone()))
|
||||
@@ -105,29 +99,11 @@ impl crate::AgentServer for CustomAgentServer {
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
Ok((connection, auth_commands))
|
||||
Ok((connection, login))
|
||||
})
|
||||
}
|
||||
|
||||
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||
fn into_any(self: Rc<Self>) -> Rc<dyn std::any::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,10 +1,9 @@
|
||||
use std::rc::Rc;
|
||||
use std::{any::Any, path::Path};
|
||||
|
||||
use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use crate::{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;
|
||||
@@ -30,12 +29,7 @@ impl AgentServer for Gemini {
|
||||
root_dir: Option<&Path>,
|
||||
delegate: AgentServerDelegate,
|
||||
cx: &mut App,
|
||||
) -> Task<
|
||||
Result<(
|
||||
Rc<dyn AgentConnection>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
)>,
|
||||
> {
|
||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<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();
|
||||
@@ -53,7 +47,7 @@ impl AgentServer for Gemini {
|
||||
{
|
||||
extra_env.insert("GEMINI_API_KEY".into(), api_key);
|
||||
}
|
||||
let (command, root_dir, auth_commands) = store
|
||||
let (command, root_dir, login) = store
|
||||
.update(cx, |store, cx| {
|
||||
let agent = store
|
||||
.get_external_agent(&GEMINI_NAME.into())
|
||||
@@ -77,7 +71,7 @@ impl AgentServer for Gemini {
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
Ok((connection, auth_commands))
|
||||
Ok((connection, login))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -86,26 +80,6 @@ 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_context.workspace = true
|
||||
assistant_text_thread.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_context = { workspace = true, features = ["test-support"] }
|
||||
assistant_text_thread = { workspace = true, features = ["test-support"] }
|
||||
buffer_diff = { workspace = true, features = ["test-support"] }
|
||||
db = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -402,7 +402,7 @@ mod tests {
|
||||
use agent::HistoryStore;
|
||||
use agent_client_protocol as acp;
|
||||
use agent_settings::AgentSettings;
|
||||
use assistant_context::ContextStore;
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
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 context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, 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 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, InlayId,
|
||||
EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, Inlay,
|
||||
MultiBuffer, ToOffset,
|
||||
actions::Paste,
|
||||
display_map::{Crease, CreaseId, FoldId, Inlay},
|
||||
display_map::{Crease, CreaseId, FoldId},
|
||||
};
|
||||
use futures::{
|
||||
FutureExt as _,
|
||||
@@ -29,7 +29,8 @@ use language::{Buffer, Language, language_settings::InlayHintKind};
|
||||
use language_model::LanguageModelImage;
|
||||
use postage::stream::Stream as _;
|
||||
use project::{
|
||||
CompletionIntent, InlayHint, InlayHintLabel, Project, ProjectItem, ProjectPath, Worktree,
|
||||
CompletionIntent, InlayHint, InlayHintLabel, InlayId, Project, ProjectItem, ProjectPath,
|
||||
Worktree,
|
||||
};
|
||||
use prompt_store::{PromptId, PromptStore};
|
||||
use rope::Point;
|
||||
@@ -75,7 +76,7 @@ pub enum MessageEditorEvent {
|
||||
|
||||
impl EventEmitter<MessageEditorEvent> for MessageEditor {}
|
||||
|
||||
const COMMAND_HINT_INLAY_ID: u32 = 0;
|
||||
const COMMAND_HINT_INLAY_ID: InlayId = InlayId::Hint(0);
|
||||
|
||||
impl MessageEditor {
|
||||
pub fn new(
|
||||
@@ -151,7 +152,7 @@ impl MessageEditor {
|
||||
let has_new_hint = !new_hints.is_empty();
|
||||
editor.splice_inlays(
|
||||
if has_hint {
|
||||
&[InlayId::Hint(COMMAND_HINT_INLAY_ID)]
|
||||
&[COMMAND_HINT_INLAY_ID]
|
||||
} else {
|
||||
&[]
|
||||
},
|
||||
@@ -628,12 +629,12 @@ impl MessageEditor {
|
||||
path: PathBuf,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Mention>> {
|
||||
let context = self.history_store.update(cx, |store, cx| {
|
||||
let text_thread_task = self.history_store.update(cx, |store, cx| {
|
||||
store.load_text_thread(path.as_path().into(), cx)
|
||||
});
|
||||
cx.spawn(async move |_, cx| {
|
||||
let context = context.await?;
|
||||
let xml = context.update(cx, |context, cx| context.to_xml(cx))?;
|
||||
let text_thread = text_thread_task.await?;
|
||||
let xml = text_thread.update(cx, |text_thread, cx| text_thread.to_xml(cx))?;
|
||||
Ok(Mention::Text {
|
||||
content: xml,
|
||||
tracked_buffers: Vec::new(),
|
||||
@@ -1590,7 +1591,7 @@ mod tests {
|
||||
use acp_thread::MentionUri;
|
||||
use agent::{HistoryStore, outline};
|
||||
use agent_client_protocol as acp;
|
||||
use assistant_context::ContextStore;
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use editor::{AnchorRangeExt as _, Editor, EditorMode};
|
||||
use fs::FakeFs;
|
||||
use futures::StreamExt as _;
|
||||
@@ -1621,8 +1622,8 @@ mod tests {
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, 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| {
|
||||
@@ -1726,8 +1727,8 @@ mod tests {
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, 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 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![]));
|
||||
@@ -1890,8 +1891,8 @@ mod tests {
|
||||
|
||||
let mut cx = VisualTestContext::from_window(*window, cx);
|
||||
|
||||
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, 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 prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
|
||||
let available_commands = Rc::new(RefCell::new(vec![
|
||||
acp::AvailableCommand {
|
||||
@@ -2130,8 +2131,8 @@ mod tests {
|
||||
opened_editors.push(buffer);
|
||||
}
|
||||
|
||||
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, 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 prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
|
||||
|
||||
let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||
@@ -2657,8 +2658,8 @@ mod tests {
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
|
||||
let history_store = cx.new(|cx| HistoryStore::new(context_store, 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| {
|
||||
|
||||
@@ -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,10 +205,9 @@ impl Render for ModeSelector {
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(Label::new("Cycle Through Modes"))
|
||||
.children(KeyBinding::for_action_in(
|
||||
.child(KeyBinding::for_action_in(
|
||||
&CycleModeSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
@@ -217,10 +216,9 @@ impl Render for ModeSelector {
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(Label::new("Toggle Mode Menu"))
|
||||
.children(KeyBinding::for_action_in(
|
||||
.child(KeyBinding::for_action_in(
|
||||
&ToggleProfileSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
|
||||
@@ -77,14 +77,8 @@ 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,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, 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(context) => self.history_store.update(cx, |this, cx| {
|
||||
this.delete_text_thread(context.path.clone(), cx)
|
||||
HistoryEntry::TextThread(text_thread) => self.history_store.update(cx, |this, cx| {
|
||||
this.delete_text_thread(text_thread.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, window, cx)
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::for_action("Delete", &RemoveSelectedThread, 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, window, cx)
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
|
||||
})
|
||||
.on_click({
|
||||
let thread_view = self.thread_view.clone();
|
||||
@@ -635,12 +635,12 @@ impl RenderOnce for AcpHistoryEntryElement {
|
||||
});
|
||||
}
|
||||
}
|
||||
HistoryEntry::TextThread(context) => {
|
||||
HistoryEntry::TextThread(text_thread) => {
|
||||
if let Some(panel) = workspace.read(cx).panel::<AgentPanel>(cx) {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel
|
||||
.open_saved_text_thread(
|
||||
context.path.clone(),
|
||||
text_thread.path.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -260,11 +260,10 @@ 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,
|
||||
auth_commands: HashMap<String, task::SpawnInTerminal>,
|
||||
login: Option<task::SpawnInTerminal>,
|
||||
history_store: Entity<HistoryStore>,
|
||||
hovered_recent_history_item: Option<usize>,
|
||||
entry_view_state: Entity<EntryViewState>,
|
||||
@@ -404,38 +403,8 @@ 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,
|
||||
@@ -447,7 +416,7 @@ impl AcpThreadView {
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
auth_commands: HashMap::default(),
|
||||
login: None,
|
||||
message_editor,
|
||||
model_selector: None,
|
||||
profile_selector: None,
|
||||
@@ -540,9 +509,8 @@ 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, auth_commands)) => {
|
||||
this.update(cx, |this, _| this.auth_commands = auth_commands)
|
||||
.ok();
|
||||
Ok((connection, login)) => {
|
||||
this.update(cx, |this, _| this.login = login).ok();
|
||||
connection
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -1083,52 +1051,20 @@ impl AcpThreadView {
|
||||
|
||||
let text = self.message_editor.read(cx).text(cx);
|
||||
let text = text.trim();
|
||||
|
||||
// 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 {
|
||||
if text == "/login" || text == "/logout" {
|
||||
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.auth_commands.is_empty();
|
||||
|
||||
let can_login = !connection.auth_methods().is_empty() || self.login.is_some();
|
||||
// Does the agent have a specific logout command? Prefer that in case they need to reset internal state.
|
||||
let logout_supported = is_logout_command
|
||||
let logout_supported = text == "/logout"
|
||||
&& self
|
||||
.available_commands
|
||||
.borrow()
|
||||
.iter()
|
||||
.any(|command| command_name == Some(command.name.as_str()));
|
||||
|
||||
.any(|command| command.name == "logout");
|
||||
if can_login && !logout_supported {
|
||||
self.message_editor
|
||||
.update(cx, |editor, cx| editor.clear(window, cx));
|
||||
@@ -1323,6 +1259,7 @@ 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(())
|
||||
})
|
||||
@@ -1485,39 +1422,25 @@ impl AcpThreadView {
|
||||
AcpThreadEvent::AvailableCommandsUpdated(available_commands) => {
|
||||
let mut available_commands = available_commands.clone();
|
||||
|
||||
// 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,
|
||||
});
|
||||
}
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
self.available_commands.replace(available_commands);
|
||||
@@ -1639,7 +1562,10 @@ impl AcpThreadView {
|
||||
self.thread_error.take();
|
||||
configuration_view.take();
|
||||
pending_auth_method.replace(method.clone());
|
||||
let authenticate = if let Some(login) = self.auth_commands.get(method.0.as_ref()).cloned() {
|
||||
let authenticate = if (method.0.as_ref() == "claude-login"
|
||||
|| method.0.as_ref() == "spawn-gemini-cli")
|
||||
&& let Some(login) = self.login.clone()
|
||||
{
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
Self::spawn_external_agent_login(login, workspace, false, window, cx)
|
||||
} else {
|
||||
@@ -2232,7 +2158,6 @@ impl AcpThreadView {
|
||||
options,
|
||||
entry_ix,
|
||||
tool_call.id.clone(),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.into_any(),
|
||||
@@ -2633,7 +2558,6 @@ 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| {
|
||||
@@ -2690,7 +2614,7 @@ impl AcpThreadView {
|
||||
seen_kinds.push(option.kind);
|
||||
|
||||
this.key_binding(
|
||||
KeyBinding::for_action_in(action, &self.focus_handle, window, cx)
|
||||
KeyBinding::for_action_in(action, &self.focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
})
|
||||
@@ -2871,12 +2795,11 @@ 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,
|
||||
)
|
||||
})
|
||||
@@ -3177,7 +3100,7 @@ impl AcpThreadView {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_recent_history(&self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
fn render_recent_history(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
let render_history = self
|
||||
.agent
|
||||
.clone()
|
||||
@@ -3206,7 +3129,6 @@ impl AcpThreadView {
|
||||
KeyBinding::for_action_in(
|
||||
&OpenHistory,
|
||||
&self.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
@@ -3345,34 +3267,48 @@ impl AcpThreadView {
|
||||
})
|
||||
.children(connection.auth_methods().iter().enumerate().rev().map(
|
||||
|(ix, method)| {
|
||||
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
|
||||
);
|
||||
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())
|
||||
};
|
||||
|
||||
this.authenticate(method_id.clone(), window, cx)
|
||||
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,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
)),
|
||||
)
|
||||
@@ -3520,7 +3456,6 @@ impl AcpThreadView {
|
||||
&changed_buffers,
|
||||
self.edits_expanded,
|
||||
pending_edits,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.when(self.edits_expanded, |parent| {
|
||||
@@ -3680,7 +3615,6 @@ 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.";
|
||||
@@ -3756,12 +3690,11 @@ 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,
|
||||
)
|
||||
}
|
||||
@@ -3779,13 +3712,8 @@ impl AcpThreadView {
|
||||
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
|
||||
})
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&RejectAll,
|
||||
&focus_handle.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
KeyBinding::for_action_in(&RejectAll, &focus_handle.clone(), cx)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.reject_all(&RejectAll, window, cx);
|
||||
@@ -3799,7 +3727,7 @@ impl AcpThreadView {
|
||||
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
|
||||
})
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&KeepAll, &focus_handle, window, cx)
|
||||
KeyBinding::for_action_in(&KeepAll, &focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
@@ -4029,12 +3957,11 @@ 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,
|
||||
)
|
||||
}
|
||||
@@ -4259,8 +4186,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, window, cx)
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::for_action("Stop Generation", &editor::actions::Cancel, cx)
|
||||
})
|
||||
.on_click(cx.listener(|this, _event, _, cx| this.cancel_generation(cx)))
|
||||
.into_any_element()
|
||||
@@ -4282,7 +4209,7 @@ impl AcpThreadView {
|
||||
this.icon_color(Color::Accent)
|
||||
}
|
||||
})
|
||||
.tooltip(move |window, cx| Tooltip::for_action(send_btn_tooltip, &Chat, window, cx))
|
||||
.tooltip(move |_window, cx| Tooltip::for_action(send_btn_tooltip, &Chat, cx))
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.send(window, cx);
|
||||
}))
|
||||
@@ -4343,15 +4270,14 @@ 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, window, cx)
|
||||
Tooltip::for_action(tooltip_label.clone(), &Follow, cx)
|
||||
} else {
|
||||
Tooltip::with_meta(
|
||||
tooltip_label.clone(),
|
||||
Some(&Follow),
|
||||
"Track the agent's location as it reads and edits files.",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -5140,7 +5066,7 @@ impl AcpThreadView {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_thread_error(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
|
||||
fn render_thread_error(&self, 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),
|
||||
@@ -5151,9 +5077,7 @@ impl AcpThreadView {
|
||||
ThreadError::ModelRequestLimitReached(plan) => {
|
||||
self.render_model_request_limit_reached_error(*plan, cx)
|
||||
}
|
||||
ThreadError::ToolUseLimitReached => {
|
||||
self.render_tool_use_limit_reached_error(window, cx)?
|
||||
}
|
||||
ThreadError::ToolUseLimitReached => self.render_tool_use_limit_reached_error(cx)?,
|
||||
};
|
||||
|
||||
Some(div().child(content))
|
||||
@@ -5344,11 +5268,7 @@ impl AcpThreadView {
|
||||
.dismiss_action(self.dismiss_error_button(cx))
|
||||
}
|
||||
|
||||
fn render_tool_use_limit_reached_error(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Callout> {
|
||||
fn render_tool_use_limit_reached_error(&self, cx: &mut Context<Self>) -> Option<Callout> {
|
||||
let thread = self.as_native_thread(cx)?;
|
||||
let supports_burn_mode = thread
|
||||
.read(cx)
|
||||
@@ -5375,7 +5295,6 @@ impl AcpThreadView {
|
||||
KeyBinding::for_action_in(
|
||||
&ContinueWithBurnMode,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
@@ -5399,13 +5318,8 @@ impl AcpThreadView {
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&ContinueThread,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
KeyBinding::for_action_in(&ContinueThread, &focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(10.))),
|
||||
)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.resume_chat(cx);
|
||||
@@ -5500,9 +5414,11 @@ impl AcpThreadView {
|
||||
HistoryEntry::AcpThread(thread) => self.history_store.update(cx, |history, cx| {
|
||||
history.delete_thread(thread.id.clone(), cx)
|
||||
}),
|
||||
HistoryEntry::TextThread(context) => self.history_store.update(cx, |history, cx| {
|
||||
history.delete_text_thread(context.path.clone(), cx)
|
||||
}),
|
||||
HistoryEntry::TextThread(text_thread) => {
|
||||
self.history_store.update(cx, |history, cx| {
|
||||
history.delete_text_thread(text_thread.path.clone(), cx)
|
||||
})
|
||||
}
|
||||
};
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
@@ -5581,7 +5497,7 @@ impl Render for AcpThreadView {
|
||||
.into_any(),
|
||||
ThreadState::Loading { .. } => v_flex()
|
||||
.flex_1()
|
||||
.child(self.render_recent_history(window, cx))
|
||||
.child(self.render_recent_history(cx))
|
||||
.into_any(),
|
||||
ThreadState::LoadError(e) => v_flex()
|
||||
.flex_1()
|
||||
@@ -5612,8 +5528,7 @@ impl Render for AcpThreadView {
|
||||
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
|
||||
.into_any()
|
||||
} else {
|
||||
this.child(self.render_recent_history(window, cx))
|
||||
.into_any()
|
||||
this.child(self.render_recent_history(cx)).into_any()
|
||||
}
|
||||
}),
|
||||
})
|
||||
@@ -5637,7 +5552,7 @@ impl Render for AcpThreadView {
|
||||
Vec::<Empty>::new()
|
||||
}
|
||||
})
|
||||
.children(self.render_thread_error(window, cx))
|
||||
.children(self.render_thread_error(cx))
|
||||
.when_some(
|
||||
self.new_server_version_available.as_ref().filter(|_| {
|
||||
!has_messages || !matches!(self.thread_state, ThreadState::Ready { .. })
|
||||
@@ -5822,7 +5737,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_context::ContextStore;
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use editor::EditorSettings;
|
||||
use fs::FakeFs;
|
||||
use gpui::{EventEmitter, SemanticVersion, TestAppContext, VisualTestContext};
|
||||
@@ -5985,10 +5900,10 @@ pub(crate) mod tests {
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let context_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
|
||||
let text_thread_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
|
||||
let history_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx)));
|
||||
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(text_thread_store, cx)));
|
||||
|
||||
let thread_view = cx.update(|window, cx| {
|
||||
cx.new(|cx| {
|
||||
@@ -6094,13 +6009,8 @@ pub(crate) mod tests {
|
||||
_root_dir: Option<&Path>,
|
||||
_delegate: AgentServerDelegate,
|
||||
_cx: &mut App,
|
||||
) -> Task<
|
||||
gpui::Result<(
|
||||
Rc<dyn AgentConnection>,
|
||||
HashMap<String, task::SpawnInTerminal>,
|
||||
)>,
|
||||
> {
|
||||
Task::ready(Ok((Rc::new(self.connection.clone()), HashMap::default())))
|
||||
) -> Task<gpui::Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
||||
Task::ready(Ok((Rc::new(self.connection.clone()), None)))
|
||||
}
|
||||
|
||||
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||
@@ -6262,10 +6172,10 @@ pub(crate) mod tests {
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let context_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
|
||||
let text_thread_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
|
||||
let history_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx)));
|
||||
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(text_thread_store, cx)));
|
||||
|
||||
let connection = Rc::new(StubAgentConnection::new());
|
||||
let thread_view = cx.update(|window, 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::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -33,9 +33,9 @@ impl LlmCompatibleProvider {
|
||||
}
|
||||
|
||||
struct AddLlmProviderInput {
|
||||
provider_name: Entity<SingleLineInput>,
|
||||
api_url: Entity<SingleLineInput>,
|
||||
api_key: Entity<SingleLineInput>,
|
||||
provider_name: Entity<InputField>,
|
||||
api_url: Entity<InputField>,
|
||||
api_key: Entity<InputField>,
|
||||
models: Vec<ModelInput>,
|
||||
}
|
||||
|
||||
@@ -76,10 +76,10 @@ struct ModelCapabilityToggles {
|
||||
}
|
||||
|
||||
struct ModelInput {
|
||||
name: Entity<SingleLineInput>,
|
||||
max_completion_tokens: Entity<SingleLineInput>,
|
||||
max_output_tokens: Entity<SingleLineInput>,
|
||||
max_tokens: Entity<SingleLineInput>,
|
||||
name: Entity<InputField>,
|
||||
max_completion_tokens: Entity<InputField>,
|
||||
max_output_tokens: Entity<InputField>,
|
||||
max_tokens: Entity<InputField>,
|
||||
capabilities: ModelCapabilityToggles,
|
||||
}
|
||||
|
||||
@@ -171,9 +171,9 @@ fn single_line_input(
|
||||
text: Option<&str>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<SingleLineInput> {
|
||||
) -> Entity<InputField> {
|
||||
cx.new(|cx| {
|
||||
let input = SingleLineInput::new(window, cx, placeholder).label(label);
|
||||
let input = InputField::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,7 +484,6 @@ impl Render for AddLlmProviderModal {
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
@@ -499,7 +498,6 @@ impl Render for AddLlmProviderModal {
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
@@ -757,12 +755,7 @@ mod tests {
|
||||
models: Vec<(&str, &str, &str, &str)>,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> Option<SharedString> {
|
||||
fn set_text(
|
||||
input: &Entity<SingleLineInput>,
|
||||
text: &str,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
fn set_text(input: &Entity<InputField>, 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, window: &mut Window, cx: &mut Context<Self>) -> ModalFooter {
|
||||
fn render_modal_footer(&self, cx: &mut Context<Self>) -> ModalFooter {
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
let is_connecting = matches!(self.state, State::Waiting);
|
||||
|
||||
@@ -584,12 +584,11 @@ 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,
|
||||
)
|
||||
}
|
||||
@@ -616,7 +615,7 @@ impl ConfigureContextServerModal {
|
||||
},
|
||||
)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, window, cx)
|
||||
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(
|
||||
@@ -634,7 +633,7 @@ impl ConfigureContextServerModal {
|
||||
)
|
||||
.disabled(is_connecting)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, window, cx)
|
||||
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(
|
||||
@@ -709,7 +708,7 @@ impl Render for ConfigureContextServerModal {
|
||||
State::Error(error) => Self::render_modal_error(error.clone()),
|
||||
}),
|
||||
)
|
||||
.footer(self.render_modal_footer(window, cx)),
|
||||
.footer(self.render_modal_footer(cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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::*,
|
||||
@@ -96,6 +97,7 @@ 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,
|
||||
}
|
||||
@@ -109,9 +111,14 @@ 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, context_server_registry, window, cx);
|
||||
let mut this = Self::new(fs, active_model, context_server_registry, window, cx);
|
||||
|
||||
if let Some(profile_id) = action.customize_tools.clone() {
|
||||
this.configure_builtin_tools(profile_id, window, cx);
|
||||
@@ -125,6 +132,7 @@ 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>,
|
||||
@@ -133,6 +141,7 @@ impl ManageProfilesModal {
|
||||
|
||||
Self {
|
||||
fs,
|
||||
active_model,
|
||||
context_server_registry,
|
||||
focus_handle,
|
||||
mode: Mode::choose_profile(window, cx),
|
||||
@@ -228,9 +237,11 @@ 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::built_in_tool_names()
|
||||
.map(|s| s.into())
|
||||
.collect::<Vec<_>>(),
|
||||
agent::supported_built_in_tool_names(
|
||||
self.active_model.as_ref().map(|model| model.provider_id()),
|
||||
)
|
||||
.map(|s| s.into())
|
||||
.collect::<Vec<_>>(),
|
||||
self.fs.clone(),
|
||||
profile_id.clone(),
|
||||
profile,
|
||||
@@ -341,10 +352,9 @@ impl ManageProfilesModal {
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.children(KeyBinding::for_action_in(
|
||||
.child(KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&self.focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
@@ -638,14 +648,13 @@ impl ManageProfilesModal {
|
||||
)
|
||||
.child(Label::new("Go Back"))
|
||||
.end_slot(
|
||||
div().children(
|
||||
div().child(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&self.focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
.size(rems_from_px(12.)),
|
||||
),
|
||||
)
|
||||
.on_click({
|
||||
@@ -689,14 +698,9 @@ impl Render for ManageProfilesModal {
|
||||
)
|
||||
.child(Label::new("Go Back"))
|
||||
.end_slot(
|
||||
div().children(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&self.focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
div().child(
|
||||
KeyBinding::for_action_in(&menu::Cancel, &self.focus_handle, cx)
|
||||
.size(rems_from_px(12.)),
|
||||
),
|
||||
)
|
||||
.on_click({
|
||||
|
||||
@@ -669,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;
|
||||
|
||||
@@ -702,7 +702,6 @@ impl Render for AgentDiffPane {
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&ToggleFocus,
|
||||
&focus_handle.clone(),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.on_click(|_event, window, cx| {
|
||||
@@ -719,14 +718,7 @@ fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControl
|
||||
let thread = thread.clone();
|
||||
|
||||
Arc::new(
|
||||
move |row,
|
||||
status: &DiffHunkStatus,
|
||||
hunk_range,
|
||||
is_created_file,
|
||||
line_height,
|
||||
editor: &Entity<Editor>,
|
||||
window: &mut Window,
|
||||
cx: &mut App| {
|
||||
move |row, status, hunk_range, is_created_file, line_height, editor, _, cx| {
|
||||
{
|
||||
render_diff_hunk_controls(
|
||||
row,
|
||||
@@ -736,7 +728,6 @@ fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControl
|
||||
line_height,
|
||||
&thread,
|
||||
editor,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -752,7 +743,6 @@ fn render_diff_hunk_controls(
|
||||
line_height: Pixels,
|
||||
thread: &AgentDiffThread,
|
||||
editor: &Entity<Editor>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyElement {
|
||||
let editor = editor.clone();
|
||||
@@ -775,13 +765,8 @@ 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),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
KeyBinding::for_action_in(&Reject, &editor.read(cx).focus_handle(cx), cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click({
|
||||
let editor = editor.clone();
|
||||
@@ -802,7 +787,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), window, cx)
|
||||
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click({
|
||||
@@ -833,14 +818,8 @@ 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,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
|
||||
}
|
||||
})
|
||||
.on_click({
|
||||
@@ -869,12 +848,11 @@ 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,
|
||||
)
|
||||
}
|
||||
@@ -1039,7 +1017,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")
|
||||
@@ -1114,7 +1092,6 @@ impl Render for AgentDiffToolbar {
|
||||
KeyBinding::for_action_in(
|
||||
&RejectAll,
|
||||
&editor_focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
@@ -1129,7 +1106,6 @@ impl Render for AgentDiffToolbar {
|
||||
KeyBinding::for_action_in(
|
||||
&KeepAll,
|
||||
&editor_focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
@@ -1206,13 +1182,8 @@ impl Render for AgentDiffToolbar {
|
||||
.child(
|
||||
Button::new("reject-all", "Reject All")
|
||||
.key_binding({
|
||||
KeyBinding::for_action_in(
|
||||
&RejectAll,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
KeyBinding::for_action_in(&RejectAll, &focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
})
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.dispatch_action(&RejectAll, window, cx)
|
||||
@@ -1221,13 +1192,8 @@ impl Render for AgentDiffToolbar {
|
||||
.child(
|
||||
Button::new("keep-all", "Keep All")
|
||||
.key_binding({
|
||||
KeyBinding::for_action_in(
|
||||
&KeepAll,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
KeyBinding::for_action_in(&KeepAll, &focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
})
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.dispatch_action(&KeepAll, window, cx)
|
||||
|
||||
@@ -96,14 +96,8 @@ impl Render for AgentModelSelector {
|
||||
.color(color)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
&ToggleModelSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||
},
|
||||
gpui::Corner::TopRight,
|
||||
cx,
|
||||
|
||||
@@ -36,8 +36,8 @@ 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};
|
||||
@@ -199,7 +199,7 @@ enum ActiveView {
|
||||
thread_view: Entity<AcpThreadView>,
|
||||
},
|
||||
TextThread {
|
||||
context_editor: Entity<TextThreadEditor>,
|
||||
text_thread_editor: Entity<TextThreadEditor>,
|
||||
title_editor: Entity<Editor>,
|
||||
buffer_search_bar: Entity<BufferSearchBar>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
@@ -301,13 +301,13 @@ impl ActiveView {
|
||||
}
|
||||
|
||||
pub fn text_thread(
|
||||
context_editor: Entity<TextThreadEditor>,
|
||||
text_thread_editor: Entity<TextThreadEditor>,
|
||||
acp_history_store: Entity<agent::HistoryStore>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let title = context_editor.read(cx).title(cx).to_string();
|
||||
let title = text_thread_editor.read(cx).title(cx).to_string();
|
||||
|
||||
let editor = cx.new(|cx| {
|
||||
let mut editor = Editor::single_line(window, cx);
|
||||
@@ -323,7 +323,7 @@ impl ActiveView {
|
||||
let subscriptions = vec![
|
||||
window.subscribe(&editor, cx, {
|
||||
{
|
||||
let context_editor = context_editor.clone();
|
||||
let text_thread_editor = text_thread_editor.clone();
|
||||
move |editor, event, window, cx| match event {
|
||||
EditorEvent::BufferEdited => {
|
||||
if suppress_first_edit {
|
||||
@@ -332,19 +332,19 @@ impl ActiveView {
|
||||
}
|
||||
let new_summary = editor.read(cx).text(cx);
|
||||
|
||||
context_editor.update(cx, |context_editor, cx| {
|
||||
context_editor
|
||||
.context()
|
||||
.update(cx, |assistant_context, cx| {
|
||||
assistant_context.set_custom_summary(new_summary, 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);
|
||||
})
|
||||
})
|
||||
}
|
||||
EditorEvent::Blurred => {
|
||||
if editor.read(cx).text(cx).is_empty() {
|
||||
let summary = context_editor
|
||||
let summary = text_thread_editor
|
||||
.read(cx)
|
||||
.context()
|
||||
.text_thread()
|
||||
.read(cx)
|
||||
.summary()
|
||||
.or_default();
|
||||
@@ -358,17 +358,17 @@ impl ActiveView {
|
||||
}
|
||||
}
|
||||
}),
|
||||
window.subscribe(&context_editor.read(cx).context().clone(), cx, {
|
||||
window.subscribe(&text_thread_editor.read(cx).text_thread().clone(), cx, {
|
||||
let editor = editor.clone();
|
||||
move |assistant_context, event, window, cx| match event {
|
||||
ContextEvent::SummaryGenerated => {
|
||||
let summary = assistant_context.read(cx).summary().or_default();
|
||||
move |text_thread, event, window, cx| match event {
|
||||
TextThreadEvent::SummaryGenerated => {
|
||||
let summary = text_thread.read(cx).summary().or_default();
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.set_text(summary, window, cx);
|
||||
})
|
||||
}
|
||||
ContextEvent::PathChanged { old_path, new_path } => {
|
||||
TextThreadEvent::PathChanged { old_path, new_path } => {
|
||||
acp_history_store.update(cx, |history_store, cx| {
|
||||
if let Some(old_path) = old_path {
|
||||
history_store
|
||||
@@ -389,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(&context_editor), window, cx)
|
||||
buffer_search_bar.set_active_pane_item(Some(&text_thread_editor), window, cx)
|
||||
});
|
||||
|
||||
Self::TextThread {
|
||||
context_editor,
|
||||
text_thread_editor,
|
||||
title_editor: editor,
|
||||
buffer_search_bar,
|
||||
_subscriptions: subscriptions,
|
||||
@@ -410,7 +410,7 @@ pub struct AgentPanel {
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
acp_history: Entity<AcpThreadHistory>,
|
||||
history_store: Entity<agent::HistoryStore>,
|
||||
text_thread_store: Entity<assistant_context::ContextStore>,
|
||||
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
context_server_registry: Entity<ContextServerRegistry>,
|
||||
inline_assist_context_store: Entity<ContextStore>,
|
||||
@@ -474,7 +474,7 @@ impl AgentPanel {
|
||||
let text_thread_store = workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
assistant_context::ContextStore::new(
|
||||
assistant_text_thread::TextThreadStore::new(
|
||||
project,
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
@@ -512,7 +512,7 @@ impl AgentPanel {
|
||||
|
||||
fn new(
|
||||
workspace: &Workspace,
|
||||
text_thread_store: Entity<assistant_context::ContextStore>,
|
||||
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -565,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 context_editor = cx.new(|cx| {
|
||||
let mut editor = TextThreadEditor::for_context(
|
||||
let text_thread_editor = cx.new(|cx| {
|
||||
let mut editor = TextThreadEditor::for_text_thread(
|
||||
context,
|
||||
fs.clone(),
|
||||
workspace.clone(),
|
||||
@@ -579,7 +579,7 @@ impl AgentPanel {
|
||||
editor
|
||||
});
|
||||
ActiveView::text_thread(
|
||||
context_editor,
|
||||
text_thread_editor,
|
||||
history_store.clone(),
|
||||
language_registry.clone(),
|
||||
window,
|
||||
@@ -736,8 +736,8 @@ impl AgentPanel {
|
||||
.log_err()
|
||||
.flatten();
|
||||
|
||||
let context_editor = cx.new(|cx| {
|
||||
let mut editor = TextThreadEditor::for_context(
|
||||
let text_thread_editor = cx.new(|cx| {
|
||||
let mut editor = TextThreadEditor::for_text_thread(
|
||||
context,
|
||||
self.fs.clone(),
|
||||
self.workspace.clone(),
|
||||
@@ -757,7 +757,7 @@ impl AgentPanel {
|
||||
|
||||
self.set_active_view(
|
||||
ActiveView::text_thread(
|
||||
context_editor.clone(),
|
||||
text_thread_editor.clone(),
|
||||
self.history_store.clone(),
|
||||
self.language_registry.clone(),
|
||||
window,
|
||||
@@ -766,7 +766,7 @@ impl AgentPanel {
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
context_editor.focus_handle(cx).focus(window);
|
||||
text_thread_editor.focus_handle(cx).focus(window);
|
||||
}
|
||||
|
||||
fn external_thread(
|
||||
@@ -905,20 +905,20 @@ impl AgentPanel {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let context = self
|
||||
let text_thread_task = self
|
||||
.history_store
|
||||
.update(cx, |store, cx| store.load_text_thread(path, cx));
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let context = context.await?;
|
||||
let text_thread = text_thread_task.await?;
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.open_text_thread(context, window, cx);
|
||||
this.open_text_thread(text_thread, window, cx);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn open_text_thread(
|
||||
&mut self,
|
||||
context: Entity<AssistantContext>,
|
||||
text_thread: Entity<TextThread>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -926,8 +926,8 @@ impl AgentPanel {
|
||||
.log_err()
|
||||
.flatten();
|
||||
let editor = cx.new(|cx| {
|
||||
TextThreadEditor::for_context(
|
||||
context,
|
||||
TextThreadEditor::for_text_thread(
|
||||
text_thread,
|
||||
self.fs.clone(),
|
||||
self.workspace.clone(),
|
||||
self.project.clone(),
|
||||
@@ -965,8 +965,10 @@ impl AgentPanel {
|
||||
ActiveView::ExternalAgentThread { thread_view } => {
|
||||
thread_view.focus_handle(cx).focus(window);
|
||||
}
|
||||
ActiveView::TextThread { context_editor, .. } => {
|
||||
context_editor.focus_handle(cx).focus(window);
|
||||
ActiveView::TextThread {
|
||||
text_thread_editor, ..
|
||||
} => {
|
||||
text_thread_editor.focus_handle(cx).focus(window);
|
||||
}
|
||||
ActiveView::History | ActiveView::Configuration => {}
|
||||
}
|
||||
@@ -1183,9 +1185,11 @@ impl AgentPanel {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
|
||||
pub(crate) fn active_text_thread_editor(&self) -> Option<Entity<TextThreadEditor>> {
|
||||
match &self.active_view {
|
||||
ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
|
||||
ActiveView::TextThread {
|
||||
text_thread_editor, ..
|
||||
} => Some(text_thread_editor.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1206,16 +1210,16 @@ impl AgentPanel {
|
||||
let new_is_special = new_is_history || new_is_config;
|
||||
|
||||
match &new_view {
|
||||
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::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::ExternalAgentThread { .. } => {}
|
||||
ActiveView::History | ActiveView::Configuration => {}
|
||||
}
|
||||
@@ -1372,7 +1376,9 @@ 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 { context_editor, .. } => context_editor.focus_handle(cx),
|
||||
ActiveView::TextThread {
|
||||
text_thread_editor, ..
|
||||
} => text_thread_editor.focus_handle(cx),
|
||||
ActiveView::Configuration => {
|
||||
if let Some(configuration) = self.configuration.as_ref() {
|
||||
configuration.focus_handle(cx)
|
||||
@@ -1395,6 +1401,10 @@ 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)
|
||||
}
|
||||
@@ -1503,17 +1513,17 @@ impl AgentPanel {
|
||||
}
|
||||
ActiveView::TextThread {
|
||||
title_editor,
|
||||
context_editor,
|
||||
text_thread_editor,
|
||||
..
|
||||
} => {
|
||||
let summary = context_editor.read(cx).context().read(cx).summary();
|
||||
let summary = text_thread_editor.read(cx).text_thread().read(cx).summary();
|
||||
|
||||
match summary {
|
||||
ContextSummary::Pending => Label::new(ContextSummary::DEFAULT)
|
||||
TextThreadSummary::Pending => Label::new(TextThreadSummary::DEFAULT)
|
||||
.color(Color::Muted)
|
||||
.truncate()
|
||||
.into_any_element(),
|
||||
ContextSummary::Content(summary) => {
|
||||
TextThreadSummary::Content(summary) => {
|
||||
if summary.done {
|
||||
div()
|
||||
.w_full()
|
||||
@@ -1526,17 +1536,17 @@ impl AgentPanel {
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
ContextSummary::Error => h_flex()
|
||||
TextThreadSummary::Error => h_flex()
|
||||
.w_full()
|
||||
.child(title_editor.clone())
|
||||
.child(
|
||||
IconButton::new("retry-summary-generation", IconName::RotateCcw)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
let context_editor = context_editor.clone();
|
||||
let text_thread_editor = text_thread_editor.clone();
|
||||
move |_, _window, cx| {
|
||||
context_editor.update(cx, |context_editor, cx| {
|
||||
context_editor.regenerate_summary(cx);
|
||||
text_thread_editor.update(cx, |text_thread_editor, cx| {
|
||||
text_thread_editor.regenerate_summary(cx);
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -1591,12 +1601,11 @@ 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,
|
||||
)
|
||||
}
|
||||
@@ -1660,7 +1669,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));
|
||||
@@ -1687,12 +1696,11 @@ 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,
|
||||
)
|
||||
}
|
||||
@@ -1726,8 +1734,8 @@ impl AgentPanel {
|
||||
this.go_back(&workspace::GoBack, window, cx);
|
||||
}))
|
||||
.tooltip({
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, window, cx)
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, cx)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1748,12 +1756,11 @@ impl AgentPanel {
|
||||
IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
|
||||
{
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"New…",
|
||||
&ToggleNewThreadMenu,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -1999,14 +2006,8 @@ impl AgentPanel {
|
||||
.when_some(self.selected_agent.icon(), |this, icon| {
|
||||
this.px(DynamicSpacing::Base02.rems(cx))
|
||||
.child(Icon::new(icon).color(Color::Muted))
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
selected_agent_label.clone(),
|
||||
None,
|
||||
"Selected Agent",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::with_meta(selected_agent_label.clone(), None, "Selected Agent", cx)
|
||||
})
|
||||
})
|
||||
.into_any_element();
|
||||
@@ -2182,7 +2183,6 @@ 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)
|
||||
@@ -2231,7 +2231,7 @@ impl AgentPanel {
|
||||
.style(ButtonStyle::Tinted(ui::TintColor::Warning))
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&OpenSettings, focus_handle, window, cx)
|
||||
KeyBinding::for_action_in(&OpenSettings, focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(|_event, window, cx| {
|
||||
@@ -2249,7 +2249,7 @@ impl AgentPanel {
|
||||
|
||||
fn render_text_thread(
|
||||
&self,
|
||||
context_editor: &Entity<TextThreadEditor>,
|
||||
text_thread_editor: &Entity<TextThreadEditor>,
|
||||
buffer_search_bar: &Entity<BufferSearchBar>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -2283,7 +2283,7 @@ impl AgentPanel {
|
||||
)
|
||||
})
|
||||
})
|
||||
.child(context_editor.clone())
|
||||
.child(text_thread_editor.clone())
|
||||
.child(self.render_drag_target(cx))
|
||||
}
|
||||
|
||||
@@ -2359,10 +2359,12 @@ impl AgentPanel {
|
||||
thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
|
||||
});
|
||||
}
|
||||
ActiveView::TextThread { context_editor, .. } => {
|
||||
context_editor.update(cx, |context_editor, cx| {
|
||||
ActiveView::TextThread {
|
||||
text_thread_editor, ..
|
||||
} => {
|
||||
text_thread_editor.update(cx, |text_thread_editor, cx| {
|
||||
TextThreadEditor::insert_dragged_files(
|
||||
context_editor,
|
||||
text_thread_editor,
|
||||
paths,
|
||||
added_worktrees,
|
||||
window,
|
||||
@@ -2433,7 +2435,7 @@ impl Render for AgentPanel {
|
||||
.child(self.render_drag_target(cx)),
|
||||
ActiveView::History => parent.child(self.acp_history.clone()),
|
||||
ActiveView::TextThread {
|
||||
context_editor,
|
||||
text_thread_editor,
|
||||
buffer_search_bar,
|
||||
..
|
||||
} => {
|
||||
@@ -2449,7 +2451,6 @@ impl Render for AgentPanel {
|
||||
true,
|
||||
err,
|
||||
&self.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
} else {
|
||||
@@ -2457,7 +2458,7 @@ impl Render for AgentPanel {
|
||||
}
|
||||
})
|
||||
.child(self.render_text_thread(
|
||||
context_editor,
|
||||
text_thread_editor,
|
||||
buffer_search_bar,
|
||||
window,
|
||||
cx,
|
||||
@@ -2535,17 +2536,17 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
|
||||
pub struct ConcreteAssistantPanelDelegate;
|
||||
|
||||
impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
|
||||
fn active_context_editor(
|
||||
fn active_text_thread_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_context_editor()
|
||||
panel.read(cx).active_text_thread_editor()
|
||||
}
|
||||
|
||||
fn open_saved_context(
|
||||
fn open_local_text_thread(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
path: Arc<Path>,
|
||||
@@ -2561,10 +2562,10 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
fn open_remote_context(
|
||||
fn open_remote_text_thread(
|
||||
&self,
|
||||
_workspace: &mut Workspace,
|
||||
_context_id: assistant_context::ContextId,
|
||||
_text_thread_id: assistant_text_thread::TextThreadId,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Workspace>,
|
||||
) -> Task<Result<Entity<TextThreadEditor>>> {
|
||||
@@ -2595,15 +2596,15 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
|
||||
thread_view.update(cx, |thread_view, cx| {
|
||||
thread_view.insert_selections(window, cx);
|
||||
});
|
||||
} else if let Some(context_editor) = panel.active_context_editor() {
|
||||
} else if let Some(text_thread_editor) = panel.active_text_thread_editor() {
|
||||
let snapshot = buffer.read(cx).snapshot(cx);
|
||||
let selection_ranges = selection_ranges
|
||||
.into_iter()
|
||||
.map(|range| range.to_point(&snapshot))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
context_editor.update(cx, |context_editor, cx| {
|
||||
context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
|
||||
text_thread_editor.update(cx, |text_thread_editor, cx| {
|
||||
text_thread_editor.quote_ranges(selection_ranges, snapshot, window, cx)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -130,12 +130,6 @@ 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)]
|
||||
@@ -256,7 +250,7 @@ pub fn init(
|
||||
) {
|
||||
AgentSettings::register(cx);
|
||||
|
||||
assistant_context::init(client.clone(), cx);
|
||||
assistant_text_thread::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_context::AssistantContext;
|
||||
use assistant_text_thread::TextThread;
|
||||
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 context: Entity<AssistantContext>,
|
||||
pub text_thread: Entity<TextThread>,
|
||||
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.context == other.context
|
||||
self.text_thread == other.text_thread
|
||||
}
|
||||
|
||||
pub fn hash_for_key<H: Hasher>(&self, state: &mut H) {
|
||||
self.context.hash(state)
|
||||
self.text_thread.hash(state)
|
||||
}
|
||||
|
||||
pub fn title(&self, cx: &App) -> SharedString {
|
||||
self.context.read(cx).summary().or_default()
|
||||
self.text_thread.read(cx).summary().or_default()
|
||||
}
|
||||
|
||||
fn load(self, cx: &App) -> Task<Option<AgentContext>> {
|
||||
let title = self.title(cx);
|
||||
let text = self.context.read(cx).to_xml(cx);
|
||||
let text = self.text_thread.read(cx).to_xml(cx);
|
||||
let context = AgentContext::TextThread(TextThreadContext {
|
||||
title,
|
||||
text: text.into(),
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::context::{
|
||||
};
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_context::AssistantContext;
|
||||
use assistant_text_thread::TextThread;
|
||||
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,
|
||||
context: Entity<AssistantContext>,
|
||||
text_thread: Entity<TextThread>,
|
||||
remove_if_exists: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<AgentContextHandle> {
|
||||
let context_id = self.next_context_id.post_inc();
|
||||
let context = AgentContextHandle::TextThread(TextThreadContextHandle {
|
||||
context,
|
||||
text_thread,
|
||||
context_id,
|
||||
});
|
||||
|
||||
@@ -353,21 +353,15 @@ impl ContextStore {
|
||||
);
|
||||
};
|
||||
}
|
||||
// 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() {
|
||||
SuggestedContext::TextThread {
|
||||
text_thread,
|
||||
name: _,
|
||||
} => {
|
||||
if let Some(text_thread) = text_thread.upgrade() {
|
||||
let context_id = self.next_context_id.post_inc();
|
||||
self.insert_context(
|
||||
AgentContextHandle::TextThread(TextThreadContextHandle {
|
||||
context,
|
||||
text_thread,
|
||||
context_id,
|
||||
}),
|
||||
cx,
|
||||
@@ -392,7 +386,7 @@ impl ContextStore {
|
||||
// }
|
||||
AgentContextHandle::TextThread(text_thread_context) => {
|
||||
self.context_text_thread_paths
|
||||
.extend(text_thread_context.context.read(cx).path().cloned());
|
||||
.extend(text_thread_context.text_thread.read(cx).path().cloned());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -414,7 +408,7 @@ impl ContextStore {
|
||||
.remove(thread_context.thread.read(cx).id());
|
||||
}
|
||||
AgentContextHandle::TextThread(text_thread_context) => {
|
||||
if let Some(path) = text_thread_context.context.read(cx).path() {
|
||||
if let Some(path) = text_thread_context.text_thread.read(cx).path() {
|
||||
self.context_text_thread_paths.remove(path);
|
||||
}
|
||||
}
|
||||
@@ -538,13 +532,9 @@ pub enum SuggestedContext {
|
||||
icon_path: Option<SharedString>,
|
||||
buffer: WeakEntity<Buffer>,
|
||||
},
|
||||
// Thread {
|
||||
// name: SharedString,
|
||||
// thread: WeakEntity<Thread>,
|
||||
// },
|
||||
TextThread {
|
||||
name: SharedString,
|
||||
context: WeakEntity<AssistantContext>,
|
||||
text_thread: WeakEntity<TextThread>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -552,7 +542,6 @@ impl SuggestedContext {
|
||||
pub fn name(&self) -> &SharedString {
|
||||
match self {
|
||||
Self::File { name, .. } => name,
|
||||
// Self::Thread { name, .. } => name,
|
||||
Self::TextThread { name, .. } => name,
|
||||
}
|
||||
}
|
||||
@@ -560,7 +549,6 @@ impl SuggestedContext {
|
||||
pub fn icon_path(&self) -> Option<SharedString> {
|
||||
match self {
|
||||
Self::File { icon_path, .. } => icon_path.clone(),
|
||||
// Self::Thread { .. } => None,
|
||||
Self::TextThread { .. } => None,
|
||||
}
|
||||
}
|
||||
@@ -568,7 +556,6 @@ 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_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 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 self.context_store.read(cx).includes_text_thread(path) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SuggestedContext::TextThread {
|
||||
name: context.summary().or_default(),
|
||||
context: weak_context,
|
||||
name: text_thread.summary().or_default(),
|
||||
text_thread: weak_text_thread,
|
||||
})
|
||||
} 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.context.clone();
|
||||
let context = text_thread_context.text_thread.clone();
|
||||
window.defer(cx, move |window, cx| {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.open_text_thread(context, window, cx)
|
||||
@@ -483,12 +483,11 @@ 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,
|
||||
)
|
||||
}
|
||||
@@ -558,12 +557,11 @@ 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 context_editor = agent_panel
|
||||
.and_then(|panel| panel.read(cx).active_context_editor())
|
||||
let text_thread_editor = agent_panel
|
||||
.and_then(|panel| panel.read(cx).active_text_thread_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(context_editor) = context_editor {
|
||||
Some(InlineAssistTarget::Editor(context_editor))
|
||||
if let Some(text_thread_editor) = text_thread_editor {
|
||||
Some(InlineAssistTarget::Editor(text_thread_editor))
|
||||
} else if let Some(workspace_editor) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
|
||||
@@ -468,12 +468,11 @@ 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,
|
||||
)
|
||||
})
|
||||
@@ -487,12 +486,11 @@ 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,
|
||||
)
|
||||
})
|
||||
@@ -505,8 +503,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, window, cx)
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, cx)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, _, cx| {
|
||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
|
||||
@@ -519,11 +517,10 @@ 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,
|
||||
)
|
||||
})
|
||||
@@ -615,13 +612,12 @@ 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,
|
||||
),
|
||||
);
|
||||
@@ -657,13 +653,12 @@ 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,12 +162,11 @@ 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, |context_editor, cx| {
|
||||
context_editor.insert_command(&info.name, window, cx)
|
||||
.update(cx, |text_thread_editor, cx| {
|
||||
text_thread_editor.insert_command(&info.name, window, cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::{
|
||||
QuoteSelection,
|
||||
language_model_selector::{LanguageModelSelector, language_model_selector},
|
||||
ui::BurnModeTooltip,
|
||||
};
|
||||
@@ -72,13 +71,13 @@ use workspace::{
|
||||
pane,
|
||||
searchable::{SearchEvent, SearchableItem},
|
||||
};
|
||||
use zed_actions::agent::ToggleModelSelector;
|
||||
use zed_actions::agent::{AddSelectionToThread, ToggleModelSelector};
|
||||
|
||||
use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};
|
||||
use assistant_context::{
|
||||
AssistantContext, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId,
|
||||
InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus,
|
||||
PendingSlashCommandStatus, ThoughtProcessOutputSection,
|
||||
use assistant_text_thread::{
|
||||
CacheStatus, Content, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId,
|
||||
MessageMetadata, MessageStatus, PendingSlashCommandStatus, TextThread, TextThreadEvent,
|
||||
TextThreadId, ThoughtProcessOutputSection,
|
||||
};
|
||||
|
||||
actions!(
|
||||
@@ -127,14 +126,14 @@ pub enum ThoughtProcessStatus {
|
||||
}
|
||||
|
||||
pub trait AgentPanelDelegate {
|
||||
fn active_context_editor(
|
||||
fn active_text_thread_editor(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Option<Entity<TextThreadEditor>>;
|
||||
|
||||
fn open_saved_context(
|
||||
fn open_local_text_thread(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
path: Arc<Path>,
|
||||
@@ -142,10 +141,10 @@ pub trait AgentPanelDelegate {
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Task<Result<()>>;
|
||||
|
||||
fn open_remote_context(
|
||||
fn open_remote_text_thread(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
context_id: ContextId,
|
||||
text_thread_id: TextThreadId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Task<Result<Entity<TextThreadEditor>>>;
|
||||
@@ -178,7 +177,7 @@ struct GlobalAssistantPanelDelegate(Arc<dyn AgentPanelDelegate>);
|
||||
impl Global for GlobalAssistantPanelDelegate {}
|
||||
|
||||
pub struct TextThreadEditor {
|
||||
context: Entity<AssistantContext>,
|
||||
text_thread: Entity<TextThread>,
|
||||
fs: Arc<dyn Fs>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
@@ -224,8 +223,8 @@ impl TextThreadEditor {
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn for_context(
|
||||
context: Entity<AssistantContext>,
|
||||
pub fn for_text_thread(
|
||||
text_thread: Entity<TextThread>,
|
||||
fs: Arc<dyn Fs>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
@@ -234,14 +233,14 @@ impl TextThreadEditor {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let completion_provider = SlashCommandCompletionProvider::new(
|
||||
context.read(cx).slash_commands().clone(),
|
||||
text_thread.read(cx).slash_commands().clone(),
|
||||
Some(cx.entity().downgrade()),
|
||||
Some(workspace.clone()),
|
||||
);
|
||||
|
||||
let editor = cx.new(|cx| {
|
||||
let mut editor =
|
||||
Editor::for_buffer(context.read(cx).buffer().clone(), None, window, cx);
|
||||
Editor::for_buffer(text_thread.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);
|
||||
@@ -265,18 +264,24 @@ impl TextThreadEditor {
|
||||
});
|
||||
|
||||
let _subscriptions = vec![
|
||||
cx.observe(&context, |_, _, cx| cx.notify()),
|
||||
cx.subscribe_in(&context, window, Self::handle_context_event),
|
||||
cx.observe(&text_thread, |_, _, cx| cx.notify()),
|
||||
cx.subscribe_in(&text_thread, window, Self::handle_text_thread_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 = 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 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 mut this = Self {
|
||||
context,
|
||||
text_thread,
|
||||
slash_commands,
|
||||
editor,
|
||||
lsp_adapter_delegate,
|
||||
@@ -338,8 +343,8 @@ impl TextThreadEditor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn context(&self) -> &Entity<AssistantContext> {
|
||||
&self.context
|
||||
pub fn text_thread(&self) -> &Entity<TextThread> {
|
||||
&self.text_thread
|
||||
}
|
||||
|
||||
pub fn editor(&self) -> &Entity<Editor> {
|
||||
@@ -351,9 +356,9 @@ impl TextThreadEditor {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.insert(&format!("/{command_name}\n\n"), window, cx)
|
||||
});
|
||||
let command = self.context.update(cx, |context, cx| {
|
||||
context.reparse(cx);
|
||||
context.parsed_slash_commands()[0].clone()
|
||||
let command = self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.reparse(cx);
|
||||
text_thread.parsed_slash_commands()[0].clone()
|
||||
});
|
||||
self.run_command(
|
||||
command.source_range,
|
||||
@@ -376,11 +381,14 @@ 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.context.update(cx, |context, cx| context.assist(cx)) {
|
||||
if let Some(user_message) = self
|
||||
.text_thread
|
||||
.update(cx, |text_thread, cx| text_thread.assist(cx))
|
||||
{
|
||||
let new_selection = {
|
||||
let cursor = user_message
|
||||
.start
|
||||
.to_offset(self.context.read(cx).buffer().read(cx));
|
||||
.to_offset(self.text_thread.read(cx).buffer().read(cx));
|
||||
cursor..cursor
|
||||
};
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
@@ -404,8 +412,8 @@ impl TextThreadEditor {
|
||||
self.last_error = None;
|
||||
|
||||
if self
|
||||
.context
|
||||
.update(cx, |context, cx| context.cancel_last_assist(cx))
|
||||
.text_thread
|
||||
.update(cx, |text_thread, cx| text_thread.cancel_last_assist(cx))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -420,13 +428,13 @@ impl TextThreadEditor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let cursors = self.cursors(cx);
|
||||
self.context.update(cx, |context, cx| {
|
||||
let messages = context
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
let messages = text_thread
|
||||
.messages_for_offsets(cursors, cx)
|
||||
.into_iter()
|
||||
.map(|message| message.id)
|
||||
.collect();
|
||||
context.cycle_message_roles(messages, cx)
|
||||
text_thread.cycle_message_roles(messages, cx)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -492,11 +500,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.context.update(cx, |context, cx| {
|
||||
context.reparse(cx);
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.reparse(cx);
|
||||
for selection in selections.iter() {
|
||||
if let Some(command) =
|
||||
context.pending_command_for_position(selection.head().text_anchor, cx)
|
||||
text_thread.pending_command_for_position(selection.head().text_anchor, cx)
|
||||
{
|
||||
commands_by_range
|
||||
.entry(command.source_range.clone())
|
||||
@@ -534,14 +542,14 @@ impl TextThreadEditor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(command) = self.slash_commands.command(name, cx) {
|
||||
let context = self.context.read(cx);
|
||||
let sections = context
|
||||
let text_thread = self.text_thread.read(cx);
|
||||
let sections = text_thread
|
||||
.slash_command_output_sections()
|
||||
.iter()
|
||||
.filter(|section| section.is_valid(context.buffer().read(cx)))
|
||||
.filter(|section| section.is_valid(text_thread.buffer().read(cx)))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let snapshot = context.buffer().read(cx).snapshot();
|
||||
let snapshot = text_thread.buffer().read(cx).snapshot();
|
||||
let output = command.run(
|
||||
arguments,
|
||||
§ions,
|
||||
@@ -551,8 +559,8 @@ impl TextThreadEditor {
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.insert_command_output(
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.insert_command_output(
|
||||
command_range,
|
||||
name,
|
||||
output,
|
||||
@@ -563,32 +571,32 @@ impl TextThreadEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_context_event(
|
||||
fn handle_text_thread_event(
|
||||
&mut self,
|
||||
_: &Entity<AssistantContext>,
|
||||
event: &ContextEvent,
|
||||
_: &Entity<TextThread>,
|
||||
event: &TextThreadEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let context_editor = cx.entity().downgrade();
|
||||
let text_thread_editor = cx.entity().downgrade();
|
||||
|
||||
match event {
|
||||
ContextEvent::MessagesEdited => {
|
||||
TextThreadEvent::MessagesEdited => {
|
||||
self.update_message_headers(cx);
|
||||
self.update_image_blocks(cx);
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
|
||||
});
|
||||
}
|
||||
ContextEvent::SummaryChanged => {
|
||||
TextThreadEvent::SummaryChanged => {
|
||||
cx.emit(EditorEvent::TitleChanged);
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
|
||||
});
|
||||
}
|
||||
ContextEvent::SummaryGenerated => {}
|
||||
ContextEvent::PathChanged { .. } => {}
|
||||
ContextEvent::StartedThoughtProcess(range) => {
|
||||
TextThreadEvent::SummaryGenerated => {}
|
||||
TextThreadEvent::PathChanged { .. } => {}
|
||||
TextThreadEvent::StartedThoughtProcess(range) => {
|
||||
let creases = self.insert_thought_process_output_sections(
|
||||
[(
|
||||
ThoughtProcessOutputSection {
|
||||
@@ -601,7 +609,7 @@ impl TextThreadEditor {
|
||||
);
|
||||
self.pending_thought_process = Some((creases[0], range.start));
|
||||
}
|
||||
ContextEvent::EndedThoughtProcess(end) => {
|
||||
TextThreadEvent::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);
|
||||
@@ -627,7 +635,7 @@ impl TextThreadEditor {
|
||||
);
|
||||
}
|
||||
}
|
||||
ContextEvent::StreamedCompletion => {
|
||||
TextThreadEvent::StreamedCompletion => {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
if let Some(scroll_position) = self.scroll_position {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
@@ -642,7 +650,7 @@ impl TextThreadEditor {
|
||||
}
|
||||
});
|
||||
}
|
||||
ContextEvent::ParsedSlashCommandsUpdated { removed, updated } => {
|
||||
TextThreadEvent::ParsedSlashCommandsUpdated { removed, updated } => {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let (&excerpt_id, _, _) = buffer.as_singleton().unwrap();
|
||||
@@ -658,12 +666,12 @@ impl TextThreadEditor {
|
||||
updated.iter().map(|command| {
|
||||
let workspace = self.workspace.clone();
|
||||
let confirm_command = Arc::new({
|
||||
let context_editor = context_editor.clone();
|
||||
let text_thread_editor = text_thread_editor.clone();
|
||||
let command = command.clone();
|
||||
move |window: &mut Window, cx: &mut App| {
|
||||
context_editor
|
||||
.update(cx, |context_editor, cx| {
|
||||
context_editor.run_command(
|
||||
text_thread_editor
|
||||
.update(cx, |text_thread_editor, cx| {
|
||||
text_thread_editor.run_command(
|
||||
command.source_range.clone(),
|
||||
&command.name,
|
||||
&command.arguments,
|
||||
@@ -713,17 +721,17 @@ impl TextThreadEditor {
|
||||
);
|
||||
})
|
||||
}
|
||||
ContextEvent::InvokedSlashCommandChanged { command_id } => {
|
||||
TextThreadEvent::InvokedSlashCommandChanged { command_id } => {
|
||||
self.update_invoked_slash_command(*command_id, window, cx);
|
||||
}
|
||||
ContextEvent::SlashCommandOutputSectionAdded { section } => {
|
||||
TextThreadEvent::SlashCommandOutputSectionAdded { section } => {
|
||||
self.insert_slash_command_output_sections([section.clone()], false, window, cx);
|
||||
}
|
||||
ContextEvent::Operation(_) => {}
|
||||
ContextEvent::ShowAssistError(error_message) => {
|
||||
TextThreadEvent::Operation(_) => {}
|
||||
TextThreadEvent::ShowAssistError(error_message) => {
|
||||
self.last_error = Some(AssistError::Message(error_message.clone()));
|
||||
}
|
||||
ContextEvent::ShowPaymentRequiredError => {
|
||||
TextThreadEvent::ShowPaymentRequiredError => {
|
||||
self.last_error = Some(AssistError::PaymentRequired);
|
||||
}
|
||||
}
|
||||
@@ -736,14 +744,14 @@ impl TextThreadEditor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(invoked_slash_command) =
|
||||
self.context.read(cx).invoked_slash_command(&command_id)
|
||||
self.text_thread.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.context.update(cx, |context, cx| {
|
||||
context.reparse(cx);
|
||||
context
|
||||
let commands = self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.reparse(cx);
|
||||
text_thread
|
||||
.pending_commands_for_range(range.clone(), cx)
|
||||
.to_vec()
|
||||
});
|
||||
@@ -764,7 +772,7 @@ impl TextThreadEditor {
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
if let Some(invoked_slash_command) =
|
||||
self.context.read(cx).invoked_slash_command(&command_id)
|
||||
self.text_thread.read(cx).invoked_slash_command(&command_id)
|
||||
{
|
||||
if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
@@ -791,7 +799,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.context.downgrade();
|
||||
let context = self.text_thread.downgrade();
|
||||
let range = buffer
|
||||
.anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
|
||||
.unwrap();
|
||||
@@ -1021,7 +1029,7 @@ impl TextThreadEditor {
|
||||
|
||||
let render_block = |message: MessageMetadata| -> RenderBlock {
|
||||
Arc::new({
|
||||
let context = self.context.clone();
|
||||
let text_thread = self.text_thread.clone();
|
||||
|
||||
move |cx| {
|
||||
let message_id = MessageId(message.timestamp);
|
||||
@@ -1085,20 +1093,19 @@ 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 context = context.clone();
|
||||
let text_thread = text_thread.clone();
|
||||
move |_, _window, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
context.cycle_message_roles(
|
||||
text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.cycle_message_roles(
|
||||
HashSet::from_iter(Some(message_id)),
|
||||
cx,
|
||||
)
|
||||
@@ -1126,12 +1133,11 @@ 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,
|
||||
)
|
||||
})
|
||||
@@ -1161,11 +1167,11 @@ impl TextThreadEditor {
|
||||
.icon_position(IconPosition::Start)
|
||||
.tooltip(Tooltip::text("View Details"))
|
||||
.on_click({
|
||||
let context = context.clone();
|
||||
let text_thread = text_thread.clone();
|
||||
let error = error.clone();
|
||||
move |_, _window, cx| {
|
||||
context.update(cx, |_, cx| {
|
||||
cx.emit(ContextEvent::ShowAssistError(
|
||||
text_thread.update(cx, |_, cx| {
|
||||
cx.emit(TextThreadEvent::ShowAssistError(
|
||||
error.clone(),
|
||||
));
|
||||
});
|
||||
@@ -1208,7 +1214,7 @@ impl TextThreadEditor {
|
||||
};
|
||||
let mut new_blocks = vec![];
|
||||
let mut block_index_to_message = vec![];
|
||||
for message in self.context.read(cx).messages(cx) {
|
||||
for message in self.text_thread.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 {
|
||||
@@ -1249,18 +1255,18 @@ impl TextThreadEditor {
|
||||
) -> Option<(String, bool)> {
|
||||
const CODE_FENCE_DELIMITER: &str = "```";
|
||||
|
||||
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
|
||||
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
|
||||
.selections
|
||||
.newest::<Point>(&display_map)
|
||||
.is_empty()
|
||||
{
|
||||
let snapshot = context_editor.buffer().read(cx).snapshot(cx);
|
||||
let snapshot = text_thread_editor.buffer().read(cx).snapshot(cx);
|
||||
let (_, _, snapshot) = snapshot.as_singleton()?;
|
||||
|
||||
let head = context_editor
|
||||
let head = text_thread_editor
|
||||
.selections
|
||||
.newest::<Point>(&display_map)
|
||||
.head();
|
||||
@@ -1280,8 +1286,8 @@ impl TextThreadEditor {
|
||||
|
||||
(!text.is_empty()).then_some((text, true))
|
||||
} else {
|
||||
let selection = context_editor.selections.newest_adjusted(&display_map);
|
||||
let buffer = context_editor.buffer().read(cx).snapshot(cx);
|
||||
let selection = text_thread_editor.selections.newest_adjusted(&display_map);
|
||||
let buffer = text_thread_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))
|
||||
@@ -1299,7 +1305,7 @@ impl TextThreadEditor {
|
||||
return;
|
||||
};
|
||||
let Some(context_editor_view) =
|
||||
agent_panel_delegate.active_context_editor(workspace, window, cx)
|
||||
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -1327,7 +1333,7 @@ impl TextThreadEditor {
|
||||
let result = maybe!({
|
||||
let agent_panel_delegate = <dyn AgentPanelDelegate>::try_global(cx)?;
|
||||
let context_editor_view =
|
||||
agent_panel_delegate.active_context_editor(workspace, window, cx)?;
|
||||
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)?;
|
||||
Self::get_selection_or_code_block(&context_editor_view, cx)
|
||||
});
|
||||
let Some((text, is_code_block)) = result else {
|
||||
@@ -1364,7 +1370,7 @@ impl TextThreadEditor {
|
||||
return;
|
||||
};
|
||||
let Some(context_editor_view) =
|
||||
agent_panel_delegate.active_context_editor(workspace, window, cx)
|
||||
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -1450,7 +1456,7 @@ impl TextThreadEditor {
|
||||
|
||||
pub fn quote_selection(
|
||||
workspace: &mut Workspace,
|
||||
_: &QuoteSelection,
|
||||
_: &AddSelectionToThread,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
@@ -1625,29 +1631,33 @@ impl TextThreadEditor {
|
||||
)
|
||||
});
|
||||
|
||||
let context = self.context.read(cx);
|
||||
let text_thread = self.text_thread.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 = context.buffer().read(cx).snapshot();
|
||||
let snapshot = text_thread.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 context.buffer().read(cx).text_for_range(selection.range()) {
|
||||
for chunk in text_thread
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.text_for_range(selection.range())
|
||||
{
|
||||
text.push_str(chunk);
|
||||
}
|
||||
} else {
|
||||
for message in context.messages(cx) {
|
||||
for message in text_thread.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 context.buffer().read(cx).text_for_range(range) {
|
||||
for chunk in text_thread.buffer().read(cx).text_for_range(range) {
|
||||
text.push_str(chunk);
|
||||
}
|
||||
if message.offset_range.end < selection.range().end {
|
||||
@@ -1758,7 +1768,7 @@ impl TextThreadEditor {
|
||||
});
|
||||
});
|
||||
|
||||
self.context.update(cx, |context, cx| {
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
for image in images {
|
||||
let Some(render_image) = image.to_image_data(cx.svg_renderer()).log_err()
|
||||
else {
|
||||
@@ -1768,7 +1778,7 @@ impl TextThreadEditor {
|
||||
let image_task = LanguageModelImage::from_image(Arc::new(image), cx).shared();
|
||||
|
||||
for image_position in image_positions.iter() {
|
||||
context.insert_content(
|
||||
text_thread.insert_content(
|
||||
Content::Image {
|
||||
anchor: image_position.text_anchor,
|
||||
image_id,
|
||||
@@ -1789,7 +1799,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
|
||||
.context
|
||||
.text_thread
|
||||
.read(cx)
|
||||
.contents(cx)
|
||||
.map(
|
||||
@@ -1837,36 +1847,36 @@ impl TextThreadEditor {
|
||||
}
|
||||
|
||||
fn split(&mut self, _: &Split, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.context.update(cx, |context, cx| {
|
||||
self.text_thread.update(cx, |text_thread, 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();
|
||||
context.split_message(range, cx);
|
||||
text_thread.split_message(range, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn title(&self, cx: &App) -> SharedString {
|
||||
self.context.read(cx).summary().or_default()
|
||||
self.text_thread.read(cx).summary().or_default()
|
||||
}
|
||||
|
||||
pub fn regenerate_summary(&mut self, cx: &mut Context<Self>) {
|
||||
self.context
|
||||
.update(cx, |context, cx| context.summarize(true, cx));
|
||||
self.text_thread
|
||||
.update(cx, |text_thread, cx| text_thread.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.context, cx)? {
|
||||
match token_state(&self.text_thread, cx)? {
|
||||
TokenState::NoTokensLeft {
|
||||
max_token_count,
|
||||
token_count,
|
||||
@@ -1914,7 +1924,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.context, cx) {
|
||||
let (style, tooltip) = match token_state(&self.text_thread, cx) {
|
||||
Some(TokenState::NoTokensLeft { .. }) => (
|
||||
ButtonStyle::Tinted(TintColor::Error),
|
||||
Some(Tooltip::text("Token limit reached")(window, cx)),
|
||||
@@ -1947,7 +1957,7 @@ impl TextThreadEditor {
|
||||
})
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&Assist, &focus_handle, window, cx)
|
||||
KeyBinding::for_action_in(&Assist, &focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(move |_event, window, cx| {
|
||||
@@ -1982,20 +1992,14 @@ 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",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
move |_window, cx| {
|
||||
Tooltip::with_meta("Add Context", None, "Type / to insert via keyboard", cx)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
let context = self.context().read(cx);
|
||||
let text_thread = self.text_thread().read(cx);
|
||||
let active_model = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()
|
||||
.map(|default| default.model)?;
|
||||
@@ -2003,7 +2007,7 @@ impl TextThreadEditor {
|
||||
return None;
|
||||
}
|
||||
|
||||
let active_completion_mode = context.completion_mode();
|
||||
let active_completion_mode = text_thread.completion_mode();
|
||||
let burn_mode_enabled = active_completion_mode == CompletionMode::Burn;
|
||||
let icon = if burn_mode_enabled {
|
||||
IconName::ZedBurnModeOn
|
||||
@@ -2018,8 +2022,8 @@ impl TextThreadEditor {
|
||||
.toggle_state(burn_mode_enabled)
|
||||
.selected_icon_color(Color::Error)
|
||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||
this.context().update(cx, |context, _cx| {
|
||||
context.set_completion_mode(match active_completion_mode {
|
||||
this.text_thread().update(cx, |text_thread, _cx| {
|
||||
text_thread.set_completion_mode(match active_completion_mode {
|
||||
CompletionMode::Burn => CompletionMode::Normal,
|
||||
CompletionMode::Normal => CompletionMode::Burn,
|
||||
});
|
||||
@@ -2078,14 +2082,8 @@ impl TextThreadEditor {
|
||||
)
|
||||
.child(Icon::new(icon).color(color).size(IconSize::XSmall)),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
&ToggleModelSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||
},
|
||||
gpui::Corner::BottomRight,
|
||||
cx,
|
||||
@@ -2652,10 +2650,10 @@ impl FollowableItem for TextThreadEditor {
|
||||
}
|
||||
|
||||
fn to_state_proto(&self, window: &Window, cx: &App) -> Option<proto::view::Variant> {
|
||||
let context = self.context.read(cx);
|
||||
let text_thread = self.text_thread.read(cx);
|
||||
Some(proto::view::Variant::ContextEditor(
|
||||
proto::view::ContextEditor {
|
||||
context_id: context.id().to_proto(),
|
||||
context_id: text_thread.id().to_proto(),
|
||||
editor: if let Some(proto::view::Variant::Editor(proto)) =
|
||||
self.editor.read(cx).to_state_proto(window, cx)
|
||||
{
|
||||
@@ -2681,22 +2679,22 @@ impl FollowableItem for TextThreadEditor {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let context_id = ContextId::from_proto(state.context_id);
|
||||
let text_thread_id = TextThreadId::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 context_editor_task = workspace.update(cx, |workspace, cx| {
|
||||
agent_panel_delegate.open_remote_context(workspace, context_id, window, cx)
|
||||
let text_thread_editor_task = workspace.update(cx, |workspace, cx| {
|
||||
agent_panel_delegate.open_remote_text_thread(workspace, text_thread_id, window, cx)
|
||||
});
|
||||
|
||||
Some(window.spawn(cx, async move |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| {
|
||||
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| {
|
||||
editor.apply_update_proto(
|
||||
&project,
|
||||
proto::update_view::Variant::Editor(proto::update_view::Editor {
|
||||
@@ -2713,7 +2711,7 @@ impl FollowableItem for TextThreadEditor {
|
||||
})
|
||||
})?
|
||||
.await?;
|
||||
Ok(context_editor)
|
||||
Ok(text_thread_editor)
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -2760,7 +2758,7 @@ impl FollowableItem for TextThreadEditor {
|
||||
}
|
||||
|
||||
fn dedup(&self, existing: &Self, _window: &Window, cx: &App) -> Option<item::Dedup> {
|
||||
if existing.context.read(cx).id() == self.context.read(cx).id() {
|
||||
if existing.text_thread.read(cx).id() == self.text_thread.read(cx).id() {
|
||||
Some(item::Dedup::KeepExisting)
|
||||
} else {
|
||||
None
|
||||
@@ -2772,17 +2770,17 @@ enum PendingSlashCommand {}
|
||||
|
||||
fn invoked_slash_command_fold_placeholder(
|
||||
command_id: InvokedSlashCommandId,
|
||||
context: WeakEntity<AssistantContext>,
|
||||
text_thread: WeakEntity<TextThread>,
|
||||
) -> FoldPlaceholder {
|
||||
FoldPlaceholder {
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
render: Arc::new(move |fold_id, _, cx| {
|
||||
let Some(context) = context.upgrade() else {
|
||||
let Some(text_thread) = text_thread.upgrade() else {
|
||||
return Empty.into_any();
|
||||
};
|
||||
|
||||
let Some(command) = context.read(cx).invoked_slash_command(&command_id) else {
|
||||
let Some(command) = text_thread.read(cx).invoked_slash_command(&command_id) else {
|
||||
return Empty.into_any();
|
||||
};
|
||||
|
||||
@@ -2823,14 +2821,15 @@ enum TokenState {
|
||||
},
|
||||
}
|
||||
|
||||
fn token_state(context: &Entity<AssistantContext>, cx: &App) -> Option<TokenState> {
|
||||
fn token_state(text_thread: &Entity<TextThread>, cx: &App) -> Option<TokenState> {
|
||||
const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
|
||||
|
||||
let model = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()?
|
||||
.model;
|
||||
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_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_state = if max_token_count.saturating_sub(token_count) == 0 {
|
||||
TokenState::NoTokensLeft {
|
||||
max_token_count,
|
||||
@@ -2942,7 +2941,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_copy_paste_whole_message(cx: &mut TestAppContext) {
|
||||
let (context, context_editor, mut cx) = setup_context_editor_text(vec![
|
||||
let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(vec![
|
||||
(Role::User, "What is the Zed editor?"),
|
||||
(
|
||||
Role::Assistant,
|
||||
@@ -2952,8 +2951,8 @@ mod tests {
|
||||
],cx).await;
|
||||
|
||||
// Select & Copy whole user message
|
||||
assert_copy_paste_context_editor(
|
||||
&context_editor,
|
||||
assert_copy_paste_text_thread_editor(
|
||||
&text_thread_editor,
|
||||
message_range(&context, 0, &mut cx),
|
||||
indoc! {"
|
||||
What is the Zed editor?
|
||||
@@ -2964,8 +2963,8 @@ mod tests {
|
||||
);
|
||||
|
||||
// Select & Copy whole assistant message
|
||||
assert_copy_paste_context_editor(
|
||||
&context_editor,
|
||||
assert_copy_paste_text_thread_editor(
|
||||
&text_thread_editor,
|
||||
message_range(&context, 1, &mut cx),
|
||||
indoc! {"
|
||||
What is the Zed editor?
|
||||
@@ -2979,7 +2978,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_copy_paste_no_selection(cx: &mut TestAppContext) {
|
||||
let (context, context_editor, mut cx) = setup_context_editor_text(
|
||||
let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(
|
||||
vec![
|
||||
(Role::User, "user1"),
|
||||
(Role::Assistant, "assistant1"),
|
||||
@@ -2992,8 +2991,8 @@ mod tests {
|
||||
|
||||
// Copy and paste first assistant message
|
||||
let message_2_range = message_range(&context, 1, &mut cx);
|
||||
assert_copy_paste_context_editor(
|
||||
&context_editor,
|
||||
assert_copy_paste_text_thread_editor(
|
||||
&text_thread_editor,
|
||||
message_2_range.start..message_2_range.start,
|
||||
indoc! {"
|
||||
user1
|
||||
@@ -3006,8 +3005,8 @@ mod tests {
|
||||
|
||||
// Copy and cut second assistant message
|
||||
let message_3_range = message_range(&context, 2, &mut cx);
|
||||
assert_copy_paste_context_editor(
|
||||
&context_editor,
|
||||
assert_copy_paste_text_thread_editor(
|
||||
&text_thread_editor,
|
||||
message_3_range.start..message_3_range.start,
|
||||
indoc! {"
|
||||
user1
|
||||
@@ -3094,29 +3093,29 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
async fn setup_context_editor_text(
|
||||
async fn setup_text_thread_editor_text(
|
||||
messages: Vec<(Role, &str)>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> (
|
||||
Entity<AssistantContext>,
|
||||
Entity<TextThread>,
|
||||
Entity<TextThreadEditor>,
|
||||
VisualTestContext,
|
||||
) {
|
||||
cx.update(init_test);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let context = create_context_with_messages(messages, cx);
|
||||
let text_thread = create_text_thread_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 context_editor = window
|
||||
let text_thread_editor = window
|
||||
.update(&mut cx, |_, window, cx| {
|
||||
cx.new(|cx| {
|
||||
TextThreadEditor::for_context(
|
||||
context.clone(),
|
||||
TextThreadEditor::for_text_thread(
|
||||
text_thread.clone(),
|
||||
fs,
|
||||
workspace.downgrade(),
|
||||
project,
|
||||
@@ -3128,59 +3127,59 @@ mod tests {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
(context, context_editor, cx)
|
||||
(text_thread, text_thread_editor, cx)
|
||||
}
|
||||
|
||||
fn message_range(
|
||||
context: &Entity<AssistantContext>,
|
||||
text_thread: &Entity<TextThread>,
|
||||
message_ix: usize,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Range<usize> {
|
||||
context.update(cx, |context, cx| {
|
||||
context
|
||||
text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread
|
||||
.messages(cx)
|
||||
.nth(message_ix)
|
||||
.unwrap()
|
||||
.anchor_range
|
||||
.to_offset(&context.buffer().read(cx).snapshot())
|
||||
.to_offset(&text_thread.buffer().read(cx).snapshot())
|
||||
})
|
||||
}
|
||||
|
||||
fn assert_copy_paste_context_editor<T: editor::ToOffset>(
|
||||
context_editor: &Entity<TextThreadEditor>,
|
||||
fn assert_copy_paste_text_thread_editor<T: editor::ToOffset>(
|
||||
text_thread_editor: &Entity<TextThreadEditor>,
|
||||
range: Range<T>,
|
||||
expected_text: &str,
|
||||
cx: &mut VisualTestContext,
|
||||
) {
|
||||
context_editor.update_in(cx, |context_editor, window, cx| {
|
||||
context_editor.editor.update(cx, |editor, cx| {
|
||||
text_thread_editor.update_in(cx, |text_thread_editor, window, cx| {
|
||||
text_thread_editor.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([range])
|
||||
});
|
||||
});
|
||||
|
||||
context_editor.copy(&Default::default(), window, cx);
|
||||
text_thread_editor.copy(&Default::default(), window, cx);
|
||||
|
||||
context_editor.editor.update(cx, |editor, cx| {
|
||||
text_thread_editor.editor.update(cx, |editor, cx| {
|
||||
editor.move_to_end(&Default::default(), window, cx);
|
||||
});
|
||||
|
||||
context_editor.paste(&Default::default(), window, cx);
|
||||
text_thread_editor.paste(&Default::default(), window, cx);
|
||||
|
||||
context_editor.editor.update(cx, |editor, cx| {
|
||||
text_thread_editor.editor.update(cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), expected_text);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn create_context_with_messages(
|
||||
fn create_text_thread_with_messages(
|
||||
mut messages: Vec<(Role, &str)>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Entity<AssistantContext> {
|
||||
) -> Entity<TextThread> {
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
cx.new(|cx| {
|
||||
let mut context = AssistantContext::local(
|
||||
let mut text_thread = TextThread::local(
|
||||
registry,
|
||||
None,
|
||||
None,
|
||||
@@ -3188,33 +3187,33 @@ mod tests {
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
cx,
|
||||
);
|
||||
let mut message_1 = context.messages(cx).next().unwrap();
|
||||
let mut message_1 = text_thread.messages(cx).next().unwrap();
|
||||
let (role, text) = messages.remove(0);
|
||||
|
||||
loop {
|
||||
if role == message_1.role {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
text_thread.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);
|
||||
context.cycle_message_roles(ids, cx);
|
||||
message_1 = context.messages(cx).next().unwrap();
|
||||
text_thread.cycle_message_roles(ids, cx);
|
||||
message_1 = text_thread.messages(cx).next().unwrap();
|
||||
}
|
||||
|
||||
let mut last_message_id = message_1.id;
|
||||
for (role, text) in messages {
|
||||
context.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
|
||||
let message = context.messages(cx).last().unwrap();
|
||||
text_thread.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
|
||||
let message = text_thread.messages(cx).last().unwrap();
|
||||
last_message_id = message.id;
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
text_thread.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(message.offset_range, text)], None, cx);
|
||||
})
|
||||
}
|
||||
|
||||
context
|
||||
text_thread
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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,8 +45,7 @@ impl Render for BurnModeTooltip {
|
||||
.child(Label::new("Burn Mode"))
|
||||
.when(self.selected, |title| title.child(turned_on));
|
||||
|
||||
let keybinding = KeyBinding::for_action(&ToggleBurnMode, window, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.)));
|
||||
let keybinding = KeyBinding::for_action(&ToggleBurnMode, cx).size(rems_from_px(12.));
|
||||
|
||||
tooltip_container(cx, |this, _| {
|
||||
this
|
||||
@@ -54,7 +53,7 @@ impl Render for BurnModeTooltip {
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(title)
|
||||
.children(keybinding)
|
||||
.child(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", window, cx)
|
||||
.tooltip(|_window, cx| {
|
||||
Tooltip::with_meta("Suggested Context", None, "Click to add it", 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 context = handle.context.clone();
|
||||
let text_thread = handle.text_thread.clone();
|
||||
Some(Rc::new(move |_, cx| {
|
||||
let text = context.read(cx).to_xml(cx);
|
||||
let text = text_thread.read(cx).to_xml(cx);
|
||||
ContextPillHover::new_text(text.into(), cx).into()
|
||||
}))
|
||||
},
|
||||
|
||||
@@ -84,10 +84,32 @@ 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(
|
||||
@@ -109,6 +131,7 @@ impl ZedAiOnboarding {
|
||||
}
|
||||
}),
|
||||
)
|
||||
.children(self.render_dismiss_button())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -180,27 +203,7 @@ impl ZedAiOnboarding {
|
||||
)
|
||||
.child(PlanDefinitions.free_plan(is_v2)),
|
||||
)
|
||||
.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)
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.children(self.render_dismiss_button())
|
||||
.child(
|
||||
v_flex()
|
||||
.mt_2()
|
||||
@@ -245,26 +248,7 @@ impl ZedAiOnboarding {
|
||||
.mb_2(),
|
||||
)
|
||||
.child(PlanDefinitions.pro_trial(is_v2, false))
|
||||
.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)
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.children(self.render_dismiss_button())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -278,26 +262,7 @@ impl ZedAiOnboarding {
|
||||
.mb_2(),
|
||||
)
|
||||
.child(PlanDefinitions.pro_plan(is_v2, false))
|
||||
.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)
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.children(self.render_dismiss_button())
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "assistant_context"
|
||||
name = "assistant_text_thread"
|
||||
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_context.rs"
|
||||
path = "src/assistant_text_thread.rs"
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
15
crates/assistant_text_thread/src/assistant_text_thread.rs
Normal file
15
crates/assistant_text_thread/src/assistant_text_thread.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
#[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());
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,3 @@
|
||||
#[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::{
|
||||
@@ -9,7 +5,7 @@ use assistant_slash_command::{
|
||||
SlashCommandResult, SlashCommandWorkingSet,
|
||||
};
|
||||
use assistant_slash_commands::FileCommandMetadata;
|
||||
use client::{self, Client, ModelRequestUsage, RequestUsage, proto, telemetry::Telemetry};
|
||||
use client::{self, ModelRequestUsage, RequestUsage, proto, telemetry::Telemetry};
|
||||
use clock::ReplicaId;
|
||||
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
|
||||
use collections::{HashMap, HashSet};
|
||||
@@ -27,7 +23,7 @@ use language_model::{
|
||||
report_assistant_event,
|
||||
};
|
||||
use open_ai::Model as OpenAiModel;
|
||||
use paths::contexts_dir;
|
||||
use paths::text_threads_dir;
|
||||
use project::Project;
|
||||
use prompt_store::PromptBuilder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -48,16 +44,10 @@ use ui::IconName;
|
||||
use util::{ResultExt, TryFutureExt, post_inc};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use crate::context_store::*;
|
||||
|
||||
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);
|
||||
pub struct TextThreadId(String);
|
||||
|
||||
impl ContextId {
|
||||
impl TextThreadId {
|
||||
pub fn new() -> Self {
|
||||
Self(Uuid::new_v4().to_string())
|
||||
}
|
||||
@@ -130,7 +120,7 @@ impl MessageStatus {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ContextOperation {
|
||||
pub enum TextThreadOperation {
|
||||
InsertMessage {
|
||||
anchor: MessageAnchor,
|
||||
metadata: MessageMetadata,
|
||||
@@ -142,7 +132,7 @@ pub enum ContextOperation {
|
||||
version: clock::Global,
|
||||
},
|
||||
UpdateSummary {
|
||||
summary: ContextSummaryContent,
|
||||
summary: TextThreadSummaryContent,
|
||||
version: clock::Global,
|
||||
},
|
||||
SlashCommandStarted {
|
||||
@@ -170,7 +160,7 @@ pub enum ContextOperation {
|
||||
BufferOperation(language::Operation),
|
||||
}
|
||||
|
||||
impl ContextOperation {
|
||||
impl TextThreadOperation {
|
||||
pub fn from_proto(op: proto::ContextOperation) -> Result<Self> {
|
||||
match op.variant.context("invalid variant")? {
|
||||
proto::context_operation::Variant::InsertMessage(insert) => {
|
||||
@@ -212,7 +202,7 @@ impl ContextOperation {
|
||||
version: language::proto::deserialize_version(&update.version),
|
||||
}),
|
||||
proto::context_operation::Variant::UpdateSummary(update) => Ok(Self::UpdateSummary {
|
||||
summary: ContextSummaryContent {
|
||||
summary: TextThreadSummaryContent {
|
||||
text: update.summary,
|
||||
done: update.done,
|
||||
timestamp: language::proto::deserialize_timestamp(
|
||||
@@ -453,7 +443,7 @@ impl ContextOperation {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ContextEvent {
|
||||
pub enum TextThreadEvent {
|
||||
ShowAssistError(SharedString),
|
||||
ShowPaymentRequiredError,
|
||||
MessagesEdited,
|
||||
@@ -476,24 +466,24 @@ pub enum ContextEvent {
|
||||
SlashCommandOutputSectionAdded {
|
||||
section: SlashCommandOutputSection<language::Anchor>,
|
||||
},
|
||||
Operation(ContextOperation),
|
||||
Operation(TextThreadOperation),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ContextSummary {
|
||||
pub enum TextThreadSummary {
|
||||
Pending,
|
||||
Content(ContextSummaryContent),
|
||||
Content(TextThreadSummaryContent),
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ContextSummaryContent {
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct TextThreadSummaryContent {
|
||||
pub text: String,
|
||||
pub done: bool,
|
||||
pub timestamp: clock::Lamport,
|
||||
}
|
||||
|
||||
impl ContextSummary {
|
||||
impl TextThreadSummary {
|
||||
pub const DEFAULT: &str = "New Text Thread";
|
||||
|
||||
pub fn or_default(&self) -> SharedString {
|
||||
@@ -505,44 +495,48 @@ impl ContextSummary {
|
||||
.map_or_else(|| message.into(), |content| content.text.clone().into())
|
||||
}
|
||||
|
||||
pub fn content(&self) -> Option<&ContextSummaryContent> {
|
||||
pub fn content(&self) -> Option<&TextThreadSummaryContent> {
|
||||
match self {
|
||||
ContextSummary::Content(content) => Some(content),
|
||||
ContextSummary::Pending | ContextSummary::Error => None,
|
||||
TextThreadSummary::Content(content) => Some(content),
|
||||
TextThreadSummary::Pending | TextThreadSummary::Error => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn content_as_mut(&mut self) -> Option<&mut ContextSummaryContent> {
|
||||
fn content_as_mut(&mut self) -> Option<&mut TextThreadSummaryContent> {
|
||||
match self {
|
||||
ContextSummary::Content(content) => Some(content),
|
||||
ContextSummary::Pending | ContextSummary::Error => None,
|
||||
TextThreadSummary::Content(content) => Some(content),
|
||||
TextThreadSummary::Pending | TextThreadSummary::Error => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn content_or_set_empty(&mut self) -> &mut ContextSummaryContent {
|
||||
fn content_or_set_empty(&mut self) -> &mut TextThreadSummaryContent {
|
||||
match self {
|
||||
ContextSummary::Content(content) => content,
|
||||
ContextSummary::Pending | ContextSummary::Error => {
|
||||
let content = ContextSummaryContent::default();
|
||||
*self = ContextSummary::Content(content);
|
||||
TextThreadSummary::Content(content) => content,
|
||||
TextThreadSummary::Pending | TextThreadSummary::Error => {
|
||||
let content = TextThreadSummaryContent {
|
||||
text: "".to_string(),
|
||||
done: false,
|
||||
timestamp: clock::Lamport::MIN,
|
||||
};
|
||||
*self = TextThreadSummary::Content(content);
|
||||
self.content_as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_pending(&self) -> bool {
|
||||
matches!(self, ContextSummary::Pending)
|
||||
matches!(self, TextThreadSummary::Pending)
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> Option<clock::Lamport> {
|
||||
match self {
|
||||
ContextSummary::Content(content) => Some(content.timestamp),
|
||||
ContextSummary::Pending | ContextSummary::Error => None,
|
||||
TextThreadSummary::Content(content) => Some(content.timestamp),
|
||||
TextThreadSummary::Pending | TextThreadSummary::Error => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ContextSummary {
|
||||
impl PartialOrd for TextThreadSummary {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.timestamp().partial_cmp(&other.timestamp())
|
||||
}
|
||||
@@ -664,27 +658,27 @@ struct PendingCompletion {
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct InvokedSlashCommandId(clock::Lamport);
|
||||
|
||||
pub struct AssistantContext {
|
||||
id: ContextId,
|
||||
pub struct TextThread {
|
||||
id: TextThreadId,
|
||||
timestamp: clock::Lamport,
|
||||
version: clock::Global,
|
||||
pending_ops: Vec<ContextOperation>,
|
||||
operations: Vec<ContextOperation>,
|
||||
pub(crate) pending_ops: Vec<TextThreadOperation>,
|
||||
operations: Vec<TextThreadOperation>,
|
||||
buffer: Entity<Buffer>,
|
||||
parsed_slash_commands: Vec<ParsedSlashCommand>,
|
||||
pub(crate) parsed_slash_commands: Vec<ParsedSlashCommand>,
|
||||
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
|
||||
edits_since_last_parse: language::Subscription,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||
pub(crate) slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||
thought_process_output_sections: Vec<ThoughtProcessOutputSection<language::Anchor>>,
|
||||
message_anchors: Vec<MessageAnchor>,
|
||||
pub(crate) message_anchors: Vec<MessageAnchor>,
|
||||
contents: Vec<Content>,
|
||||
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
summary: ContextSummary,
|
||||
pub(crate) messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
summary: TextThreadSummary,
|
||||
summary_task: Task<Option<()>>,
|
||||
completion_count: usize,
|
||||
pending_completions: Vec<PendingCompletion>,
|
||||
token_count: Option<u64>,
|
||||
pub(crate) token_count: Option<u64>,
|
||||
pending_token_count: Task<Option<()>>,
|
||||
pending_save: Task<Result<()>>,
|
||||
pending_cache_warming_task: Task<Option<()>>,
|
||||
@@ -707,9 +701,9 @@ impl ContextAnnotation for ParsedSlashCommand {
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ContextEvent> for AssistantContext {}
|
||||
impl EventEmitter<TextThreadEvent> for TextThread {}
|
||||
|
||||
impl AssistantContext {
|
||||
impl TextThread {
|
||||
pub fn local(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
project: Option<Entity<Project>>,
|
||||
@@ -719,7 +713,7 @@ impl AssistantContext {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
ContextId::new(),
|
||||
TextThreadId::new(),
|
||||
ReplicaId::default(),
|
||||
language::Capability::ReadWrite,
|
||||
language_registry,
|
||||
@@ -740,7 +734,7 @@ impl AssistantContext {
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
id: ContextId,
|
||||
id: TextThreadId,
|
||||
replica_id: ReplicaId,
|
||||
capability: language::Capability,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
@@ -776,7 +770,7 @@ impl AssistantContext {
|
||||
slash_command_output_sections: Vec::new(),
|
||||
thought_process_output_sections: Vec::new(),
|
||||
edits_since_last_parse: edits_since_last_slash_command_parse,
|
||||
summary: ContextSummary::Pending,
|
||||
summary: TextThreadSummary::Pending,
|
||||
summary_task: Task::ready(None),
|
||||
completion_count: Default::default(),
|
||||
pending_completions: Default::default(),
|
||||
@@ -796,7 +790,7 @@ impl AssistantContext {
|
||||
};
|
||||
|
||||
let first_message_id = MessageId(clock::Lamport {
|
||||
replica_id: 0,
|
||||
replica_id: ReplicaId::LOCAL,
|
||||
value: 0,
|
||||
});
|
||||
let message = MessageAnchor {
|
||||
@@ -819,12 +813,12 @@ impl AssistantContext {
|
||||
this
|
||||
}
|
||||
|
||||
pub(crate) fn serialize(&self, cx: &App) -> SavedContext {
|
||||
pub(crate) fn serialize(&self, cx: &App) -> SavedTextThread {
|
||||
let buffer = self.buffer.read(cx);
|
||||
SavedContext {
|
||||
SavedTextThread {
|
||||
id: Some(self.id.clone()),
|
||||
zed: "context".into(),
|
||||
version: SavedContext::VERSION.into(),
|
||||
version: SavedTextThread::VERSION.into(),
|
||||
text: buffer.text(),
|
||||
messages: self
|
||||
.messages(cx)
|
||||
@@ -872,7 +866,7 @@ impl AssistantContext {
|
||||
}
|
||||
|
||||
pub fn deserialize(
|
||||
saved_context: SavedContext,
|
||||
saved_context: SavedTextThread,
|
||||
path: Arc<Path>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
@@ -881,7 +875,7 @@ impl AssistantContext {
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
|
||||
let id = saved_context.id.clone().unwrap_or_else(TextThreadId::new);
|
||||
let mut this = Self::new(
|
||||
id,
|
||||
ReplicaId::default(),
|
||||
@@ -902,7 +896,7 @@ impl AssistantContext {
|
||||
this
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &ContextId {
|
||||
pub fn id(&self) -> &TextThreadId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
@@ -910,9 +904,9 @@ impl AssistantContext {
|
||||
self.timestamp.replica_id
|
||||
}
|
||||
|
||||
pub fn version(&self, cx: &App) -> ContextVersion {
|
||||
ContextVersion {
|
||||
context: self.version.clone(),
|
||||
pub fn version(&self, cx: &App) -> TextThreadVersion {
|
||||
TextThreadVersion {
|
||||
text_thread: self.version.clone(),
|
||||
buffer: self.buffer.read(cx).version(),
|
||||
}
|
||||
}
|
||||
@@ -934,7 +928,7 @@ impl AssistantContext {
|
||||
|
||||
pub fn serialize_ops(
|
||||
&self,
|
||||
since: &ContextVersion,
|
||||
since: &TextThreadVersion,
|
||||
cx: &App,
|
||||
) -> Task<Vec<proto::ContextOperation>> {
|
||||
let buffer_ops = self
|
||||
@@ -945,7 +939,7 @@ impl AssistantContext {
|
||||
let mut context_ops = self
|
||||
.operations
|
||||
.iter()
|
||||
.filter(|op| !since.context.observed(op.timestamp()))
|
||||
.filter(|op| !since.text_thread.observed(op.timestamp()))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
context_ops.extend(self.pending_ops.iter().cloned());
|
||||
@@ -969,13 +963,13 @@ impl AssistantContext {
|
||||
|
||||
pub fn apply_ops(
|
||||
&mut self,
|
||||
ops: impl IntoIterator<Item = ContextOperation>,
|
||||
ops: impl IntoIterator<Item = TextThreadOperation>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mut buffer_ops = Vec::new();
|
||||
for op in ops {
|
||||
match op {
|
||||
ContextOperation::BufferOperation(buffer_op) => buffer_ops.push(buffer_op),
|
||||
TextThreadOperation::BufferOperation(buffer_op) => buffer_ops.push(buffer_op),
|
||||
op @ _ => self.pending_ops.push(op),
|
||||
}
|
||||
}
|
||||
@@ -984,7 +978,7 @@ impl AssistantContext {
|
||||
self.flush_ops(cx);
|
||||
}
|
||||
|
||||
fn flush_ops(&mut self, cx: &mut Context<AssistantContext>) {
|
||||
fn flush_ops(&mut self, cx: &mut Context<TextThread>) {
|
||||
let mut changed_messages = HashSet::default();
|
||||
let mut summary_generated = false;
|
||||
|
||||
@@ -997,7 +991,7 @@ impl AssistantContext {
|
||||
|
||||
let timestamp = op.timestamp();
|
||||
match op.clone() {
|
||||
ContextOperation::InsertMessage {
|
||||
TextThreadOperation::InsertMessage {
|
||||
anchor, metadata, ..
|
||||
} => {
|
||||
if self.messages_metadata.contains_key(&anchor.id) {
|
||||
@@ -1007,7 +1001,7 @@ impl AssistantContext {
|
||||
self.insert_message(anchor, metadata, cx);
|
||||
}
|
||||
}
|
||||
ContextOperation::UpdateMessage {
|
||||
TextThreadOperation::UpdateMessage {
|
||||
message_id,
|
||||
metadata: new_metadata,
|
||||
..
|
||||
@@ -1018,7 +1012,7 @@ impl AssistantContext {
|
||||
changed_messages.insert(message_id);
|
||||
}
|
||||
}
|
||||
ContextOperation::UpdateSummary {
|
||||
TextThreadOperation::UpdateSummary {
|
||||
summary: new_summary,
|
||||
..
|
||||
} => {
|
||||
@@ -1027,11 +1021,11 @@ impl AssistantContext {
|
||||
.timestamp()
|
||||
.is_none_or(|current_timestamp| new_summary.timestamp > current_timestamp)
|
||||
{
|
||||
self.summary = ContextSummary::Content(new_summary);
|
||||
self.summary = TextThreadSummary::Content(new_summary);
|
||||
summary_generated = true;
|
||||
}
|
||||
}
|
||||
ContextOperation::SlashCommandStarted {
|
||||
TextThreadOperation::SlashCommandStarted {
|
||||
id,
|
||||
output_range,
|
||||
name,
|
||||
@@ -1048,9 +1042,9 @@ impl AssistantContext {
|
||||
timestamp: id.0,
|
||||
},
|
||||
);
|
||||
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id: id });
|
||||
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id: id });
|
||||
}
|
||||
ContextOperation::SlashCommandOutputSectionAdded { section, .. } => {
|
||||
TextThreadOperation::SlashCommandOutputSectionAdded { section, .. } => {
|
||||
let buffer = self.buffer.read(cx);
|
||||
if let Err(ix) = self
|
||||
.slash_command_output_sections
|
||||
@@ -1058,10 +1052,10 @@ impl AssistantContext {
|
||||
{
|
||||
self.slash_command_output_sections
|
||||
.insert(ix, section.clone());
|
||||
cx.emit(ContextEvent::SlashCommandOutputSectionAdded { section });
|
||||
cx.emit(TextThreadEvent::SlashCommandOutputSectionAdded { section });
|
||||
}
|
||||
}
|
||||
ContextOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
|
||||
TextThreadOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
|
||||
let buffer = self.buffer.read(cx);
|
||||
if let Err(ix) = self
|
||||
.thought_process_output_sections
|
||||
@@ -1071,7 +1065,7 @@ impl AssistantContext {
|
||||
.insert(ix, section.clone());
|
||||
}
|
||||
}
|
||||
ContextOperation::SlashCommandFinished {
|
||||
TextThreadOperation::SlashCommandFinished {
|
||||
id,
|
||||
error_message,
|
||||
timestamp,
|
||||
@@ -1090,10 +1084,10 @@ impl AssistantContext {
|
||||
slash_command.status = InvokedSlashCommandStatus::Finished;
|
||||
}
|
||||
}
|
||||
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id: id });
|
||||
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id: id });
|
||||
}
|
||||
}
|
||||
ContextOperation::BufferOperation(_) => unreachable!(),
|
||||
TextThreadOperation::BufferOperation(_) => unreachable!(),
|
||||
}
|
||||
|
||||
self.version.observe(timestamp);
|
||||
@@ -1103,43 +1097,43 @@ impl AssistantContext {
|
||||
|
||||
if !changed_messages.is_empty() {
|
||||
self.message_roles_updated(changed_messages, cx);
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
cx.emit(TextThreadEvent::MessagesEdited);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
if summary_generated {
|
||||
cx.emit(ContextEvent::SummaryChanged);
|
||||
cx.emit(ContextEvent::SummaryGenerated);
|
||||
cx.emit(TextThreadEvent::SummaryChanged);
|
||||
cx.emit(TextThreadEvent::SummaryGenerated);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn can_apply_op(&self, op: &ContextOperation, cx: &App) -> bool {
|
||||
fn can_apply_op(&self, op: &TextThreadOperation, cx: &App) -> bool {
|
||||
if !self.version.observed_all(op.version()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match op {
|
||||
ContextOperation::InsertMessage { anchor, .. } => self
|
||||
TextThreadOperation::InsertMessage { anchor, .. } => self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.version
|
||||
.observed(anchor.start.timestamp),
|
||||
ContextOperation::UpdateMessage { message_id, .. } => {
|
||||
TextThreadOperation::UpdateMessage { message_id, .. } => {
|
||||
self.messages_metadata.contains_key(message_id)
|
||||
}
|
||||
ContextOperation::UpdateSummary { .. } => true,
|
||||
ContextOperation::SlashCommandStarted { output_range, .. } => {
|
||||
TextThreadOperation::UpdateSummary { .. } => true,
|
||||
TextThreadOperation::SlashCommandStarted { output_range, .. } => {
|
||||
self.has_received_operations_for_anchor_range(output_range.clone(), cx)
|
||||
}
|
||||
ContextOperation::SlashCommandOutputSectionAdded { section, .. } => {
|
||||
TextThreadOperation::SlashCommandOutputSectionAdded { section, .. } => {
|
||||
self.has_received_operations_for_anchor_range(section.range.clone(), cx)
|
||||
}
|
||||
ContextOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
|
||||
TextThreadOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
|
||||
self.has_received_operations_for_anchor_range(section.range.clone(), cx)
|
||||
}
|
||||
ContextOperation::SlashCommandFinished { .. } => true,
|
||||
ContextOperation::BufferOperation(_) => {
|
||||
TextThreadOperation::SlashCommandFinished { .. } => true,
|
||||
TextThreadOperation::BufferOperation(_) => {
|
||||
panic!("buffer operations should always be applied")
|
||||
}
|
||||
}
|
||||
@@ -1160,9 +1154,9 @@ impl AssistantContext {
|
||||
observed_start && observed_end
|
||||
}
|
||||
|
||||
fn push_op(&mut self, op: ContextOperation, cx: &mut Context<Self>) {
|
||||
fn push_op(&mut self, op: TextThreadOperation, cx: &mut Context<Self>) {
|
||||
self.operations.push(op.clone());
|
||||
cx.emit(ContextEvent::Operation(op));
|
||||
cx.emit(TextThreadEvent::Operation(op));
|
||||
}
|
||||
|
||||
pub fn buffer(&self) -> &Entity<Buffer> {
|
||||
@@ -1185,7 +1179,7 @@ impl AssistantContext {
|
||||
self.path.as_ref()
|
||||
}
|
||||
|
||||
pub fn summary(&self) -> &ContextSummary {
|
||||
pub fn summary(&self) -> &TextThreadSummary {
|
||||
&self.summary
|
||||
}
|
||||
|
||||
@@ -1246,13 +1240,13 @@ impl AssistantContext {
|
||||
language::BufferEvent::Operation {
|
||||
operation,
|
||||
is_local: true,
|
||||
} => cx.emit(ContextEvent::Operation(ContextOperation::BufferOperation(
|
||||
operation.clone(),
|
||||
))),
|
||||
} => cx.emit(TextThreadEvent::Operation(
|
||||
TextThreadOperation::BufferOperation(operation.clone()),
|
||||
)),
|
||||
language::BufferEvent::Edited => {
|
||||
self.count_remaining_tokens(cx);
|
||||
self.reparse(cx);
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
cx.emit(TextThreadEvent::MessagesEdited);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -1518,7 +1512,7 @@ impl AssistantContext {
|
||||
if !updated_parsed_slash_commands.is_empty()
|
||||
|| !removed_parsed_slash_command_ranges.is_empty()
|
||||
{
|
||||
cx.emit(ContextEvent::ParsedSlashCommandsUpdated {
|
||||
cx.emit(TextThreadEvent::ParsedSlashCommandsUpdated {
|
||||
removed: removed_parsed_slash_command_ranges,
|
||||
updated: updated_parsed_slash_commands,
|
||||
});
|
||||
@@ -1592,7 +1586,7 @@ impl AssistantContext {
|
||||
&& (!command.range.start.is_valid(buffer) || !command.range.end.is_valid(buffer))
|
||||
{
|
||||
command.status = InvokedSlashCommandStatus::Finished;
|
||||
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
|
||||
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
|
||||
invalidated_command_ids.push(command_id);
|
||||
}
|
||||
}
|
||||
@@ -1601,7 +1595,7 @@ impl AssistantContext {
|
||||
let version = self.version.clone();
|
||||
let timestamp = self.next_timestamp();
|
||||
self.push_op(
|
||||
ContextOperation::SlashCommandFinished {
|
||||
TextThreadOperation::SlashCommandFinished {
|
||||
id: command_id,
|
||||
timestamp,
|
||||
error_message: None,
|
||||
@@ -1906,9 +1900,9 @@ impl AssistantContext {
|
||||
}
|
||||
}
|
||||
|
||||
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
|
||||
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
|
||||
this.push_op(
|
||||
ContextOperation::SlashCommandFinished {
|
||||
TextThreadOperation::SlashCommandFinished {
|
||||
id: command_id,
|
||||
timestamp,
|
||||
error_message,
|
||||
@@ -1931,9 +1925,9 @@ impl AssistantContext {
|
||||
timestamp: command_id.0,
|
||||
},
|
||||
);
|
||||
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
|
||||
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
|
||||
self.push_op(
|
||||
ContextOperation::SlashCommandStarted {
|
||||
TextThreadOperation::SlashCommandStarted {
|
||||
id: command_id,
|
||||
output_range: command_range,
|
||||
name: name.to_string(),
|
||||
@@ -1957,13 +1951,13 @@ impl AssistantContext {
|
||||
};
|
||||
self.slash_command_output_sections
|
||||
.insert(insertion_ix, section.clone());
|
||||
cx.emit(ContextEvent::SlashCommandOutputSectionAdded {
|
||||
cx.emit(TextThreadEvent::SlashCommandOutputSectionAdded {
|
||||
section: section.clone(),
|
||||
});
|
||||
let version = self.version.clone();
|
||||
let timestamp = self.next_timestamp();
|
||||
self.push_op(
|
||||
ContextOperation::SlashCommandOutputSectionAdded {
|
||||
TextThreadOperation::SlashCommandOutputSectionAdded {
|
||||
timestamp,
|
||||
section,
|
||||
version,
|
||||
@@ -1992,7 +1986,7 @@ impl AssistantContext {
|
||||
let version = self.version.clone();
|
||||
let timestamp = self.next_timestamp();
|
||||
self.push_op(
|
||||
ContextOperation::ThoughtProcessOutputSectionAdded {
|
||||
TextThreadOperation::ThoughtProcessOutputSectionAdded {
|
||||
timestamp,
|
||||
section,
|
||||
version,
|
||||
@@ -2111,7 +2105,7 @@ impl AssistantContext {
|
||||
let end = buffer
|
||||
.anchor_before(message_old_end_offset + chunk_len);
|
||||
context_event = Some(
|
||||
ContextEvent::StartedThoughtProcess(start..end),
|
||||
TextThreadEvent::StartedThoughtProcess(start..end),
|
||||
);
|
||||
} else {
|
||||
// This ensures that all the thinking chunks are inserted inside the thinking tag
|
||||
@@ -2129,7 +2123,7 @@ impl AssistantContext {
|
||||
if let Some(start) = thought_process_stack.pop() {
|
||||
let end = buffer.anchor_before(message_old_end_offset);
|
||||
context_event =
|
||||
Some(ContextEvent::EndedThoughtProcess(end));
|
||||
Some(TextThreadEvent::EndedThoughtProcess(end));
|
||||
thought_process_output_section =
|
||||
Some(ThoughtProcessOutputSection {
|
||||
range: start..end,
|
||||
@@ -2159,7 +2153,7 @@ impl AssistantContext {
|
||||
cx.emit(context_event);
|
||||
}
|
||||
|
||||
cx.emit(ContextEvent::StreamedCompletion);
|
||||
cx.emit(TextThreadEvent::StreamedCompletion);
|
||||
|
||||
Some(())
|
||||
})?;
|
||||
@@ -2180,7 +2174,7 @@ impl AssistantContext {
|
||||
this.update(cx, |this, cx| {
|
||||
let error_message = if let Some(error) = result.as_ref().err() {
|
||||
if error.is::<PaymentRequiredError>() {
|
||||
cx.emit(ContextEvent::ShowPaymentRequiredError);
|
||||
cx.emit(TextThreadEvent::ShowPaymentRequiredError);
|
||||
this.update_metadata(assistant_message_id, cx, |metadata| {
|
||||
metadata.status = MessageStatus::Canceled;
|
||||
});
|
||||
@@ -2191,7 +2185,7 @@ impl AssistantContext {
|
||||
.map(|err| err.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
cx.emit(ContextEvent::ShowAssistError(SharedString::from(
|
||||
cx.emit(TextThreadEvent::ShowAssistError(SharedString::from(
|
||||
error_message.clone(),
|
||||
)));
|
||||
this.update_metadata(assistant_message_id, cx, |metadata| {
|
||||
@@ -2408,13 +2402,13 @@ impl AssistantContext {
|
||||
if let Some(metadata) = self.messages_metadata.get_mut(&id) {
|
||||
f(metadata);
|
||||
metadata.timestamp = timestamp;
|
||||
let operation = ContextOperation::UpdateMessage {
|
||||
let operation = TextThreadOperation::UpdateMessage {
|
||||
message_id: id,
|
||||
metadata: metadata.clone(),
|
||||
version,
|
||||
};
|
||||
self.push_op(operation, cx);
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
cx.emit(TextThreadEvent::MessagesEdited);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -2478,7 +2472,7 @@ impl AssistantContext {
|
||||
};
|
||||
self.insert_message(anchor.clone(), metadata.clone(), cx);
|
||||
self.push_op(
|
||||
ContextOperation::InsertMessage {
|
||||
TextThreadOperation::InsertMessage {
|
||||
anchor: anchor.clone(),
|
||||
metadata,
|
||||
version,
|
||||
@@ -2501,7 +2495,7 @@ impl AssistantContext {
|
||||
Err(ix) => ix,
|
||||
};
|
||||
self.contents.insert(insertion_ix, content);
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
cx.emit(TextThreadEvent::MessagesEdited);
|
||||
}
|
||||
|
||||
pub fn contents<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = Content> {
|
||||
@@ -2576,7 +2570,7 @@ impl AssistantContext {
|
||||
};
|
||||
self.insert_message(suffix.clone(), suffix_metadata.clone(), cx);
|
||||
self.push_op(
|
||||
ContextOperation::InsertMessage {
|
||||
TextThreadOperation::InsertMessage {
|
||||
anchor: suffix.clone(),
|
||||
metadata: suffix_metadata,
|
||||
version,
|
||||
@@ -2626,7 +2620,7 @@ impl AssistantContext {
|
||||
};
|
||||
self.insert_message(selection.clone(), selection_metadata.clone(), cx);
|
||||
self.push_op(
|
||||
ContextOperation::InsertMessage {
|
||||
TextThreadOperation::InsertMessage {
|
||||
anchor: selection.clone(),
|
||||
metadata: selection_metadata,
|
||||
version,
|
||||
@@ -2638,7 +2632,7 @@ impl AssistantContext {
|
||||
};
|
||||
|
||||
if !edited_buffer {
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
cx.emit(TextThreadEvent::MessagesEdited);
|
||||
}
|
||||
new_messages
|
||||
} else {
|
||||
@@ -2652,7 +2646,7 @@ impl AssistantContext {
|
||||
new_metadata: MessageMetadata,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
cx.emit(TextThreadEvent::MessagesEdited);
|
||||
|
||||
self.messages_metadata.insert(new_anchor.id, new_metadata);
|
||||
|
||||
@@ -2688,15 +2682,15 @@ impl AssistantContext {
|
||||
// If there is no summary, it is set with `done: false` so that "Loading Summary…" can
|
||||
// be displayed.
|
||||
match self.summary {
|
||||
ContextSummary::Pending | ContextSummary::Error => {
|
||||
self.summary = ContextSummary::Content(ContextSummaryContent {
|
||||
TextThreadSummary::Pending | TextThreadSummary::Error => {
|
||||
self.summary = TextThreadSummary::Content(TextThreadSummaryContent {
|
||||
text: "".to_string(),
|
||||
done: false,
|
||||
timestamp: clock::Lamport::default(),
|
||||
timestamp: clock::Lamport::MIN,
|
||||
});
|
||||
replace_old = true;
|
||||
}
|
||||
ContextSummary::Content(_) => {}
|
||||
TextThreadSummary::Content(_) => {}
|
||||
}
|
||||
|
||||
self.summary_task = cx.spawn(async move |this, cx| {
|
||||
@@ -2718,13 +2712,13 @@ impl AssistantContext {
|
||||
}
|
||||
summary.text.extend(lines.next());
|
||||
summary.timestamp = timestamp;
|
||||
let operation = ContextOperation::UpdateSummary {
|
||||
let operation = TextThreadOperation::UpdateSummary {
|
||||
summary: summary.clone(),
|
||||
version,
|
||||
};
|
||||
this.push_op(operation, cx);
|
||||
cx.emit(ContextEvent::SummaryChanged);
|
||||
cx.emit(ContextEvent::SummaryGenerated);
|
||||
cx.emit(TextThreadEvent::SummaryChanged);
|
||||
cx.emit(TextThreadEvent::SummaryGenerated);
|
||||
})?;
|
||||
|
||||
// Stop if the LLM generated multiple lines.
|
||||
@@ -2748,13 +2742,13 @@ impl AssistantContext {
|
||||
if let Some(summary) = this.summary.content_as_mut() {
|
||||
summary.done = true;
|
||||
summary.timestamp = timestamp;
|
||||
let operation = ContextOperation::UpdateSummary {
|
||||
let operation = TextThreadOperation::UpdateSummary {
|
||||
summary: summary.clone(),
|
||||
version,
|
||||
};
|
||||
this.push_op(operation, cx);
|
||||
cx.emit(ContextEvent::SummaryChanged);
|
||||
cx.emit(ContextEvent::SummaryGenerated);
|
||||
cx.emit(TextThreadEvent::SummaryChanged);
|
||||
cx.emit(TextThreadEvent::SummaryGenerated);
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -2764,8 +2758,8 @@ impl AssistantContext {
|
||||
|
||||
if let Err(err) = result {
|
||||
this.update(cx, |this, cx| {
|
||||
this.summary = ContextSummary::Error;
|
||||
cx.emit(ContextEvent::SummaryChanged);
|
||||
this.summary = TextThreadSummary::Error;
|
||||
cx.emit(TextThreadEvent::SummaryChanged);
|
||||
})
|
||||
.log_err();
|
||||
log::error!("Error generating context summary: {}", err);
|
||||
@@ -2871,7 +2865,7 @@ impl AssistantContext {
|
||||
&mut self,
|
||||
debounce: Option<Duration>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut Context<AssistantContext>,
|
||||
cx: &mut Context<TextThread>,
|
||||
) {
|
||||
if self.replica_id() != ReplicaId::default() {
|
||||
// Prevent saving a remote context for now.
|
||||
@@ -2902,7 +2896,7 @@ impl AssistantContext {
|
||||
let mut discriminant = 1;
|
||||
let mut new_path;
|
||||
loop {
|
||||
new_path = contexts_dir().join(&format!(
|
||||
new_path = text_threads_dir().join(&format!(
|
||||
"{} - {}.zed.json",
|
||||
summary.trim(),
|
||||
discriminant
|
||||
@@ -2914,7 +2908,7 @@ impl AssistantContext {
|
||||
}
|
||||
}
|
||||
|
||||
fs.create_dir(contexts_dir().as_ref()).await?;
|
||||
fs.create_dir(text_threads_dir().as_ref()).await?;
|
||||
|
||||
// rename before write ensures that only one file exists
|
||||
if let Some(old_path) = old_path.as_ref()
|
||||
@@ -2936,7 +2930,7 @@ impl AssistantContext {
|
||||
let new_path: Arc<Path> = new_path.clone().into();
|
||||
move |this, cx| {
|
||||
this.path = Some(new_path.clone());
|
||||
cx.emit(ContextEvent::PathChanged { old_path, new_path });
|
||||
cx.emit(TextThreadEvent::PathChanged { old_path, new_path });
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
@@ -2955,7 +2949,7 @@ impl AssistantContext {
|
||||
summary.timestamp = timestamp;
|
||||
summary.done = true;
|
||||
summary.text = custom_summary;
|
||||
cx.emit(ContextEvent::SummaryChanged);
|
||||
cx.emit(TextThreadEvent::SummaryChanged);
|
||||
}
|
||||
|
||||
fn update_model_request_usage(&self, amount: u32, limit: UsageLimit, cx: &mut App) {
|
||||
@@ -2975,23 +2969,23 @@ impl AssistantContext {
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ContextVersion {
|
||||
context: clock::Global,
|
||||
pub struct TextThreadVersion {
|
||||
text_thread: clock::Global,
|
||||
buffer: clock::Global,
|
||||
}
|
||||
|
||||
impl ContextVersion {
|
||||
impl TextThreadVersion {
|
||||
pub fn from_proto(proto: &proto::ContextVersion) -> Self {
|
||||
Self {
|
||||
context: language::proto::deserialize_version(&proto.context_version),
|
||||
text_thread: language::proto::deserialize_version(&proto.context_version),
|
||||
buffer: language::proto::deserialize_version(&proto.buffer_version),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_proto(&self, context_id: ContextId) -> proto::ContextVersion {
|
||||
pub fn to_proto(&self, context_id: TextThreadId) -> proto::ContextVersion {
|
||||
proto::ContextVersion {
|
||||
context_id: context_id.to_proto(),
|
||||
context_version: language::proto::serialize_version(&self.context),
|
||||
context_version: language::proto::serialize_version(&self.text_thread),
|
||||
buffer_version: language::proto::serialize_version(&self.buffer),
|
||||
}
|
||||
}
|
||||
@@ -3059,8 +3053,8 @@ pub struct SavedMessage {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SavedContext {
|
||||
pub id: Option<ContextId>,
|
||||
pub struct SavedTextThread {
|
||||
pub id: Option<TextThreadId>,
|
||||
pub zed: String,
|
||||
pub version: String,
|
||||
pub text: String,
|
||||
@@ -3072,7 +3066,7 @@ pub struct SavedContext {
|
||||
pub thought_process_output_sections: Vec<ThoughtProcessOutputSection<usize>>,
|
||||
}
|
||||
|
||||
impl SavedContext {
|
||||
impl SavedTextThread {
|
||||
pub const VERSION: &'static str = "0.4.0";
|
||||
|
||||
pub fn from_json(json: &str) -> Result<Self> {
|
||||
@@ -3082,9 +3076,9 @@ impl SavedContext {
|
||||
.context("version not found")?
|
||||
{
|
||||
serde_json::Value::String(version) => match version.as_str() {
|
||||
SavedContext::VERSION => {
|
||||
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
|
||||
}
|
||||
SavedTextThread::VERSION => Ok(serde_json::from_value::<SavedTextThread>(
|
||||
saved_context_json,
|
||||
)?),
|
||||
SavedContextV0_3_0::VERSION => {
|
||||
let saved_context =
|
||||
serde_json::from_value::<SavedContextV0_3_0>(saved_context_json)?;
|
||||
@@ -3109,18 +3103,18 @@ impl SavedContext {
|
||||
fn into_ops(
|
||||
self,
|
||||
buffer: &Entity<Buffer>,
|
||||
cx: &mut Context<AssistantContext>,
|
||||
) -> Vec<ContextOperation> {
|
||||
cx: &mut Context<TextThread>,
|
||||
) -> Vec<TextThreadOperation> {
|
||||
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::default()) {
|
||||
if message.id == MessageId(clock::Lamport::MIN) {
|
||||
first_message_metadata = Some(message.metadata);
|
||||
} else {
|
||||
operations.push(ContextOperation::InsertMessage {
|
||||
operations.push(TextThreadOperation::InsertMessage {
|
||||
anchor: MessageAnchor {
|
||||
id: message.id,
|
||||
start: buffer.read(cx).anchor_before(message.start),
|
||||
@@ -3140,8 +3134,8 @@ impl SavedContext {
|
||||
|
||||
if let Some(metadata) = first_message_metadata {
|
||||
let timestamp = next_timestamp.tick();
|
||||
operations.push(ContextOperation::UpdateMessage {
|
||||
message_id: MessageId(clock::Lamport::default()),
|
||||
operations.push(TextThreadOperation::UpdateMessage {
|
||||
message_id: MessageId(clock::Lamport::MIN),
|
||||
metadata: MessageMetadata {
|
||||
role: metadata.role,
|
||||
status: metadata.status,
|
||||
@@ -3156,7 +3150,7 @@ impl SavedContext {
|
||||
let buffer = buffer.read(cx);
|
||||
for section in self.slash_command_output_sections {
|
||||
let timestamp = next_timestamp.tick();
|
||||
operations.push(ContextOperation::SlashCommandOutputSectionAdded {
|
||||
operations.push(TextThreadOperation::SlashCommandOutputSectionAdded {
|
||||
timestamp,
|
||||
section: SlashCommandOutputSection {
|
||||
range: buffer.anchor_after(section.range.start)
|
||||
@@ -3173,7 +3167,7 @@ impl SavedContext {
|
||||
|
||||
for section in self.thought_process_output_sections {
|
||||
let timestamp = next_timestamp.tick();
|
||||
operations.push(ContextOperation::ThoughtProcessOutputSectionAdded {
|
||||
operations.push(TextThreadOperation::ThoughtProcessOutputSectionAdded {
|
||||
timestamp,
|
||||
section: ThoughtProcessOutputSection {
|
||||
range: buffer.anchor_after(section.range.start)
|
||||
@@ -3186,8 +3180,8 @@ impl SavedContext {
|
||||
}
|
||||
|
||||
let timestamp = next_timestamp.tick();
|
||||
operations.push(ContextOperation::UpdateSummary {
|
||||
summary: ContextSummaryContent {
|
||||
operations.push(TextThreadOperation::UpdateSummary {
|
||||
summary: TextThreadSummaryContent {
|
||||
text: self.summary,
|
||||
done: true,
|
||||
timestamp,
|
||||
@@ -3217,7 +3211,7 @@ struct SavedMessageMetadataPreV0_4_0 {
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SavedContextV0_3_0 {
|
||||
id: Option<ContextId>,
|
||||
id: Option<TextThreadId>,
|
||||
zed: String,
|
||||
version: String,
|
||||
text: String,
|
||||
@@ -3230,11 +3224,11 @@ struct SavedContextV0_3_0 {
|
||||
impl SavedContextV0_3_0 {
|
||||
const VERSION: &'static str = "0.3.0";
|
||||
|
||||
fn upgrade(self) -> SavedContext {
|
||||
SavedContext {
|
||||
fn upgrade(self) -> SavedTextThread {
|
||||
SavedTextThread {
|
||||
id: self.id,
|
||||
zed: self.zed,
|
||||
version: SavedContext::VERSION.into(),
|
||||
version: SavedTextThread::VERSION.into(),
|
||||
text: self.text,
|
||||
messages: self
|
||||
.messages
|
||||
@@ -3266,7 +3260,7 @@ impl SavedContextV0_3_0 {
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SavedContextV0_2_0 {
|
||||
id: Option<ContextId>,
|
||||
id: Option<TextThreadId>,
|
||||
zed: String,
|
||||
version: String,
|
||||
text: String,
|
||||
@@ -3278,7 +3272,7 @@ struct SavedContextV0_2_0 {
|
||||
impl SavedContextV0_2_0 {
|
||||
const VERSION: &'static str = "0.2.0";
|
||||
|
||||
fn upgrade(self) -> SavedContext {
|
||||
fn upgrade(self) -> SavedTextThread {
|
||||
SavedContextV0_3_0 {
|
||||
id: self.id,
|
||||
zed: self.zed,
|
||||
@@ -3295,7 +3289,7 @@ impl SavedContextV0_2_0 {
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SavedContextV0_1_0 {
|
||||
id: Option<ContextId>,
|
||||
id: Option<TextThreadId>,
|
||||
zed: String,
|
||||
version: String,
|
||||
text: String,
|
||||
@@ -3309,7 +3303,7 @@ struct SavedContextV0_1_0 {
|
||||
impl SavedContextV0_1_0 {
|
||||
const VERSION: &'static str = "0.1.0";
|
||||
|
||||
fn upgrade(self) -> SavedContext {
|
||||
fn upgrade(self) -> SavedTextThread {
|
||||
SavedContextV0_2_0 {
|
||||
id: self.id,
|
||||
zed: self.zed,
|
||||
@@ -3324,7 +3318,7 @@ impl SavedContextV0_1_0 {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SavedContextMetadata {
|
||||
pub struct SavedTextThreadMetadata {
|
||||
pub title: SharedString,
|
||||
pub path: Arc<Path>,
|
||||
pub mtime: chrono::DateTime<chrono::Local>,
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
AssistantContext, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
|
||||
SavedContextMetadata,
|
||||
SavedTextThread, SavedTextThreadMetadata, TextThread, TextThreadEvent, TextThreadId,
|
||||
TextThreadOperation, TextThreadVersion,
|
||||
};
|
||||
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, EventEmitter, Task, WeakEntity};
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, Task, WeakEntity};
|
||||
use language::LanguageRegistry;
|
||||
use paths::contexts_dir;
|
||||
use paths::text_threads_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(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);
|
||||
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);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RemoteContextMetadata {
|
||||
pub id: ContextId,
|
||||
pub struct RemoteTextThreadMetadata {
|
||||
pub id: TextThreadId,
|
||||
pub summary: Option<String>,
|
||||
}
|
||||
|
||||
pub struct ContextStore {
|
||||
contexts: Vec<ContextHandle>,
|
||||
contexts_metadata: Vec<SavedContextMetadata>,
|
||||
pub struct TextThreadStore {
|
||||
text_threads: Vec<TextThreadHandle>,
|
||||
text_threads_metadata: Vec<SavedTextThreadMetadata>,
|
||||
context_server_slash_command_ids: HashMap<ContextServerId, Vec<SlashCommandId>>,
|
||||
host_contexts: Vec<RemoteContextMetadata>,
|
||||
host_text_threads: Vec<RemoteTextThreadMetadata>,
|
||||
fs: Arc<dyn Fs>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
@@ -58,34 +58,28 @@ pub struct ContextStore {
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
}
|
||||
|
||||
pub enum ContextStoreEvent {
|
||||
ContextCreated(ContextId),
|
||||
enum TextThreadHandle {
|
||||
Weak(WeakEntity<TextThread>),
|
||||
Strong(Entity<TextThread>),
|
||||
}
|
||||
|
||||
impl EventEmitter<ContextStoreEvent> for ContextStore {}
|
||||
|
||||
enum ContextHandle {
|
||||
Weak(WeakEntity<AssistantContext>),
|
||||
Strong(Entity<AssistantContext>),
|
||||
}
|
||||
|
||||
impl ContextHandle {
|
||||
fn upgrade(&self) -> Option<Entity<AssistantContext>> {
|
||||
impl TextThreadHandle {
|
||||
fn upgrade(&self) -> Option<Entity<TextThread>> {
|
||||
match self {
|
||||
ContextHandle::Weak(weak) => weak.upgrade(),
|
||||
ContextHandle::Strong(strong) => Some(strong.clone()),
|
||||
TextThreadHandle::Weak(weak) => weak.upgrade(),
|
||||
TextThreadHandle::Strong(strong) => Some(strong.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn downgrade(&self) -> WeakEntity<AssistantContext> {
|
||||
fn downgrade(&self) -> WeakEntity<TextThread> {
|
||||
match self {
|
||||
ContextHandle::Weak(weak) => weak.clone(),
|
||||
ContextHandle::Strong(strong) => strong.downgrade(),
|
||||
TextThreadHandle::Weak(weak) => weak.clone(),
|
||||
TextThreadHandle::Strong(strong) => strong.downgrade(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextStore {
|
||||
impl TextThreadStore {
|
||||
pub fn new(
|
||||
project: Entity<Project>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
@@ -97,14 +91,14 @@ impl ContextStore {
|
||||
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(contexts_dir(), CONTEXT_WATCH_DURATION).await;
|
||||
let (mut events, _) = fs.watch(text_threads_dir(), CONTEXT_WATCH_DURATION).await;
|
||||
|
||||
let this = cx.new(|cx: &mut Context<Self>| {
|
||||
let mut this = Self {
|
||||
contexts: Vec::new(),
|
||||
contexts_metadata: Vec::new(),
|
||||
text_threads: Vec::new(),
|
||||
text_threads_metadata: Vec::new(),
|
||||
context_server_slash_command_ids: HashMap::default(),
|
||||
host_contexts: Vec::new(),
|
||||
host_text_threads: Vec::new(),
|
||||
fs,
|
||||
languages,
|
||||
slash_commands,
|
||||
@@ -142,10 +136,10 @@ impl ContextStore {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn fake(project: Entity<Project>, cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
contexts: Default::default(),
|
||||
contexts_metadata: Default::default(),
|
||||
text_threads: Default::default(),
|
||||
text_threads_metadata: Default::default(),
|
||||
context_server_slash_command_ids: Default::default(),
|
||||
host_contexts: Default::default(),
|
||||
host_text_threads: Default::default(),
|
||||
fs: project.read(cx).fs().clone(),
|
||||
languages: project.read(cx).languages().clone(),
|
||||
slash_commands: Arc::default(),
|
||||
@@ -166,13 +160,13 @@ impl ContextStore {
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.host_contexts = envelope
|
||||
this.host_text_threads = envelope
|
||||
.payload
|
||||
.contexts
|
||||
.into_iter()
|
||||
.map(|context| RemoteContextMetadata {
|
||||
id: ContextId::from_proto(context.context_id),
|
||||
summary: context.summary,
|
||||
.map(|text_thread| RemoteTextThreadMetadata {
|
||||
id: TextThreadId::from_proto(text_thread.context_id),
|
||||
summary: text_thread.summary,
|
||||
})
|
||||
.collect();
|
||||
cx.notify();
|
||||
@@ -184,25 +178,25 @@ impl ContextStore {
|
||||
envelope: TypedEnvelope<proto::OpenContext>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::OpenContextResponse> {
|
||||
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
||||
let context_id = TextThreadId::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 context = this
|
||||
.loaded_context_for_id(&context_id, cx)
|
||||
let text_thread = this
|
||||
.loaded_text_thread_for_id(&context_id, cx)
|
||||
.context("context not found")?;
|
||||
anyhow::ensure!(
|
||||
context.read(cx).replica_id() == ReplicaId::default(),
|
||||
text_thread.read(cx).replica_id() == ReplicaId::default(),
|
||||
"context must be opened via the host"
|
||||
);
|
||||
|
||||
anyhow::Ok(
|
||||
context
|
||||
text_thread
|
||||
.read(cx)
|
||||
.serialize_ops(&ContextVersion::default(), cx),
|
||||
.serialize_ops(&TextThreadVersion::default(), cx),
|
||||
)
|
||||
})??;
|
||||
let operations = operations.await;
|
||||
@@ -222,15 +216,14 @@ impl ContextStore {
|
||||
"can only create contexts as the host"
|
||||
);
|
||||
|
||||
let context = this.create(cx);
|
||||
let context_id = context.read(cx).id().clone();
|
||||
cx.emit(ContextStoreEvent::ContextCreated(context_id.clone()));
|
||||
let text_thread = this.create(cx);
|
||||
let context_id = text_thread.read(cx).id().clone();
|
||||
|
||||
anyhow::Ok((
|
||||
context_id,
|
||||
context
|
||||
text_thread
|
||||
.read(cx)
|
||||
.serialize_ops(&ContextVersion::default(), cx),
|
||||
.serialize_ops(&TextThreadVersion::default(), cx),
|
||||
))
|
||||
})??;
|
||||
let operations = operations.await;
|
||||
@@ -246,11 +239,11 @@ impl ContextStore {
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
||||
if let Some(context) = this.loaded_context_for_id(&context_id, 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 operation_proto = envelope.payload.operation.context("invalid operation")?;
|
||||
let operation = ContextOperation::from_proto(operation_proto)?;
|
||||
context.update(cx, |context, cx| context.apply_ops([operation], cx));
|
||||
let operation = TextThreadOperation::from_proto(operation_proto)?;
|
||||
text_thread.update(cx, |text_thread, cx| text_thread.apply_ops([operation], cx));
|
||||
}
|
||||
Ok(())
|
||||
})?
|
||||
@@ -269,12 +262,12 @@ impl ContextStore {
|
||||
|
||||
let mut local_versions = Vec::new();
|
||||
for remote_version_proto in envelope.payload.contexts {
|
||||
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 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 client = this.client.clone();
|
||||
let project_id = envelope.payload.project_id;
|
||||
cx.background_spawn(async move {
|
||||
@@ -308,9 +301,9 @@ impl ContextStore {
|
||||
}
|
||||
|
||||
if is_shared {
|
||||
self.contexts.retain_mut(|context| {
|
||||
if let Some(strong_context) = context.upgrade() {
|
||||
*context = ContextHandle::Strong(strong_context);
|
||||
self.text_threads.retain_mut(|text_thread| {
|
||||
if let Some(strong_context) = text_thread.upgrade() {
|
||||
*text_thread = TextThreadHandle::Strong(strong_context);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -345,12 +338,12 @@ impl ContextStore {
|
||||
self.synchronize_contexts(cx);
|
||||
}
|
||||
project::Event::DisconnectedFromHost => {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
});
|
||||
true
|
||||
@@ -358,20 +351,24 @@ impl ContextStore {
|
||||
false
|
||||
}
|
||||
});
|
||||
self.host_contexts.clear();
|
||||
self.host_text_threads.clear();
|
||||
cx.notify();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unordered_contexts(&self) -> impl Iterator<Item = &SavedContextMetadata> {
|
||||
self.contexts_metadata.iter()
|
||||
pub fn unordered_text_threads(&self) -> impl Iterator<Item = &SavedTextThreadMetadata> {
|
||||
self.text_threads_metadata.iter()
|
||||
}
|
||||
|
||||
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
|
||||
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> {
|
||||
let context = cx.new(|cx| {
|
||||
AssistantContext::local(
|
||||
TextThread::local(
|
||||
self.languages.clone(),
|
||||
Some(self.project.clone()),
|
||||
Some(self.telemetry.clone()),
|
||||
@@ -380,14 +377,11 @@ impl ContextStore {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
self.register_context(&context, cx);
|
||||
self.register_text_thread(&context, cx);
|
||||
context
|
||||
}
|
||||
|
||||
pub fn create_remote_context(
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<AssistantContext>>> {
|
||||
pub fn create_remote(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<TextThread>>> {
|
||||
let project = self.project.read(cx);
|
||||
let Some(project_id) = project.remote_id() else {
|
||||
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
|
||||
@@ -403,10 +397,10 @@ impl ContextStore {
|
||||
let request = self.client.request(proto::CreateContext { project_id });
|
||||
cx.spawn(async move |this, cx| {
|
||||
let response = request.await?;
|
||||
let context_id = ContextId::from_proto(response.context_id);
|
||||
let context_id = TextThreadId::from_proto(response.context_id);
|
||||
let context_proto = response.context.context("invalid context")?;
|
||||
let context = cx.new(|cx| {
|
||||
AssistantContext::new(
|
||||
let text_thread = cx.new(|cx| {
|
||||
TextThread::new(
|
||||
context_id.clone(),
|
||||
replica_id,
|
||||
capability,
|
||||
@@ -423,29 +417,29 @@ impl ContextStore {
|
||||
context_proto
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(ContextOperation::from_proto)
|
||||
.map(TextThreadOperation::from_proto)
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})
|
||||
.await?;
|
||||
context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||
text_thread.update(cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||
this.update(cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
if let Some(existing_context) = this.loaded_text_thread_for_id(&context_id, cx) {
|
||||
existing_context
|
||||
} else {
|
||||
this.register_context(&context, cx);
|
||||
this.register_text_thread(&text_thread, cx);
|
||||
this.synchronize_contexts(cx);
|
||||
context
|
||||
text_thread
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_local_context(
|
||||
pub fn open_local(
|
||||
&mut self,
|
||||
path: Arc<Path>,
|
||||
cx: &Context<Self>,
|
||||
) -> Task<Result<Entity<AssistantContext>>> {
|
||||
if let Some(existing_context) = self.loaded_context_for_path(&path, cx) {
|
||||
) -> Task<Result<Entity<TextThread>>> {
|
||||
if let Some(existing_context) = self.loaded_text_thread_for_path(&path, cx) {
|
||||
return Task::ready(Ok(existing_context));
|
||||
}
|
||||
|
||||
@@ -457,7 +451,7 @@ impl ContextStore {
|
||||
let path = path.clone();
|
||||
async move {
|
||||
let saved_context = fs.load(&path).await?;
|
||||
SavedContext::from_json(&saved_context)
|
||||
SavedTextThread::from_json(&saved_context)
|
||||
}
|
||||
});
|
||||
let prompt_builder = self.prompt_builder.clone();
|
||||
@@ -466,7 +460,7 @@ impl ContextStore {
|
||||
cx.spawn(async move |this, cx| {
|
||||
let saved_context = load.await?;
|
||||
let context = cx.new(|cx| {
|
||||
AssistantContext::deserialize(
|
||||
TextThread::deserialize(
|
||||
saved_context,
|
||||
path.clone(),
|
||||
languages,
|
||||
@@ -478,21 +472,17 @@ impl ContextStore {
|
||||
)
|
||||
})?;
|
||||
this.update(cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_context_for_path(&path, cx) {
|
||||
if let Some(existing_context) = this.loaded_text_thread_for_path(&path, cx) {
|
||||
existing_context
|
||||
} else {
|
||||
this.register_context(&context, cx);
|
||||
this.register_text_thread(&context, cx);
|
||||
context
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_local_context(
|
||||
&mut self,
|
||||
path: Arc<Path>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
pub fn delete_local(&mut self, path: Arc<Path>, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
let fs = self.fs.clone();
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
@@ -506,57 +496,57 @@ impl ContextStore {
|
||||
.await?;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.contexts.retain(|context| {
|
||||
context
|
||||
this.text_threads.retain(|text_thread| {
|
||||
text_thread
|
||||
.upgrade()
|
||||
.and_then(|context| context.read(cx).path())
|
||||
.and_then(|text_thread| text_thread.read(cx).path())
|
||||
!= Some(&path)
|
||||
});
|
||||
this.contexts_metadata
|
||||
.retain(|context| context.path.as_ref() != path.as_ref());
|
||||
this.text_threads_metadata
|
||||
.retain(|text_thread| text_thread.path.as_ref() != path.as_ref());
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn loaded_context_for_id(
|
||||
pub fn loaded_text_thread_for_id(
|
||||
&self,
|
||||
id: &ContextId,
|
||||
id: &TextThreadId,
|
||||
cx: &App,
|
||||
) -> Option<Entity<AssistantContext>> {
|
||||
self.contexts.iter().find_map(|context| {
|
||||
let context = context.upgrade()?;
|
||||
if context.read(cx).id() == id {
|
||||
Some(context)
|
||||
) -> 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)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_remote_context(
|
||||
pub fn open_remote(
|
||||
&mut self,
|
||||
context_id: ContextId,
|
||||
text_thread_id: TextThreadId,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<AssistantContext>>> {
|
||||
) -> Task<Result<Entity<TextThread>>> {
|
||||
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_context_for_id(&context_id, cx) {
|
||||
if let Some(context) = self.loaded_text_thread_for_id(&text_thread_id, cx) {
|
||||
return Task::ready(Ok(context));
|
||||
}
|
||||
|
||||
@@ -567,16 +557,16 @@ impl ContextStore {
|
||||
let telemetry = self.telemetry.clone();
|
||||
let request = self.client.request(proto::OpenContext {
|
||||
project_id,
|
||||
context_id: context_id.to_proto(),
|
||||
context_id: text_thread_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 context = cx.new(|cx| {
|
||||
AssistantContext::new(
|
||||
context_id.clone(),
|
||||
let text_thread = cx.new(|cx| {
|
||||
TextThread::new(
|
||||
text_thread_id.clone(),
|
||||
replica_id,
|
||||
capability,
|
||||
language_registry,
|
||||
@@ -592,38 +582,40 @@ impl ContextStore {
|
||||
context_proto
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(ContextOperation::from_proto)
|
||||
.map(TextThreadOperation::from_proto)
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})
|
||||
.await?;
|
||||
context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||
text_thread.update(cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||
this.update(cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
if let Some(existing_context) = this.loaded_text_thread_for_id(&text_thread_id, cx)
|
||||
{
|
||||
existing_context
|
||||
} else {
|
||||
this.register_context(&context, cx);
|
||||
this.register_text_thread(&text_thread, cx);
|
||||
this.synchronize_contexts(cx);
|
||||
context
|
||||
text_thread
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn register_context(&mut self, context: &Entity<AssistantContext>, cx: &mut Context<Self>) {
|
||||
fn register_text_thread(&mut self, text_thread: &Entity<TextThread>, cx: &mut Context<Self>) {
|
||||
let handle = if self.project_is_shared {
|
||||
ContextHandle::Strong(context.clone())
|
||||
TextThreadHandle::Strong(text_thread.clone())
|
||||
} else {
|
||||
ContextHandle::Weak(context.downgrade())
|
||||
TextThreadHandle::Weak(text_thread.downgrade())
|
||||
};
|
||||
self.contexts.push(handle);
|
||||
self.text_threads.push(handle);
|
||||
self.advertise_contexts(cx);
|
||||
cx.subscribe(context, Self::handle_context_event).detach();
|
||||
cx.subscribe(text_thread, Self::handle_context_event)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn handle_context_event(
|
||||
&mut self,
|
||||
context: Entity<AssistantContext>,
|
||||
event: &ContextEvent,
|
||||
text_thread: Entity<TextThread>,
|
||||
event: &TextThreadEvent,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(project_id) = self.project.read(cx).remote_id() else {
|
||||
@@ -631,12 +623,12 @@ impl ContextStore {
|
||||
};
|
||||
|
||||
match event {
|
||||
ContextEvent::SummaryChanged => {
|
||||
TextThreadEvent::SummaryChanged => {
|
||||
self.advertise_contexts(cx);
|
||||
}
|
||||
ContextEvent::PathChanged { old_path, new_path } => {
|
||||
TextThreadEvent::PathChanged { old_path, new_path } => {
|
||||
if let Some(old_path) = old_path.as_ref() {
|
||||
for metadata in &mut self.contexts_metadata {
|
||||
for metadata in &mut self.text_threads_metadata {
|
||||
if &metadata.path == old_path {
|
||||
metadata.path = new_path.clone();
|
||||
break;
|
||||
@@ -644,8 +636,8 @@ impl ContextStore {
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextEvent::Operation(operation) => {
|
||||
let context_id = context.read(cx).id().to_proto();
|
||||
TextThreadEvent::Operation(operation) => {
|
||||
let context_id = text_thread.read(cx).id().to_proto();
|
||||
let operation = operation.to_proto();
|
||||
self.client
|
||||
.send(proto::UpdateContext {
|
||||
@@ -670,15 +662,15 @@ impl ContextStore {
|
||||
}
|
||||
|
||||
let contexts = self
|
||||
.contexts
|
||||
.text_threads
|
||||
.iter()
|
||||
.rev()
|
||||
.filter_map(|context| {
|
||||
let context = context.upgrade()?.read(cx);
|
||||
if context.replica_id() == ReplicaId::default() {
|
||||
.filter_map(|text_thread| {
|
||||
let text_thread = text_thread.upgrade()?.read(cx);
|
||||
if text_thread.replica_id() == ReplicaId::default() {
|
||||
Some(proto::ContextMetadata {
|
||||
context_id: context.id().to_proto(),
|
||||
summary: context
|
||||
context_id: text_thread.id().to_proto(),
|
||||
summary: text_thread
|
||||
.summary()
|
||||
.content()
|
||||
.map(|summary| summary.text.clone()),
|
||||
@@ -701,13 +693,13 @@ impl ContextStore {
|
||||
return;
|
||||
};
|
||||
|
||||
let contexts = self
|
||||
.contexts
|
||||
let text_threads = self
|
||||
.text_threads
|
||||
.iter()
|
||||
.filter_map(|context| {
|
||||
let context = context.upgrade()?.read(cx);
|
||||
if context.replica_id() != ReplicaId::default() {
|
||||
Some(context.version(cx).to_proto(context.id().clone()))
|
||||
.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()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -717,26 +709,27 @@ impl ContextStore {
|
||||
let client = self.client.clone();
|
||||
let request = self.client.request(proto::SynchronizeContexts {
|
||||
project_id,
|
||||
contexts,
|
||||
contexts: text_threads,
|
||||
});
|
||||
cx.spawn(async move |this, cx| {
|
||||
let response = request.await?;
|
||||
|
||||
let mut context_ids = Vec::new();
|
||||
let mut text_thread_ids = Vec::new();
|
||||
let mut operations = Vec::new();
|
||||
this.read_with(cx, |this, cx| {
|
||||
for context_version_proto in response.contexts {
|
||||
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 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 operations = futures::future::join_all(operations).await;
|
||||
for (context_id, operations) in context_ids.into_iter().zip(operations) {
|
||||
for (context_id, operations) in text_thread_ids.into_iter().zip(operations) {
|
||||
for operation in operations {
|
||||
client.send(proto::UpdateContext {
|
||||
project_id,
|
||||
@@ -751,8 +744,8 @@ impl ContextStore {
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn search(&self, query: String, cx: &App) -> Task<Vec<SavedContextMetadata>> {
|
||||
let metadata = self.contexts_metadata.clone();
|
||||
pub fn search(&self, query: String, cx: &App) -> Task<Vec<SavedTextThreadMetadata>> {
|
||||
let metadata = self.text_threads_metadata.clone();
|
||||
let executor = cx.background_executor().clone();
|
||||
cx.background_spawn(async move {
|
||||
if query.is_empty() {
|
||||
@@ -782,20 +775,16 @@ impl ContextStore {
|
||||
})
|
||||
}
|
||||
|
||||
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(contexts_dir()).await?;
|
||||
fs.create_dir(text_threads_dir()).await?;
|
||||
|
||||
let mut paths = fs.read_dir(contexts_dir()).await?;
|
||||
let mut contexts = Vec::<SavedContextMetadata>::new();
|
||||
let mut paths = fs.read_dir(text_threads_dir()).await?;
|
||||
let mut contexts = Vec::<SavedTextThreadMetadata>::new();
|
||||
while let Some(path) = paths.next().await {
|
||||
let path = path?;
|
||||
if path.extension() != Some(OsStr::new("json")) {
|
||||
@@ -821,7 +810,7 @@ impl ContextStore {
|
||||
.lines()
|
||||
.next()
|
||||
{
|
||||
contexts.push(SavedContextMetadata {
|
||||
contexts.push(SavedTextThreadMetadata {
|
||||
title: title.to_string().into(),
|
||||
path: path.into(),
|
||||
mtime: metadata.mtime.timestamp_for_user().into(),
|
||||
@@ -829,10 +818,10 @@ impl ContextStore {
|
||||
}
|
||||
}
|
||||
}
|
||||
contexts.sort_unstable_by_key(|context| Reverse(context.mtime));
|
||||
contexts.sort_unstable_by_key(|text_thread| Reverse(text_thread.mtime));
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.contexts_metadata = contexts;
|
||||
this.text_threads_metadata = contexts;
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
@@ -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 lements.
|
||||
/// we have no more free elements.
|
||||
free: usize,
|
||||
}
|
||||
|
||||
|
||||
@@ -119,21 +119,19 @@ 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, Default, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DiffHunkSummary {
|
||||
buffer_range: Range<Anchor>,
|
||||
}
|
||||
@@ -114,7 +114,9 @@ impl sum_tree::Summary for DiffHunkSummary {
|
||||
type Context<'a> = &'a text::BufferSnapshot;
|
||||
|
||||
fn zero(_cx: Self::Context<'_>) -> Self {
|
||||
Default::default()
|
||||
DiffHunkSummary {
|
||||
buffer_range: Anchor::MIN..Anchor::MIN,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
|
||||
@@ -937,7 +939,9 @@ 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::default());
|
||||
self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
|
||||
buffer_range: Anchor::MIN..Anchor::MIN,
|
||||
});
|
||||
cx.emit(BufferDiffEvent::DiffChanged {
|
||||
changed_range: Some(Anchor::MIN..Anchor::MAX),
|
||||
});
|
||||
@@ -1368,7 +1372,7 @@ mod tests {
|
||||
use gpui::TestAppContext;
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
use rand::{Rng as _, rngs::StdRng};
|
||||
use text::{Buffer, BufferId, Rope};
|
||||
use text::{Buffer, BufferId, ReplicaId, Rope};
|
||||
use unindent::Unindent as _;
|
||||
use util::test::marked_text_ranges;
|
||||
|
||||
@@ -1393,7 +1397,7 @@ mod tests {
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
|
||||
let mut buffer = Buffer::new(ReplicaId::LOCAL, 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),
|
||||
@@ -1467,7 +1471,7 @@ mod tests {
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
|
||||
let buffer = Buffer::new(ReplicaId::LOCAL, 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);
|
||||
@@ -1536,7 +1540,7 @@ mod tests {
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
|
||||
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
|
||||
let diff = cx
|
||||
.update(|cx| {
|
||||
BufferDiffSnapshot::new_with_base_text(
|
||||
@@ -1799,7 +1803,7 @@ mod tests {
|
||||
|
||||
for example in table {
|
||||
let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
|
||||
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
|
||||
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
|
||||
let hunk_range =
|
||||
buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
|
||||
|
||||
@@ -1872,7 +1876,11 @@ mod tests {
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text.clone());
|
||||
let buffer = Buffer::new(
|
||||
ReplicaId::LOCAL,
|
||||
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| {
|
||||
@@ -1945,7 +1953,7 @@ mod tests {
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1);
|
||||
let mut buffer = Buffer::new(ReplicaId::LOCAL, 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;
|
||||
use text::{BufferId, ReplicaId};
|
||||
use util::ResultExt;
|
||||
|
||||
pub const ACKNOWLEDGE_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(250);
|
||||
@@ -65,7 +65,12 @@ impl ChannelBuffer {
|
||||
|
||||
let buffer = cx.new(|cx| {
|
||||
let capability = channel_store.read(cx).channel_capability(channel.id);
|
||||
language::Buffer::remote(buffer_id, response.replica_id as u16, capability, base_text)
|
||||
language::Buffer::remote(
|
||||
buffer_id,
|
||||
ReplicaId::new(response.replica_id as u16),
|
||||
capability,
|
||||
base_text,
|
||||
)
|
||||
})?;
|
||||
buffer.update(cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
|
||||
|
||||
@@ -272,7 +277,7 @@ impl ChannelBuffer {
|
||||
self.connected
|
||||
}
|
||||
|
||||
pub fn replica_id(&self, cx: &App) -> u16 {
|
||||
pub fn replica_id(&self, cx: &App) -> ReplicaId {
|
||||
self.buffer.read(cx).replica_id()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ pub enum CliRequest {
|
||||
wsl: Option<String>,
|
||||
wait: bool,
|
||||
open_new_workspace: Option<bool>,
|
||||
reuse: bool,
|
||||
env: Option<HashMap<String, String>>,
|
||||
user_data_dir: Option<String>,
|
||||
},
|
||||
|
||||
@@ -62,11 +62,14 @@ struct Args {
|
||||
#[arg(short, long)]
|
||||
wait: bool,
|
||||
/// Add files to the currently open workspace
|
||||
#[arg(short, long, overrides_with = "new")]
|
||||
#[arg(short, long, overrides_with_all = ["new", "reuse"])]
|
||||
add: bool,
|
||||
/// Create a new workspace
|
||||
#[arg(short, long, overrides_with = "add")]
|
||||
#[arg(short, long, overrides_with_all = ["add", "reuse"])]
|
||||
new: bool,
|
||||
/// Reuse an existing window, replacing its workspace
|
||||
#[arg(short, long, overrides_with_all = ["add", "new"])]
|
||||
reuse: bool,
|
||||
/// Sets a custom directory for all user data (e.g., database, extensions, logs).
|
||||
/// This overrides the default platform-specific data directory location:
|
||||
#[cfg_attr(target_os = "macos", doc = "`~/Library/Application Support/Zed`.")]
|
||||
@@ -374,6 +377,7 @@ fn main() -> Result<()> {
|
||||
wsl,
|
||||
wait: args.wait,
|
||||
open_new_workspace,
|
||||
reuse: args.reuse,
|
||||
env,
|
||||
user_data_dir: user_data_dir_for_thread,
|
||||
})?;
|
||||
|
||||
@@ -23,7 +23,7 @@ pub(super) struct Socks5Authorization<'a> {
|
||||
|
||||
/// Socks Proxy Protocol Version
|
||||
///
|
||||
/// V4 allows idenfication using a user_id
|
||||
/// V4 allows identification using a user_id
|
||||
/// V5 allows authorization using a username and password
|
||||
pub(super) enum SocksVersion<'a> {
|
||||
V4 {
|
||||
|
||||
@@ -943,7 +943,7 @@ impl Collaborator {
|
||||
pub fn from_proto(message: proto::Collaborator) -> Result<Self> {
|
||||
Ok(Self {
|
||||
peer_id: message.peer_id.context("invalid peer id")?,
|
||||
replica_id: message.replica_id as ReplicaId,
|
||||
replica_id: ReplicaId::new(message.replica_id as u16),
|
||||
user_id: message.user_id as UserId,
|
||||
is_host: message.is_host,
|
||||
committer_name: message.committer_name,
|
||||
|
||||
@@ -4,33 +4,73 @@ use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
fmt, iter,
|
||||
fmt,
|
||||
};
|
||||
|
||||
pub use system_clock::*;
|
||||
|
||||
pub const LOCAL_BRANCH_REPLICA_ID: u16 = u16::MAX;
|
||||
pub const AGENT_REPLICA_ID: u16 = u16::MAX - 1;
|
||||
|
||||
/// A unique identifier for each distributed node.
|
||||
pub type ReplicaId = u16;
|
||||
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
|
||||
pub struct ReplicaId(u16);
|
||||
|
||||
impl ReplicaId {
|
||||
/// The local replica
|
||||
pub const LOCAL: ReplicaId = ReplicaId(0);
|
||||
/// The remote replica of the connected remote server.
|
||||
pub const REMOTE_SERVER: ReplicaId = ReplicaId(1);
|
||||
/// The agent's unique identifier.
|
||||
pub const AGENT: ReplicaId = ReplicaId(2);
|
||||
/// A local branch.
|
||||
pub const LOCAL_BRANCH: ReplicaId = ReplicaId(3);
|
||||
/// The first collaborative replica ID, any replica equal or greater than this is a collaborative replica.
|
||||
pub const FIRST_COLLAB_ID: ReplicaId = ReplicaId(8);
|
||||
|
||||
pub fn new(id: u16) -> Self {
|
||||
ReplicaId(id)
|
||||
}
|
||||
|
||||
pub fn as_u16(&self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn is_remote(self) -> bool {
|
||||
self == ReplicaId::REMOTE_SERVER || self >= ReplicaId::FIRST_COLLAB_ID
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ReplicaId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if *self == ReplicaId::LOCAL {
|
||||
write!(f, "<local>")
|
||||
} else if *self == ReplicaId::REMOTE_SERVER {
|
||||
write!(f, "<remote>")
|
||||
} else if *self == ReplicaId::AGENT {
|
||||
write!(f, "<agent>")
|
||||
} else if *self == ReplicaId::LOCAL_BRANCH {
|
||||
write!(f, "<branch>")
|
||||
} else {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A [Lamport sequence number](https://en.wikipedia.org/wiki/Lamport_timestamp).
|
||||
pub type Seq = u32;
|
||||
|
||||
/// A [Lamport timestamp](https://en.wikipedia.org/wiki/Lamport_timestamp),
|
||||
/// used to determine the ordering of events in the editor.
|
||||
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Lamport {
|
||||
pub replica_id: ReplicaId,
|
||||
pub value: Seq,
|
||||
}
|
||||
|
||||
/// A [vector clock](https://en.wikipedia.org/wiki/Vector_clock).
|
||||
/// A [version vector](https://en.wikipedia.org/wiki/Version_vector).
|
||||
#[derive(Clone, Default, Hash, Eq, PartialEq)]
|
||||
pub struct Global {
|
||||
values: SmallVec<[u32; 8]>,
|
||||
local_branch_value: u32,
|
||||
// 4 is chosen as it is the biggest count that does not increase the size of the field itself.
|
||||
// Coincidentally, it also covers all the important non-collab replica ids.
|
||||
values: SmallVec<[u32; 4]>,
|
||||
}
|
||||
|
||||
impl Global {
|
||||
@@ -38,30 +78,31 @@ impl Global {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Fetches the sequence number for the given replica ID.
|
||||
pub fn get(&self, replica_id: ReplicaId) -> Seq {
|
||||
if replica_id == LOCAL_BRANCH_REPLICA_ID {
|
||||
self.local_branch_value
|
||||
} else {
|
||||
self.values.get(replica_id as usize).copied().unwrap_or(0) as Seq
|
||||
}
|
||||
self.values.get(replica_id.0 as usize).copied().unwrap_or(0) as Seq
|
||||
}
|
||||
|
||||
/// Observe the lamport timestamp.
|
||||
///
|
||||
/// This sets the current sequence number of the observed replica ID to the maximum of this global's observed sequence and the observed timestamp.
|
||||
pub fn observe(&mut self, timestamp: Lamport) {
|
||||
debug_assert_ne!(timestamp.replica_id, Lamport::MAX.replica_id);
|
||||
if timestamp.value > 0 {
|
||||
if timestamp.replica_id == LOCAL_BRANCH_REPLICA_ID {
|
||||
self.local_branch_value = cmp::max(self.local_branch_value, timestamp.value);
|
||||
} else {
|
||||
let new_len = timestamp.replica_id as usize + 1;
|
||||
if new_len > self.values.len() {
|
||||
self.values.resize(new_len, 0);
|
||||
}
|
||||
|
||||
let entry = &mut self.values[timestamp.replica_id as usize];
|
||||
*entry = cmp::max(*entry, timestamp.value);
|
||||
let new_len = timestamp.replica_id.0 as usize + 1;
|
||||
if new_len > self.values.len() {
|
||||
self.values.resize(new_len, 0);
|
||||
}
|
||||
|
||||
let entry = &mut self.values[timestamp.replica_id.0 as usize];
|
||||
*entry = cmp::max(*entry, timestamp.value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Join another global.
|
||||
///
|
||||
/// This observes all timestamps from the other global.
|
||||
#[doc(alias = "synchronize")]
|
||||
pub fn join(&mut self, other: &Self) {
|
||||
if other.values.len() > self.values.len() {
|
||||
self.values.resize(other.values.len(), 0);
|
||||
@@ -70,34 +111,36 @@ impl Global {
|
||||
for (left, right) in self.values.iter_mut().zip(&other.values) {
|
||||
*left = cmp::max(*left, *right);
|
||||
}
|
||||
|
||||
self.local_branch_value = cmp::max(self.local_branch_value, other.local_branch_value);
|
||||
}
|
||||
|
||||
/// Meet another global.
|
||||
///
|
||||
/// Sets all unobserved timestamps of this global to the sequences of other and sets all observed timestamps of this global to the minimum observed of both globals.
|
||||
pub fn meet(&mut self, other: &Self) {
|
||||
if other.values.len() > self.values.len() {
|
||||
self.values.resize(other.values.len(), 0);
|
||||
}
|
||||
|
||||
let mut new_len = 0;
|
||||
for (ix, (left, right)) in self
|
||||
.values
|
||||
.iter_mut()
|
||||
.zip(other.values.iter().chain(iter::repeat(&0)))
|
||||
.enumerate()
|
||||
{
|
||||
if *left == 0 {
|
||||
*left = *right;
|
||||
} else if *right > 0 {
|
||||
*left = cmp::min(*left, *right);
|
||||
for (ix, (left, &right)) in self.values.iter_mut().zip(&other.values).enumerate() {
|
||||
match (*left, right) {
|
||||
// left has not observed the replica
|
||||
(0, _) => *left = right,
|
||||
// right has not observed the replica
|
||||
(_, 0) => (),
|
||||
(_, _) => *left = cmp::min(*left, right),
|
||||
}
|
||||
|
||||
if *left != 0 {
|
||||
new_len = ix + 1;
|
||||
}
|
||||
}
|
||||
self.values.resize(new_len, 0);
|
||||
self.local_branch_value = cmp::min(self.local_branch_value, other.local_branch_value);
|
||||
if other.values.len() == self.values.len() {
|
||||
// only truncate if other was equal or shorter (which at this point
|
||||
// cant be due to the resize above) to `self` as otherwise we would
|
||||
// truncate the unprocessed tail that is guaranteed to contain
|
||||
// non-null timestamps
|
||||
self.values.truncate(new_len);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn observed(&self, timestamp: Lamport) -> bool {
|
||||
@@ -105,20 +148,18 @@ impl Global {
|
||||
}
|
||||
|
||||
pub fn observed_any(&self, other: &Self) -> bool {
|
||||
self.values
|
||||
.iter()
|
||||
.zip(other.values.iter())
|
||||
.any(|(left, right)| *right > 0 && left >= right)
|
||||
|| (other.local_branch_value > 0 && self.local_branch_value >= other.local_branch_value)
|
||||
self.iter()
|
||||
.zip(other.iter())
|
||||
.any(|(left, right)| right.value > 0 && left.value >= right.value)
|
||||
}
|
||||
|
||||
pub fn observed_all(&self, other: &Self) -> bool {
|
||||
let mut rhs = other.values.iter();
|
||||
self.values.iter().all(|left| match rhs.next() {
|
||||
Some(right) => left >= right,
|
||||
None => true,
|
||||
}) && rhs.next().is_none()
|
||||
&& self.local_branch_value >= other.local_branch_value
|
||||
if self.values.len() < other.values.len() {
|
||||
return false;
|
||||
}
|
||||
self.iter()
|
||||
.zip(other.iter())
|
||||
.all(|(left, right)| left.value >= right.value)
|
||||
}
|
||||
|
||||
pub fn changed_since(&self, other: &Self) -> bool {
|
||||
@@ -128,21 +169,21 @@ impl Global {
|
||||
.iter()
|
||||
.zip(other.values.iter())
|
||||
.any(|(left, right)| left > right)
|
||||
|| self.local_branch_value > other.local_branch_value
|
||||
}
|
||||
|
||||
pub fn most_recent(&self) -> Option<Lamport> {
|
||||
self.iter().max_by_key(|timestamp| timestamp.value)
|
||||
}
|
||||
|
||||
/// Iterates all replicas observed by this global as well as any unobserved replicas whose ID is lower than the highest observed replica.
|
||||
pub fn iter(&self) -> impl Iterator<Item = Lamport> + '_ {
|
||||
self.values
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(replica_id, seq)| Lamport {
|
||||
replica_id: replica_id as ReplicaId,
|
||||
replica_id: ReplicaId(replica_id as u16),
|
||||
value: *seq,
|
||||
})
|
||||
.chain((self.local_branch_value > 0).then_some(Lamport {
|
||||
replica_id: LOCAL_BRANCH_REPLICA_ID,
|
||||
value: self.local_branch_value,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,12 +214,12 @@ impl PartialOrd for Lamport {
|
||||
|
||||
impl Lamport {
|
||||
pub const MIN: Self = Self {
|
||||
replica_id: ReplicaId::MIN,
|
||||
replica_id: ReplicaId(u16::MIN),
|
||||
value: Seq::MIN,
|
||||
};
|
||||
|
||||
pub const MAX: Self = Self {
|
||||
replica_id: ReplicaId::MAX,
|
||||
replica_id: ReplicaId(u16::MAX),
|
||||
value: Seq::MAX,
|
||||
};
|
||||
|
||||
@@ -190,7 +231,7 @@ impl Lamport {
|
||||
}
|
||||
|
||||
pub fn as_u64(self) -> u64 {
|
||||
((self.value as u64) << 32) | (self.replica_id as u64)
|
||||
((self.value as u64) << 32) | (self.replica_id.0 as u64)
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) -> Self {
|
||||
@@ -211,7 +252,7 @@ impl fmt::Debug for Lamport {
|
||||
} else if *self == Self::MIN {
|
||||
write!(f, "Lamport {{MIN}}")
|
||||
} else {
|
||||
write!(f, "Lamport {{{}: {}}}", self.replica_id, self.value)
|
||||
write!(f, "Lamport {{{:?}: {}}}", self.replica_id, self.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,16 +261,10 @@ impl fmt::Debug for Global {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Global {{")?;
|
||||
for timestamp in self.iter() {
|
||||
if timestamp.replica_id > 0 {
|
||||
if timestamp.replica_id.0 > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
if timestamp.replica_id == LOCAL_BRANCH_REPLICA_ID {
|
||||
write!(f, "<branch>: {}", timestamp.value)?;
|
||||
} else if timestamp.replica_id == AGENT_REPLICA_ID {
|
||||
write!(f, "<agent>: {}", timestamp.value)?;
|
||||
} else {
|
||||
write!(f, "{}: {}", timestamp.replica_id, timestamp.value)?;
|
||||
}
|
||||
write!(f, "{:?}: {}", timestamp.replica_id, timestamp.value)?;
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
|
||||
@@ -47,7 +47,9 @@ reqwest = { version = "0.11", features = ["json"] }
|
||||
reqwest_client.workspace = true
|
||||
rpc.workspace = true
|
||||
scrypt = "0.11"
|
||||
# sea-orm and sea-orm-macros versions must match exactly.
|
||||
sea-orm = { version = "=1.1.10", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
|
||||
sea-orm-macros = "=1.1.10"
|
||||
semantic_version.workspace = true
|
||||
semver.workspace = true
|
||||
serde.workspace = true
|
||||
@@ -71,7 +73,7 @@ uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
agent_settings.workspace = true
|
||||
assistant_context.workspace = true
|
||||
assistant_text_thread.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
async-trait.workspace = true
|
||||
audio.workspace = true
|
||||
|
||||
@@ -62,9 +62,9 @@ impl Database {
|
||||
.iter()
|
||||
.map(|c| c.replica_id)
|
||||
.collect::<HashSet<_>>();
|
||||
let mut replica_id = ReplicaId(0);
|
||||
let mut replica_id = ReplicaId(clock::ReplicaId::FIRST_COLLAB_ID.as_u16() as i32);
|
||||
while replica_ids.contains(&replica_id) {
|
||||
replica_id.0 += 1;
|
||||
replica_id = ReplicaId(replica_id.0 + 1);
|
||||
}
|
||||
let collaborator = channel_buffer_collaborator::ActiveModel {
|
||||
channel_id: ActiveValue::Set(channel_id),
|
||||
@@ -203,7 +203,7 @@ impl Database {
|
||||
while let Some(row) = rows.next().await {
|
||||
let row = row?;
|
||||
let timestamp = clock::Lamport {
|
||||
replica_id: row.replica_id as u16,
|
||||
replica_id: clock::ReplicaId::new(row.replica_id as u16),
|
||||
value: row.lamport_timestamp as u32,
|
||||
};
|
||||
server_version.observe(timestamp);
|
||||
@@ -701,7 +701,11 @@ impl Database {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut text_buffer = text::Buffer::new(0, text::BufferId::new(1).unwrap(), base_text);
|
||||
let mut text_buffer = text::Buffer::new(
|
||||
clock::ReplicaId::LOCAL,
|
||||
text::BufferId::new(1).unwrap(),
|
||||
base_text,
|
||||
);
|
||||
text_buffer.apply_ops(operations.into_iter().filter_map(operation_from_wire));
|
||||
|
||||
let base_text = text_buffer.text();
|
||||
@@ -934,7 +938,7 @@ pub fn operation_from_wire(operation: proto::Operation) -> Option<text::Operatio
|
||||
match operation.variant? {
|
||||
proto::operation::Variant::Edit(edit) => Some(text::Operation::Edit(EditOperation {
|
||||
timestamp: clock::Lamport {
|
||||
replica_id: edit.replica_id as text::ReplicaId,
|
||||
replica_id: clock::ReplicaId::new(edit.replica_id as u16),
|
||||
value: edit.lamport_timestamp,
|
||||
},
|
||||
version: version_from_wire(&edit.version),
|
||||
@@ -949,7 +953,7 @@ pub fn operation_from_wire(operation: proto::Operation) -> Option<text::Operatio
|
||||
})),
|
||||
proto::operation::Variant::Undo(undo) => Some(text::Operation::Undo(UndoOperation {
|
||||
timestamp: clock::Lamport {
|
||||
replica_id: undo.replica_id as text::ReplicaId,
|
||||
replica_id: clock::ReplicaId::new(undo.replica_id as u16),
|
||||
value: undo.lamport_timestamp,
|
||||
},
|
||||
version: version_from_wire(&undo.version),
|
||||
@@ -959,7 +963,7 @@ pub fn operation_from_wire(operation: proto::Operation) -> Option<text::Operatio
|
||||
.map(|c| {
|
||||
(
|
||||
clock::Lamport {
|
||||
replica_id: c.replica_id as text::ReplicaId,
|
||||
replica_id: clock::ReplicaId::new(c.replica_id as u16),
|
||||
value: c.lamport_timestamp,
|
||||
},
|
||||
c.count,
|
||||
@@ -975,7 +979,7 @@ fn version_from_wire(message: &[proto::VectorClockEntry]) -> clock::Global {
|
||||
let mut version = clock::Global::new();
|
||||
for entry in message {
|
||||
version.observe(clock::Lamport {
|
||||
replica_id: entry.replica_id as text::ReplicaId,
|
||||
replica_id: clock::ReplicaId::new(entry.replica_id as u16),
|
||||
value: entry.timestamp,
|
||||
});
|
||||
}
|
||||
@@ -986,7 +990,7 @@ fn version_to_wire(version: &clock::Global) -> Vec<proto::VectorClockEntry> {
|
||||
let mut message = Vec::new();
|
||||
for entry in version.iter() {
|
||||
message.push(proto::VectorClockEntry {
|
||||
replica_id: entry.replica_id as u32,
|
||||
replica_id: entry.replica_id.as_u16() as u32,
|
||||
timestamp: entry.value,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,14 +91,18 @@ impl Database {
|
||||
.await?;
|
||||
}
|
||||
|
||||
let replica_id = if is_ssh_project { 1 } else { 0 };
|
||||
let replica_id = if is_ssh_project {
|
||||
clock::ReplicaId::REMOTE_SERVER
|
||||
} else {
|
||||
clock::ReplicaId::LOCAL
|
||||
};
|
||||
|
||||
project_collaborator::ActiveModel {
|
||||
project_id: ActiveValue::set(project.id),
|
||||
connection_id: ActiveValue::set(connection.id as i32),
|
||||
connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
|
||||
user_id: ActiveValue::set(participant.user_id),
|
||||
replica_id: ActiveValue::set(ReplicaId(replica_id)),
|
||||
replica_id: ActiveValue::set(ReplicaId(replica_id.as_u16() as i32)),
|
||||
is_host: ActiveValue::set(true),
|
||||
id: ActiveValue::NotSet,
|
||||
committer_name: ActiveValue::Set(None),
|
||||
@@ -841,7 +845,7 @@ impl Database {
|
||||
.iter()
|
||||
.map(|c| c.replica_id)
|
||||
.collect::<HashSet<_>>();
|
||||
let mut replica_id = ReplicaId(1);
|
||||
let mut replica_id = ReplicaId(clock::ReplicaId::FIRST_COLLAB_ID.as_u16() as i32);
|
||||
while replica_ids.contains(&replica_id) {
|
||||
replica_id.0 += 1;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
use crate::test_both_dbs;
|
||||
use language::proto::{self, serialize_version};
|
||||
use text::Buffer;
|
||||
use text::{Buffer, ReplicaId};
|
||||
|
||||
test_both_dbs!(
|
||||
test_channel_buffers,
|
||||
@@ -70,7 +70,11 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut buffer_a = Buffer::new(0, text::BufferId::new(1).unwrap(), "".to_string());
|
||||
let mut buffer_a = Buffer::new(
|
||||
ReplicaId::new(0),
|
||||
text::BufferId::new(1).unwrap(),
|
||||
"".to_string(),
|
||||
);
|
||||
let operations = vec![
|
||||
buffer_a.edit([(0..0, "hello world")]),
|
||||
buffer_a.edit([(5..5, ", cruel")]),
|
||||
@@ -95,7 +99,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||
.unwrap();
|
||||
|
||||
let mut buffer_b = Buffer::new(
|
||||
0,
|
||||
ReplicaId::new(0),
|
||||
text::BufferId::new(1).unwrap(),
|
||||
buffer_response_b.base_text,
|
||||
);
|
||||
@@ -124,7 +128,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||
rpc::proto::Collaborator {
|
||||
user_id: a_id.to_proto(),
|
||||
peer_id: Some(rpc::proto::PeerId { id: 1, owner_id }),
|
||||
replica_id: 0,
|
||||
replica_id: ReplicaId::FIRST_COLLAB_ID.as_u16() as u32,
|
||||
is_host: false,
|
||||
committer_name: None,
|
||||
committer_email: None,
|
||||
@@ -132,7 +136,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||
rpc::proto::Collaborator {
|
||||
user_id: b_id.to_proto(),
|
||||
peer_id: Some(rpc::proto::PeerId { id: 2, owner_id }),
|
||||
replica_id: 1,
|
||||
replica_id: ReplicaId::FIRST_COLLAB_ID.as_u16() as u32 + 1,
|
||||
is_host: false,
|
||||
committer_name: None,
|
||||
committer_email: None,
|
||||
@@ -228,7 +232,8 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.join_channel_buffer(channel, user_id, connection_id)
|
||||
let res = db
|
||||
.join_channel_buffer(channel, user_id, connection_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -239,7 +244,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
||||
);
|
||||
|
||||
text_buffers.push(Buffer::new(
|
||||
0,
|
||||
ReplicaId::new(res.replica_id as u16),
|
||||
text::BufferId::new(1).unwrap(),
|
||||
"".to_string(),
|
||||
));
|
||||
@@ -276,7 +281,12 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
||||
db.join_channel_buffer(buffers[1].channel_id, user_id, connection_id)
|
||||
.await
|
||||
.unwrap();
|
||||
text_buffers[1] = Buffer::new(1, text::BufferId::new(1).unwrap(), "def".to_string());
|
||||
let replica_id = text_buffers[1].replica_id();
|
||||
text_buffers[1] = Buffer::new(
|
||||
replica_id,
|
||||
text::BufferId::new(1).unwrap(),
|
||||
"def".to_string(),
|
||||
);
|
||||
update_buffer(
|
||||
buffers[1].channel_id,
|
||||
user_id,
|
||||
@@ -304,20 +314,32 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
||||
rpc::proto::ChannelBufferVersion {
|
||||
channel_id: buffers[0].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[0].version()),
|
||||
version: serialize_version(&text_buffers[0].version())
|
||||
.into_iter()
|
||||
.filter(
|
||||
|vector| vector.replica_id == text_buffers[0].replica_id().as_u16() as u32
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
rpc::proto::ChannelBufferVersion {
|
||||
channel_id: buffers[1].channel_id.to_proto(),
|
||||
epoch: 1,
|
||||
version: serialize_version(&text_buffers[1].version())
|
||||
.into_iter()
|
||||
.filter(|vector| vector.replica_id == text_buffers[1].replica_id() as u32)
|
||||
.filter(
|
||||
|vector| vector.replica_id == text_buffers[1].replica_id().as_u16() as u32
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
rpc::proto::ChannelBufferVersion {
|
||||
channel_id: buffers[2].channel_id.to_proto(),
|
||||
epoch: 0,
|
||||
version: serialize_version(&text_buffers[2].version()),
|
||||
version: serialize_version(&text_buffers[2].version())
|
||||
.into_iter()
|
||||
.filter(
|
||||
|vector| vector.replica_id == text_buffers[2].replica_id().as_u16() as u32
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
@@ -343,7 +343,6 @@ impl Server {
|
||||
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferForSymbol>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferById>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::SynchronizeBuffers>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::InlayHints>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::ResolveInlayHint>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GetColorPresentation>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>)
|
||||
|
||||
@@ -505,7 +505,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
label: "third_method(…)".into(),
|
||||
detail: Some("fn(&mut self, B, C, D) -> E".into()),
|
||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
// no snippet placehodlers
|
||||
// no snippet placeholders
|
||||
new_text: "third_method".to_string(),
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(1, 32),
|
||||
@@ -1849,10 +1849,40 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
..lsp::ServerCapabilities::default()
|
||||
};
|
||||
client_a.language_registry().add(rust_lang());
|
||||
|
||||
// Set up the language server to return an additional inlay hint on each request.
|
||||
let edits_made = Arc::new(AtomicUsize::new(0));
|
||||
let closure_edits_made = Arc::clone(&edits_made);
|
||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: capabilities.clone(),
|
||||
initializer: Some(Box::new(move |fake_language_server| {
|
||||
let closure_edits_made = closure_edits_made.clone();
|
||||
fake_language_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
|
||||
move |params, _| {
|
||||
let edits_made_2 = Arc::clone(&closure_edits_made);
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
let edits_made =
|
||||
AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
position: lsp::Position::new(0, edits_made as u32),
|
||||
label: lsp::InlayHintLabel::String(edits_made.to_string()),
|
||||
kind: None,
|
||||
text_edits: None,
|
||||
tooltip: None,
|
||||
padding_left: None,
|
||||
padding_right: None,
|
||||
data: None,
|
||||
}]))
|
||||
}
|
||||
},
|
||||
);
|
||||
})),
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
@@ -1894,61 +1924,20 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
.unwrap();
|
||||
|
||||
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
|
||||
executor.start_waiting();
|
||||
|
||||
// The host opens a rust file.
|
||||
let _buffer_a = project_a
|
||||
.update(cx_a, |project, cx| {
|
||||
project.open_local_buffer(path!("/a/main.rs"), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let editor_a = workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
let file_a = workspace_a.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
|
||||
});
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
|
||||
// Set up the language server to return an additional inlay hint on each request.
|
||||
let edits_made = Arc::new(AtomicUsize::new(0));
|
||||
let closure_edits_made = Arc::clone(&edits_made);
|
||||
fake_language_server
|
||||
.set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||
let edits_made_2 = Arc::clone(&closure_edits_made);
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
let edits_made = AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
position: lsp::Position::new(0, edits_made as u32),
|
||||
label: lsp::InlayHintLabel::String(edits_made.to_string()),
|
||||
kind: None,
|
||||
text_edits: None,
|
||||
tooltip: None,
|
||||
padding_left: None,
|
||||
padding_right: None,
|
||||
data: None,
|
||||
}]))
|
||||
}
|
||||
})
|
||||
.next()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let editor_a = file_a.await.unwrap().downcast::<Editor>().unwrap();
|
||||
executor.run_until_parked();
|
||||
|
||||
let initial_edit = edits_made.load(atomic::Ordering::Acquire);
|
||||
editor_a.update(cx_a, |editor, _| {
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec![initial_edit.to_string()],
|
||||
extract_hint_labels(editor),
|
||||
extract_hint_labels(editor, cx),
|
||||
"Host should get its first hints when opens an editor"
|
||||
);
|
||||
});
|
||||
@@ -1963,10 +1952,10 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec![initial_edit.to_string()],
|
||||
extract_hint_labels(editor),
|
||||
extract_hint_labels(editor, cx),
|
||||
"Client should get its first hints when opens an editor"
|
||||
);
|
||||
});
|
||||
@@ -1981,16 +1970,16 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
cx_b.focus(&editor_b);
|
||||
|
||||
executor.run_until_parked();
|
||||
editor_a.update(cx_a, |editor, _| {
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec![after_client_edit.to_string()],
|
||||
extract_hint_labels(editor),
|
||||
extract_hint_labels(editor, cx),
|
||||
);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec![after_client_edit.to_string()],
|
||||
extract_hint_labels(editor),
|
||||
extract_hint_labels(editor, cx),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2004,16 +1993,16 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
cx_a.focus(&editor_a);
|
||||
|
||||
executor.run_until_parked();
|
||||
editor_a.update(cx_a, |editor, _| {
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec![after_host_edit.to_string()],
|
||||
extract_hint_labels(editor),
|
||||
extract_hint_labels(editor, cx),
|
||||
);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec![after_host_edit.to_string()],
|
||||
extract_hint_labels(editor),
|
||||
extract_hint_labels(editor, cx),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2025,26 +2014,22 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
.expect("inlay refresh request failed");
|
||||
|
||||
executor.run_until_parked();
|
||||
editor_a.update(cx_a, |editor, _| {
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec![after_special_edit_for_refresh.to_string()],
|
||||
extract_hint_labels(editor),
|
||||
extract_hint_labels(editor, cx),
|
||||
"Host should react to /refresh LSP request"
|
||||
);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec![after_special_edit_for_refresh.to_string()],
|
||||
extract_hint_labels(editor),
|
||||
extract_hint_labels(editor, cx),
|
||||
"Guest should get a /refresh LSP request propagated by host"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// This test started hanging on seed 2 after the theme settings
|
||||
// PR. The hypothesis is that it's been buggy for a while, but got lucky
|
||||
// on seeds.
|
||||
#[ignore]
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_inlay_hint_refresh_is_forwarded(
|
||||
cx_a: &mut TestAppContext,
|
||||
@@ -2206,18 +2191,18 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
executor.finish_waiting();
|
||||
|
||||
executor.run_until_parked();
|
||||
editor_a.update(cx_a, |editor, _| {
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert!(
|
||||
extract_hint_labels(editor).is_empty(),
|
||||
extract_hint_labels(editor, cx).is_empty(),
|
||||
"Host should get no hints due to them turned off"
|
||||
);
|
||||
});
|
||||
|
||||
executor.run_until_parked();
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec!["initial hint".to_string()],
|
||||
extract_hint_labels(editor),
|
||||
extract_hint_labels(editor, cx),
|
||||
"Client should get its first hints when opens an editor"
|
||||
);
|
||||
});
|
||||
@@ -2229,18 +2214,18 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
.into_response()
|
||||
.expect("inlay refresh request failed");
|
||||
executor.run_until_parked();
|
||||
editor_a.update(cx_a, |editor, _| {
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert!(
|
||||
extract_hint_labels(editor).is_empty(),
|
||||
extract_hint_labels(editor, cx).is_empty(),
|
||||
"Host should get no hints due to them turned off, even after the /refresh"
|
||||
);
|
||||
});
|
||||
|
||||
executor.run_until_parked();
|
||||
editor_b.update(cx_b, |editor, _| {
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec!["other hint".to_string()],
|
||||
extract_hint_labels(editor),
|
||||
extract_hint_labels(editor, cx),
|
||||
"Guest should get a /refresh LSP request propagated by host despite host hints are off"
|
||||
);
|
||||
});
|
||||
@@ -4217,15 +4202,35 @@ fn tab_undo_assert(
|
||||
cx_b.assert_editor_state(expected_initial);
|
||||
}
|
||||
|
||||
fn extract_hint_labels(editor: &Editor) -> Vec<String> {
|
||||
let mut labels = Vec::new();
|
||||
for hint in editor.inlay_hint_cache().hints() {
|
||||
match hint.label {
|
||||
project::InlayHintLabel::String(s) => labels.push(s),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
fn extract_hint_labels(editor: &Editor, cx: &mut App) -> Vec<String> {
|
||||
let lsp_store = editor.project().unwrap().read(cx).lsp_store();
|
||||
|
||||
let mut all_cached_labels = Vec::new();
|
||||
let mut all_fetched_hints = Vec::new();
|
||||
for buffer in editor.buffer().read(cx).all_buffers() {
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
let hints = &lsp_store.latest_lsp_data(&buffer, cx).inlay_hints();
|
||||
all_cached_labels.extend(hints.all_cached_hints().into_iter().map(|hint| {
|
||||
let mut label = hint.text().to_string();
|
||||
if hint.padding_left {
|
||||
label.insert(0, ' ');
|
||||
}
|
||||
if hint.padding_right {
|
||||
label.push_str(" ");
|
||||
}
|
||||
label
|
||||
}));
|
||||
all_fetched_hints.extend(hints.all_fetched_hints());
|
||||
});
|
||||
}
|
||||
labels
|
||||
|
||||
assert!(
|
||||
all_fetched_hints.is_empty(),
|
||||
"Did not expect background hints fetch tasks, but got {} of them",
|
||||
all_fetched_hints.len()
|
||||
);
|
||||
|
||||
all_cached_labels
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
||||
@@ -6,8 +6,8 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_context::ContextStore;
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, assert_hunks};
|
||||
use call::{ActiveCall, ParticipantLocation, Room, room};
|
||||
use client::{RECEIVE_TIMEOUT, User};
|
||||
@@ -6877,9 +6877,9 @@ async fn test_context_collaboration_with_reconnect(
|
||||
});
|
||||
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context_store_a = cx_a
|
||||
let text_thread_store_a = cx_a
|
||||
.update(|cx| {
|
||||
ContextStore::new(
|
||||
TextThreadStore::new(
|
||||
project_a.clone(),
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
@@ -6888,9 +6888,9 @@ async fn test_context_collaboration_with_reconnect(
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let context_store_b = cx_b
|
||||
let text_thread_store_b = cx_b
|
||||
.update(|cx| {
|
||||
ContextStore::new(
|
||||
TextThreadStore::new(
|
||||
project_b.clone(),
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
@@ -6901,60 +6901,60 @@ async fn test_context_collaboration_with_reconnect(
|
||||
.unwrap();
|
||||
|
||||
// Client A creates a new chats.
|
||||
let context_a = context_store_a.update(cx_a, |store, cx| store.create(cx));
|
||||
let text_thread_a = text_thread_store_a.update(cx_a, |store, cx| store.create(cx));
|
||||
executor.run_until_parked();
|
||||
|
||||
// Client B retrieves host's contexts and joins one.
|
||||
let context_b = context_store_b
|
||||
let text_thread_b = text_thread_store_b
|
||||
.update(cx_b, |store, cx| {
|
||||
let host_contexts = store.host_contexts().to_vec();
|
||||
assert_eq!(host_contexts.len(), 1);
|
||||
store.open_remote_context(host_contexts[0].id.clone(), cx)
|
||||
let host_text_threads = store.host_text_threads().collect::<Vec<_>>();
|
||||
assert_eq!(host_text_threads.len(), 1);
|
||||
store.open_remote(host_text_threads[0].id.clone(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Host and guest make changes
|
||||
context_a.update(cx_a, |context, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
text_thread_a.update(cx_a, |text_thread, cx| {
|
||||
text_thread.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "Host change\n")], None, cx)
|
||||
})
|
||||
});
|
||||
context_b.update(cx_b, |context, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
text_thread_b.update(cx_b, |text_thread, cx| {
|
||||
text_thread.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "Guest change\n")], None, cx)
|
||||
})
|
||||
});
|
||||
executor.run_until_parked();
|
||||
assert_eq!(
|
||||
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
|
||||
text_thread_a.read_with(cx_a, |text_thread, cx| text_thread.buffer().read(cx).text()),
|
||||
"Guest change\nHost change\n"
|
||||
);
|
||||
assert_eq!(
|
||||
context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
|
||||
text_thread_b.read_with(cx_b, |text_thread, cx| text_thread.buffer().read(cx).text()),
|
||||
"Guest change\nHost change\n"
|
||||
);
|
||||
|
||||
// Disconnect client A and make some changes while disconnected.
|
||||
server.disconnect_client(client_a.peer_id().unwrap());
|
||||
server.forbid_connections();
|
||||
context_a.update(cx_a, |context, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
text_thread_a.update(cx_a, |text_thread, cx| {
|
||||
text_thread.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "Host offline change\n")], None, cx)
|
||||
})
|
||||
});
|
||||
context_b.update(cx_b, |context, cx| {
|
||||
context.buffer().update(cx, |buffer, cx| {
|
||||
text_thread_b.update(cx_b, |text_thread, cx| {
|
||||
text_thread.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "Guest offline change\n")], None, cx)
|
||||
})
|
||||
});
|
||||
executor.run_until_parked();
|
||||
assert_eq!(
|
||||
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
|
||||
text_thread_a.read_with(cx_a, |text_thread, cx| text_thread.buffer().read(cx).text()),
|
||||
"Host offline change\nGuest change\nHost change\n"
|
||||
);
|
||||
assert_eq!(
|
||||
context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
|
||||
text_thread_b.read_with(cx_b, |text_thread, cx| text_thread.buffer().read(cx).text()),
|
||||
"Guest offline change\nGuest change\nHost change\n"
|
||||
);
|
||||
|
||||
@@ -6962,11 +6962,11 @@ async fn test_context_collaboration_with_reconnect(
|
||||
server.allow_connections();
|
||||
executor.advance_clock(RECEIVE_TIMEOUT);
|
||||
assert_eq!(
|
||||
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
|
||||
text_thread_a.read_with(cx_a, |text_thread, cx| text_thread.buffer().read(cx).text()),
|
||||
"Guest offline change\nHost offline change\nGuest change\nHost change\n"
|
||||
);
|
||||
assert_eq!(
|
||||
context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
|
||||
text_thread_b.read_with(cx_b, |text_thread, cx| text_thread.buffer().read(cx).text()),
|
||||
"Guest offline change\nHost offline change\nGuest change\nHost change\n"
|
||||
);
|
||||
|
||||
@@ -6974,8 +6974,8 @@ async fn test_context_collaboration_with_reconnect(
|
||||
server.forbid_connections();
|
||||
server.disconnect_client(client_a.peer_id().unwrap());
|
||||
executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
|
||||
context_b.read_with(cx_b, |context, cx| {
|
||||
assert!(context.buffer().read(cx).read_only());
|
||||
text_thread_b.read_with(cx_b, |text_thread, cx| {
|
||||
assert!(text_thread.buffer().read(cx).read_only());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -358,7 +358,7 @@ impl TestServer {
|
||||
settings::KeymapFile::load_asset_allow_partial_failure(os_keymap, cx).unwrap(),
|
||||
);
|
||||
language_model::LanguageModelRegistry::test(cx);
|
||||
assistant_context::init(client.clone(), cx);
|
||||
assistant_text_thread::init(client.clone(), cx);
|
||||
agent_settings::init(cx);
|
||||
});
|
||||
|
||||
|
||||
@@ -3037,6 +3037,10 @@ impl Panel for CollabPanel {
|
||||
"CollabPanel"
|
||||
}
|
||||
|
||||
fn panel_key() -> &'static str {
|
||||
COLLABORATION_PANEL_KEY
|
||||
}
|
||||
|
||||
fn activation_priority(&self) -> u32 {
|
||||
6
|
||||
}
|
||||
|
||||
@@ -612,6 +612,10 @@ impl Panel for NotificationPanel {
|
||||
"NotificationPanel"
|
||||
}
|
||||
|
||||
fn panel_key() -> &'static str {
|
||||
NOTIFICATION_PANEL_KEY
|
||||
}
|
||||
|
||||
fn position(&self, _: &Window, cx: &App) -> DockPosition {
|
||||
NotificationPanelSettings::get_global(cx).dock
|
||||
}
|
||||
@@ -734,19 +738,17 @@ impl Render for NotificationToast {
|
||||
.on_modifiers_changed(cx.listener(|_, _, _, cx| cx.notify()))
|
||||
.child(
|
||||
IconButton::new(close_id, close_icon)
|
||||
.tooltip(move |window, cx| {
|
||||
.tooltip(move |_window, cx| {
|
||||
if suppress {
|
||||
Tooltip::for_action(
|
||||
"Suppress.\nClose with click.",
|
||||
&workspace::SuppressNotification,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::for_action(
|
||||
"Close.\nSuppress with shift-click",
|
||||
&menu::Cancel,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -443,7 +443,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
window: &mut Window,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let matching_command = self.matches.get(ix)?;
|
||||
@@ -462,10 +462,9 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
command.name.clone(),
|
||||
matching_command.positions.clone(),
|
||||
))
|
||||
.children(KeyBinding::for_action_in(
|
||||
.child(KeyBinding::for_action_in(
|
||||
&*command.action,
|
||||
&self.previous_focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
),
|
||||
|
||||
@@ -9,6 +9,7 @@ license = "GPL-3.0-or-later"
|
||||
bincode.workspace = true
|
||||
cfg-if.workspace = true
|
||||
crash-handler.workspace = true
|
||||
extension_host.workspace = true
|
||||
log.workspace = true
|
||||
minidumper.workspace = true
|
||||
paths.workspace = true
|
||||
|
||||
@@ -33,17 +33,31 @@ const CRASH_HANDLER_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
static PANIC_THREAD_ID: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
pub async fn init(crash_init: InitCrashHandler) {
|
||||
if *RELEASE_CHANNEL == ReleaseChannel::Dev && env::var("ZED_GENERATE_MINIDUMPS").is_err() {
|
||||
let old_hook = panic::take_hook();
|
||||
panic::set_hook(Box::new(move |info| {
|
||||
unsafe { env::set_var("RUST_BACKTRACE", "1") };
|
||||
old_hook(info);
|
||||
// prevent the macOS crash dialog from popping up
|
||||
std::process::exit(1);
|
||||
}));
|
||||
return;
|
||||
} else {
|
||||
panic::set_hook(Box::new(panic_hook));
|
||||
let gen_var = match env::var("ZED_GENERATE_MINIDUMPS") {
|
||||
Ok(v) => {
|
||||
if v == "false" || v == "0" {
|
||||
Some(false)
|
||||
} else {
|
||||
Some(true)
|
||||
}
|
||||
}
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
match (gen_var, *RELEASE_CHANNEL) {
|
||||
(Some(false), _) | (None, ReleaseChannel::Dev) => {
|
||||
let old_hook = panic::take_hook();
|
||||
panic::set_hook(Box::new(move |info| {
|
||||
unsafe { env::set_var("RUST_BACKTRACE", "1") };
|
||||
old_hook(info);
|
||||
// prevent the macOS crash dialog from popping up
|
||||
std::process::exit(1);
|
||||
}));
|
||||
return;
|
||||
}
|
||||
(Some(true), _) | (None, _) => {
|
||||
panic::set_hook(Box::new(panic_hook));
|
||||
}
|
||||
}
|
||||
|
||||
let exe = env::current_exe().expect("unable to find ourselves");
|
||||
@@ -272,6 +286,11 @@ impl minidumper::ServerHandler for CrashServer {
|
||||
}
|
||||
|
||||
pub fn panic_hook(info: &PanicHookInfo) {
|
||||
// Don't handle a panic on threads that are not relevant to the main execution.
|
||||
if extension_host::wasm_host::IS_WASM_THREAD.with(|v| v.load(Ordering::Acquire)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let message = info
|
||||
.payload()
|
||||
.downcast_ref::<&str>()
|
||||
|
||||
@@ -43,6 +43,8 @@ use workspace::{
|
||||
};
|
||||
use zed_actions::ToggleFocus;
|
||||
|
||||
const DEBUG_PANEL_KEY: &str = "DebugPanel";
|
||||
|
||||
pub struct DebugPanel {
|
||||
size: Pixels,
|
||||
active_session: Option<Entity<DebugSession>>,
|
||||
@@ -614,12 +616,11 @@ impl DebugPanel {
|
||||
})
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Start Debug Session",
|
||||
&crate::Start,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -692,12 +693,11 @@ impl DebugPanel {
|
||||
))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Pause Program",
|
||||
&Pause,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -717,12 +717,11 @@ impl DebugPanel {
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Continue Program",
|
||||
&Continue,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -742,12 +741,11 @@ impl DebugPanel {
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Step Over",
|
||||
&StepOver,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -768,12 +766,11 @@ impl DebugPanel {
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Step In",
|
||||
&StepInto,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -791,12 +788,11 @@ impl DebugPanel {
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Step Out",
|
||||
&StepOut,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -814,12 +810,11 @@ impl DebugPanel {
|
||||
))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Rerun Session",
|
||||
&RerunSession,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -859,12 +854,11 @@ impl DebugPanel {
|
||||
} else {
|
||||
"Terminate All Threads"
|
||||
};
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
label,
|
||||
&Stop,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -891,12 +885,11 @@ impl DebugPanel {
|
||||
))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Detach",
|
||||
&Detach,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -1414,6 +1407,10 @@ impl Panel for DebugPanel {
|
||||
"DebugPanel"
|
||||
}
|
||||
|
||||
fn panel_key() -> &'static str {
|
||||
DEBUG_PANEL_KEY
|
||||
}
|
||||
|
||||
fn position(&self, _window: &Window, cx: &App) -> DockPosition {
|
||||
DebuggerSettings::get_global(cx).dock.into()
|
||||
}
|
||||
|
||||
@@ -96,7 +96,9 @@ impl NewProcessModal {
|
||||
let debug_picker = cx.new(|cx| {
|
||||
let delegate =
|
||||
DebugDelegate::new(debug_panel.downgrade(), task_store.clone());
|
||||
Picker::uniform_list(delegate, window, cx).modal(false)
|
||||
Picker::list(delegate, window, cx)
|
||||
.modal(false)
|
||||
.list_measure_all()
|
||||
});
|
||||
|
||||
let configure_mode = ConfigureMode::new(window, cx);
|
||||
@@ -745,22 +747,15 @@ impl Render for NewProcessModal {
|
||||
== 0;
|
||||
let secondary_action = menu::SecondaryConfirm.boxed_clone();
|
||||
container
|
||||
.child(div().children(
|
||||
KeyBinding::for_action(&*secondary_action, window, cx).map(
|
||||
|keybind| {
|
||||
Button::new("edit-attach-task", "Edit in debug.json")
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(keybind)
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(
|
||||
secondary_action.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.disabled(disabled)
|
||||
},
|
||||
),
|
||||
))
|
||||
.child(div().child({
|
||||
Button::new("edit-attach-task", "Edit in debug.json")
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(KeyBinding::for_action(&*secondary_action, cx))
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(secondary_action.boxed_clone(), cx)
|
||||
})
|
||||
.disabled(disabled)
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.child(div().child(self.adapter_drop_down_menu(window, cx))),
|
||||
@@ -1053,7 +1048,7 @@ impl DebugDelegate {
|
||||
Some(TaskSourceKind::Lsp { language_name, .. }) => {
|
||||
Some(format!("LSP: {language_name}"))
|
||||
}
|
||||
Some(TaskSourceKind::Language { .. }) => None,
|
||||
Some(TaskSourceKind::Language { name }) => Some(format!("Lang: {name}")),
|
||||
_ => context.clone().and_then(|ctx| {
|
||||
ctx.task_context
|
||||
.task_variables
|
||||
@@ -1447,56 +1442,48 @@ impl PickerDelegate for DebugDelegate {
|
||||
.justify_between()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.children({
|
||||
.child({
|
||||
let action = menu::SecondaryConfirm.boxed_clone();
|
||||
if self.matches.is_empty() {
|
||||
Some(
|
||||
Button::new("edit-debug-json", "Edit debug.json")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(|_picker, _, window, cx| {
|
||||
window.dispatch_action(
|
||||
zed_actions::OpenProjectDebugTasks.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
cx.emit(DismissEvent);
|
||||
})),
|
||||
)
|
||||
Button::new("edit-debug-json", "Edit debug.json")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(|_picker, _, window, cx| {
|
||||
window.dispatch_action(
|
||||
zed_actions::OpenProjectDebugTasks.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
cx.emit(DismissEvent);
|
||||
}))
|
||||
} else {
|
||||
KeyBinding::for_action(&*action, window, cx).map(|keybind| {
|
||||
Button::new("edit-debug-task", "Edit in debug.json")
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(keybind)
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(action.boxed_clone(), cx)
|
||||
})
|
||||
})
|
||||
Button::new("edit-debug-task", "Edit in debug.json")
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(KeyBinding::for_action(&*action, cx))
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(action.boxed_clone(), cx)
|
||||
})
|
||||
}
|
||||
})
|
||||
.map(|this| {
|
||||
if (current_modifiers.alt || self.matches.is_empty()) && !self.prompt.is_empty() {
|
||||
let action = picker::ConfirmInput { secondary: false }.boxed_clone();
|
||||
this.children(KeyBinding::for_action(&*action, window, cx).map(|keybind| {
|
||||
this.child({
|
||||
Button::new("launch-custom", "Launch Custom")
|
||||
.key_binding(keybind)
|
||||
.key_binding(KeyBinding::for_action(&*action, cx))
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(action.boxed_clone(), cx)
|
||||
})
|
||||
}))
|
||||
})
|
||||
} else {
|
||||
this.children(KeyBinding::for_action(&menu::Confirm, window, cx).map(
|
||||
|keybind| {
|
||||
let is_recent_selected =
|
||||
self.divider_index >= Some(self.selected_index);
|
||||
let run_entry_label =
|
||||
if is_recent_selected { "Rerun" } else { "Spawn" };
|
||||
this.child({
|
||||
let is_recent_selected = self.divider_index >= Some(self.selected_index);
|
||||
let run_entry_label = if is_recent_selected { "Rerun" } else { "Spawn" };
|
||||
|
||||
Button::new("spawn", run_entry_label)
|
||||
.key_binding(keybind)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(menu::Confirm.boxed_clone(), cx);
|
||||
})
|
||||
},
|
||||
))
|
||||
Button::new("spawn", run_entry_label)
|
||||
.key_binding(KeyBinding::for_action(&menu::Confirm, cx))
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(menu::Confirm.boxed_clone(), cx);
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
Some(footer.into_any_element())
|
||||
|
||||
@@ -386,6 +386,7 @@ pub(crate) fn new_debugger_pane(
|
||||
Default::default(),
|
||||
None,
|
||||
NoAction.boxed_clone(),
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -565,14 +566,13 @@ pub(crate) fn new_debugger_pane(
|
||||
}))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
let zoomed_text =
|
||||
if zoomed { "Minimize" } else { "Expand" };
|
||||
Tooltip::for_action_in(
|
||||
zoomed_text,
|
||||
&ToggleExpandItem,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -607,13 +607,12 @@ impl BreakpointList {
|
||||
.when_some(toggle_label, |this, (label, meta)| {
|
||||
this.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::with_meta_in(
|
||||
label,
|
||||
Some(&ToggleEnableBreakpoint),
|
||||
meta,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -634,13 +633,12 @@ impl BreakpointList {
|
||||
.when_some(remove_breakpoint_tooltip, |this, tooltip| {
|
||||
this.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::with_meta_in(
|
||||
"Remove Breakpoint",
|
||||
Some(&UnsetBreakpoint),
|
||||
tooltip,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -819,7 +817,7 @@ impl LineBreakpoint {
|
||||
)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
if is_enabled {
|
||||
"Disable Breakpoint"
|
||||
@@ -828,7 +826,6 @@ impl LineBreakpoint {
|
||||
},
|
||||
&ToggleEnableBreakpoint,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -980,7 +977,7 @@ impl DataBreakpoint {
|
||||
)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
if is_enabled {
|
||||
"Disable Data Breakpoint"
|
||||
@@ -989,7 +986,6 @@ impl DataBreakpoint {
|
||||
},
|
||||
&ToggleEnableBreakpoint,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -1085,7 +1081,7 @@ impl ExceptionBreakpoint {
|
||||
)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
if is_enabled {
|
||||
"Disable Exception Breakpoint"
|
||||
@@ -1094,7 +1090,6 @@ impl ExceptionBreakpoint {
|
||||
},
|
||||
&ToggleEnableBreakpoint,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -1402,12 +1397,11 @@ impl RenderOnce for BreakpointOptionsStrip {
|
||||
.disabled(!supports_logs)
|
||||
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::Log))
|
||||
.on_click(self.on_click_callback(ActiveBreakpointStripMode::Log))
|
||||
.tooltip(|window, cx| {
|
||||
.tooltip(|_window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Set Log Message",
|
||||
None,
|
||||
"Set log message to display (instead of stopping) when a breakpoint is hit.",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
@@ -1438,12 +1432,11 @@ impl RenderOnce for BreakpointOptionsStrip {
|
||||
.disabled(!supports_condition)
|
||||
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::Condition))
|
||||
.on_click(self.on_click_callback(ActiveBreakpointStripMode::Condition))
|
||||
.tooltip(|window, cx| {
|
||||
.tooltip(|_window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Set Condition",
|
||||
None,
|
||||
"Set condition to evaluate when a breakpoint is hit. Program execution will stop only when the condition is met.",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
@@ -1474,12 +1467,11 @@ impl RenderOnce for BreakpointOptionsStrip {
|
||||
.disabled(!supports_hit_condition)
|
||||
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::HitCondition))
|
||||
.on_click(self.on_click_callback(ActiveBreakpointStripMode::HitCondition))
|
||||
.tooltip(|window, cx| {
|
||||
.tooltip(|_window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Set Hit Condition",
|
||||
None,
|
||||
"Set expression that controls how many hits of the breakpoint are ignored.",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -484,12 +484,11 @@ impl Render for Console {
|
||||
.tooltip({
|
||||
let query_focus_handle = query_focus_handle.clone();
|
||||
|
||||
move |window, cx| {
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Evaluate",
|
||||
&Confirm,
|
||||
&query_focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -872,8 +872,8 @@ impl StackFrameList {
|
||||
"filter-by-visible-worktree-stack-frame-list",
|
||||
IconName::ListFilter,
|
||||
)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action(tooltip_title, &ToggleUserFrames, window, cx)
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::for_action(tooltip_title, &ToggleUserFrames, cx)
|
||||
})
|
||||
.toggle_state(self.list_filter == StackFrameFilter::OnlyUserFrames)
|
||||
.icon_size(IconSize::Small)
|
||||
|
||||
@@ -1306,14 +1306,8 @@ impl VariableList {
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Remove Watch",
|
||||
&RemoveWatch,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::for_action_in("Remove Watch", &RemoveWatch, &focus_handle, cx)
|
||||
})
|
||||
.icon_size(ui::IconSize::Indicator),
|
||||
),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user