Compare commits

..

10 Commits

Author SHA1 Message Date
Richard Feldman
74e80993a3 Move login metehods out of AgentServer 2025-10-22 12:27:15 -04:00
Richard Feldman
70993b3ce3 Use Vec<String> since that's what extensions will use. 2025-10-22 11:34:05 -04:00
Richard Feldman
a47a387186 Separate out local and remote login/logout 2025-10-22 11:31:13 -04:00
Richard Feldman
5f6f7edc99 Restore return value 2025-10-22 11:17:16 -04:00
Richard Feldman
4f46ac8c2d Don't have default login/logout commands 2025-10-22 11:09:35 -04:00
Richard Feldman
946c41b4b3 Revert "Use a static slice rather than vec"
This reverts commit 7f7312ca8f.
2025-10-22 11:09:05 -04:00
Richard Feldman
7f7312ca8f Use a static slice rather than vec 2025-10-22 11:09:02 -04:00
Richard Feldman
4232b59a59 Split out login_commands and logout_commands 2025-10-22 11:03:47 -04:00
Richard Feldman
b00901c126 Handle oauth fallback 2025-10-22 10:21:07 -04:00
Richard Feldman
e1a83a5fe6 Support multiple agent connections 2025-10-21 22:24:57 -04:00
426 changed files with 11058 additions and 18406 deletions

View File

@@ -5,16 +5,12 @@
# Arrays are merged together though. See: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
# The intent for this file is to configure CI build process with a divergance from Zed developers experience; for example, in this config file
# we use `-D warnings` for rustflags (which makes compilation fail in presence of warnings during build process). Placing that in developers `config.toml`
# would be inconvenient.
# would be incovenient.
# The reason for not using the RUSTFLAGS environment variable is that doing so would override all the settings in the config.toml file, even if the contents of the latter are completely nonsensical. See: https://github.com/rust-lang/cargo/issues/5376
# Here, we opted to use `[target.'cfg(all())']` instead of `[build]` because `[target.'**']` is guaranteed to be cumulative.
[target.'cfg(all())']
rustflags = ["-D", "warnings"]
# We don't need fullest debug information for dev stuff (tests etc.) in CI.
[profile.dev]
debug = "limited"
# Use Mold on Linux, because it's faster than GNU ld and LLD.
#
# We no longer set this in the default `config.toml` so that developers can opt in to Wild, which

View File

@@ -4,17 +4,3 @@ sequential-db-tests = { max-threads = 1 }
[[profile.default.overrides]]
filter = 'package(db)'
test-group = 'sequential-db-tests'
# Run slowest tests first.
#
[[profile.default.overrides]]
filter = 'package(worktree) and test(test_random_worktree_changes)'
priority = 100
[[profile.default.overrides]]
filter = 'package(collab) and (test(random_project_collaboration_tests) or test(random_channel_buffer_tests) or test(test_contact_requests) or test(test_basic_following))'
priority = 99
[[profile.default.overrides]]
filter = 'package(extension_host) and test(test_extension_store_with_test_extension)'
priority = 99

View File

@@ -1,35 +0,0 @@
name: Bug Report (Git)
description: Zed Git Related Bugs
type: "Bug"
labels: ["git"]
title: "Git: <a short description of the Git bug>"
body:
- type: textarea
attributes:
label: Summary
description: Describe the bug with a one-line summary, and provide detailed reproduction steps
value: |
<!-- Please insert a one-line summary of the issue below -->
SUMMARY_SENTENCE_HERE
### Description
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
Steps to trigger the problem:
1.
2.
3.
**Expected Behavior**:
**Actual Behavior**:
validations:
required: true
- type: textarea
id: environment
attributes:
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: copy system specs into clipboard"'
placeholder: |
Output of "zed: copy system specs into clipboard"
validations:
required: true

View File

@@ -33,10 +33,9 @@ body:
required: true
- type: textarea
attributes:
label: If applicable, attach your `Zed.log` file to this issue.
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
description: |
macOS: `~/Library/Logs/Zed/Zed.log`
Windows: `C:\Users\YOU\AppData\Local\Zed\logs\Zed.log`
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
value: |

View File

@@ -15,11 +15,8 @@ runs:
node-version: "18"
- name: Limit target directory size
env:
MAX_SIZE: ${{ runner.os == 'macOS' && 300 || 100 }}
shell: bash -euxo pipefail {0}
# Use the variable in the run command
run: script/clear-target-dir-if-larger-than ${{ env.MAX_SIZE }}
run: script/clear-target-dir-if-larger-than 100
- name: Run tests
shell: bash -euxo pipefail {0}

View File

@@ -177,7 +177,7 @@ jobs:
uses: ./.github/actions/check_style
- name: Check for typos
uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1 # v1.38.1
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6
with:
config: ./typos.toml
@@ -296,53 +296,49 @@ jobs:
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- namespace-profile-ubuntu22-x86-16x32-custom
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: |
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
echo "$HOME/.cargo-nextest/bin" >> "$GITHUB_PATH"
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Configure Go and Rust cache
uses: namespacelabs/nscloud-cache-action@v1
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
path: |
/home/runner/.cargo-nextest
/home/runner/.rustup
./target
save-if: ${{ github.ref == 'refs/heads/main' }}
# cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Install 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: cargo clippy
run: ./script/clippy
- name: Run tests
shell: bash -euxo pipefail {0}
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
uses: ./.github/actions/run_tests
- name: Build other binaries and features
run: |
cargo build -p zed
cargo check -p workspace
cargo check -p gpui --examples
- name: cargo clippy
run: ./script/clippy
# Even the Linux runner is not stateful, in theory there is no need to do this cleanup.
# But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code
# to clean up the config file, Ive included the cleanup code here as a precaution.
# While its not strictly necessary at this moment, I believe its better to err on the side of caution.
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo
doctests:
# Nextest currently doesn't support doctests, so run them separately and in parallel.
@@ -851,8 +847,7 @@ jobs:
auto-release-preview:
name: Auto release preview
if: |
false
&& startsWith(github.ref, 'refs/tags/v')
startsWith(github.ref, 'refs/tags/v')
&& endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, bundle-windows-x64]
runs-on:

1
.gitignore vendored
View File

@@ -25,7 +25,6 @@
/crates/collab/seed.json
/crates/theme/schemas/theme.json
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
/crates/project_panel/benches/linux_repo_snapshot.txt
/dev.zed.Zed*.json
/node_modules/
/plugins/bin

415
Cargo.lock generated
View File

@@ -142,7 +142,7 @@ dependencies = [
"agent_servers",
"agent_settings",
"anyhow",
"assistant_text_thread",
"assistant_context",
"chrono",
"client",
"clock",
@@ -151,7 +151,7 @@ dependencies = [
"context_server",
"ctor",
"db",
"derive_more 0.99.20",
"derive_more",
"editor",
"env_logger 0.11.8",
"fs",
@@ -210,30 +210,16 @@ dependencies = [
[[package]]
name = "agent-client-protocol"
version = "0.5.0"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f655394a107cd601bd2e5375c2d909ea83adc65678a0e0e8d77613d3c848a7d"
checksum = "3aaa2bd05a2401887945f8bfd70026e90bc3cf96c62ab9eba2779835bf21dc60"
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",
@@ -315,9 +301,9 @@ dependencies = [
"ai_onboarding",
"anyhow",
"arrayvec",
"assistant_context",
"assistant_slash_command",
"assistant_slash_commands",
"assistant_text_thread",
"audio",
"buffer_diff",
"chrono",
@@ -802,6 +788,53 @@ 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"
@@ -809,7 +842,7 @@ dependencies = [
"anyhow",
"async-trait",
"collections",
"derive_more 0.99.20",
"derive_more",
"extension",
"futures 0.3.31",
"gpui",
@@ -859,53 +892,6 @@ 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"
@@ -1184,9 +1170,9 @@ dependencies = [
[[package]]
name = "async-tar"
version = "0.5.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1937db2d56578aa3919b9bdb0e5100693fd7d1c0f145c53eb81fbb03e217550"
checksum = "a42f905d4f623faf634bbd1e001e84e0efc24694afa64be9ad239bf6ca49e1f8"
dependencies = [
"async-std",
"filetime",
@@ -2078,7 +2064,7 @@ dependencies = [
"bitflags 2.9.4",
"cexpr",
"clang-sys",
"itertools 0.12.1",
"itertools 0.11.0",
"log",
"prettyplease",
"proc-macro2",
@@ -2098,7 +2084,7 @@ dependencies = [
"bitflags 2.9.4",
"cexpr",
"clang-sys",
"itertools 0.12.1",
"itertools 0.11.0",
"log",
"prettyplease",
"proc-macro2",
@@ -3093,7 +3079,7 @@ dependencies = [
"cloud_llm_client",
"collections",
"credentials_provider",
"derive_more 0.99.20",
"derive_more",
"feature_flags",
"fs",
"futures 0.3.31",
@@ -3324,8 +3310,8 @@ version = "0.44.0"
dependencies = [
"agent_settings",
"anyhow",
"assistant_context",
"assistant_slash_command",
"assistant_text_thread",
"async-trait",
"async-tungstenite",
"audio",
@@ -3393,7 +3379,6 @@ dependencies = [
"rpc",
"scrypt",
"sea-orm",
"sea-orm-macros",
"semantic_version",
"semver",
"serde",
@@ -3554,7 +3539,7 @@ name = "command_palette_hooks"
version = "0.1.0"
dependencies = [
"collections",
"derive_more 0.99.20",
"derive_more",
"gpui",
"workspace",
]
@@ -4132,7 +4117,6 @@ dependencies = [
"bincode 1.3.3",
"cfg-if",
"crash-handler",
"extension_host",
"log",
"mach2 0.5.0",
"minidumper",
@@ -4872,27 +4856,6 @@ 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"
@@ -4902,18 +4865,6 @@ 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"
@@ -5051,7 +5002,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users 0.5.2",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -5381,7 +5332,6 @@ dependencies = [
"rand 0.9.2",
"regex",
"release_channel",
"rope",
"rpc",
"schemars 1.0.4",
"serde",
@@ -5656,7 +5606,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -5705,61 +5655,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "eval"
version = "0.1.0"
dependencies = [
"acp_thread",
"agent",
"agent-client-protocol",
"agent_settings",
"agent_ui",
"anyhow",
"async-trait",
"buffer_diff",
"chrono",
"clap",
"client",
"collections",
"debug_adapter_extension",
"dirs 4.0.0",
"dotenvy",
"env_logger 0.11.8",
"extension",
"fs",
"futures 0.3.31",
"gpui",
"gpui_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"
@@ -5898,7 +5793,6 @@ dependencies = [
"fs",
"futures 0.3.31",
"gpui",
"gpui_tokio",
"http_client",
"language",
"language_extension",
@@ -6447,14 +6341,6 @@ 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"
@@ -6954,35 +6840,6 @@ 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"
@@ -7018,7 +6875,7 @@ dependencies = [
"askpass",
"async-trait",
"collections",
"derive_more 0.99.20",
"derive_more",
"futures 0.3.31",
"git2",
"gpui",
@@ -7258,7 +7115,7 @@ dependencies = [
[[package]]
name = "gpui"
version = "0.2.2"
version = "0.2.1"
dependencies = [
"anyhow",
"as-raw-xcb-connection",
@@ -7284,7 +7141,7 @@ dependencies = [
"core-video",
"cosmic-text",
"ctor",
"derive_more 0.99.20",
"derive_more",
"embed-resource",
"env_logger 0.11.8",
"etagere",
@@ -7783,7 +7640,7 @@ dependencies = [
"async-fs",
"async-tar",
"bytes 1.10.1",
"derive_more 0.99.20",
"derive_more",
"futures 0.3.31",
"http 1.3.1",
"http-body 1.0.1",
@@ -9853,28 +9710,6 @@ 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"
@@ -10459,7 +10294,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -10911,6 +10746,7 @@ dependencies = [
"telemetry",
"theme",
"ui",
"ui_input",
"util",
"vim_mode_setting",
"workspace",
@@ -11508,7 +11344,7 @@ dependencies = [
[[package]]
name = "pet"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"clap",
"env_logger 0.10.2",
@@ -11545,7 +11381,7 @@ dependencies = [
[[package]]
name = "pet-conda"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -11564,7 +11400,7 @@ dependencies = [
[[package]]
name = "pet-core"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"clap",
"lazy_static",
@@ -11579,7 +11415,7 @@ dependencies = [
[[package]]
name = "pet-env-var-path"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"lazy_static",
"log",
@@ -11595,7 +11431,7 @@ dependencies = [
[[package]]
name = "pet-fs"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11604,7 +11440,7 @@ dependencies = [
[[package]]
name = "pet-global-virtualenvs"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11617,7 +11453,7 @@ dependencies = [
[[package]]
name = "pet-homebrew"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"lazy_static",
"log",
@@ -11635,7 +11471,7 @@ dependencies = [
[[package]]
name = "pet-jsonrpc"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"env_logger 0.10.2",
"log",
@@ -11648,7 +11484,7 @@ dependencies = [
[[package]]
name = "pet-linux-global-python"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11661,7 +11497,7 @@ dependencies = [
[[package]]
name = "pet-mac-commandlinetools"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11674,7 +11510,7 @@ dependencies = [
[[package]]
name = "pet-mac-python-org"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11687,7 +11523,7 @@ dependencies = [
[[package]]
name = "pet-mac-xcode"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11700,7 +11536,7 @@ dependencies = [
[[package]]
name = "pet-pipenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11713,7 +11549,7 @@ dependencies = [
[[package]]
name = "pet-pixi"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11725,7 +11561,7 @@ dependencies = [
[[package]]
name = "pet-poetry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"base64 0.22.1",
"lazy_static",
@@ -11746,7 +11582,7 @@ dependencies = [
[[package]]
name = "pet-pyenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"lazy_static",
"log",
@@ -11764,7 +11600,7 @@ dependencies = [
[[package]]
name = "pet-python-utils"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -11781,7 +11617,7 @@ dependencies = [
[[package]]
name = "pet-reporter"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"env_logger 0.10.2",
"log",
@@ -11795,7 +11631,7 @@ dependencies = [
[[package]]
name = "pet-telemetry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -11810,7 +11646,7 @@ dependencies = [
[[package]]
name = "pet-venv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11822,7 +11658,7 @@ dependencies = [
[[package]]
name = "pet-virtualenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11834,7 +11670,7 @@ dependencies = [
[[package]]
name = "pet-virtualenvwrapper"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11847,7 +11683,7 @@ dependencies = [
[[package]]
name = "pet-windows-registry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"lazy_static",
"log",
@@ -11865,7 +11701,7 @@ dependencies = [
[[package]]
name = "pet-windows-store"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"lazy_static",
"log",
@@ -12864,30 +12700,6 @@ 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"
@@ -13471,7 +13283,7 @@ dependencies = [
"once_cell",
"socket2 0.6.1",
"tracing",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -14314,7 +14126,6 @@ dependencies = [
"log",
"rand 0.9.2",
"rayon",
"regex",
"smallvec",
"sum_tree",
"unicode-segmentation",
@@ -14555,7 +14366,7 @@ dependencies = [
"errno 0.3.14",
"libc",
"linux-raw-sys 0.11.0",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -15045,7 +14856,6 @@ dependencies = [
"futures 0.3.31",
"gpui",
"language",
"lsp",
"menu",
"project",
"schemars 1.0.4",
@@ -15313,19 +15123,6 @@ 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"
@@ -15354,7 +15151,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"derive_more 0.99.20",
"derive_more",
"ec4rs",
"fs",
"futures 0.3.31",
@@ -15432,10 +15229,8 @@ dependencies = [
"menu",
"node_runtime",
"paths",
"picker",
"pretty_assertions",
"project",
"release_channel",
"schemars 1.0.4",
"search",
"serde",
@@ -17021,7 +16816,7 @@ dependencies = [
"getrandom 0.3.4",
"once_cell",
"rustix 1.1.2",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -17135,6 +16930,7 @@ dependencies = [
"parking_lot",
"postage",
"rand 0.9.2",
"regex",
"rope",
"smallvec",
"sum_tree",
@@ -17148,7 +16944,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"derive_more 0.99.20",
"derive_more",
"fs",
"futures 0.3.31",
"gpui",
@@ -18353,8 +18149,10 @@ version = "0.1.0"
dependencies = [
"component",
"editor",
"fuzzy",
"gpui",
"menu",
"picker",
"settings",
"theme",
"ui",
@@ -18497,12 +18295,6 @@ 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"
@@ -19706,7 +19498,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.48.0",
]
[[package]]
@@ -20912,7 +20704,6 @@ dependencies = [
"cargo_metadata",
"cargo_toml",
"clap",
"gh-workflow",
"indoc",
"toml 0.8.23",
"toml_edit 0.22.27",
@@ -21098,7 +20889,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.211.0"
version = "0.210.0"
dependencies = [
"acp_tools",
"activity_indicator",
@@ -21144,7 +20935,6 @@ dependencies = [
"file_finder",
"fs",
"futures 0.3.31",
"gh-workflow",
"git",
"git_hosting_providers",
"git_ui",
@@ -21673,7 +21463,6 @@ dependencies = [
"serde",
"serde_json",
"settings",
"telemetry",
"text",
"ui",
"ui_input",

View File

@@ -13,7 +13,7 @@ members = [
"crates/anthropic",
"crates/askpass",
"crates/assets",
"crates/assistant_text_thread",
"crates/assistant_context",
"crates/assistant_slash_command",
"crates/assistant_slash_commands",
"crates/audio",
@@ -58,7 +58,7 @@ members = [
"crates/edit_prediction_context",
"crates/zeta2_tools",
"crates/editor",
"crates/eval",
# "crates/eval",
"crates/explorer_command_injector",
"crates/extension",
"crates/extension_api",
@@ -70,7 +70,6 @@ members = [
"crates/file_finder",
"crates/file_icons",
"crates/fs",
"crates/fs_benchmarks",
"crates/fsevent",
"crates/fuzzy",
"crates/git",
@@ -246,7 +245,7 @@ ai_onboarding = { path = "crates/ai_onboarding" }
anthropic = { path = "crates/anthropic" }
askpass = { path = "crates/askpass" }
assets = { path = "crates/assets" }
assistant_text_thread = { path = "crates/assistant_text_thread" }
assistant_context = { path = "crates/assistant_context" }
assistant_slash_command = { path = "crates/assistant_slash_command" }
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
audio = { path = "crates/audio" }
@@ -438,7 +437,7 @@ zlog_settings = { path = "crates/zlog_settings" }
# External crates
#
agent-client-protocol = { version = "0.5.0", features = ["unstable"] }
agent-client-protocol = { version = "=0.4.3", features = ["unstable"] }
aho-corasick = "1.1"
alacritty_terminal = "0.25.1-rc1"
any_vec = "0.14"
@@ -452,7 +451,7 @@ async-fs = "2.1"
async-lock = "2.1"
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
async-recursion = "1.0.0"
async-tar = "0.5.1"
async-tar = "0.5.0"
async-task = "4.7"
async-trait = "0.1"
async-tungstenite = "0.31.0"
@@ -506,7 +505,6 @@ 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"
@@ -581,14 +579,14 @@ partial-json-fixer = "0.5.3"
parse_int = "0.9"
pciid-parser = "0.8.0"
pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
portable-pty = "0.9.0"
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
@@ -901,5 +899,4 @@ ignored = [
"serde",
"component",
"documented",
"sea-orm-macros",
]

View File

@@ -139,7 +139,7 @@
"find": "buffer_search::Deploy",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
"ctrl->": "agent::AddSelectionToThread",
"ctrl->": "agent::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
@@ -243,7 +243,7 @@
"ctrl-shift-i": "agent::ToggleOptionsMenu",
"ctrl-alt-shift-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl->": "agent::AddSelectionToThread",
"ctrl->": "agent::QuoteSelection",
"ctrl-alt-e": "agent::RemoveAllContext",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
@@ -366,7 +366,7 @@
}
},
{
"context": "RulesLibrary",
"context": "PromptLibrary",
"bindings": {
"new": "rules_library::NewRule",
"ctrl-n": "rules_library::NewRule",
@@ -539,7 +539,7 @@
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
"ctrl-shift-space": "editor::ShowWordCompletions",
"ctrl-shift-space": "editor::ShowSignatureHelp",
"ctrl-.": "editor::ToggleCodeActions",
"ctrl-k r": "editor::RevealInFileManager",
"ctrl-k p": "editor::CopyPath",
@@ -799,7 +799,7 @@
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPreviousHunk",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-i": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints"
}
},
@@ -1080,8 +1080,7 @@
{
"context": "StashList || (StashList > Picker > Editor)",
"bindings": {
"ctrl-shift-backspace": "stash_picker::DropStashItem",
"ctrl-shift-v": "stash_picker::ShowStashItem"
"ctrl-shift-backspace": "stash_picker::DropStashItem"
}
},
{
@@ -1094,7 +1093,7 @@
"paste": "terminal::Paste",
"shift-insert": "terminal::Paste",
"ctrl-shift-v": "terminal::Paste",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-i": "assistant::InlineAssist",
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
"alt-.": ["terminal::SendText", "\u001b."],
@@ -1267,22 +1266,12 @@
"ctrl-pagedown": "settings_editor::FocusNextFile"
}
},
{
"context": "StashDiff > Editor",
"bindings": {
"ctrl-space": "git::ApplyCurrentStash",
"ctrl-shift-space": "git::PopCurrentStash",
"ctrl-shift-backspace": "git::DropCurrentStash"
}
},
{
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"up": "settings_editor::FocusPreviousNavEntry",
"shift-tab": "settings_editor::FocusPreviousNavEntry",
"down": "settings_editor::FocusNextNavEntry",
"tab": "settings_editor::FocusNextNavEntry",
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",
@@ -1290,13 +1279,5 @@
"home": "settings_editor::FocusFirstNavEntry",
"end": "settings_editor::FocusLastNavEntry"
}
},
{
"context": "Zeta2Feedback > Editor",
"bindings": {
"enter": "editor::Newline",
"ctrl-enter up": "dev::Zeta2RatePredictionPositive",
"ctrl-enter down": "dev::Zeta2RatePredictionNegative"
}
}
]

View File

@@ -142,7 +142,7 @@
"cmd-\"": "editor::ExpandAllDiffHunks",
"cmd-alt-g b": "git::Blame",
"cmd-alt-g m": "git::OpenModifiedFiles",
"cmd-i": "editor::ShowSignatureHelp",
"cmd-shift-space": "editor::ShowSignatureHelp",
"f9": "editor::ToggleBreakpoint",
"shift-f9": "editor::EditLogBreakpoint",
"ctrl-f12": "editor::GoToDeclaration",
@@ -163,7 +163,7 @@
"cmd-alt-f": "buffer_search::DeployReplace",
"cmd-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
"cmd-e": ["buffer_search::Deploy", { "focus": false }],
"cmd->": "agent::AddSelectionToThread",
"cmd->": "agent::QuoteSelection",
"cmd-<": "assistant::InsertIntoEditor",
"cmd-alt-e": "editor::SelectEnclosingSymbol",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
@@ -282,7 +282,7 @@
"cmd-shift-i": "agent::ToggleOptionsMenu",
"cmd-alt-shift-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"cmd->": "agent::AddSelectionToThread",
"cmd->": "agent::QuoteSelection",
"cmd-alt-e": "agent::RemoveAllContext",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-ctrl-b": "agent::ToggleBurnMode",
@@ -423,7 +423,7 @@
}
},
{
"context": "RulesLibrary",
"context": "PromptLibrary",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "rules_library::NewRule",
@@ -864,7 +864,7 @@
"cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPreviousHunk",
"ctrl-enter": "assistant::InlineAssist",
"cmd-i": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints"
}
},
@@ -1153,8 +1153,7 @@
"context": "StashList || (StashList > Picker > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-backspace": "stash_picker::DropStashItem",
"ctrl-shift-v": "stash_picker::ShowStashItem"
"ctrl-shift-backspace": "stash_picker::DropStashItem"
}
},
{
@@ -1168,7 +1167,7 @@
"cmd-a": "editor::SelectAll",
"cmd-k": "terminal::Clear",
"cmd-n": "workspace::NewTerminal",
"ctrl-enter": "assistant::InlineAssist",
"cmd-i": "assistant::InlineAssist",
"ctrl-_": null, // emacs undo
// Some nice conveniences
"cmd-backspace": ["terminal::SendText", "\u0015"], // ctrl-u: clear line
@@ -1372,23 +1371,12 @@
"cmd-}": "settings_editor::FocusNextFile"
}
},
{
"context": "StashDiff > Editor",
"use_key_equivalents": true,
"bindings": {
"ctrl-space": "git::ApplyCurrentStash",
"ctrl-shift-space": "git::PopCurrentStash",
"ctrl-shift-backspace": "git::DropCurrentStash"
}
},
{
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"up": "settings_editor::FocusPreviousNavEntry",
"shift-tab": "settings_editor::FocusPreviousNavEntry",
"down": "settings_editor::FocusNextNavEntry",
"tab": "settings_editor::FocusNextNavEntry",
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",
@@ -1396,13 +1384,5 @@
"home": "settings_editor::FocusFirstNavEntry",
"end": "settings_editor::FocusLastNavEntry"
}
},
{
"context": "Zeta2Feedback > Editor",
"bindings": {
"enter": "editor::Newline",
"cmd-enter up": "dev::Zeta2RatePredictionPositive",
"cmd-enter down": "dev::Zeta2RatePredictionNegative"
}
}
]

View File

@@ -134,7 +134,7 @@
"ctrl-k z": "editor::ToggleSoftWrap",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
"ctrl-shift-.": "agent::AddSelectionToThread",
"ctrl-shift-.": "agent::QuoteSelection",
"ctrl-shift-,": "assistant::InsertIntoEditor",
"shift-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
@@ -244,7 +244,7 @@
"ctrl-shift-i": "agent::ToggleOptionsMenu",
// "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl-shift-.": "agent::AddSelectionToThread",
"ctrl-shift-.": "agent::QuoteSelection",
"shift-alt-e": "agent::RemoveAllContext",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
@@ -375,7 +375,7 @@
}
},
{
"context": "RulesLibrary",
"context": "PromptLibrary",
"use_key_equivalents": true,
"bindings": {
"ctrl-n": "rules_library::NewRule",
@@ -548,7 +548,7 @@
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
"ctrl-shift-space": "editor::ShowWordCompletions",
"ctrl-shift-space": "editor::ShowSignatureHelp",
"ctrl-.": "editor::ToggleCodeActions",
"ctrl-k r": "editor::RevealInFileManager",
"ctrl-k p": "editor::CopyPath",
@@ -812,7 +812,7 @@
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPreviousHunk",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-i": "assistant::InlineAssist",
"ctrl-shift-;": "editor::ToggleInlayHints"
}
},
@@ -1106,8 +1106,7 @@
"context": "StashList || (StashList > Picker > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-backspace": "stash_picker::DropStashItem",
"ctrl-shift-v": "stash_picker::ShowStashItem"
"ctrl-shift-backspace": "stash_picker::DropStashItem"
}
},
{
@@ -1120,7 +1119,7 @@
"shift-insert": "terminal::Paste",
"ctrl-v": "terminal::Paste",
"ctrl-shift-v": "terminal::Paste",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-i": "assistant::InlineAssist",
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
"alt-.": ["terminal::SendText", "\u001b."],
@@ -1295,23 +1294,12 @@
"ctrl-pagedown": "settings_editor::FocusNextFile"
}
},
{
"context": "StashDiff > Editor",
"use_key_equivalents": true,
"bindings": {
"ctrl-space": "git::ApplyCurrentStash",
"ctrl-shift-space": "git::PopCurrentStash",
"ctrl-shift-backspace": "git::DropCurrentStash"
}
},
{
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"up": "settings_editor::FocusPreviousNavEntry",
"shift-tab": "settings_editor::FocusPreviousNavEntry",
"down": "settings_editor::FocusNextNavEntry",
"tab": "settings_editor::FocusNextNavEntry",
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",
@@ -1319,13 +1307,5 @@
"home": "settings_editor::FocusFirstNavEntry",
"end": "settings_editor::FocusLastNavEntry"
}
},
{
"context": "Zeta2Feedback > Editor",
"bindings": {
"enter": "editor::Newline",
"ctrl-enter up": "dev::Zeta2RatePredictionPositive",
"ctrl-enter down": "dev::Zeta2RatePredictionNegative"
}
}
]

View File

@@ -17,8 +17,8 @@
"bindings": {
"ctrl-i": "agent::ToggleFocus",
"ctrl-shift-i": "agent::ToggleFocus",
"ctrl-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
"ctrl-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
"ctrl-shift-l": "agent::QuoteSelection", // In cursor uses "Ask" mode
"ctrl-l": "agent::QuoteSelection", // In cursor uses "Agent" mode
"ctrl-k": "assistant::InlineAssist",
"ctrl-shift-k": "assistant::InsertIntoEditor"
}

View File

@@ -8,23 +8,13 @@
"ctrl-g": "menu::Cancel"
}
},
{
// Workaround to avoid falling back to default bindings.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE: must be declared before the `Editor` override.
// NOTE: in macos the 'ctrl-x' 'ctrl-p' and 'ctrl-n' rebindings are not needed, since they default to 'cmd'.
"context": "Editor",
"bindings": {
"ctrl-g": null, // currently activates `go_to_line::Toggle` when there is nothing to cancel
"ctrl-x": null, // currently activates `editor::Cut` if no following key is pressed for 1 second
"ctrl-p": null, // currently activates `file_finder::Toggle` when the cursor is on the first character of the buffer
"ctrl-n": null // currently activates `workspace::NewFile` when the cursor is on the last character of the buffer
}
},
{
"context": "Editor",
"bindings": {
"alt-x": "command_palette::Toggle",
"ctrl-g": "editor::Cancel",
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
"alt-g g": "go_to_line::Toggle", // goto-line
"alt-g alt-g": "go_to_line::Toggle", // goto-line
"ctrl-space": "editor::SetMark", // set-mark
@@ -43,8 +33,8 @@
"alt-m": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }], // back-to-indentation
"alt-left": "editor::MoveToPreviousWordStart", // left-word
"alt-right": "editor::MoveToNextWordEnd", // right-word
"alt-f": "editor::MoveToNextWordEnd", // forward-word
"alt-b": "editor::MoveToPreviousWordStart", // backward-word
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
"alt-l": "editor::ConvertToLowerCase", // downcase-word
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
@@ -108,7 +98,7 @@
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-m": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }],
"alt-f": "editor::SelectToNextWordEnd",
"alt-b": "editor::SelectToPreviousWordStart",
"alt-b": "editor::SelectToPreviousSubwordStart",
"alt-{": "editor::SelectToStartOfParagraph",
"alt-}": "editor::SelectToEndOfParagraph",
"ctrl-up": "editor::SelectToStartOfParagraph",
@@ -136,28 +126,15 @@
"ctrl-n": "editor::SignatureHelpNext"
}
},
// Example setting for using emacs-style tab
// (i.e. indent the current line / selection or perform symbol completion depending on context)
// {
// "context": "Editor && !showing_code_actions && !showing_completions",
// "bindings": {
// "tab": "editor::AutoIndent" // indent-for-tab-command
// }
// },
{
"context": "Workspace",
"bindings": {
"alt-x": "command_palette::Toggle", // execute-extended-command
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
// "ctrl-x ctrl-c": "workspace::CloseWindow" // in case you only want to exit the current Zed instance
"ctrl-x ctrl-c": "zed::Quit", // save-buffers-kill-terminal
"ctrl-x 5 0": "workspace::CloseWindow", // delete-frame
"ctrl-x 5 2": "workspace::NewWindow", // make-frame-command
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
// "ctrl-x 1": "pane::JoinAll", // in case you prefer to delete the splits but keep the buffers open
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
@@ -168,19 +145,10 @@
}
},
{
// Workaround to enable using native emacs from the Zed terminal.
// Workaround to enable using emacs in the Zed terminal.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE:
// "terminal::SendKeystroke" only works for a single key stroke (e.g. ctrl-x),
// so override with null for compound sequences (e.g. ctrl-x ctrl-c).
"context": "Terminal",
"bindings": {
// If you want to perfect your emacs-in-zed setup, also consider the following.
// You may need to enable "option_as_meta" from the Zed settings for "alt-x" to work.
// "alt-x": ["terminal::SendKeystroke", "alt-x"],
// "ctrl-x": ["terminal::SendKeystroke", "ctrl-x"],
// "ctrl-n": ["terminal::SendKeystroke", "ctrl-n"],
// ...
"ctrl-x ctrl-c": null, // save-buffers-kill-terminal
"ctrl-x ctrl-f": null, // find-file
"ctrl-x ctrl-s": null, // save-buffer

View File

@@ -17,8 +17,8 @@
"bindings": {
"cmd-i": "agent::ToggleFocus",
"cmd-shift-i": "agent::ToggleFocus",
"cmd-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
"cmd-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
"cmd-shift-l": "agent::QuoteSelection", // In cursor uses "Ask" mode
"cmd-l": "agent::QuoteSelection", // In cursor uses "Agent" mode
"cmd-k": "assistant::InlineAssist",
"cmd-shift-k": "assistant::InsertIntoEditor"
}

View File

@@ -9,19 +9,13 @@
"ctrl-g": "menu::Cancel"
}
},
{
// Workaround to avoid falling back to default bindings.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE: must be declared before the `Editor` override.
"context": "Editor",
"bindings": {
"ctrl-g": null // currently activates `go_to_line::Toggle` when there is nothing to cancel
}
},
{
"context": "Editor",
"bindings": {
"alt-x": "command_palette::Toggle",
"ctrl-g": "editor::Cancel",
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
"alt-g g": "go_to_line::Toggle", // goto-line
"alt-g alt-g": "go_to_line::Toggle", // goto-line
"ctrl-space": "editor::SetMark", // set-mark
@@ -40,8 +34,8 @@
"alt-m": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }], // back-to-indentation
"alt-left": "editor::MoveToPreviousWordStart", // left-word
"alt-right": "editor::MoveToNextWordEnd", // right-word
"alt-f": "editor::MoveToNextWordEnd", // forward-word
"alt-b": "editor::MoveToPreviousWordStart", // backward-word
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
"alt-l": "editor::ConvertToLowerCase", // downcase-word
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
@@ -105,7 +99,7 @@
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-m": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }],
"alt-f": "editor::SelectToNextWordEnd",
"alt-b": "editor::SelectToPreviousWordStart",
"alt-b": "editor::SelectToPreviousSubwordStart",
"alt-{": "editor::SelectToStartOfParagraph",
"alt-}": "editor::SelectToEndOfParagraph",
"ctrl-up": "editor::SelectToStartOfParagraph",
@@ -133,28 +127,15 @@
"ctrl-n": "editor::SignatureHelpNext"
}
},
// Example setting for using emacs-style tab
// (i.e. indent the current line / selection or perform symbol completion depending on context)
// {
// "context": "Editor && !showing_code_actions && !showing_completions",
// "bindings": {
// "tab": "editor::AutoIndent" // indent-for-tab-command
// }
// },
{
"context": "Workspace",
"bindings": {
"alt-x": "command_palette::Toggle", // execute-extended-command
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
// "ctrl-x ctrl-c": "workspace::CloseWindow" // in case you only want to exit the current Zed instance
"ctrl-x ctrl-c": "zed::Quit", // save-buffers-kill-terminal
"ctrl-x 5 0": "workspace::CloseWindow", // delete-frame
"ctrl-x 5 2": "workspace::NewWindow", // make-frame-command
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
// "ctrl-x 1": "pane::JoinAll", // in case you prefer to delete the splits but keep the buffers open
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
@@ -165,19 +146,10 @@
}
},
{
// Workaround to enable using native emacs from the Zed terminal.
// Workaround to enable using emacs in the Zed terminal.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE:
// "terminal::SendKeystroke" only works for a single key stroke (e.g. ctrl-x),
// so override with null for compound sequences (e.g. ctrl-x ctrl-c).
"context": "Terminal",
"bindings": {
// If you want to perfect your emacs-in-zed setup, also consider the following.
// You may need to enable "option_as_meta" from the Zed settings for "alt-x" to work.
// "alt-x": ["terminal::SendKeystroke", "alt-x"],
// "ctrl-x": ["terminal::SendKeystroke", "ctrl-x"],
// "ctrl-n": ["terminal::SendKeystroke", "ctrl-n"],
// ...
"ctrl-x ctrl-c": null, // save-buffers-kill-terminal
"ctrl-x ctrl-f": null, // find-file
"ctrl-x ctrl-s": null, // save-buffer

View File

@@ -422,66 +422,56 @@
{
"context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
"bindings": {
// Movement
"h": "vim::WrappingLeft",
";": "vim::HelixCollapseSelection",
":": "command_palette::Toggle",
"m": "vim::PushHelixMatch",
"s": "vim::HelixSelectRegex",
"]": ["vim::PushHelixNext", { "around": true }],
"[": ["vim::PushHelixPrevious", { "around": true }],
"left": "vim::WrappingLeft",
"l": "vim::WrappingRight",
"right": "vim::WrappingRight",
"t": ["vim::PushFindForward", { "before": true, "multiline": true }],
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
"alt-.": "vim::RepeatFind",
// Changes
"shift-r": "editor::Paste",
"`": "vim::ConvertToLowerCase",
"alt-`": "vim::ConvertToUpperCase",
"insert": "vim::InsertBefore",
"shift-u": "editor::Redo",
"ctrl-r": "vim::Redo",
"h": "vim::WrappingLeft",
"l": "vim::WrappingRight",
"y": "vim::HelixYank",
"p": "vim::HelixPaste",
"shift-p": ["vim::HelixPaste", { "before": true }],
"shift-p": ["vim::HelixPaste", { "before": true }],
"alt-;": "vim::OtherEnd",
"ctrl-r": "vim::Redo",
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
"t": ["vim::PushFindForward", { "before": true, "multiline": true }],
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
">": "vim::Indent",
"<": "vim::Outdent",
"=": "vim::AutoIndent",
"d": "vim::HelixDelete",
"c": "vim::HelixSubstitute",
"alt-c": "vim::HelixSubstituteNoYank",
// Selection manipulation
"s": "vim::HelixSelectRegex",
"`": "vim::ConvertToLowerCase",
"alt-`": "vim::ConvertToUpperCase",
"g q": "vim::PushRewrap",
"g w": "vim::PushRewrap",
"insert": "vim::InsertBefore",
"alt-.": "vim::RepeatFind",
"alt-s": ["editor::SplitSelectionIntoLines", { "keep_selections": true }],
";": "vim::HelixCollapseSelection",
"alt-;": "vim::OtherEnd",
",": "vim::HelixKeepNewestSelection",
"shift-c": "vim::HelixDuplicateBelow",
"alt-shift-c": "vim::HelixDuplicateAbove",
"%": "editor::SelectAll",
"x": "vim::HelixSelectLine",
"shift-x": "editor::SelectLine",
"ctrl-c": "editor::ToggleComments",
"alt-o": "editor::SelectLargerSyntaxNode",
"alt-i": "editor::SelectSmallerSyntaxNode",
"alt-p": "editor::SelectPreviousSyntaxNode",
"alt-n": "editor::SelectNextSyntaxNode",
// Goto mode
"g e": "vim::EndOfDocument",
"g h": "vim::StartOfLine",
"g n": "pane::ActivateNextItem",
"g p": "pane::ActivatePreviousItem",
// "tab": "pane::ActivateNextItem",
// "shift-tab": "pane::ActivatePrevItem",
"shift-h": "pane::ActivatePreviousItem",
"shift-l": "pane::ActivateNextItem",
"g l": "vim::EndOfLine",
"g h": "vim::StartOfLine",
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
"g e": "vim::EndOfDocument",
"g .": "vim::HelixGotoLastModification", // go to last modification
"g r": "editor::FindAllReferences", // zed specific
"g t": "vim::WindowTop",
"g c": "vim::WindowMiddle",
"g b": "vim::WindowBottom",
"g r": "editor::FindAllReferences", // zed specific
"g n": "pane::ActivateNextItem",
"shift-l": "pane::ActivateNextItem",
"g p": "pane::ActivatePreviousItem",
"shift-h": "pane::ActivatePreviousItem",
"g .": "vim::HelixGotoLastModification", // go to last modification
"shift-r": "editor::Paste",
"x": "vim::HelixSelectLine",
"shift-x": "editor::SelectLine",
"%": "editor::SelectAll",
// Window mode
"space w h": "workspace::ActivatePaneLeft",
"space w l": "workspace::ActivatePaneRight",
@@ -492,7 +482,6 @@
"space w r": "pane::SplitRight",
"space w v": "pane::SplitDown",
"space w d": "pane::SplitDown",
// Space mode
"space f": "file_finder::Toggle",
"space k": "editor::Hover",
@@ -503,18 +492,16 @@
"space a": "editor::ToggleCodeActions",
"space h": "editor::SelectAllMatches",
"space c": "editor::ToggleComments",
"space p": "editor::Paste",
"space y": "editor::Copy",
// Other
":": "command_palette::Toggle",
"m": "vim::PushHelixMatch",
"]": ["vim::PushHelixNext", { "around": true }],
"[": ["vim::PushHelixPrevious", { "around": true }],
"g q": "vim::PushRewrap",
"g w": "vim::PushRewrap",
// "tab": "pane::ActivateNextItem",
// "shift-tab": "pane::ActivatePrevItem",
"space p": "editor::Paste",
"shift-u": "editor::Redo",
"ctrl-c": "editor::ToggleComments",
"d": "vim::HelixDelete",
"c": "vim::HelixSubstitute",
"alt-c": "vim::HelixSubstituteNoYank",
"shift-c": "vim::HelixDuplicateBelow",
"alt-shift-c": "vim::HelixDuplicateAbove",
",": "vim::HelixKeepNewestSelection"
}
},
{
@@ -983,9 +970,7 @@
"bindings": {
"ctrl-h": "editor::Backspace",
"ctrl-u": "editor::DeleteToBeginningOfLine",
"ctrl-w": "editor::DeleteToPreviousWordStart",
"ctrl-p": "menu::SelectPrevious",
"ctrl-n": "menu::SelectNext"
"ctrl-w": "editor::DeleteToPreviousWordStart"
}
},
{

View File

@@ -1,8 +1,8 @@
{
"$schema": "zed://schemas/settings",
/// The displayed name of this project. If not set or null, the root directory name
/// The displayed name of this project. If not set or empty, the root directory name
/// will be displayed.
"project_name": null,
"project_name": "",
// The name of the Zed theme to use for the UI.
//
// `mode` is one of:
@@ -1091,10 +1091,10 @@
// Only the file Zed had indexed will be used, not necessary all the gitignored files.
//
// Can accept 3 values:
// * "all": Use all gitignored files
// * "indexed": Use only the files Zed had indexed
// * "smart": Be smart and search for ignored when called from a gitignored worktree
"include_ignored": "smart"
// * `true`: Use all gitignored files
// * `false`: Use only the files Zed had indexed
// * `null`: Be smart and search for ignored when called from a gitignored worktree
"include_ignored": null
},
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
@@ -1350,9 +1350,7 @@
// Whether to show the active language button in the status bar.
"active_language_button": true,
// Whether to show the cursor position button in the status bar.
"cursor_position_button": true,
// Whether to show active line endings button in the status bar.
"line_endings_button": false
"cursor_position_button": true
},
// Settings specific to the terminal
"terminal": {
@@ -1741,7 +1739,7 @@
}
},
"Kotlin": {
"language_servers": ["!kotlin-language-server", "kotlin-lsp", "..."]
"language_servers": ["kotlin-language-server", "!kotlin-lsp", "..."]
},
"LaTeX": {
"formatter": "language_server",
@@ -1820,11 +1818,10 @@
},
"SystemVerilog": {
"format_on_save": "off",
"language_servers": ["!slang", "..."],
"use_on_type_format": false
},
"Vue.js": {
"language_servers": ["vue-language-server", "vtsls", "..."],
"language_servers": ["vue-language-server", "..."],
"prettier": {
"allowed": true
}

View File

@@ -49,9 +49,8 @@
"panel.background": "#3a3735ff",
"panel.focused_border": "#83a598ff",
"pane.focused_border": null,
"scrollbar.thumb.active_background": "#83a598ac",
"scrollbar.thumb.hover_background": "#fbf1c74c",
"scrollbar.thumb.background": "#a899844c",
"scrollbar.thumb.background": "#fbf1c74c",
"scrollbar.thumb.hover_background": "#494340ff",
"scrollbar.thumb.border": "#494340ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#373432ff",
@@ -455,9 +454,8 @@
"panel.background": "#393634ff",
"panel.focused_border": "#83a598ff",
"pane.focused_border": null,
"scrollbar.thumb.active_background": "#83a598ac",
"scrollbar.thumb.hover_background": "#fbf1c74c",
"scrollbar.thumb.background": "#a899844c",
"scrollbar.thumb.background": "#fbf1c74c",
"scrollbar.thumb.hover_background": "#494340ff",
"scrollbar.thumb.border": "#494340ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#343130ff",
@@ -861,9 +859,8 @@
"panel.background": "#3b3735ff",
"panel.focused_border": null,
"pane.focused_border": null,
"scrollbar.thumb.active_background": "#83a598ac",
"scrollbar.thumb.hover_background": "#fbf1c74c",
"scrollbar.thumb.background": "#a899844c",
"scrollbar.thumb.background": "#fbf1c74c",
"scrollbar.thumb.hover_background": "#494340ff",
"scrollbar.thumb.border": "#494340ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#393634ff",
@@ -1267,9 +1264,8 @@
"panel.background": "#ecddb4ff",
"panel.focused_border": null,
"pane.focused_border": null,
"scrollbar.thumb.active_background": "#458588ac",
"scrollbar.thumb.hover_background": "#2828284c",
"scrollbar.thumb.background": "#7c6f644c",
"scrollbar.thumb.background": "#2828284c",
"scrollbar.thumb.hover_background": "#ddcca7ff",
"scrollbar.thumb.border": "#ddcca7ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#eee0b7ff",
@@ -1673,9 +1669,8 @@
"panel.background": "#ecddb5ff",
"panel.focused_border": null,
"pane.focused_border": null,
"scrollbar.thumb.active_background": "#458588ac",
"scrollbar.thumb.hover_background": "#2828284c",
"scrollbar.thumb.background": "#7c6f644c",
"scrollbar.thumb.background": "#2828284c",
"scrollbar.thumb.hover_background": "#ddcca7ff",
"scrollbar.thumb.border": "#ddcca7ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#eee1bbff",
@@ -2079,9 +2074,8 @@
"panel.background": "#ecdcb3ff",
"panel.focused_border": null,
"pane.focused_border": null,
"scrollbar.thumb.active_background": "#458588ac",
"scrollbar.thumb.hover_background": "#2828284c",
"scrollbar.thumb.background": "#7c6f644c",
"scrollbar.thumb.background": "#2828284c",
"scrollbar.thumb.hover_background": "#ddcca7ff",
"scrollbar.thumb.border": "#ddcca7ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#eddeb5ff",

View File

@@ -328,7 +328,7 @@ impl ToolCall {
location: acp::ToolCallLocation,
project: WeakEntity<Project>,
cx: &mut AsyncApp,
) -> Option<ResolvedLocation> {
) -> Option<AgentLocation> {
let buffer = project
.update(cx, |project, cx| {
project
@@ -350,14 +350,17 @@ impl ToolCall {
})
.ok()?;
Some(ResolvedLocation { buffer, position })
Some(AgentLocation {
buffer: buffer.downgrade(),
position,
})
}
fn resolve_locations(
&self,
project: Entity<Project>,
cx: &mut App,
) -> Task<Vec<Option<ResolvedLocation>>> {
) -> Task<Vec<Option<AgentLocation>>> {
let locations = self.locations.clone();
project.update(cx, |_, cx| {
cx.spawn(async move |project, cx| {
@@ -371,23 +374,6 @@ impl ToolCall {
}
}
// Separate so we can hold a strong reference to the buffer
// for saving on the thread
#[derive(Clone, Debug, PartialEq, Eq)]
struct ResolvedLocation {
buffer: Entity<Buffer>,
position: Anchor,
}
impl From<&ResolvedLocation> for AgentLocation {
fn from(value: &ResolvedLocation) -> Self {
Self {
buffer: value.buffer.downgrade(),
position: value.position,
}
}
}
#[derive(Debug)]
pub enum ToolCallStatus {
/// The tool call hasn't started running yet, but we start showing it to
@@ -1407,46 +1393,35 @@ impl AcpThread {
let task = tool_call.resolve_locations(project, cx);
cx.spawn(async move |this, cx| {
let resolved_locations = task.await;
this.update(cx, |this, cx| {
let project = this.project.clone();
for location in resolved_locations.iter().flatten() {
this.shared_buffers
.insert(location.buffer.clone(), location.buffer.read(cx).snapshot());
}
let Some((ix, tool_call)) = this.tool_call_mut(&id) else {
return;
};
if let Some(Some(location)) = resolved_locations.last() {
project.update(cx, |project, cx| {
let should_ignore = if let Some(agent_location) = project
.agent_location()
.filter(|agent_location| agent_location.buffer == location.buffer)
{
let snapshot = location.buffer.read(cx).snapshot();
let old_position = agent_location.position.to_point(&snapshot);
let new_position = location.position.to_point(&snapshot);
// ignore this so that when we get updates from the edit tool
// the position doesn't reset to the startof line
old_position.row == new_position.row
&& old_position.column > new_position.column
} else {
false
};
if !should_ignore {
project.set_agent_location(Some(location.into()), cx);
if let Some(agent_location) = project.agent_location() {
let should_ignore = agent_location.buffer == location.buffer
&& location
.buffer
.update(cx, |buffer, _| {
let snapshot = buffer.snapshot();
let old_position =
agent_location.position.to_point(&snapshot);
let new_position = location.position.to_point(&snapshot);
// ignore this so that when we get updates from the edit tool
// the position doesn't reset to the startof line
old_position.row == new_position.row
&& old_position.column > new_position.column
})
.ok()
.unwrap_or_default();
if !should_ignore {
project.set_agent_location(Some(location.clone()), cx);
}
}
});
}
let resolved_locations = resolved_locations
.iter()
.map(|l| l.as_ref().map(|l| AgentLocation::from(l)))
.collect::<Vec<_>>();
if tool_call.resolved_locations != resolved_locations {
tool_call.resolved_locations = resolved_locations;
cx.emit(AcpThreadEvent::EntryUpdated(ix));

View File

@@ -236,21 +236,21 @@ impl PendingDiff {
fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
let ranges = self.excerpt_ranges(cx);
let base_text = self.base_text.clone();
let new_buffer = self.new_buffer.read(cx);
let language_registry = new_buffer.language_registry();
let language_registry = self.new_buffer.read(cx).language_registry();
let path = new_buffer
let path = self
.new_buffer
.read(cx)
.file()
.map(|file| file.path().display(file.path_style(cx)))
.unwrap_or("untitled".into())
.into();
let replica_id = new_buffer.replica_id();
// Replace the buffer in the multibuffer with the snapshot
let buffer = cx.new(|cx| {
let language = self.new_buffer.read(cx).language().cloned();
let buffer = TextBuffer::new_normalized(
replica_id,
0,
cx.entity_id().as_non_zero_u64().into(),
self.new_buffer.read(cx).line_ending(),
self.new_buffer.read(cx).as_rope().clone(),

View File

@@ -1,15 +1,10 @@
use agent_client_protocol as acp;
use anyhow::Result;
use futures::{FutureExt as _, future::Shared};
use gpui::{App, AppContext, AsyncApp, Context, Entity, Task};
use gpui::{App, AppContext, Context, Entity, Task};
use language::LanguageRegistry;
use markdown::Markdown;
use project::Project;
use settings::{Settings as _, SettingsLocation};
use std::{path::PathBuf, process::ExitStatus, sync::Arc, time::Instant};
use task::Shell;
use terminal::terminal_settings::TerminalSettings;
use util::get_default_system_shell_preferring_bash;
pub struct Terminal {
id: acp::TerminalId,
@@ -175,68 +170,3 @@ impl Terminal {
)
}
}
pub async fn create_terminal_entity(
command: String,
args: &[String],
env_vars: Vec<(String, String)>,
cwd: Option<PathBuf>,
project: &Entity<Project>,
cx: &mut AsyncApp,
) -> Result<Entity<terminal::Terminal>> {
let mut env = if let Some(dir) = &cwd {
project
.update(cx, |project, cx| {
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
project.directory_environment(&shell, dir.clone().into(), cx)
})?
.await
.unwrap_or_default()
} else {
Default::default()
};
// Disables paging for `git` and hopefully other commands
env.insert("PAGER".into(), "".into());
env.extend(env_vars);
// Use remote shell or default system shell, as appropriate
let shell = project
.update(cx, |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
.map(Shell::Program)
})?
.unwrap_or_else(|| Shell::Program(get_default_system_shell_preferring_bash()));
let is_windows = project
.read_with(cx, |project, cx| project.path_style(cx).is_windows())
.unwrap_or(cfg!(windows));
let (task_command, task_args) = task::ShellBuilder::new(&shell, is_windows)
.redirect_stdin_to_dev_null()
.build(Some(command.clone()), &args);
project
.update(cx, |project, cx| {
project.create_terminal_task(
task::SpawnInTerminal {
command: Some(task_command),
args: task_args,
cwd,
env,
..Default::default()
},
cx,
)
})?
.await
}

View File

@@ -93,8 +93,8 @@ struct WatchedConnection {
messages: Vec<WatchedConnectionMessage>,
list_state: ListState,
connection: Weak<acp::ClientSideConnection>,
incoming_request_methods: HashMap<acp::RequestId, Arc<str>>,
outgoing_request_methods: HashMap<acp::RequestId, Arc<str>>,
incoming_request_methods: HashMap<i32, Arc<str>>,
outgoing_request_methods: HashMap<i32, Arc<str>>,
_task: Task<()>,
}
@@ -175,7 +175,7 @@ impl AcpTools {
}
};
method_map.insert(id.clone(), method.clone());
method_map.insert(id, method.clone());
(Some(id), method.into(), MessageType::Request, Ok(params))
}
acp::StreamMessageContent::Response { id, result } => {
@@ -338,7 +338,6 @@ impl AcpTools {
.children(
message
.request_id
.as_ref()
.map(|req_id| div().child(ui::Chip::new(req_id.to_string()))),
),
)
@@ -390,7 +389,7 @@ impl AcpTools {
struct WatchedConnectionMessage {
name: SharedString,
request_id: Option<acp::RequestId>,
request_id: Option<i32>,
direction: acp::StreamMessageDirection,
message_type: MessageType,
params: Result<Option<serde_json::Value>, acp::Error>,

View File

@@ -11,7 +11,8 @@ use language::{
LanguageServerStatusUpdate, ServerHealth,
};
use project::{
LanguageServerProgress, LspStoreEvent, Project, ProjectEnvironmentEvent,
EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project,
ProjectEnvironmentEvent,
git_store::{GitStoreEvent, Repository},
};
use smallvec::SmallVec;
@@ -326,20 +327,20 @@ impl ActivityIndicator {
.flatten()
}
fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a String> {
fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a EnvironmentErrorMessage> {
self.project.read(cx).peek_environment_error(cx)
}
fn content_to_render(&mut self, cx: &mut Context<Self>) -> Option<Content> {
// Show if any direnv calls failed
if let Some(message) = self.pending_environment_error(cx) {
if let Some(error) = self.pending_environment_error(cx) {
return Some(Content {
icon: Some(
Icon::new(IconName::Warning)
.size(IconSize::Small)
.into_any_element(),
),
message: message.clone(),
message: error.0.clone(),
on_click: Some(Arc::new(move |this, window, cx| {
this.project.update(cx, |project, cx| {
project.pop_environment_error(cx);

View File

@@ -10,8 +10,6 @@ path = "src/agent.rs"
[features]
test-support = ["db/test-support"]
eval = []
edit-agent-eval = []
e2e = []
[lints]
@@ -24,7 +22,7 @@ agent-client-protocol.workspace = true
agent_servers.workspace = true
agent_settings.workspace = true
anyhow.workspace = true
assistant_text_thread.workspace = true
assistant_context.workspace = true
chrono.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
@@ -76,7 +74,7 @@ zstd.workspace = true
[dev-dependencies]
agent_servers = { workspace = true, "features" = ["test-support"] }
assistant_text_thread = { workspace = true, "features" = ["test-support"] }
assistant_context = { workspace = true, "features" = ["test-support"] }
client = { workspace = true, "features" = ["test-support"] }
clock = { workspace = true, "features" = ["test-support"] }
context_server = { workspace = true, "features" = ["test-support"] }

View File

@@ -48,10 +48,24 @@ use util::rel_path::RelPath;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectSnapshot {
pub worktree_snapshots: Vec<project::telemetry_snapshot::TelemetryWorktreeSnapshot>,
pub worktree_snapshots: Vec<WorktreeSnapshot>,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WorktreeSnapshot {
pub worktree_path: String,
pub git_state: Option<GitState>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GitState {
pub remote_url: Option<String>,
pub head_sha: Option<String>,
pub current_branch: Option<String>,
pub diff: Option<String>,
}
const RULES_FILE_NAMES: [&str; 9] = [
".rules",
".cursorrules",
@@ -1266,9 +1280,8 @@ mod internal_tests {
)
.await;
let project = Project::test(fs.clone(), [], cx).await;
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let agent = NativeAgent::new(
project.clone(),
history_store,
@@ -1328,9 +1341,8 @@ mod internal_tests {
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/", json!({ "a": {} })).await;
let project = Project::test(fs.clone(), [], cx).await;
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let connection = NativeAgentConnection(
NativeAgent::new(
project.clone(),
@@ -1404,9 +1416,8 @@ mod internal_tests {
.await;
let project = Project::test(fs.clone(), [], cx).await;
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
// Create the agent and connection
let agent = NativeAgent::new(
@@ -1477,9 +1488,8 @@ mod internal_tests {
)
.await;
let project = Project::test(fs.clone(), [path!("/a").as_ref()], cx).await;
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let agent = NativeAgent::new(
project.clone(),
history_store.clone(),

View File

@@ -31,7 +31,7 @@ use std::{
use util::path;
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_extract_handle_command_output() {
// Test how well agent generates multiple edit hunks.
//
@@ -108,7 +108,7 @@ fn eval_extract_handle_command_output() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_delete_run_git_blame() {
// Model | Pass rate
// ----------------------------|----------
@@ -171,7 +171,7 @@ fn eval_delete_run_git_blame() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_translate_doc_comments() {
// Model | Pass rate
// ============================================
@@ -234,7 +234,7 @@ fn eval_translate_doc_comments() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_use_wasi_sdk_in_compile_parser_to_wasm() {
// Model | Pass rate
// ============================================
@@ -360,7 +360,7 @@ fn eval_use_wasi_sdk_in_compile_parser_to_wasm() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_disable_cursor_blinking() {
// Model | Pass rate
// ============================================
@@ -446,7 +446,7 @@ fn eval_disable_cursor_blinking() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_from_pixels_constructor() {
// Results for 2025-06-13
//
@@ -656,7 +656,7 @@ fn eval_from_pixels_constructor() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_zode() {
// Model | Pass rate
// ============================================
@@ -763,7 +763,7 @@ fn eval_zode() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_add_overwrite_test() {
// Model | Pass rate
// ============================================
@@ -995,7 +995,7 @@ fn eval_add_overwrite_test() {
}
#[test]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
#[cfg_attr(not(feature = "eval"), ignore)]
fn eval_create_empty_file() {
// Check that Edit Agent can create a file without writing its
// thoughts into it. This issue is not specific to empty files, but
@@ -1490,20 +1490,9 @@ 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())

View File

@@ -308,13 +308,12 @@ mod tests {
use indoc::indoc;
use language::{BufferId, TextBuffer};
use rand::prelude::*;
use text::ReplicaId;
use util::test::{generate_marked_text, marked_text_ranges};
#[test]
fn test_empty_query() {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
"Hello world\nThis is a test\nFoo bar baz",
);
@@ -328,7 +327,7 @@ mod tests {
#[test]
fn test_streaming_exact_match() {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
"Hello world\nThis is a test\nFoo bar baz",
);
@@ -352,7 +351,7 @@ mod tests {
#[test]
fn test_streaming_fuzzy_match() {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
indoc! {"
function foo(a, b) {
@@ -386,7 +385,7 @@ mod tests {
#[test]
fn test_incremental_improvement() {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
"Line 1\nLine 2\nLine 3\nLine 4\nLine 5",
);
@@ -411,7 +410,7 @@ mod tests {
#[test]
fn test_incomplete_lines_buffering() {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
indoc! {"
The quick brown fox
@@ -438,7 +437,7 @@ mod tests {
#[test]
fn test_multiline_fuzzy_match() {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
indoc! {r#"
impl Display for User {
@@ -692,11 +691,7 @@ mod tests {
}
"#};
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
text.to_string(),
);
let buffer = TextBuffer::new(0, BufferId::new(1).unwrap(), text.to_string());
let snapshot = buffer.snapshot();
let mut matcher = StreamingFuzzyMatcher::new(snapshot.clone());
@@ -729,7 +724,7 @@ mod tests {
#[track_caller]
fn assert_location_resolution(text_with_expected_range: &str, query: &str, rng: &mut StdRng) {
let (text, expected_ranges) = marked_text_ranges(text_with_expected_range, false);
let buffer = TextBuffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), text.clone());
let buffer = TextBuffer::new(0, BufferId::new(1).unwrap(), text.clone());
let snapshot = buffer.snapshot();
let mut matcher = StreamingFuzzyMatcher::new(snapshot);

View File

@@ -2,12 +2,12 @@ use crate::{DbThread, DbThreadMetadata, ThreadsDatabase};
use acp_thread::MentionUri;
use agent_client_protocol as acp;
use anyhow::{Context as _, Result, anyhow};
use assistant_text_thread::{SavedTextThreadMetadata, TextThread};
use assistant_context::{AssistantContext, SavedContextMetadata};
use chrono::{DateTime, Utc};
use db::kvp::KEY_VALUE_STORE;
use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
use itertools::Itertools;
use paths::text_threads_dir;
use paths::contexts_dir;
use project::Project;
use serde::{Deserialize, Serialize};
use std::{collections::VecDeque, path::Path, rc::Rc, sync::Arc, time::Duration};
@@ -50,23 +50,21 @@ pub fn load_agent_thread(
#[derive(Clone, Debug)]
pub enum HistoryEntry {
AcpThread(DbThreadMetadata),
TextThread(SavedTextThreadMetadata),
TextThread(SavedContextMetadata),
}
impl HistoryEntry {
pub fn updated_at(&self) -> DateTime<Utc> {
match self {
HistoryEntry::AcpThread(thread) => thread.updated_at,
HistoryEntry::TextThread(text_thread) => text_thread.mtime.to_utc(),
HistoryEntry::TextThread(context) => context.mtime.to_utc(),
}
}
pub fn id(&self) -> HistoryEntryId {
match self {
HistoryEntry::AcpThread(thread) => HistoryEntryId::AcpThread(thread.id.clone()),
HistoryEntry::TextThread(text_thread) => {
HistoryEntryId::TextThread(text_thread.path.clone())
}
HistoryEntry::TextThread(context) => HistoryEntryId::TextThread(context.path.clone()),
}
}
@@ -76,9 +74,9 @@ impl HistoryEntry {
id: thread.id.clone(),
name: thread.title.to_string(),
},
HistoryEntry::TextThread(text_thread) => MentionUri::TextThread {
path: text_thread.path.as_ref().to_owned(),
name: text_thread.title.to_string(),
HistoryEntry::TextThread(context) => MentionUri::TextThread {
path: context.path.as_ref().to_owned(),
name: context.title.to_string(),
},
}
}
@@ -92,7 +90,7 @@ impl HistoryEntry {
&thread.title
}
}
HistoryEntry::TextThread(text_thread) => &text_thread.title,
HistoryEntry::TextThread(context) => &context.title,
}
}
}
@@ -122,7 +120,7 @@ enum SerializedRecentOpen {
pub struct HistoryStore {
threads: Vec<DbThreadMetadata>,
entries: Vec<HistoryEntry>,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
text_thread_store: Entity<assistant_context::ContextStore>,
recently_opened_entries: VecDeque<HistoryEntryId>,
_subscriptions: Vec<gpui::Subscription>,
_save_recently_opened_entries_task: Task<()>,
@@ -130,7 +128,7 @@ pub struct HistoryStore {
impl HistoryStore {
pub fn new(
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
text_thread_store: Entity<assistant_context::ContextStore>,
cx: &mut Context<Self>,
) -> Self {
let subscriptions =
@@ -194,16 +192,16 @@ impl HistoryStore {
cx: &mut Context<Self>,
) -> Task<Result<()>> {
self.text_thread_store
.update(cx, |store, cx| store.delete_local(path, cx))
.update(cx, |store, cx| store.delete_local_context(path, cx))
}
pub fn load_text_thread(
&self,
path: Arc<Path>,
cx: &mut Context<Self>,
) -> Task<Result<Entity<TextThread>>> {
) -> Task<Result<Entity<AssistantContext>>> {
self.text_thread_store
.update(cx, |store, cx| store.open_local(path, cx))
.update(cx, |store, cx| store.open_local_context(path, cx))
}
pub fn reload(&self, cx: &mut Context<Self>) {
@@ -245,7 +243,7 @@ impl HistoryStore {
history_entries.extend(
self.text_thread_store
.read(cx)
.unordered_text_threads()
.unordered_contexts()
.cloned()
.map(HistoryEntry::TextThread),
);
@@ -280,14 +278,14 @@ impl HistoryStore {
let context_entries = self
.text_thread_store
.read(cx)
.unordered_text_threads()
.flat_map(|text_thread| {
.unordered_contexts()
.flat_map(|context| {
self.recently_opened_entries
.iter()
.enumerate()
.flat_map(|(index, entry)| match entry {
HistoryEntryId::TextThread(path) if &text_thread.path == path => {
Some((index, HistoryEntry::TextThread(text_thread.clone())))
HistoryEntryId::TextThread(path) if &context.path == path => {
Some((index, HistoryEntry::TextThread(context.clone())))
}
_ => None,
})
@@ -349,7 +347,7 @@ impl HistoryStore {
acp::SessionId(id.as_str().into()),
)),
SerializedRecentOpen::TextThread(file_name) => Some(
HistoryEntryId::TextThread(text_threads_dir().join(file_name).into()),
HistoryEntryId::TextThread(contexts_dir().join(file_name).into()),
),
})
.collect();

View File

@@ -2,6 +2,7 @@ use std::{any::Any, path::Path, rc::Rc, sync::Arc};
use agent_servers::{AgentServer, AgentServerDelegate};
use anyhow::Result;
use collections::HashMap;
use fs::Fs;
use gpui::{App, Entity, SharedString, Task};
use prompt_store::PromptStore;
@@ -41,7 +42,7 @@ impl AgentServer for NativeAgentServer {
) -> Task<
Result<(
Rc<dyn acp_thread::AgentConnection>,
Option<task::SpawnInTerminal>,
HashMap<String, task::SpawnInTerminal>,
)>,
> {
log::debug!(
@@ -67,7 +68,7 @@ impl AgentServer for NativeAgentServer {
Ok((
Rc::new(connection) as Rc<dyn acp_thread::AgentConnection>,
None,
HashMap::default(),
))
})
}
@@ -81,7 +82,7 @@ impl AgentServer for NativeAgentServer {
mod tests {
use super::*;
use assistant_text_thread::TextThreadStore;
use assistant_context::ContextStore;
use gpui::AppContext;
agent_servers::e2e_tests::common_e2e_tests!(
@@ -116,9 +117,8 @@ mod tests {
});
let history = cx.update(|cx| {
let text_thread_store =
cx.new(move |cx| TextThreadStore::fake(project.clone(), cx));
cx.new(move |cx| HistoryStore::new(text_thread_store, cx))
let context_store = cx.new(move |cx| ContextStore::fake(project.clone(), cx));
cx.new(move |cx| HistoryStore::new(context_store, cx))
});
NativeAgentServer::new(fs.clone(), history)

View File

@@ -1834,9 +1834,8 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
fake_fs.insert_tree(path!("/test"), json!({})).await;
let project = Project::test(fake_fs.clone(), [Path::new("/test")], cx).await;
let cwd = Path::new("/test");
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
// Create agent and connection
let agent = NativeAgent::new(
@@ -1996,7 +1995,7 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) {
locations: vec![],
raw_input: Some(json!({})),
raw_output: None,
meta: Some(json!({ "tool_name": "thinking" })),
meta: None,
}
);
let update = expect_tool_call_update_fields(&mut events).await;

View File

@@ -1,8 +1,9 @@
use crate::{
ContextServerRegistry, CopyPathTool, CreateDirectoryTool, DbLanguageModel, DbThread,
DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GrepTool,
DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GitState, GrepTool,
ListDirectoryTool, MovePathTool, NowTool, OpenTool, ProjectSnapshot, ReadFileTool,
SystemPromptTemplate, Template, Templates, TerminalTool, ThinkingTool, WebSearchTool,
WorktreeSnapshot,
};
use acp_thread::{MentionUri, UserMessageId};
use action_log::ActionLog;
@@ -25,6 +26,7 @@ use futures::{
future::Shared,
stream::FuturesUnordered,
};
use git::repository::DiffType;
use gpui::{
App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,
};
@@ -35,7 +37,10 @@ use language_model::{
LanguageModelToolResultContent, LanguageModelToolSchemaFormat, LanguageModelToolUse,
LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage, ZED_CLOUD_PROVIDER_ID,
};
use project::Project;
use project::{
Project,
git_store::{GitStore, RepositoryState},
};
use prompt_store::ProjectContext;
use schemars::{JsonSchema, Schema};
use serde::{Deserialize, Serialize};
@@ -745,13 +750,7 @@ impl Thread {
let title = tool.initial_title(tool_use.input.clone(), cx);
let kind = tool.kind();
stream.send_tool_call(
&tool_use.id,
&tool_use.name,
title,
kind,
tool_use.input.clone(),
);
stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone());
let output = tool_result
.as_ref()
@@ -881,17 +880,101 @@ impl Thread {
project: Entity<Project>,
cx: &mut Context<Self>,
) -> Task<Arc<ProjectSnapshot>> {
let task = project::telemetry_snapshot::TelemetrySnapshot::new(&project, cx);
let git_store = project.read(cx).git_store().clone();
let worktree_snapshots: Vec<_> = project
.read(cx)
.visible_worktrees(cx)
.map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
.collect();
cx.spawn(async move |_, _| {
let snapshot = task.await;
let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
Arc::new(ProjectSnapshot {
worktree_snapshots: snapshot.worktree_snapshots,
worktree_snapshots,
timestamp: Utc::now(),
})
})
}
fn worktree_snapshot(
worktree: Entity<project::Worktree>,
git_store: Entity<GitStore>,
cx: &App,
) -> Task<WorktreeSnapshot> {
cx.spawn(async move |cx| {
// Get worktree path and snapshot
let worktree_info = cx.update(|app_cx| {
let worktree = worktree.read(app_cx);
let path = worktree.abs_path().to_string_lossy().into_owned();
let snapshot = worktree.snapshot();
(path, snapshot)
});
let Ok((worktree_path, _snapshot)) = worktree_info else {
return WorktreeSnapshot {
worktree_path: String::new(),
git_state: None,
};
};
let git_state = git_store
.update(cx, |git_store, cx| {
git_store
.repositories()
.values()
.find(|repo| {
repo.read(cx)
.abs_path_to_repo_path(&worktree.read(cx).abs_path())
.is_some()
})
.cloned()
})
.ok()
.flatten()
.map(|repo| {
repo.update(cx, |repo, _| {
let current_branch =
repo.branch.as_ref().map(|branch| branch.name().to_owned());
repo.send_job(None, |state, _| async move {
let RepositoryState::Local { backend, .. } = state else {
return GitState {
remote_url: None,
head_sha: None,
current_branch,
diff: None,
};
};
let remote_url = backend.remote_url("origin");
let head_sha = backend.head_sha().await;
let diff = backend.diff(DiffType::HeadToWorktree).await.ok();
GitState {
remote_url,
head_sha,
current_branch,
diff,
}
})
})
});
let git_state = match git_state {
Some(git_state) => match git_state.ok() {
Some(git_state) => git_state.await.ok(),
None => None,
},
None => None,
};
WorktreeSnapshot {
worktree_path,
git_state,
}
})
}
pub fn project_context(&self) -> &Entity<ProjectContext> {
&self.project_context
}
@@ -1050,18 +1133,14 @@ impl Thread {
Ok(())
}
pub fn latest_request_token_usage(&self) -> Option<language_model::TokenUsage> {
pub fn latest_token_usage(&self) -> Option<acp_thread::TokenUsage> {
let last_user_message = self.last_user_message()?;
let tokens = self.request_token_usage.get(&last_user_message.id)?;
Some(*tokens)
}
pub fn latest_token_usage(&self) -> Option<acp_thread::TokenUsage> {
let usage = self.latest_request_token_usage()?;
let model = self.model.clone()?;
Some(acp_thread::TokenUsage {
max_tokens: model.max_token_count_for_mode(self.completion_mode.into()),
used_tokens: usage.total_tokens(),
used_tokens: tokens.total_tokens(),
})
}
@@ -1104,14 +1183,6 @@ impl Thread {
self.run_turn(cx)
}
#[cfg(feature = "eval")]
pub fn proceed(
&mut self,
cx: &mut Context<Self>,
) -> Result<mpsc::UnboundedReceiver<Result<ThreadEvent>>> {
self.run_turn(cx)
}
fn run_turn(
&mut self,
cx: &mut Context<Self>,
@@ -1479,13 +1550,7 @@ impl Thread {
});
if push_new_tool_use {
event_stream.send_tool_call(
&tool_use.id,
&tool_use.name,
title,
kind,
tool_use.input.clone(),
);
event_stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone());
last_message
.content
.push(AgentMessageContent::ToolUse(tool_use.clone()));
@@ -1857,7 +1922,7 @@ impl Thread {
.tools
.iter()
.filter_map(|(tool_name, tool)| {
if tool.supports_provider(&model.provider_id())
if tool.supported_provider(&model.provider_id())
&& profile.is_tool_enabled(tool_name)
{
Some((truncate(tool_name), tool.clone()))
@@ -2133,7 +2198,7 @@ where
/// Some tools rely on a provider for the underlying billing or other reasons.
/// Allow the tool to check if they are compatible, or should be filtered out.
fn supports_provider(_provider: &LanguageModelProviderId) -> bool {
fn supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
true
}
@@ -2174,7 +2239,7 @@ pub trait AnyAgentTool {
fn kind(&self) -> acp::ToolKind;
fn initial_title(&self, input: serde_json::Value, _cx: &mut App) -> SharedString;
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value>;
fn supports_provider(&self, _provider: &LanguageModelProviderId) -> bool {
fn supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
true
}
fn run(
@@ -2219,8 +2284,8 @@ where
Ok(json)
}
fn supports_provider(&self, provider: &LanguageModelProviderId) -> bool {
T::supports_provider(provider)
fn supported_provider(&self, provider: &LanguageModelProviderId) -> bool {
self.0.supported_provider(provider)
}
fn run(
@@ -2280,7 +2345,6 @@ impl ThreadEventStream {
fn send_tool_call(
&self,
id: &LanguageModelToolUseId,
tool_name: &str,
title: SharedString,
kind: acp::ToolKind,
input: serde_json::Value,
@@ -2288,7 +2352,6 @@ impl ThreadEventStream {
self.0
.unbounded_send(Ok(ThreadEvent::ToolCall(Self::initial_tool_call(
id,
tool_name,
title.to_string(),
kind,
input,
@@ -2298,15 +2361,12 @@ impl ThreadEventStream {
fn initial_tool_call(
id: &LanguageModelToolUseId,
tool_name: &str,
title: String,
kind: acp::ToolKind,
input: serde_json::Value,
) -> acp::ToolCall {
acp::ToolCall {
meta: Some(serde_json::json!({
"tool_name": tool_name
})),
meta: None,
id: acp::ToolCallId(id.to_string().into()),
title,
kind,

View File

@@ -40,19 +40,13 @@ pub use web_search_tool::*;
macro_rules! tools {
($($tool:ty),* $(,)?) => {
/// A list of all built-in tool names
pub fn supported_built_in_tool_names(provider: Option<language_model::LanguageModelProviderId>) -> impl Iterator<Item = String> {
pub fn built_in_tool_names() -> impl Iterator<Item = String> {
[
$(
(if let Some(provider) = provider.as_ref() {
<$tool>::supports_provider(provider)
} else {
true
})
.then(|| <$tool>::name().to_string()),
<$tool>::name().to_string(),
)*
]
.into_iter()
.flatten()
}
/// A list of all built-in tools

View File

@@ -57,7 +57,7 @@ impl AgentTool for WebSearchTool {
}
/// We currently only support Zed Cloud as a provider.
fn supports_provider(provider: &LanguageModelProviderId) -> bool {
fn supported_provider(&self, provider: &LanguageModelProviderId) -> bool {
provider == &ZED_CLOUD_PROVIDER_ID
}

View File

@@ -9,7 +9,9 @@ use futures::io::BufReader;
use project::Project;
use project::agent_server_store::AgentServerCommand;
use serde::Deserialize;
use util::ResultExt as _;
use settings::{Settings as _, SettingsLocation};
use task::Shell;
use util::{ResultExt as _, get_default_system_shell_preferring_bash};
use std::path::PathBuf;
use std::{any::Any, cell::RefCell};
@@ -21,7 +23,7 @@ use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString, Task, WeakEntit
use acp_thread::{AcpThread, AuthRequired, LoadError, TerminalProviderEvent};
use terminal::TerminalBuilder;
use terminal::terminal_settings::{AlternateScroll, CursorShape};
use terminal::terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
#[derive(Debug, Error)]
#[error("Unsupported version")]
@@ -38,7 +40,7 @@ pub struct AcpConnection {
// NB: Don't move this into the wait_task, since we need to ensure the process is
// killed on drop (setting kill_on_drop on the command seems to not always work).
child: smol::process::Child,
_io_task: Task<Result<(), acp::Error>>,
_io_task: Task<Result<()>>,
_wait_task: Task<Result<()>>,
_stderr_task: Task<Result<()>>,
}
@@ -814,18 +816,62 @@ impl acp::Client for ClientDelegate {
let thread = self.session_thread(&args.session_id)?;
let project = thread.read_with(&self.cx, |thread, _cx| thread.project().clone())?;
let terminal_entity = acp_thread::create_terminal_entity(
args.command.clone(),
&args.args,
args.env
.into_iter()
.map(|env| (env.name, env.value))
.collect(),
args.cwd.clone(),
&project,
&mut self.cx.clone(),
)
.await?;
let mut env = if let Some(dir) = &args.cwd {
project
.update(&mut self.cx.clone(), |project, cx| {
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
project.directory_environment(&shell, dir.clone().into(), cx)
})?
.await
.unwrap_or_default()
} else {
Default::default()
};
// Disables paging for `git` and hopefully other commands
env.insert("PAGER".into(), "".into());
for var in args.env {
env.insert(var.name, var.value);
}
// Use remote shell or default system shell, as appropriate
let shell = project
.update(&mut self.cx.clone(), |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
.map(Shell::Program)
})?
.unwrap_or_else(|| Shell::Program(get_default_system_shell_preferring_bash()));
let is_windows = project
.read_with(&self.cx, |project, cx| project.path_style(cx).is_windows())
.unwrap_or(cfg!(windows));
let (task_command, task_args) = task::ShellBuilder::new(&shell, is_windows)
.redirect_stdin_to_dev_null()
.build(Some(args.command.clone()), &args.args);
let terminal_entity = project
.update(&mut self.cx.clone(), |project, cx| {
project.create_terminal_task(
task::SpawnInTerminal {
command: Some(task_command),
args: task_args,
cwd: args.cwd.clone(),
env,
..Default::default()
},
cx,
)
})?
.await?;
// Register with renderer
let terminal_entity = thread.update(&mut self.cx.clone(), |thread, cx| {

View File

@@ -73,7 +73,12 @@ pub trait AgentServer: Send {
root_dir: Option<&Path>,
delegate: AgentServerDelegate,
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>>;
) -> Task<
Result<(
Rc<dyn AgentConnection>,
HashMap<String, task::SpawnInTerminal>,
)>,
>;
fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
}
@@ -84,6 +89,30 @@ impl dyn AgentServer {
}
}
/// Extension trait for ACP-specific agent capabilities.
/// This trait is only implemented by agents that use the Agent Client Protocol (ACP).
pub trait AcpAgentServer: AgentServer {
/// Returns the list of slash commands that should trigger Zed's authentication UI
/// when running locally (e.g., "/login").
/// These commands will be intercepted by Zed to show the auth method selection UI.
fn local_login_commands(&self) -> Vec<String>;
/// Returns the list of slash commands that should trigger Zed's authentication UI
/// when running remotely (e.g., "/login").
/// These commands will be intercepted by Zed to show the auth method selection UI.
fn remote_login_commands(&self) -> Vec<String>;
/// Returns the list of logout-related slash commands that should be sent to the agent
/// when running locally to let it reset internal state (e.g., "/logout").
/// These commands will be added to available_commands and passed through to the agent.
fn local_logout_commands(&self) -> Vec<String>;
/// Returns the list of logout-related slash commands that should be sent to the agent
/// when running remotely to let it reset internal state (e.g., "/logout").
/// These commands will be added to available_commands and passed through to the agent.
fn remote_logout_commands(&self) -> Vec<String>;
}
/// Load the default proxy environment variables to pass through to the agent
pub fn load_proxy_env(cx: &mut App) -> HashMap<String, String> {
let proxy_url = cx

View File

@@ -7,10 +7,11 @@ use std::sync::Arc;
use std::{any::Any, path::PathBuf};
use anyhow::{Context as _, Result};
use collections::HashMap;
use gpui::{App, AppContext as _, SharedString, Task};
use project::agent_server_store::{AllAgentServersSettings, CLAUDE_CODE_NAME};
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
use acp_thread::AgentConnection;
#[derive(Clone)]
@@ -60,7 +61,12 @@ impl AgentServer for ClaudeCode {
root_dir: Option<&Path>,
delegate: AgentServerDelegate,
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
) -> Task<
Result<(
Rc<dyn AgentConnection>,
HashMap<String, task::SpawnInTerminal>,
)>,
> {
let name = self.name();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
@@ -69,7 +75,7 @@ impl AgentServer for ClaudeCode {
let default_mode = self.default_mode(cx);
cx.spawn(async move |cx| {
let (command, root_dir, login) = store
let (command, root_dir, auth_commands) = store
.update(cx, |store, cx| {
let agent = store
.get_external_agent(&CLAUDE_CODE_NAME.into())
@@ -92,7 +98,7 @@ impl AgentServer for ClaudeCode {
cx,
)
.await?;
Ok((connection, login))
Ok((connection, auth_commands))
})
}
@@ -100,3 +106,21 @@ impl AgentServer for ClaudeCode {
self
}
}
impl AcpAgentServer for ClaudeCode {
fn local_login_commands(&self) -> Vec<String> {
vec!["login".to_string()]
}
fn remote_login_commands(&self) -> Vec<String> {
vec!["login".to_string()]
}
fn local_logout_commands(&self) -> Vec<String> {
vec!["logout".to_string()]
}
fn remote_logout_commands(&self) -> Vec<String> {
vec!["logout".to_string()]
}
}

View File

@@ -5,12 +5,13 @@ use std::{any::Any, path::Path};
use acp_thread::AgentConnection;
use agent_client_protocol as acp;
use anyhow::{Context as _, Result};
use collections::HashMap;
use fs::Fs;
use gpui::{App, AppContext as _, SharedString, Task};
use project::agent_server_store::{AllAgentServersSettings, CODEX_NAME};
use settings::{SettingsStore, update_settings_file};
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
#[derive(Clone)]
pub struct Codex;
@@ -61,7 +62,12 @@ impl AgentServer for Codex {
root_dir: Option<&Path>,
delegate: AgentServerDelegate,
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
) -> Task<
Result<(
Rc<dyn AgentConnection>,
HashMap<String, task::SpawnInTerminal>,
)>,
> {
let name = self.name();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
@@ -70,7 +76,7 @@ impl AgentServer for Codex {
let default_mode = self.default_mode(cx);
cx.spawn(async move |cx| {
let (command, root_dir, login) = store
let (command, root_dir, auth_commands) = store
.update(cx, |store, cx| {
let agent = store
.get_external_agent(&CODEX_NAME.into())
@@ -96,7 +102,7 @@ impl AgentServer for Codex {
cx,
)
.await?;
Ok((connection, login))
Ok((connection, auth_commands))
})
}
@@ -104,3 +110,21 @@ impl AgentServer for Codex {
self
}
}
impl AcpAgentServer for Codex {
fn local_login_commands(&self) -> Vec<String> {
vec![]
}
fn remote_login_commands(&self) -> Vec<String> {
vec![]
}
fn local_logout_commands(&self) -> Vec<String> {
vec![]
}
fn remote_logout_commands(&self) -> Vec<String> {
vec![]
}
}

View File

@@ -1,12 +1,13 @@
use crate::{AgentServerDelegate, load_proxy_env};
use crate::{AcpAgentServer, AgentServerDelegate, load_proxy_env};
use acp_thread::AgentConnection;
use agent_client_protocol as acp;
use anyhow::{Context as _, Result};
use collections::HashMap;
use fs::Fs;
use gpui::{App, AppContext as _, SharedString, Task};
use project::agent_server_store::{AllAgentServersSettings, ExternalAgentServerName};
use settings::{SettingsStore, update_settings_file};
use std::{path::Path, rc::Rc, sync::Arc};
use std::{any::Any, path::Path, rc::Rc, sync::Arc};
use ui::IconName;
/// A generic agent server implementation for custom user-defined agents
@@ -65,7 +66,12 @@ impl crate::AgentServer for CustomAgentServer {
root_dir: Option<&Path>,
delegate: AgentServerDelegate,
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
) -> Task<
Result<(
Rc<dyn AgentConnection>,
HashMap<String, task::SpawnInTerminal>,
)>,
> {
let name = self.name();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
@@ -74,7 +80,7 @@ impl crate::AgentServer for CustomAgentServer {
let extra_env = load_proxy_env(cx);
cx.spawn(async move |cx| {
let (command, root_dir, login) = store
let (command, root_dir, auth_commands) = store
.update(cx, |store, cx| {
let agent = store
.get_external_agent(&ExternalAgentServerName(name.clone()))
@@ -99,11 +105,29 @@ impl crate::AgentServer for CustomAgentServer {
cx,
)
.await?;
Ok((connection, login))
Ok((connection, auth_commands))
})
}
fn into_any(self: Rc<Self>) -> Rc<dyn std::any::Any> {
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
}
impl AcpAgentServer for CustomAgentServer {
fn local_login_commands(&self) -> Vec<String> {
vec![]
}
fn remote_login_commands(&self) -> Vec<String> {
vec![]
}
fn local_logout_commands(&self) -> Vec<String> {
vec![]
}
fn remote_logout_commands(&self) -> Vec<String> {
vec![]
}
}

View File

@@ -1,9 +1,10 @@
use std::rc::Rc;
use std::{any::Any, path::Path};
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
use acp_thread::AgentConnection;
use anyhow::{Context as _, Result};
use collections::HashMap;
use gpui::{App, SharedString, Task};
use language_models::provider::google::GoogleLanguageModelProvider;
use project::agent_server_store::GEMINI_NAME;
@@ -29,7 +30,12 @@ impl AgentServer for Gemini {
root_dir: Option<&Path>,
delegate: AgentServerDelegate,
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
) -> Task<
Result<(
Rc<dyn AgentConnection>,
HashMap<String, task::SpawnInTerminal>,
)>,
> {
let name = self.name();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
@@ -47,7 +53,7 @@ impl AgentServer for Gemini {
{
extra_env.insert("GEMINI_API_KEY".into(), api_key);
}
let (command, root_dir, login) = store
let (command, root_dir, auth_commands) = store
.update(cx, |store, cx| {
let agent = store
.get_external_agent(&GEMINI_NAME.into())
@@ -71,7 +77,7 @@ impl AgentServer for Gemini {
cx,
)
.await?;
Ok((connection, login))
Ok((connection, auth_commands))
})
}
@@ -80,6 +86,26 @@ impl AgentServer for Gemini {
}
}
impl AcpAgentServer for Gemini {
fn local_login_commands(&self) -> Vec<String> {
vec!["login".to_string()]
}
fn remote_login_commands(&self) -> Vec<String> {
// When remote, OAuth doesn't work, so login is handled via the
// auth_commands mapping (oauth-personal -> spawn-gemini-cli)
vec![]
}
fn local_logout_commands(&self) -> Vec<String> {
vec![]
}
fn remote_logout_commands(&self) -> Vec<String> {
vec![]
}
}
#[cfg(test)]
pub(crate) mod tests {
use project::agent_server_store::AgentServerCommand;

View File

@@ -25,7 +25,7 @@ agent_settings.workspace = true
ai_onboarding.workspace = true
anyhow.workspace = true
arrayvec.workspace = true
assistant_text_thread.workspace = true
assistant_context.workspace = true
assistant_slash_command.workspace = true
assistant_slash_commands.workspace = true
audio.workspace = true
@@ -102,7 +102,7 @@ zed_actions.workspace = true
[dev-dependencies]
acp_thread = { workspace = true, features = ["test-support"] }
agent = { workspace = true, features = ["test-support"] }
assistant_text_thread = { workspace = true, features = ["test-support"] }
assistant_context = { workspace = true, features = ["test-support"] }
buffer_diff = { workspace = true, features = ["test-support"] }
db = { workspace = true, features = ["test-support"] }
editor = { workspace = true, features = ["test-support"] }

View File

@@ -402,7 +402,7 @@ mod tests {
use agent::HistoryStore;
use agent_client_protocol as acp;
use agent_settings::AgentSettings;
use assistant_text_thread::TextThreadStore;
use assistant_context::ContextStore;
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
use editor::{EditorSettings, RowInfo};
use fs::FakeFs;
@@ -466,8 +466,8 @@ mod tests {
connection.send_update(session_id, acp::SessionUpdate::ToolCall(tool_call), cx)
});
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let view_state = cx.new(|_cx| {
EntryViewState::new(

View File

@@ -11,10 +11,10 @@ use assistant_slash_commands::codeblock_fence_for_path;
use collections::{HashMap, HashSet};
use editor::{
Addon, Anchor, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement,
EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, Inlay,
EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, InlayId,
MultiBuffer, ToOffset,
actions::Paste,
display_map::{Crease, CreaseId, FoldId},
display_map::{Crease, CreaseId, FoldId, Inlay},
};
use futures::{
FutureExt as _,
@@ -29,8 +29,7 @@ use language::{Buffer, Language, language_settings::InlayHintKind};
use language_model::LanguageModelImage;
use postage::stream::Stream as _;
use project::{
CompletionIntent, InlayHint, InlayHintLabel, InlayId, Project, ProjectItem, ProjectPath,
Worktree,
CompletionIntent, InlayHint, InlayHintLabel, Project, ProjectItem, ProjectPath, Worktree,
};
use prompt_store::{PromptId, PromptStore};
use rope::Point;
@@ -76,7 +75,7 @@ pub enum MessageEditorEvent {
impl EventEmitter<MessageEditorEvent> for MessageEditor {}
const COMMAND_HINT_INLAY_ID: InlayId = InlayId::Hint(0);
const COMMAND_HINT_INLAY_ID: u32 = 0;
impl MessageEditor {
pub fn new(
@@ -152,7 +151,7 @@ impl MessageEditor {
let has_new_hint = !new_hints.is_empty();
editor.splice_inlays(
if has_hint {
&[COMMAND_HINT_INLAY_ID]
&[InlayId::Hint(COMMAND_HINT_INLAY_ID)]
} else {
&[]
},
@@ -629,12 +628,12 @@ impl MessageEditor {
path: PathBuf,
cx: &mut Context<Self>,
) -> Task<Result<Mention>> {
let text_thread_task = self.history_store.update(cx, |store, cx| {
let context = self.history_store.update(cx, |store, cx| {
store.load_text_thread(path.as_path().into(), cx)
});
cx.spawn(async move |_, cx| {
let text_thread = text_thread_task.await?;
let xml = text_thread.update(cx, |text_thread, cx| text_thread.to_xml(cx))?;
let context = context.await?;
let xml = context.update(cx, |context, cx| context.to_xml(cx))?;
Ok(Mention::Text {
content: xml,
tracked_buffers: Vec::new(),
@@ -1591,7 +1590,7 @@ mod tests {
use acp_thread::MentionUri;
use agent::{HistoryStore, outline};
use agent_client_protocol as acp;
use assistant_text_thread::TextThreadStore;
use assistant_context::ContextStore;
use editor::{AnchorRangeExt as _, Editor, EditorMode};
use fs::FakeFs;
use futures::StreamExt as _;
@@ -1622,8 +1621,8 @@ mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
@@ -1727,8 +1726,8 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
// Start with no available commands - simulating Claude which doesn't support slash commands
let available_commands = Rc::new(RefCell::new(vec![]));
@@ -1891,8 +1890,8 @@ mod tests {
let mut cx = VisualTestContext::from_window(*window, cx);
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
let available_commands = Rc::new(RefCell::new(vec![
acp::AvailableCommand {
@@ -2131,8 +2130,8 @@ mod tests {
opened_editors.push(buffer);
}
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| {
@@ -2658,8 +2657,8 @@ mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {

View File

@@ -194,7 +194,7 @@ impl Render for ModeSelector {
trigger_button,
Tooltip::element({
let focus_handle = self.focus_handle.clone();
move |_window, cx| {
move |window, cx| {
v_flex()
.gap_1()
.child(
@@ -205,9 +205,10 @@ impl Render for ModeSelector {
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.child(Label::new("Cycle Through Modes"))
.child(KeyBinding::for_action_in(
.children(KeyBinding::for_action_in(
&CycleModeSelector,
&focus_handle,
window,
cx,
)),
)
@@ -216,9 +217,10 @@ impl Render for ModeSelector {
.gap_2()
.justify_between()
.child(Label::new("Toggle Mode Menu"))
.child(KeyBinding::for_action_in(
.children(KeyBinding::for_action_in(
&ToggleProfileSelector,
&focus_handle,
window,
cx,
)),
)

View File

@@ -77,8 +77,14 @@ impl Render for AcpModelSelectorPopover {
.ml_0p5(),
)
.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
move |_window, cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
gpui::Corner::BottomRight,
cx,

View File

@@ -324,8 +324,8 @@ impl AcpThreadHistory {
HistoryEntry::AcpThread(thread) => self
.history_store
.update(cx, |this, cx| this.delete_thread(thread.id.clone(), cx)),
HistoryEntry::TextThread(text_thread) => self.history_store.update(cx, |this, cx| {
this.delete_text_thread(text_thread.path.clone(), cx)
HistoryEntry::TextThread(context) => self.history_store.update(cx, |this, cx| {
this.delete_text_thread(context.path.clone(), cx)
}),
};
task.detach_and_log_err(cx);
@@ -423,8 +423,8 @@ impl AcpThreadHistory {
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.tooltip(move |_window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
.tooltip(move |window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
})
.on_click(
cx.listener(move |this, _, _, cx| this.remove_thread(ix, cx)),
@@ -595,8 +595,8 @@ impl RenderOnce for AcpHistoryEntryElement {
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.tooltip(move |_window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
.tooltip(move |window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
})
.on_click({
let thread_view = self.thread_view.clone();
@@ -635,12 +635,12 @@ impl RenderOnce for AcpHistoryEntryElement {
});
}
}
HistoryEntry::TextThread(text_thread) => {
HistoryEntry::TextThread(context) => {
if let Some(panel) = workspace.read(cx).panel::<AgentPanel>(cx) {
panel.update(cx, |panel, cx| {
panel
.open_saved_text_thread(
text_thread.path.clone(),
context.path.clone(),
window,
cx,
)

View File

@@ -260,10 +260,11 @@ impl ThreadFeedbackState {
pub struct AcpThreadView {
agent: Rc<dyn AgentServer>,
acp_agent: Option<Rc<dyn agent_servers::AcpAgentServer>>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
thread_state: ThreadState,
login: Option<task::SpawnInTerminal>,
auth_commands: HashMap<String, task::SpawnInTerminal>,
history_store: Entity<HistoryStore>,
hovered_recent_history_item: Option<usize>,
entry_view_state: Entity<EntryViewState>,
@@ -403,8 +404,38 @@ impl AcpThreadView {
let show_codex_windows_warning = crate::ExternalAgent::parse_built_in(agent.as_ref())
== Some(crate::ExternalAgent::Codex);
// Try to downcast to AcpAgentServer for ACP-specific functionality
let acp_agent = agent
.clone()
.into_any()
.downcast::<agent_servers::Gemini>()
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
.or_else(|_| {
agent
.clone()
.into_any()
.downcast::<agent_servers::ClaudeCode>()
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
})
.or_else(|_| {
agent
.clone()
.into_any()
.downcast::<agent_servers::Codex>()
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
})
.or_else(|_| {
agent
.clone()
.into_any()
.downcast::<agent_servers::CustomAgentServer>()
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
})
.ok();
Self {
agent: agent.clone(),
acp_agent,
workspace: workspace.clone(),
project: project.clone(),
entry_view_state,
@@ -416,7 +447,7 @@ impl AcpThreadView {
window,
cx,
),
login: None,
auth_commands: HashMap::default(),
message_editor,
model_selector: None,
profile_selector: None,
@@ -509,8 +540,9 @@ impl AcpThreadView {
let connect_task = agent.connect(root_dir.as_deref(), delegate, cx);
let load_task = cx.spawn_in(window, async move |this, cx| {
let connection = match connect_task.await {
Ok((connection, login)) => {
this.update(cx, |this, _| this.login = login).ok();
Ok((connection, auth_commands)) => {
this.update(cx, |this, _| this.auth_commands = auth_commands)
.ok();
connection
}
Err(err) => {
@@ -1051,20 +1083,52 @@ impl AcpThreadView {
let text = self.message_editor.read(cx).text(cx);
let text = text.trim();
if text == "/login" || text == "/logout" {
// Check if this is a login or logout command (only for ACP agents)
let command_name = text.strip_prefix('/');
let is_remote = self.project.read(cx).is_via_remote_server();
let (login_commands, logout_commands) = if let Some(acp_agent) = &self.acp_agent {
let login = if is_remote {
acp_agent.remote_login_commands()
} else {
acp_agent.local_login_commands()
};
let logout = if is_remote {
acp_agent.remote_logout_commands()
} else {
acp_agent.local_logout_commands()
};
(login, logout)
} else {
(vec![], vec![])
};
let is_login_command = if let Some(cmd) = command_name {
login_commands.iter().any(|c| c == cmd)
} else {
false
};
let is_logout_command = if let Some(cmd) = command_name {
logout_commands.iter().any(|c| c == cmd)
} else {
false
};
if is_login_command || is_logout_command {
let ThreadState::Ready { thread, .. } = &self.thread_state else {
return;
};
let connection = thread.read(cx).connection().clone();
let can_login = !connection.auth_methods().is_empty() || self.login.is_some();
let can_login = !connection.auth_methods().is_empty() || !self.auth_commands.is_empty();
// Does the agent have a specific logout command? Prefer that in case they need to reset internal state.
let logout_supported = text == "/logout"
let logout_supported = is_logout_command
&& self
.available_commands
.borrow()
.iter()
.any(|command| command.name == "logout");
.any(|command| command_name == Some(command.name.as_str()));
if can_login && !logout_supported {
self.message_editor
.update(cx, |editor, cx| editor.clear(window, cx));
@@ -1259,7 +1323,6 @@ impl AcpThreadView {
.await?;
this.update_in(cx, |this, window, cx| {
this.send_impl(message_editor, window, cx);
this.focus_handle(cx).focus(window);
})?;
anyhow::Ok(())
})
@@ -1422,25 +1485,39 @@ impl AcpThreadView {
AcpThreadEvent::AvailableCommandsUpdated(available_commands) => {
let mut available_commands = available_commands.clone();
if thread
.read(cx)
.connection()
.auth_methods()
.iter()
.any(|method| method.id.0.as_ref() == "claude-login")
{
available_commands.push(acp::AvailableCommand {
name: "login".to_owned(),
description: "Authenticate".to_owned(),
input: None,
meta: None,
});
available_commands.push(acp::AvailableCommand {
name: "logout".to_owned(),
description: "Authenticate".to_owned(),
input: None,
meta: None,
});
// Add auth commands only for ACP agents
if let Some(acp_agent) = &self.acp_agent {
let is_remote = self.project.read(cx).is_via_remote_server();
let login_commands = if is_remote {
acp_agent.remote_login_commands()
} else {
acp_agent.local_login_commands()
};
let logout_commands = if is_remote {
acp_agent.remote_logout_commands()
} else {
acp_agent.local_logout_commands()
};
// Add login commands from the agent
for command_name in login_commands {
available_commands.push(acp::AvailableCommand {
name: command_name,
description: "Authenticate".to_owned(),
input: None,
meta: None,
});
}
// Add logout commands from the agent
for command_name in logout_commands {
available_commands.push(acp::AvailableCommand {
name: command_name,
description: "Authenticate".to_owned(),
input: None,
meta: None,
});
}
}
self.available_commands.replace(available_commands);
@@ -1562,10 +1639,7 @@ impl AcpThreadView {
self.thread_error.take();
configuration_view.take();
pending_auth_method.replace(method.clone());
let authenticate = if (method.0.as_ref() == "claude-login"
|| method.0.as_ref() == "spawn-gemini-cli")
&& let Some(login) = self.login.clone()
{
let authenticate = if let Some(login) = self.auth_commands.get(method.0.as_ref()).cloned() {
if let Some(workspace) = self.workspace.upgrade() {
Self::spawn_external_agent_login(login, workspace, false, window, cx)
} else {
@@ -2158,6 +2232,7 @@ impl AcpThreadView {
options,
entry_ix,
tool_call.id.clone(),
window,
cx,
))
.into_any(),
@@ -2558,6 +2633,7 @@ impl AcpThreadView {
options: &[acp::PermissionOption],
entry_ix: usize,
tool_call_id: acp::ToolCallId,
window: &Window,
cx: &Context<Self>,
) -> Div {
let is_first = self.thread().is_some_and(|thread| {
@@ -2614,7 +2690,7 @@ impl AcpThreadView {
seen_kinds.push(option.kind);
this.key_binding(
KeyBinding::for_action_in(action, &self.focus_handle, cx)
KeyBinding::for_action_in(action, &self.focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(10.))),
)
})
@@ -2795,11 +2871,12 @@ impl AcpThreadView {
.icon_size(IconSize::Small)
.icon_color(Color::Error)
.label_size(LabelSize::Small)
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta(
"Stop This Command",
None,
"Also possible by placing your cursor inside the terminal and using regular terminal bindings.",
window,
cx,
)
})
@@ -3100,7 +3177,7 @@ impl AcpThreadView {
)
}
fn render_recent_history(&self, cx: &mut Context<Self>) -> AnyElement {
fn render_recent_history(&self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
let render_history = self
.agent
.clone()
@@ -3129,6 +3206,7 @@ impl AcpThreadView {
KeyBinding::for_action_in(
&OpenHistory,
&self.focus_handle(cx),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -3267,48 +3345,34 @@ impl AcpThreadView {
})
.children(connection.auth_methods().iter().enumerate().rev().map(
|(ix, method)| {
let (method_id, name) = if self
.project
.read(cx)
.is_via_remote_server()
&& method.id.0.as_ref() == "oauth-personal"
&& method.name == "Log in with Google"
{
("spawn-gemini-cli".into(), "Log in with Gemini CLI".into())
} else {
(method.id.0.clone(), method.name.clone())
};
let method_id = method.id.clone();
let method_id_str = method.id.0.to_string();
Button::new(
SharedString::from(method.id.0.clone()),
method.name.clone(),
)
.label_size(LabelSize::Small)
.map(|this| {
if ix == 0 {
this.style(ButtonStyle::Tinted(TintColor::Warning))
} else {
this.style(ButtonStyle::Outlined)
}
})
.when_some(method.description.clone(), |this, description| {
this.tooltip(Tooltip::text(description))
})
.on_click({
cx.listener(move |this, _, window, cx| {
telemetry::event!(
"Authenticate Agent Started",
agent = this.agent.telemetry_id(),
method = method_id_str
);
Button::new(SharedString::from(method_id.clone()), name)
.label_size(LabelSize::Small)
.map(|this| {
if ix == 0 {
this.style(ButtonStyle::Tinted(TintColor::Warning))
} else {
this.style(ButtonStyle::Outlined)
}
})
.when_some(
method.description.clone(),
|this, description| {
this.tooltip(Tooltip::text(description))
},
)
.on_click({
cx.listener(move |this, _, window, cx| {
telemetry::event!(
"Authenticate Agent Started",
agent = this.agent.telemetry_id(),
method = method_id
);
this.authenticate(
acp::AuthMethodId(method_id.clone()),
window,
cx,
)
})
this.authenticate(method_id.clone(), window, cx)
})
})
},
)),
)
@@ -3456,6 +3520,7 @@ impl AcpThreadView {
&changed_buffers,
self.edits_expanded,
pending_edits,
window,
cx,
))
.when(self.edits_expanded, |parent| {
@@ -3615,6 +3680,7 @@ impl AcpThreadView {
changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
expanded: bool,
pending_edits: bool,
window: &mut Window,
cx: &Context<Self>,
) -> Div {
const EDIT_NOT_READY_TOOLTIP_LABEL: &str = "Wait until file edits are complete.";
@@ -3690,11 +3756,12 @@ impl AcpThreadView {
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Review Changes",
&OpenAgentDiff,
&focus_handle,
window,
cx,
)
}
@@ -3712,8 +3779,13 @@ impl AcpThreadView {
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
})
.key_binding(
KeyBinding::for_action_in(&RejectAll, &focus_handle.clone(), cx)
.map(|kb| kb.size(rems_from_px(10.))),
KeyBinding::for_action_in(
&RejectAll,
&focus_handle.clone(),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(move |this, _, window, cx| {
this.reject_all(&RejectAll, window, cx);
@@ -3727,7 +3799,7 @@ impl AcpThreadView {
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
})
.key_binding(
KeyBinding::for_action_in(&KeepAll, &focus_handle, cx)
KeyBinding::for_action_in(&KeepAll, &focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(move |this, _, window, cx| {
@@ -3957,11 +4029,12 @@ impl AcpThreadView {
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip({
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
expand_tooltip,
&ExpandMessageEditor,
&focus_handle,
window,
cx,
)
}
@@ -4186,8 +4259,8 @@ impl AcpThreadView {
IconButton::new("stop-generation", IconName::Stop)
.icon_color(Color::Error)
.style(ButtonStyle::Tinted(ui::TintColor::Error))
.tooltip(move |_window, cx| {
Tooltip::for_action("Stop Generation", &editor::actions::Cancel, cx)
.tooltip(move |window, cx| {
Tooltip::for_action("Stop Generation", &editor::actions::Cancel, window, cx)
})
.on_click(cx.listener(|this, _event, _, cx| this.cancel_generation(cx)))
.into_any_element()
@@ -4209,7 +4282,7 @@ impl AcpThreadView {
this.icon_color(Color::Accent)
}
})
.tooltip(move |_window, cx| Tooltip::for_action(send_btn_tooltip, &Chat, cx))
.tooltip(move |window, cx| Tooltip::for_action(send_btn_tooltip, &Chat, window, cx))
.on_click(cx.listener(|this, _, window, cx| {
this.send(window, cx);
}))
@@ -4270,14 +4343,15 @@ impl AcpThreadView {
.icon_color(Color::Muted)
.toggle_state(following)
.selected_icon_color(Some(Color::Custom(cx.theme().players().agent().cursor)))
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
if following {
Tooltip::for_action(tooltip_label.clone(), &Follow, cx)
Tooltip::for_action(tooltip_label.clone(), &Follow, window, cx)
} else {
Tooltip::with_meta(
tooltip_label.clone(),
Some(&Follow),
"Track the agent's location as it reads and edits files.",
window,
cx,
)
}
@@ -5066,7 +5140,7 @@ impl AcpThreadView {
}
}
fn render_thread_error(&self, cx: &mut Context<Self>) -> Option<Div> {
fn render_thread_error(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
let content = match self.thread_error.as_ref()? {
ThreadError::Other(error) => self.render_any_thread_error(error.clone(), cx),
ThreadError::Refusal => self.render_refusal_error(cx),
@@ -5077,7 +5151,9 @@ impl AcpThreadView {
ThreadError::ModelRequestLimitReached(plan) => {
self.render_model_request_limit_reached_error(*plan, cx)
}
ThreadError::ToolUseLimitReached => self.render_tool_use_limit_reached_error(cx)?,
ThreadError::ToolUseLimitReached => {
self.render_tool_use_limit_reached_error(window, cx)?
}
};
Some(div().child(content))
@@ -5268,7 +5344,11 @@ impl AcpThreadView {
.dismiss_action(self.dismiss_error_button(cx))
}
fn render_tool_use_limit_reached_error(&self, cx: &mut Context<Self>) -> Option<Callout> {
fn render_tool_use_limit_reached_error(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Callout> {
let thread = self.as_native_thread(cx)?;
let supports_burn_mode = thread
.read(cx)
@@ -5295,6 +5375,7 @@ impl AcpThreadView {
KeyBinding::for_action_in(
&ContinueWithBurnMode,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
@@ -5318,8 +5399,13 @@ impl AcpThreadView {
.layer(ElevationIndex::ModalSurface)
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(&ContinueThread, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(10.))),
KeyBinding::for_action_in(
&ContinueThread,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(|this, _, _window, cx| {
this.resume_chat(cx);
@@ -5414,11 +5500,9 @@ impl AcpThreadView {
HistoryEntry::AcpThread(thread) => self.history_store.update(cx, |history, cx| {
history.delete_thread(thread.id.clone(), cx)
}),
HistoryEntry::TextThread(text_thread) => {
self.history_store.update(cx, |history, cx| {
history.delete_text_thread(text_thread.path.clone(), cx)
})
}
HistoryEntry::TextThread(context) => self.history_store.update(cx, |history, cx| {
history.delete_text_thread(context.path.clone(), cx)
}),
};
task.detach_and_log_err(cx);
}
@@ -5497,7 +5581,7 @@ impl Render for AcpThreadView {
.into_any(),
ThreadState::Loading { .. } => v_flex()
.flex_1()
.child(self.render_recent_history(cx))
.child(self.render_recent_history(window, cx))
.into_any(),
ThreadState::LoadError(e) => v_flex()
.flex_1()
@@ -5528,7 +5612,8 @@ impl Render for AcpThreadView {
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
.into_any()
} else {
this.child(self.render_recent_history(cx)).into_any()
this.child(self.render_recent_history(window, cx))
.into_any()
}
}),
})
@@ -5552,7 +5637,7 @@ impl Render for AcpThreadView {
Vec::<Empty>::new()
}
})
.children(self.render_thread_error(cx))
.children(self.render_thread_error(window, cx))
.when_some(
self.new_server_version_available.as_ref().filter(|_| {
!has_messages || !matches!(self.thread_state, ThreadState::Ready { .. })
@@ -5737,7 +5822,7 @@ fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
pub(crate) mod tests {
use acp_thread::StubAgentConnection;
use agent_client_protocol::SessionId;
use assistant_text_thread::TextThreadStore;
use assistant_context::ContextStore;
use editor::EditorSettings;
use fs::FakeFs;
use gpui::{EventEmitter, SemanticVersion, TestAppContext, VisualTestContext};
@@ -5900,10 +5985,10 @@ pub(crate) mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let text_thread_store =
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
let context_store =
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
let history_store =
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(text_thread_store, cx)));
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx)));
let thread_view = cx.update(|window, cx| {
cx.new(|cx| {
@@ -6009,8 +6094,13 @@ pub(crate) mod tests {
_root_dir: Option<&Path>,
_delegate: AgentServerDelegate,
_cx: &mut App,
) -> Task<gpui::Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
Task::ready(Ok((Rc::new(self.connection.clone()), None)))
) -> Task<
gpui::Result<(
Rc<dyn AgentConnection>,
HashMap<String, task::SpawnInTerminal>,
)>,
> {
Task::ready(Ok((Rc::new(self.connection.clone()), HashMap::default())))
}
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
@@ -6172,10 +6262,10 @@ pub(crate) mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let text_thread_store =
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
let context_store =
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
let history_store =
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(text_thread_store, cx)));
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx)));
let connection = Rc::new(StubAgentConnection::new());
let thread_view = cx.update(|window, cx| {

View File

@@ -10,7 +10,7 @@ use settings::{OpenAiCompatibleSettingsContent, update_settings_file};
use ui::{
Banner, Checkbox, KeyBinding, Modal, ModalFooter, ModalHeader, Section, ToggleState, prelude::*,
};
use ui_input::InputField;
use ui_input::SingleLineInput;
use workspace::{ModalView, Workspace};
#[derive(Clone, Copy)]
@@ -33,9 +33,9 @@ impl LlmCompatibleProvider {
}
struct AddLlmProviderInput {
provider_name: Entity<InputField>,
api_url: Entity<InputField>,
api_key: Entity<InputField>,
provider_name: Entity<SingleLineInput>,
api_url: Entity<SingleLineInput>,
api_key: Entity<SingleLineInput>,
models: Vec<ModelInput>,
}
@@ -76,10 +76,10 @@ struct ModelCapabilityToggles {
}
struct ModelInput {
name: Entity<InputField>,
max_completion_tokens: Entity<InputField>,
max_output_tokens: Entity<InputField>,
max_tokens: Entity<InputField>,
name: Entity<SingleLineInput>,
max_completion_tokens: Entity<SingleLineInput>,
max_output_tokens: Entity<SingleLineInput>,
max_tokens: Entity<SingleLineInput>,
capabilities: ModelCapabilityToggles,
}
@@ -171,9 +171,9 @@ fn single_line_input(
text: Option<&str>,
window: &mut Window,
cx: &mut App,
) -> Entity<InputField> {
) -> Entity<SingleLineInput> {
cx.new(|cx| {
let input = InputField::new(window, cx, placeholder).label(label);
let input = SingleLineInput::new(window, cx, placeholder).label(label);
if let Some(text) = text {
input
.editor()
@@ -431,7 +431,7 @@ impl Focusable for AddLlmProviderModal {
impl ModalView for AddLlmProviderModal {}
impl Render for AddLlmProviderModal {
fn render(&mut self, _window: &mut ui::Window, cx: &mut ui::Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut ui::Window, cx: &mut ui::Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
div()
@@ -484,6 +484,7 @@ impl Render for AddLlmProviderModal {
KeyBinding::for_action_in(
&menu::Cancel,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -498,6 +499,7 @@ impl Render for AddLlmProviderModal {
KeyBinding::for_action_in(
&menu::Confirm,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -755,7 +757,12 @@ mod tests {
models: Vec<(&str, &str, &str, &str)>,
cx: &mut VisualTestContext,
) -> Option<SharedString> {
fn set_text(input: &Entity<InputField>, text: &str, window: &mut Window, cx: &mut App) {
fn set_text(
input: &Entity<SingleLineInput>,
text: &str,
window: &mut Window,
cx: &mut App,
) {
input.update(cx, |input, cx| {
input.editor().update(cx, |editor, cx| {
editor.set_text(text, window, cx);

View File

@@ -566,7 +566,7 @@ impl ConfigureContextServerModal {
.into_any_element()
}
fn render_modal_footer(&self, cx: &mut Context<Self>) -> ModalFooter {
fn render_modal_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> ModalFooter {
let focus_handle = self.focus_handle(cx);
let is_connecting = matches!(self.state, State::Waiting);
@@ -584,11 +584,12 @@ impl ConfigureContextServerModal {
.icon_size(IconSize::Small)
.tooltip({
let repository_url = repository_url.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::with_meta(
"Open Repository",
None,
repository_url.clone(),
window,
cx,
)
}
@@ -615,7 +616,7 @@ impl ConfigureContextServerModal {
},
)
.key_binding(
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, cx)
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(
@@ -633,7 +634,7 @@ impl ConfigureContextServerModal {
)
.disabled(is_connecting)
.key_binding(
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(
@@ -708,7 +709,7 @@ impl Render for ConfigureContextServerModal {
State::Error(error) => Self::render_modal_error(error.clone()),
}),
)
.footer(self.render_modal_footer(cx)),
.footer(self.render_modal_footer(window, cx)),
)
}
}

View File

@@ -7,7 +7,6 @@ use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profil
use editor::Editor;
use fs::Fs;
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, prelude::*};
use language_model::LanguageModel;
use settings::Settings as _;
use ui::{
KeyBinding, ListItem, ListItemSpacing, ListSeparator, Navigable, NavigableEntry, prelude::*,
@@ -97,7 +96,6 @@ pub struct NewProfileMode {
pub struct ManageProfilesModal {
fs: Arc<dyn Fs>,
context_server_registry: Entity<ContextServerRegistry>,
active_model: Option<Arc<dyn LanguageModel>>,
focus_handle: FocusHandle,
mode: Mode,
}
@@ -111,14 +109,9 @@ impl ManageProfilesModal {
workspace.register_action(|workspace, action: &ManageProfiles, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
let fs = workspace.app_state().fs.clone();
let active_model = panel
.read(cx)
.active_native_agent_thread(cx)
.and_then(|thread| thread.read(cx).model().cloned());
let context_server_registry = panel.read(cx).context_server_registry().clone();
workspace.toggle_modal(window, cx, |window, cx| {
let mut this = Self::new(fs, active_model, context_server_registry, window, cx);
let mut this = Self::new(fs, context_server_registry, window, cx);
if let Some(profile_id) = action.customize_tools.clone() {
this.configure_builtin_tools(profile_id, window, cx);
@@ -132,7 +125,6 @@ impl ManageProfilesModal {
pub fn new(
fs: Arc<dyn Fs>,
active_model: Option<Arc<dyn LanguageModel>>,
context_server_registry: Entity<ContextServerRegistry>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -141,7 +133,6 @@ impl ManageProfilesModal {
Self {
fs,
active_model,
context_server_registry,
focus_handle,
mode: Mode::choose_profile(window, cx),
@@ -237,11 +228,9 @@ impl ManageProfilesModal {
let tool_picker = cx.new(|cx| {
let delegate = ToolPickerDelegate::builtin_tools(
//todo: This causes the web search tool to show up even it only works when using zed hosted models
agent::supported_built_in_tool_names(
self.active_model.as_ref().map(|model| model.provider_id()),
)
.map(|s| s.into())
.collect::<Vec<_>>(),
agent::built_in_tool_names()
.map(|s| s.into())
.collect::<Vec<_>>(),
self.fs.clone(),
profile_id.clone(),
profile,
@@ -352,9 +341,10 @@ impl ManageProfilesModal {
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(KeyBinding::for_action_in(
.children(KeyBinding::for_action_in(
&menu::Confirm,
&self.focus_handle,
window,
cx,
)),
)
@@ -648,13 +638,14 @@ impl ManageProfilesModal {
)
.child(Label::new("Go Back"))
.end_slot(
div().child(
div().children(
KeyBinding::for_action_in(
&menu::Cancel,
&self.focus_handle,
window,
cx,
)
.size(rems_from_px(12.)),
.map(|kb| kb.size(rems_from_px(12.))),
),
)
.on_click({
@@ -698,9 +689,14 @@ impl Render for ManageProfilesModal {
)
.child(Label::new("Go Back"))
.end_slot(
div().child(
KeyBinding::for_action_in(&menu::Cancel, &self.focus_handle, cx)
.size(rems_from_px(12.)),
div().children(
KeyBinding::for_action_in(
&menu::Cancel,
&self.focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
),
)
.on_click({

View File

@@ -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,6 +702,7 @@ impl Render for AgentDiffPane {
.key_binding(KeyBinding::for_action_in(
&ToggleFocus,
&focus_handle.clone(),
window,
cx,
))
.on_click(|_event, window, cx| {
@@ -718,7 +719,14 @@ fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControl
let thread = thread.clone();
Arc::new(
move |row, status, hunk_range, is_created_file, line_height, editor, _, cx| {
move |row,
status: &DiffHunkStatus,
hunk_range,
is_created_file,
line_height,
editor: &Entity<Editor>,
window: &mut Window,
cx: &mut App| {
{
render_diff_hunk_controls(
row,
@@ -728,6 +736,7 @@ fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControl
line_height,
&thread,
editor,
window,
cx,
)
}
@@ -743,6 +752,7 @@ fn render_diff_hunk_controls(
line_height: Pixels,
thread: &AgentDiffThread,
editor: &Entity<Editor>,
window: &mut Window,
cx: &mut App,
) -> AnyElement {
let editor = editor.clone();
@@ -765,8 +775,13 @@ fn render_diff_hunk_controls(
Button::new(("reject", row as u64), "Reject")
.disabled(is_created_file)
.key_binding(
KeyBinding::for_action_in(&Reject, &editor.read(cx).focus_handle(cx), cx)
.map(|kb| kb.size(rems_from_px(12.))),
KeyBinding::for_action_in(
&Reject,
&editor.read(cx).focus_handle(cx),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click({
let editor = editor.clone();
@@ -787,7 +802,7 @@ fn render_diff_hunk_controls(
}),
Button::new(("keep", row as u64), "Keep")
.key_binding(
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), cx)
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click({
@@ -818,8 +833,14 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |_window, cx| {
Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in(
"Next Hunk",
&GoToHunk,
&focus_handle,
window,
cx,
)
}
})
.on_click({
@@ -848,11 +869,12 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Previous Hunk",
&GoToPreviousHunk,
&focus_handle,
window,
cx,
)
}
@@ -1017,7 +1039,7 @@ impl ToolbarItemView for AgentDiffToolbar {
}
impl Render for AgentDiffToolbar {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let spinner_icon = div()
.px_0p5()
.id("generating")
@@ -1092,6 +1114,7 @@ impl Render for AgentDiffToolbar {
KeyBinding::for_action_in(
&RejectAll,
&editor_focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
@@ -1106,6 +1129,7 @@ impl Render for AgentDiffToolbar {
KeyBinding::for_action_in(
&KeepAll,
&editor_focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
@@ -1182,8 +1206,13 @@ impl Render for AgentDiffToolbar {
.child(
Button::new("reject-all", "Reject All")
.key_binding({
KeyBinding::for_action_in(&RejectAll, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.)))
KeyBinding::for_action_in(
&RejectAll,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
})
.on_click(cx.listener(|this, _, window, cx| {
this.dispatch_action(&RejectAll, window, cx)
@@ -1192,8 +1221,13 @@ impl Render for AgentDiffToolbar {
.child(
Button::new("keep-all", "Keep All")
.key_binding({
KeyBinding::for_action_in(&KeepAll, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.)))
KeyBinding::for_action_in(
&KeepAll,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
})
.on_click(cx.listener(|this, _, window, cx| {
this.dispatch_action(&KeepAll, window, cx)

View File

@@ -96,8 +96,14 @@ impl Render for AgentModelSelector {
.color(color)
.size(IconSize::XSmall),
),
move |_window, cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
gpui::Corner::TopRight,
cx,

View File

@@ -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 {
text_thread_editor: Entity<TextThreadEditor>,
context_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(
text_thread_editor: Entity<TextThreadEditor>,
context_editor: Entity<TextThreadEditor>,
acp_history_store: Entity<agent::HistoryStore>,
language_registry: Arc<LanguageRegistry>,
window: &mut Window,
cx: &mut App,
) -> Self {
let title = text_thread_editor.read(cx).title(cx).to_string();
let title = context_editor.read(cx).title(cx).to_string();
let editor = cx.new(|cx| {
let mut editor = Editor::single_line(window, cx);
@@ -323,7 +323,7 @@ impl ActiveView {
let subscriptions = vec![
window.subscribe(&editor, cx, {
{
let text_thread_editor = text_thread_editor.clone();
let context_editor = context_editor.clone();
move |editor, event, window, cx| match event {
EditorEvent::BufferEdited => {
if suppress_first_edit {
@@ -332,19 +332,19 @@ impl ActiveView {
}
let new_summary = editor.read(cx).text(cx);
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor
.text_thread()
.update(cx, |text_thread, cx| {
text_thread.set_custom_summary(new_summary, cx);
context_editor.update(cx, |context_editor, cx| {
context_editor
.context()
.update(cx, |assistant_context, cx| {
assistant_context.set_custom_summary(new_summary, cx);
})
})
}
EditorEvent::Blurred => {
if editor.read(cx).text(cx).is_empty() {
let summary = text_thread_editor
let summary = context_editor
.read(cx)
.text_thread()
.context()
.read(cx)
.summary()
.or_default();
@@ -358,17 +358,17 @@ impl ActiveView {
}
}
}),
window.subscribe(&text_thread_editor.read(cx).text_thread().clone(), cx, {
window.subscribe(&context_editor.read(cx).context().clone(), cx, {
let editor = editor.clone();
move |text_thread, event, window, cx| match event {
TextThreadEvent::SummaryGenerated => {
let summary = text_thread.read(cx).summary().or_default();
move |assistant_context, event, window, cx| match event {
ContextEvent::SummaryGenerated => {
let summary = assistant_context.read(cx).summary().or_default();
editor.update(cx, |editor, cx| {
editor.set_text(summary, window, cx);
})
}
TextThreadEvent::PathChanged { old_path, new_path } => {
ContextEvent::PathChanged { old_path, new_path } => {
acp_history_store.update(cx, |history_store, cx| {
if let Some(old_path) = old_path {
history_store
@@ -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(&text_thread_editor), window, cx)
buffer_search_bar.set_active_pane_item(Some(&context_editor), window, cx)
});
Self::TextThread {
text_thread_editor,
context_editor,
title_editor: editor,
buffer_search_bar,
_subscriptions: subscriptions,
@@ -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_text_thread::TextThreadStore>,
text_thread_store: Entity<assistant_context::ContextStore>,
prompt_store: Option<Entity<PromptStore>>,
context_server_registry: Entity<ContextServerRegistry>,
inline_assist_context_store: Entity<ContextStore>,
@@ -474,7 +474,7 @@ impl AgentPanel {
let text_thread_store = workspace
.update(cx, |workspace, cx| {
let project = workspace.project().clone();
assistant_text_thread::TextThreadStore::new(
assistant_context::ContextStore::new(
project,
prompt_builder,
slash_commands,
@@ -512,7 +512,7 @@ impl AgentPanel {
fn new(
workspace: &Workspace,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
text_thread_store: Entity<assistant_context::ContextStore>,
prompt_store: Option<Entity<PromptStore>>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -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 text_thread_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_text_thread(
let context_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_context(
context,
fs.clone(),
workspace.clone(),
@@ -579,7 +579,7 @@ impl AgentPanel {
editor
});
ActiveView::text_thread(
text_thread_editor,
context_editor,
history_store.clone(),
language_registry.clone(),
window,
@@ -736,8 +736,8 @@ impl AgentPanel {
.log_err()
.flatten();
let text_thread_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_text_thread(
let context_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_context(
context,
self.fs.clone(),
self.workspace.clone(),
@@ -757,7 +757,7 @@ impl AgentPanel {
self.set_active_view(
ActiveView::text_thread(
text_thread_editor.clone(),
context_editor.clone(),
self.history_store.clone(),
self.language_registry.clone(),
window,
@@ -766,7 +766,7 @@ impl AgentPanel {
window,
cx,
);
text_thread_editor.focus_handle(cx).focus(window);
context_editor.focus_handle(cx).focus(window);
}
fn external_thread(
@@ -905,20 +905,20 @@ impl AgentPanel {
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let text_thread_task = self
let context = self
.history_store
.update(cx, |store, cx| store.load_text_thread(path, cx));
cx.spawn_in(window, async move |this, cx| {
let text_thread = text_thread_task.await?;
let context = context.await?;
this.update_in(cx, |this, window, cx| {
this.open_text_thread(text_thread, window, cx);
this.open_text_thread(context, window, cx);
})
})
}
pub(crate) fn open_text_thread(
&mut self,
text_thread: Entity<TextThread>,
context: Entity<AssistantContext>,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -926,8 +926,8 @@ impl AgentPanel {
.log_err()
.flatten();
let editor = cx.new(|cx| {
TextThreadEditor::for_text_thread(
text_thread,
TextThreadEditor::for_context(
context,
self.fs.clone(),
self.workspace.clone(),
self.project.clone(),
@@ -965,10 +965,8 @@ impl AgentPanel {
ActiveView::ExternalAgentThread { thread_view } => {
thread_view.focus_handle(cx).focus(window);
}
ActiveView::TextThread {
text_thread_editor, ..
} => {
text_thread_editor.focus_handle(cx).focus(window);
ActiveView::TextThread { context_editor, .. } => {
context_editor.focus_handle(cx).focus(window);
}
ActiveView::History | ActiveView::Configuration => {}
}
@@ -1185,11 +1183,9 @@ impl AgentPanel {
}
}
pub(crate) fn active_text_thread_editor(&self) -> Option<Entity<TextThreadEditor>> {
pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
match &self.active_view {
ActiveView::TextThread {
text_thread_editor, ..
} => Some(text_thread_editor.clone()),
ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
_ => None,
}
}
@@ -1210,16 +1206,16 @@ impl AgentPanel {
let new_is_special = new_is_history || new_is_config;
match &new_view {
ActiveView::TextThread {
text_thread_editor, ..
} => self.history_store.update(cx, |store, cx| {
if let Some(path) = text_thread_editor.read(cx).text_thread().read(cx).path() {
store.push_recently_opened_entry(
agent::HistoryEntryId::TextThread(path.clone()),
cx,
)
}
}),
ActiveView::TextThread { context_editor, .. } => {
self.history_store.update(cx, |store, cx| {
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
store.push_recently_opened_entry(
agent::HistoryEntryId::TextThread(path.clone()),
cx,
)
}
})
}
ActiveView::ExternalAgentThread { .. } => {}
ActiveView::History | ActiveView::Configuration => {}
}
@@ -1376,9 +1372,7 @@ impl Focusable for AgentPanel {
match &self.active_view {
ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
ActiveView::History => self.acp_history.focus_handle(cx),
ActiveView::TextThread {
text_thread_editor, ..
} => text_thread_editor.focus_handle(cx),
ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
ActiveView::Configuration => {
if let Some(configuration) = self.configuration.as_ref() {
configuration.focus_handle(cx)
@@ -1401,10 +1395,6 @@ impl Panel for AgentPanel {
"AgentPanel"
}
fn panel_key() -> &'static str {
AGENT_PANEL_KEY
}
fn position(&self, _window: &Window, cx: &App) -> DockPosition {
agent_panel_dock_position(cx)
}
@@ -1513,17 +1503,17 @@ impl AgentPanel {
}
ActiveView::TextThread {
title_editor,
text_thread_editor,
context_editor,
..
} => {
let summary = text_thread_editor.read(cx).text_thread().read(cx).summary();
let summary = context_editor.read(cx).context().read(cx).summary();
match summary {
TextThreadSummary::Pending => Label::new(TextThreadSummary::DEFAULT)
ContextSummary::Pending => Label::new(ContextSummary::DEFAULT)
.color(Color::Muted)
.truncate()
.into_any_element(),
TextThreadSummary::Content(summary) => {
ContextSummary::Content(summary) => {
if summary.done {
div()
.w_full()
@@ -1536,17 +1526,17 @@ impl AgentPanel {
.into_any_element()
}
}
TextThreadSummary::Error => h_flex()
ContextSummary::Error => h_flex()
.w_full()
.child(title_editor.clone())
.child(
IconButton::new("retry-summary-generation", IconName::RotateCcw)
.icon_size(IconSize::Small)
.on_click({
let text_thread_editor = text_thread_editor.clone();
let context_editor = context_editor.clone();
move |_, _window, cx| {
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor.regenerate_summary(cx);
context_editor.update(cx, |context_editor, cx| {
context_editor.regenerate_summary(cx);
});
}
})
@@ -1601,11 +1591,12 @@ impl AgentPanel {
.icon_size(IconSize::Small),
{
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Toggle Agent Menu",
&ToggleOptionsMenu,
&focus_handle,
window,
cx,
)
}
@@ -1669,7 +1660,7 @@ impl AgentPanel {
.separator();
menu = menu
.action("Rules", Box::new(OpenRulesLibrary::default()))
.action("Rules", Box::new(OpenRulesLibrary::default()))
.action("Settings", Box::new(OpenSettings))
.separator()
.action(full_screen_label, Box::new(ToggleZoom));
@@ -1696,11 +1687,12 @@ impl AgentPanel {
.trigger_with_tooltip(
IconButton::new("agent-nav-menu", icon).icon_size(IconSize::Small),
{
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Toggle Recent Threads",
&ToggleNavigationMenu,
&focus_handle,
window,
cx,
)
}
@@ -1734,8 +1726,8 @@ impl AgentPanel {
this.go_back(&workspace::GoBack, window, cx);
}))
.tooltip({
move |_window, cx| {
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, window, cx)
}
})
}
@@ -1756,11 +1748,12 @@ 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,
)
}
@@ -2006,8 +1999,14 @@ 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", cx)
.tooltip(move |window, cx| {
Tooltip::with_meta(
selected_agent_label.clone(),
None,
"Selected Agent",
window,
cx,
)
})
})
.into_any_element();
@@ -2183,6 +2182,7 @@ impl AgentPanel {
border_bottom: bool,
configuration_error: &ConfigurationError,
focus_handle: &FocusHandle,
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
let zed_provider_configured = AgentSettings::get_global(cx)
@@ -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, cx)
KeyBinding::for_action_in(&OpenSettings, focus_handle, window, 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,
text_thread_editor: &Entity<TextThreadEditor>,
context_editor: &Entity<TextThreadEditor>,
buffer_search_bar: &Entity<BufferSearchBar>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -2283,7 +2283,7 @@ impl AgentPanel {
)
})
})
.child(text_thread_editor.clone())
.child(context_editor.clone())
.child(self.render_drag_target(cx))
}
@@ -2359,12 +2359,10 @@ impl AgentPanel {
thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
});
}
ActiveView::TextThread {
text_thread_editor, ..
} => {
text_thread_editor.update(cx, |text_thread_editor, cx| {
ActiveView::TextThread { context_editor, .. } => {
context_editor.update(cx, |context_editor, cx| {
TextThreadEditor::insert_dragged_files(
text_thread_editor,
context_editor,
paths,
added_worktrees,
window,
@@ -2435,7 +2433,7 @@ impl Render for AgentPanel {
.child(self.render_drag_target(cx)),
ActiveView::History => parent.child(self.acp_history.clone()),
ActiveView::TextThread {
text_thread_editor,
context_editor,
buffer_search_bar,
..
} => {
@@ -2451,6 +2449,7 @@ impl Render for AgentPanel {
true,
err,
&self.focus_handle(cx),
window,
cx,
))
} else {
@@ -2458,7 +2457,7 @@ impl Render for AgentPanel {
}
})
.child(self.render_text_thread(
text_thread_editor,
context_editor,
buffer_search_bar,
window,
cx,
@@ -2536,17 +2535,17 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
pub struct ConcreteAssistantPanelDelegate;
impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
fn active_text_thread_editor(
fn active_context_editor(
&self,
workspace: &mut Workspace,
_window: &mut Window,
cx: &mut Context<Workspace>,
) -> Option<Entity<TextThreadEditor>> {
let panel = workspace.panel::<AgentPanel>(cx)?;
panel.read(cx).active_text_thread_editor()
panel.read(cx).active_context_editor()
}
fn open_local_text_thread(
fn open_saved_context(
&self,
workspace: &mut Workspace,
path: Arc<Path>,
@@ -2562,10 +2561,10 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
})
}
fn open_remote_text_thread(
fn open_remote_context(
&self,
_workspace: &mut Workspace,
_text_thread_id: assistant_text_thread::TextThreadId,
_context_id: assistant_context::ContextId,
_window: &mut Window,
_cx: &mut Context<Workspace>,
) -> Task<Result<Entity<TextThreadEditor>>> {
@@ -2596,15 +2595,15 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
thread_view.update(cx, |thread_view, cx| {
thread_view.insert_selections(window, cx);
});
} else if let Some(text_thread_editor) = panel.active_text_thread_editor() {
} else if let Some(context_editor) = panel.active_context_editor() {
let snapshot = buffer.read(cx).snapshot(cx);
let selection_ranges = selection_ranges
.into_iter()
.map(|range| range.to_point(&snapshot))
.collect::<Vec<_>>();
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor.quote_ranges(selection_ranges, snapshot, window, cx)
context_editor.update(cx, |context_editor, cx| {
context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
});
}
});

View File

@@ -130,6 +130,12 @@ actions!(
]
);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Action)]
#[action(namespace = agent)]
#[action(deprecated_aliases = ["assistant::QuoteSelection"])]
/// Quotes the current selection in the agent panel's message editor.
pub struct QuoteSelection;
/// Creates a new conversation thread, optionally based on an existing thread.
#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
#[action(namespace = agent)]
@@ -250,7 +256,7 @@ pub fn init(
) {
AgentSettings::register(cx);
assistant_text_thread::init(client.clone(), cx);
assistant_context::init(client.clone(), cx);
rules_library::init(cx);
if !is_eval {
// Initializing the language model from the user settings messes with the eval, so we only initialize them when

View File

@@ -1,5 +1,5 @@
use agent::outline;
use assistant_text_thread::TextThread;
use assistant_context::AssistantContext;
use futures::future;
use futures::{FutureExt, future::Shared};
use gpui::{App, AppContext as _, ElementId, Entity, SharedString, Task};
@@ -581,7 +581,7 @@ impl Display for ThreadContext {
#[derive(Debug, Clone)]
pub struct TextThreadContextHandle {
pub text_thread: Entity<TextThread>,
pub context: Entity<AssistantContext>,
pub context_id: ContextId,
}
@@ -595,20 +595,20 @@ pub struct TextThreadContext {
impl TextThreadContextHandle {
// pub fn lookup_key() ->
pub fn eq_for_key(&self, other: &Self) -> bool {
self.text_thread == other.text_thread
self.context == other.context
}
pub fn hash_for_key<H: Hasher>(&self, state: &mut H) {
self.text_thread.hash(state)
self.context.hash(state)
}
pub fn title(&self, cx: &App) -> SharedString {
self.text_thread.read(cx).summary().or_default()
self.context.read(cx).summary().or_default()
}
fn load(self, cx: &App) -> Task<Option<AgentContext>> {
let title = self.title(cx);
let text = self.text_thread.read(cx).to_xml(cx);
let text = self.context.read(cx).to_xml(cx);
let context = AgentContext::TextThread(TextThreadContext {
title,
text: text.into(),

View File

@@ -5,7 +5,7 @@ use crate::context::{
};
use agent_client_protocol as acp;
use anyhow::{Context as _, Result, anyhow};
use assistant_text_thread::TextThread;
use assistant_context::AssistantContext;
use collections::{HashSet, IndexSet};
use futures::{self, FutureExt};
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
@@ -200,13 +200,13 @@ impl ContextStore {
pub fn add_text_thread(
&mut self,
text_thread: Entity<TextThread>,
context: Entity<AssistantContext>,
remove_if_exists: bool,
cx: &mut Context<Self>,
) -> Option<AgentContextHandle> {
let context_id = self.next_context_id.post_inc();
let context = AgentContextHandle::TextThread(TextThreadContextHandle {
text_thread,
context,
context_id,
});
@@ -353,15 +353,21 @@ impl ContextStore {
);
};
}
SuggestedContext::TextThread {
text_thread,
name: _,
} => {
if let Some(text_thread) = text_thread.upgrade() {
// SuggestedContext::Thread { thread, name: _ } => {
// if let Some(thread) = thread.upgrade() {
// let context_id = self.next_context_id.post_inc();
// self.insert_context(
// AgentContextHandle::Thread(ThreadContextHandle { thread, context_id }),
// cx,
// );
// }
// }
SuggestedContext::TextThread { context, name: _ } => {
if let Some(context) = context.upgrade() {
let context_id = self.next_context_id.post_inc();
self.insert_context(
AgentContextHandle::TextThread(TextThreadContextHandle {
text_thread,
context,
context_id,
}),
cx,
@@ -386,7 +392,7 @@ impl ContextStore {
// }
AgentContextHandle::TextThread(text_thread_context) => {
self.context_text_thread_paths
.extend(text_thread_context.text_thread.read(cx).path().cloned());
.extend(text_thread_context.context.read(cx).path().cloned());
}
_ => {}
}
@@ -408,7 +414,7 @@ impl ContextStore {
.remove(thread_context.thread.read(cx).id());
}
AgentContextHandle::TextThread(text_thread_context) => {
if let Some(path) = text_thread_context.text_thread.read(cx).path() {
if let Some(path) = text_thread_context.context.read(cx).path() {
self.context_text_thread_paths.remove(path);
}
}
@@ -532,9 +538,13 @@ pub enum SuggestedContext {
icon_path: Option<SharedString>,
buffer: WeakEntity<Buffer>,
},
// Thread {
// name: SharedString,
// thread: WeakEntity<Thread>,
// },
TextThread {
name: SharedString,
text_thread: WeakEntity<TextThread>,
context: WeakEntity<AssistantContext>,
},
}
@@ -542,6 +552,7 @@ impl SuggestedContext {
pub fn name(&self) -> &SharedString {
match self {
Self::File { name, .. } => name,
// Self::Thread { name, .. } => name,
Self::TextThread { name, .. } => name,
}
}
@@ -549,6 +560,7 @@ impl SuggestedContext {
pub fn icon_path(&self) -> Option<SharedString> {
match self {
Self::File { icon_path, .. } => icon_path.clone(),
// Self::Thread { .. } => None,
Self::TextThread { .. } => None,
}
}
@@ -556,6 +568,7 @@ impl SuggestedContext {
pub fn kind(&self) -> ContextKind {
match self {
Self::File { .. } => ContextKind::File,
// Self::Thread { .. } => ContextKind::Thread,
Self::TextThread { .. } => ContextKind::TextThread,
}
}

View File

@@ -132,19 +132,19 @@ impl ContextStrip {
let workspace = self.workspace.upgrade()?;
let panel = workspace.read(cx).panel::<AgentPanel>(cx)?.read(cx);
if let Some(active_text_thread_editor) = panel.active_text_thread_editor() {
let text_thread = active_text_thread_editor.read(cx).text_thread();
let weak_text_thread = text_thread.downgrade();
let text_thread = text_thread.read(cx);
let path = text_thread.path()?;
if let Some(active_context_editor) = panel.active_context_editor() {
let context = active_context_editor.read(cx).context();
let weak_context = context.downgrade();
let context = context.read(cx);
let path = context.path()?;
if self.context_store.read(cx).includes_text_thread(path) {
return None;
}
Some(SuggestedContext::TextThread {
name: text_thread.summary().or_default(),
text_thread: weak_text_thread,
name: context.summary().or_default(),
context: weak_context,
})
} else {
None
@@ -332,7 +332,7 @@ impl ContextStrip {
AgentContextHandle::TextThread(text_thread_context) => {
workspace.update(cx, |workspace, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
let context = text_thread_context.text_thread.clone();
let context = text_thread_context.context.clone();
window.defer(cx, move |window, cx| {
panel.update(cx, |panel, cx| {
panel.open_text_thread(context, window, cx)
@@ -483,11 +483,12 @@ impl Render for ContextStrip {
.style(ui::ButtonStyle::Filled),
{
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
window,
cx,
)
}
@@ -557,11 +558,12 @@ impl Render for ContextStrip {
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Remove All Context",
&RemoveAllContext,
&focus_handle,
window,
cx,
)
}

View File

@@ -1508,8 +1508,8 @@ impl InlineAssistant {
return Some(InlineAssistTarget::Terminal(terminal_view));
}
let text_thread_editor = agent_panel
.and_then(|panel| panel.read(cx).active_text_thread_editor())
let context_editor = agent_panel
.and_then(|panel| panel.read(cx).active_context_editor())
.and_then(|editor| {
let editor = &editor.read(cx).editor().clone();
if editor.read(cx).is_focused(window) {
@@ -1519,8 +1519,8 @@ impl InlineAssistant {
}
});
if let Some(text_thread_editor) = text_thread_editor {
Some(InlineAssistTarget::Editor(text_thread_editor))
if let Some(context_editor) = context_editor {
Some(InlineAssistTarget::Editor(context_editor))
} else if let Some(workspace_editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))

View File

@@ -468,11 +468,12 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta(
mode.tooltip_interrupt(),
Some(&menu::Cancel),
"Changes won't be discarded",
window,
cx,
)
})
@@ -486,11 +487,12 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta(
mode.tooltip_restart(),
Some(&menu::Confirm),
"Changes will be discarded",
window,
cx,
)
})
@@ -503,8 +505,8 @@ impl<T: 'static> PromptEditor<T> {
let accept = IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |_window, cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, cx)
.tooltip(move |window, cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, window, cx)
})
.on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
@@ -517,10 +519,11 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("confirm", IconName::PlayFilled)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|_window, cx| {
.tooltip(|window, cx| {
Tooltip::for_action(
"Execute Generated Command",
&menu::SecondaryConfirm,
window,
cx,
)
})
@@ -612,12 +615,13 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |_window, cx| {
move |window, cx| {
cx.new(|cx| {
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
KeyBinding::for_action_in(
&CyclePreviousInlineAssist,
&focus_handle,
window,
cx,
),
);
@@ -653,12 +657,13 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |_window, cx| {
move |window, cx| {
cx.new(|cx| {
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
KeyBinding::for_action_in(
&CycleNextInlineAssist,
&focus_handle,
window,
cx,
),
);

View File

@@ -162,11 +162,12 @@ impl Render for ProfileSelector {
PickerPopoverMenu::new(
picker,
trigger_button,
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Toggle Profile Menu",
&ToggleProfileSelector,
&focus_handle,
window,
cx,
)
},

View File

@@ -155,8 +155,8 @@ impl PickerDelegate for SlashCommandDelegate {
match command {
SlashCommandEntry::Info(info) => {
self.active_context_editor
.update(cx, |text_thread_editor, cx| {
text_thread_editor.insert_command(&info.name, window, cx)
.update(cx, |context_editor, cx| {
context_editor.insert_command(&info.name, window, cx)
})
.ok();
}

View File

@@ -1,4 +1,5 @@
use crate::{
QuoteSelection,
language_model_selector::{LanguageModelSelector, language_model_selector},
ui::BurnModeTooltip,
};
@@ -71,13 +72,13 @@ use workspace::{
pane,
searchable::{SearchEvent, SearchableItem},
};
use zed_actions::agent::{AddSelectionToThread, ToggleModelSelector};
use zed_actions::agent::ToggleModelSelector;
use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};
use assistant_text_thread::{
CacheStatus, Content, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId,
MessageMetadata, MessageStatus, PendingSlashCommandStatus, TextThread, TextThreadEvent,
TextThreadId, ThoughtProcessOutputSection,
use assistant_context::{
AssistantContext, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId,
InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus,
PendingSlashCommandStatus, ThoughtProcessOutputSection,
};
actions!(
@@ -126,14 +127,14 @@ pub enum ThoughtProcessStatus {
}
pub trait AgentPanelDelegate {
fn active_text_thread_editor(
fn active_context_editor(
&self,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Option<Entity<TextThreadEditor>>;
fn open_local_text_thread(
fn open_saved_context(
&self,
workspace: &mut Workspace,
path: Arc<Path>,
@@ -141,10 +142,10 @@ pub trait AgentPanelDelegate {
cx: &mut Context<Workspace>,
) -> Task<Result<()>>;
fn open_remote_text_thread(
fn open_remote_context(
&self,
workspace: &mut Workspace,
text_thread_id: TextThreadId,
context_id: ContextId,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Task<Result<Entity<TextThreadEditor>>>;
@@ -177,7 +178,7 @@ struct GlobalAssistantPanelDelegate(Arc<dyn AgentPanelDelegate>);
impl Global for GlobalAssistantPanelDelegate {}
pub struct TextThreadEditor {
text_thread: Entity<TextThread>,
context: Entity<AssistantContext>,
fs: Arc<dyn Fs>,
slash_commands: Arc<SlashCommandWorkingSet>,
workspace: WeakEntity<Workspace>,
@@ -223,8 +224,8 @@ impl TextThreadEditor {
.detach();
}
pub fn for_text_thread(
text_thread: Entity<TextThread>,
pub fn for_context(
context: Entity<AssistantContext>,
fs: Arc<dyn Fs>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
@@ -233,14 +234,14 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) -> Self {
let completion_provider = SlashCommandCompletionProvider::new(
text_thread.read(cx).slash_commands().clone(),
context.read(cx).slash_commands().clone(),
Some(cx.entity().downgrade()),
Some(workspace.clone()),
);
let editor = cx.new(|cx| {
let mut editor =
Editor::for_buffer(text_thread.read(cx).buffer().clone(), None, window, cx);
Editor::for_buffer(context.read(cx).buffer().clone(), None, window, cx);
editor.disable_scrollbars_and_minimap(window, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_line_numbers(false, cx);
@@ -264,24 +265,18 @@ impl TextThreadEditor {
});
let _subscriptions = vec![
cx.observe(&text_thread, |_, _, cx| cx.notify()),
cx.subscribe_in(&text_thread, window, Self::handle_text_thread_event),
cx.observe(&context, |_, _, cx| cx.notify()),
cx.subscribe_in(&context, window, Self::handle_context_event),
cx.subscribe_in(&editor, window, Self::handle_editor_event),
cx.subscribe_in(&editor, window, Self::handle_editor_search_event),
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
];
let slash_command_sections = text_thread
.read(cx)
.slash_command_output_sections()
.to_vec();
let thought_process_sections = text_thread
.read(cx)
.thought_process_output_sections()
.to_vec();
let slash_commands = text_thread.read(cx).slash_commands().clone();
let slash_command_sections = context.read(cx).slash_command_output_sections().to_vec();
let thought_process_sections = context.read(cx).thought_process_output_sections().to_vec();
let slash_commands = context.read(cx).slash_commands().clone();
let mut this = Self {
text_thread,
context,
slash_commands,
editor,
lsp_adapter_delegate,
@@ -343,8 +338,8 @@ impl TextThreadEditor {
});
}
pub fn text_thread(&self) -> &Entity<TextThread> {
&self.text_thread
pub fn context(&self) -> &Entity<AssistantContext> {
&self.context
}
pub fn editor(&self) -> &Entity<Editor> {
@@ -356,9 +351,9 @@ impl TextThreadEditor {
self.editor.update(cx, |editor, cx| {
editor.insert(&format!("/{command_name}\n\n"), window, cx)
});
let command = self.text_thread.update(cx, |text_thread, cx| {
text_thread.reparse(cx);
text_thread.parsed_slash_commands()[0].clone()
let command = self.context.update(cx, |context, cx| {
context.reparse(cx);
context.parsed_slash_commands()[0].clone()
});
self.run_command(
command.source_range,
@@ -381,14 +376,11 @@ impl TextThreadEditor {
fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.last_error = None;
if let Some(user_message) = self
.text_thread
.update(cx, |text_thread, cx| text_thread.assist(cx))
{
if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) {
let new_selection = {
let cursor = user_message
.start
.to_offset(self.text_thread.read(cx).buffer().read(cx));
.to_offset(self.context.read(cx).buffer().read(cx));
cursor..cursor
};
self.editor.update(cx, |editor, cx| {
@@ -412,8 +404,8 @@ impl TextThreadEditor {
self.last_error = None;
if self
.text_thread
.update(cx, |text_thread, cx| text_thread.cancel_last_assist(cx))
.context
.update(cx, |context, cx| context.cancel_last_assist(cx))
{
return;
}
@@ -428,13 +420,13 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) {
let cursors = self.cursors(cx);
self.text_thread.update(cx, |text_thread, cx| {
let messages = text_thread
self.context.update(cx, |context, cx| {
let messages = context
.messages_for_offsets(cursors, cx)
.into_iter()
.map(|message| message.id)
.collect();
text_thread.cycle_message_roles(messages, cx)
context.cycle_message_roles(messages, cx)
});
}
@@ -500,11 +492,11 @@ impl TextThreadEditor {
let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
let mut commands_by_range = HashMap::default();
let workspace = self.workspace.clone();
self.text_thread.update(cx, |text_thread, cx| {
text_thread.reparse(cx);
self.context.update(cx, |context, cx| {
context.reparse(cx);
for selection in selections.iter() {
if let Some(command) =
text_thread.pending_command_for_position(selection.head().text_anchor, cx)
context.pending_command_for_position(selection.head().text_anchor, cx)
{
commands_by_range
.entry(command.source_range.clone())
@@ -542,14 +534,14 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) {
if let Some(command) = self.slash_commands.command(name, cx) {
let text_thread = self.text_thread.read(cx);
let sections = text_thread
let context = self.context.read(cx);
let sections = context
.slash_command_output_sections()
.iter()
.filter(|section| section.is_valid(text_thread.buffer().read(cx)))
.filter(|section| section.is_valid(context.buffer().read(cx)))
.cloned()
.collect::<Vec<_>>();
let snapshot = text_thread.buffer().read(cx).snapshot();
let snapshot = context.buffer().read(cx).snapshot();
let output = command.run(
arguments,
&sections,
@@ -559,8 +551,8 @@ impl TextThreadEditor {
window,
cx,
);
self.text_thread.update(cx, |text_thread, cx| {
text_thread.insert_command_output(
self.context.update(cx, |context, cx| {
context.insert_command_output(
command_range,
name,
output,
@@ -571,32 +563,32 @@ impl TextThreadEditor {
}
}
fn handle_text_thread_event(
fn handle_context_event(
&mut self,
_: &Entity<TextThread>,
event: &TextThreadEvent,
_: &Entity<AssistantContext>,
event: &ContextEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
let text_thread_editor = cx.entity().downgrade();
let context_editor = cx.entity().downgrade();
match event {
TextThreadEvent::MessagesEdited => {
ContextEvent::MessagesEdited => {
self.update_message_headers(cx);
self.update_image_blocks(cx);
self.text_thread.update(cx, |text_thread, cx| {
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
self.context.update(cx, |context, cx| {
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
});
}
TextThreadEvent::SummaryChanged => {
ContextEvent::SummaryChanged => {
cx.emit(EditorEvent::TitleChanged);
self.text_thread.update(cx, |text_thread, cx| {
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
self.context.update(cx, |context, cx| {
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
});
}
TextThreadEvent::SummaryGenerated => {}
TextThreadEvent::PathChanged { .. } => {}
TextThreadEvent::StartedThoughtProcess(range) => {
ContextEvent::SummaryGenerated => {}
ContextEvent::PathChanged { .. } => {}
ContextEvent::StartedThoughtProcess(range) => {
let creases = self.insert_thought_process_output_sections(
[(
ThoughtProcessOutputSection {
@@ -609,7 +601,7 @@ impl TextThreadEditor {
);
self.pending_thought_process = Some((creases[0], range.start));
}
TextThreadEvent::EndedThoughtProcess(end) => {
ContextEvent::EndedThoughtProcess(end) => {
if let Some((crease_id, start)) = self.pending_thought_process.take() {
self.editor.update(cx, |editor, cx| {
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
@@ -635,7 +627,7 @@ impl TextThreadEditor {
);
}
}
TextThreadEvent::StreamedCompletion => {
ContextEvent::StreamedCompletion => {
self.editor.update(cx, |editor, cx| {
if let Some(scroll_position) = self.scroll_position {
let snapshot = editor.snapshot(window, cx);
@@ -650,7 +642,7 @@ impl TextThreadEditor {
}
});
}
TextThreadEvent::ParsedSlashCommandsUpdated { removed, updated } => {
ContextEvent::ParsedSlashCommandsUpdated { removed, updated } => {
self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx).snapshot(cx);
let (&excerpt_id, _, _) = buffer.as_singleton().unwrap();
@@ -666,12 +658,12 @@ impl TextThreadEditor {
updated.iter().map(|command| {
let workspace = self.workspace.clone();
let confirm_command = Arc::new({
let text_thread_editor = text_thread_editor.clone();
let context_editor = context_editor.clone();
let command = command.clone();
move |window: &mut Window, cx: &mut App| {
text_thread_editor
.update(cx, |text_thread_editor, cx| {
text_thread_editor.run_command(
context_editor
.update(cx, |context_editor, cx| {
context_editor.run_command(
command.source_range.clone(),
&command.name,
&command.arguments,
@@ -721,17 +713,17 @@ impl TextThreadEditor {
);
})
}
TextThreadEvent::InvokedSlashCommandChanged { command_id } => {
ContextEvent::InvokedSlashCommandChanged { command_id } => {
self.update_invoked_slash_command(*command_id, window, cx);
}
TextThreadEvent::SlashCommandOutputSectionAdded { section } => {
ContextEvent::SlashCommandOutputSectionAdded { section } => {
self.insert_slash_command_output_sections([section.clone()], false, window, cx);
}
TextThreadEvent::Operation(_) => {}
TextThreadEvent::ShowAssistError(error_message) => {
ContextEvent::Operation(_) => {}
ContextEvent::ShowAssistError(error_message) => {
self.last_error = Some(AssistError::Message(error_message.clone()));
}
TextThreadEvent::ShowPaymentRequiredError => {
ContextEvent::ShowPaymentRequiredError => {
self.last_error = Some(AssistError::PaymentRequired);
}
}
@@ -744,14 +736,14 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) {
if let Some(invoked_slash_command) =
self.text_thread.read(cx).invoked_slash_command(&command_id)
self.context.read(cx).invoked_slash_command(&command_id)
&& let InvokedSlashCommandStatus::Finished = invoked_slash_command.status
{
let run_commands_in_ranges = invoked_slash_command.run_commands_in_ranges.clone();
for range in run_commands_in_ranges {
let commands = self.text_thread.update(cx, |text_thread, cx| {
text_thread.reparse(cx);
text_thread
let commands = self.context.update(cx, |context, cx| {
context.reparse(cx);
context
.pending_commands_for_range(range.clone(), cx)
.to_vec()
});
@@ -772,7 +764,7 @@ impl TextThreadEditor {
self.editor.update(cx, |editor, cx| {
if let Some(invoked_slash_command) =
self.text_thread.read(cx).invoked_slash_command(&command_id)
self.context.read(cx).invoked_slash_command(&command_id)
{
if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status {
let buffer = editor.buffer().read(cx).snapshot(cx);
@@ -799,7 +791,7 @@ impl TextThreadEditor {
let buffer = editor.buffer().read(cx).snapshot(cx);
let (&excerpt_id, _buffer_id, _buffer_snapshot) =
buffer.as_singleton().unwrap();
let context = self.text_thread.downgrade();
let context = self.context.downgrade();
let range = buffer
.anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
.unwrap();
@@ -1029,7 +1021,7 @@ impl TextThreadEditor {
let render_block = |message: MessageMetadata| -> RenderBlock {
Arc::new({
let text_thread = self.text_thread.clone();
let context = self.context.clone();
move |cx| {
let message_id = MessageId(message.timestamp);
@@ -1093,19 +1085,20 @@ impl TextThreadEditor {
.child(label)
.children(spinner),
)
.tooltip(|_window, cx| {
.tooltip(|window, cx| {
Tooltip::with_meta(
"Toggle message role",
None,
"Available roles: You (User), Agent, System",
window,
cx,
)
})
.on_click({
let text_thread = text_thread.clone();
let context = context.clone();
move |_, _window, cx| {
text_thread.update(cx, |text_thread, cx| {
text_thread.cycle_message_roles(
context.update(cx, |context, cx| {
context.cycle_message_roles(
HashSet::from_iter(Some(message_id)),
cx,
)
@@ -1133,11 +1126,12 @@ impl TextThreadEditor {
.size(IconSize::XSmall)
.color(Color::Hint),
)
.tooltip(|_window, cx| {
.tooltip(|window, cx| {
Tooltip::with_meta(
"Context Cached",
None,
"Large messages cached to optimize performance",
window,
cx,
)
})
@@ -1167,11 +1161,11 @@ impl TextThreadEditor {
.icon_position(IconPosition::Start)
.tooltip(Tooltip::text("View Details"))
.on_click({
let text_thread = text_thread.clone();
let context = context.clone();
let error = error.clone();
move |_, _window, cx| {
text_thread.update(cx, |_, cx| {
cx.emit(TextThreadEvent::ShowAssistError(
context.update(cx, |_, cx| {
cx.emit(ContextEvent::ShowAssistError(
error.clone(),
));
});
@@ -1214,7 +1208,7 @@ impl TextThreadEditor {
};
let mut new_blocks = vec![];
let mut block_index_to_message = vec![];
for message in self.text_thread.read(cx).messages(cx) {
for message in self.context.read(cx).messages(cx) {
if blocks_to_remove.remove(&message.id).is_some() {
// This is an old message that we might modify.
let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
@@ -1255,18 +1249,18 @@ impl TextThreadEditor {
) -> Option<(String, bool)> {
const CODE_FENCE_DELIMITER: &str = "```";
let text_thread_editor = context_editor_view.read(cx).editor.clone();
text_thread_editor.update(cx, |text_thread_editor, cx| {
let display_map = text_thread_editor.display_snapshot(cx);
if text_thread_editor
let context_editor = context_editor_view.read(cx).editor.clone();
context_editor.update(cx, |context_editor, cx| {
let display_map = context_editor.display_snapshot(cx);
if context_editor
.selections
.newest::<Point>(&display_map)
.is_empty()
{
let snapshot = text_thread_editor.buffer().read(cx).snapshot(cx);
let snapshot = context_editor.buffer().read(cx).snapshot(cx);
let (_, _, snapshot) = snapshot.as_singleton()?;
let head = text_thread_editor
let head = context_editor
.selections
.newest::<Point>(&display_map)
.head();
@@ -1286,8 +1280,8 @@ impl TextThreadEditor {
(!text.is_empty()).then_some((text, true))
} else {
let selection = text_thread_editor.selections.newest_adjusted(&display_map);
let buffer = text_thread_editor.buffer().read(cx).snapshot(cx);
let selection = context_editor.selections.newest_adjusted(&display_map);
let buffer = context_editor.buffer().read(cx).snapshot(cx);
let selected_text = buffer.text_for_range(selection.range()).collect::<String>();
(!selected_text.is_empty()).then_some((selected_text, false))
@@ -1305,7 +1299,7 @@ impl TextThreadEditor {
return;
};
let Some(context_editor_view) =
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
agent_panel_delegate.active_context_editor(workspace, window, cx)
else {
return;
};
@@ -1333,7 +1327,7 @@ impl TextThreadEditor {
let result = maybe!({
let agent_panel_delegate = <dyn AgentPanelDelegate>::try_global(cx)?;
let context_editor_view =
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)?;
agent_panel_delegate.active_context_editor(workspace, window, cx)?;
Self::get_selection_or_code_block(&context_editor_view, cx)
});
let Some((text, is_code_block)) = result else {
@@ -1370,7 +1364,7 @@ impl TextThreadEditor {
return;
};
let Some(context_editor_view) =
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
agent_panel_delegate.active_context_editor(workspace, window, cx)
else {
return;
};
@@ -1456,7 +1450,7 @@ impl TextThreadEditor {
pub fn quote_selection(
workspace: &mut Workspace,
_: &AddSelectionToThread,
_: &QuoteSelection,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
@@ -1631,33 +1625,29 @@ impl TextThreadEditor {
)
});
let text_thread = self.text_thread.read(cx);
let context = self.context.read(cx);
let mut text = String::new();
// If selection is empty, we want to copy the entire line
if selection.range().is_empty() {
let snapshot = text_thread.buffer().read(cx).snapshot();
let snapshot = context.buffer().read(cx).snapshot();
let point = snapshot.offset_to_point(selection.range().start);
selection.start = snapshot.point_to_offset(Point::new(point.row, 0));
selection.end = snapshot
.point_to_offset(cmp::min(Point::new(point.row + 1, 0), snapshot.max_point()));
for chunk in text_thread
.buffer()
.read(cx)
.text_for_range(selection.range())
{
for chunk in context.buffer().read(cx).text_for_range(selection.range()) {
text.push_str(chunk);
}
} else {
for message in text_thread.messages(cx) {
for message in context.messages(cx) {
if message.offset_range.start >= selection.range().end {
break;
} else if message.offset_range.end >= selection.range().start {
let range = cmp::max(message.offset_range.start, selection.range().start)
..cmp::min(message.offset_range.end, selection.range().end);
if !range.is_empty() {
for chunk in text_thread.buffer().read(cx).text_for_range(range) {
for chunk in context.buffer().read(cx).text_for_range(range) {
text.push_str(chunk);
}
if message.offset_range.end < selection.range().end {
@@ -1768,7 +1758,7 @@ impl TextThreadEditor {
});
});
self.text_thread.update(cx, |text_thread, cx| {
self.context.update(cx, |context, cx| {
for image in images {
let Some(render_image) = image.to_image_data(cx.svg_renderer()).log_err()
else {
@@ -1778,7 +1768,7 @@ impl TextThreadEditor {
let image_task = LanguageModelImage::from_image(Arc::new(image), cx).shared();
for image_position in image_positions.iter() {
text_thread.insert_content(
context.insert_content(
Content::Image {
anchor: image_position.text_anchor,
image_id,
@@ -1799,7 +1789,7 @@ impl TextThreadEditor {
let excerpt_id = *buffer.as_singleton().unwrap().0;
let old_blocks = std::mem::take(&mut self.image_blocks);
let new_blocks = self
.text_thread
.context
.read(cx)
.contents(cx)
.map(
@@ -1847,36 +1837,36 @@ impl TextThreadEditor {
}
fn split(&mut self, _: &Split, _window: &mut Window, cx: &mut Context<Self>) {
self.text_thread.update(cx, |text_thread, cx| {
self.context.update(cx, |context, cx| {
let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
for selection in selections.as_ref() {
let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
let range = selection
.map(|endpoint| endpoint.to_offset(&buffer))
.range();
text_thread.split_message(range, cx);
context.split_message(range, cx);
}
});
}
fn save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context<Self>) {
self.text_thread.update(cx, |text_thread, cx| {
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
self.context.update(cx, |context, cx| {
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
});
}
pub fn title(&self, cx: &App) -> SharedString {
self.text_thread.read(cx).summary().or_default()
self.context.read(cx).summary().or_default()
}
pub fn regenerate_summary(&mut self, cx: &mut Context<Self>) {
self.text_thread
.update(cx, |text_thread, cx| text_thread.summarize(true, cx));
self.context
.update(cx, |context, cx| context.summarize(true, cx));
}
fn render_remaining_tokens(&self, cx: &App) -> Option<impl IntoElement + use<>> {
let (token_count_color, token_count, max_token_count, tooltip) =
match token_state(&self.text_thread, cx)? {
match token_state(&self.context, cx)? {
TokenState::NoTokensLeft {
max_token_count,
token_count,
@@ -1924,7 +1914,7 @@ impl TextThreadEditor {
fn render_send_button(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
let (style, tooltip) = match token_state(&self.text_thread, cx) {
let (style, tooltip) = match token_state(&self.context, cx) {
Some(TokenState::NoTokensLeft { .. }) => (
ButtonStyle::Tinted(TintColor::Error),
Some(Tooltip::text("Token limit reached")(window, cx)),
@@ -1957,7 +1947,7 @@ impl TextThreadEditor {
})
.layer(ElevationIndex::ModalSurface)
.key_binding(
KeyBinding::for_action_in(&Assist, &focus_handle, cx)
KeyBinding::for_action_in(&Assist, &focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(move |_event, window, cx| {
@@ -1992,14 +1982,20 @@ impl TextThreadEditor {
.icon_color(Color::Muted)
.selected_icon_color(Color::Accent)
.selected_style(ButtonStyle::Filled),
move |_window, cx| {
Tooltip::with_meta("Add Context", None, "Type / to insert via keyboard", cx)
move |window, cx| {
Tooltip::with_meta(
"Add Context",
None,
"Type / to insert via keyboard",
window,
cx,
)
},
)
}
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let text_thread = self.text_thread().read(cx);
let context = self.context().read(cx);
let active_model = LanguageModelRegistry::read_global(cx)
.default_model()
.map(|default| default.model)?;
@@ -2007,7 +2003,7 @@ impl TextThreadEditor {
return None;
}
let active_completion_mode = text_thread.completion_mode();
let active_completion_mode = context.completion_mode();
let burn_mode_enabled = active_completion_mode == CompletionMode::Burn;
let icon = if burn_mode_enabled {
IconName::ZedBurnModeOn
@@ -2022,8 +2018,8 @@ impl TextThreadEditor {
.toggle_state(burn_mode_enabled)
.selected_icon_color(Color::Error)
.on_click(cx.listener(move |this, _event, _window, cx| {
this.text_thread().update(cx, |text_thread, _cx| {
text_thread.set_completion_mode(match active_completion_mode {
this.context().update(cx, |context, _cx| {
context.set_completion_mode(match active_completion_mode {
CompletionMode::Burn => CompletionMode::Normal,
CompletionMode::Normal => CompletionMode::Burn,
});
@@ -2082,8 +2078,14 @@ impl TextThreadEditor {
)
.child(Icon::new(icon).color(color).size(IconSize::XSmall)),
),
move |_window, cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
gpui::Corner::BottomRight,
cx,
@@ -2650,10 +2652,10 @@ impl FollowableItem for TextThreadEditor {
}
fn to_state_proto(&self, window: &Window, cx: &App) -> Option<proto::view::Variant> {
let text_thread = self.text_thread.read(cx);
let context = self.context.read(cx);
Some(proto::view::Variant::ContextEditor(
proto::view::ContextEditor {
context_id: text_thread.id().to_proto(),
context_id: context.id().to_proto(),
editor: if let Some(proto::view::Variant::Editor(proto)) =
self.editor.read(cx).to_state_proto(window, cx)
{
@@ -2679,22 +2681,22 @@ impl FollowableItem for TextThreadEditor {
unreachable!()
};
let text_thread_id = TextThreadId::from_proto(state.context_id);
let context_id = ContextId::from_proto(state.context_id);
let editor_state = state.editor?;
let project = workspace.read(cx).project().clone();
let agent_panel_delegate = <dyn AgentPanelDelegate>::try_global(cx)?;
let text_thread_editor_task = workspace.update(cx, |workspace, cx| {
agent_panel_delegate.open_remote_text_thread(workspace, text_thread_id, window, cx)
let context_editor_task = workspace.update(cx, |workspace, cx| {
agent_panel_delegate.open_remote_context(workspace, context_id, window, cx)
});
Some(window.spawn(cx, async move |cx| {
let text_thread_editor = text_thread_editor_task.await?;
text_thread_editor
.update_in(cx, |text_thread_editor, window, cx| {
text_thread_editor.remote_id = Some(id);
text_thread_editor.editor.update(cx, |editor, cx| {
let context_editor = context_editor_task.await?;
context_editor
.update_in(cx, |context_editor, window, cx| {
context_editor.remote_id = Some(id);
context_editor.editor.update(cx, |editor, cx| {
editor.apply_update_proto(
&project,
proto::update_view::Variant::Editor(proto::update_view::Editor {
@@ -2711,7 +2713,7 @@ impl FollowableItem for TextThreadEditor {
})
})?
.await?;
Ok(text_thread_editor)
Ok(context_editor)
}))
}
@@ -2758,7 +2760,7 @@ impl FollowableItem for TextThreadEditor {
}
fn dedup(&self, existing: &Self, _window: &Window, cx: &App) -> Option<item::Dedup> {
if existing.text_thread.read(cx).id() == self.text_thread.read(cx).id() {
if existing.context.read(cx).id() == self.context.read(cx).id() {
Some(item::Dedup::KeepExisting)
} else {
None
@@ -2770,17 +2772,17 @@ enum PendingSlashCommand {}
fn invoked_slash_command_fold_placeholder(
command_id: InvokedSlashCommandId,
text_thread: WeakEntity<TextThread>,
context: WeakEntity<AssistantContext>,
) -> FoldPlaceholder {
FoldPlaceholder {
constrain_width: false,
merge_adjacent: false,
render: Arc::new(move |fold_id, _, cx| {
let Some(text_thread) = text_thread.upgrade() else {
let Some(context) = context.upgrade() else {
return Empty.into_any();
};
let Some(command) = text_thread.read(cx).invoked_slash_command(&command_id) else {
let Some(command) = context.read(cx).invoked_slash_command(&command_id) else {
return Empty.into_any();
};
@@ -2821,15 +2823,14 @@ enum TokenState {
},
}
fn token_state(text_thread: &Entity<TextThread>, cx: &App) -> Option<TokenState> {
fn token_state(context: &Entity<AssistantContext>, cx: &App) -> Option<TokenState> {
const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
let model = LanguageModelRegistry::read_global(cx)
.default_model()?
.model;
let token_count = text_thread.read(cx).token_count()?;
let max_token_count =
model.max_token_count_for_mode(text_thread.read(cx).completion_mode().into());
let token_count = context.read(cx).token_count()?;
let max_token_count = model.max_token_count_for_mode(context.read(cx).completion_mode().into());
let token_state = if max_token_count.saturating_sub(token_count) == 0 {
TokenState::NoTokensLeft {
max_token_count,
@@ -2941,7 +2942,7 @@ mod tests {
#[gpui::test]
async fn test_copy_paste_whole_message(cx: &mut TestAppContext) {
let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(vec![
let (context, context_editor, mut cx) = setup_context_editor_text(vec![
(Role::User, "What is the Zed editor?"),
(
Role::Assistant,
@@ -2951,8 +2952,8 @@ mod tests {
],cx).await;
// Select & Copy whole user message
assert_copy_paste_text_thread_editor(
&text_thread_editor,
assert_copy_paste_context_editor(
&context_editor,
message_range(&context, 0, &mut cx),
indoc! {"
What is the Zed editor?
@@ -2963,8 +2964,8 @@ mod tests {
);
// Select & Copy whole assistant message
assert_copy_paste_text_thread_editor(
&text_thread_editor,
assert_copy_paste_context_editor(
&context_editor,
message_range(&context, 1, &mut cx),
indoc! {"
What is the Zed editor?
@@ -2978,7 +2979,7 @@ mod tests {
#[gpui::test]
async fn test_copy_paste_no_selection(cx: &mut TestAppContext) {
let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(
let (context, context_editor, mut cx) = setup_context_editor_text(
vec![
(Role::User, "user1"),
(Role::Assistant, "assistant1"),
@@ -2991,8 +2992,8 @@ mod tests {
// Copy and paste first assistant message
let message_2_range = message_range(&context, 1, &mut cx);
assert_copy_paste_text_thread_editor(
&text_thread_editor,
assert_copy_paste_context_editor(
&context_editor,
message_2_range.start..message_2_range.start,
indoc! {"
user1
@@ -3005,8 +3006,8 @@ mod tests {
// Copy and cut second assistant message
let message_3_range = message_range(&context, 2, &mut cx);
assert_copy_paste_text_thread_editor(
&text_thread_editor,
assert_copy_paste_context_editor(
&context_editor,
message_3_range.start..message_3_range.start,
indoc! {"
user1
@@ -3093,29 +3094,29 @@ mod tests {
}
}
async fn setup_text_thread_editor_text(
async fn setup_context_editor_text(
messages: Vec<(Role, &str)>,
cx: &mut TestAppContext,
) -> (
Entity<TextThread>,
Entity<AssistantContext>,
Entity<TextThreadEditor>,
VisualTestContext,
) {
cx.update(init_test);
let fs = FakeFs::new(cx.executor());
let text_thread = create_text_thread_with_messages(messages, cx);
let context = create_context_with_messages(messages, cx);
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let workspace = window.root(cx).unwrap();
let mut cx = VisualTestContext::from_window(*window, cx);
let text_thread_editor = window
let context_editor = window
.update(&mut cx, |_, window, cx| {
cx.new(|cx| {
TextThreadEditor::for_text_thread(
text_thread.clone(),
TextThreadEditor::for_context(
context.clone(),
fs,
workspace.downgrade(),
project,
@@ -3127,59 +3128,59 @@ mod tests {
})
.unwrap();
(text_thread, text_thread_editor, cx)
(context, context_editor, cx)
}
fn message_range(
text_thread: &Entity<TextThread>,
context: &Entity<AssistantContext>,
message_ix: usize,
cx: &mut TestAppContext,
) -> Range<usize> {
text_thread.update(cx, |text_thread, cx| {
text_thread
context.update(cx, |context, cx| {
context
.messages(cx)
.nth(message_ix)
.unwrap()
.anchor_range
.to_offset(&text_thread.buffer().read(cx).snapshot())
.to_offset(&context.buffer().read(cx).snapshot())
})
}
fn assert_copy_paste_text_thread_editor<T: editor::ToOffset>(
text_thread_editor: &Entity<TextThreadEditor>,
fn assert_copy_paste_context_editor<T: editor::ToOffset>(
context_editor: &Entity<TextThreadEditor>,
range: Range<T>,
expected_text: &str,
cx: &mut VisualTestContext,
) {
text_thread_editor.update_in(cx, |text_thread_editor, window, cx| {
text_thread_editor.editor.update(cx, |editor, cx| {
context_editor.update_in(cx, |context_editor, window, cx| {
context_editor.editor.update(cx, |editor, cx| {
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
s.select_ranges([range])
});
});
text_thread_editor.copy(&Default::default(), window, cx);
context_editor.copy(&Default::default(), window, cx);
text_thread_editor.editor.update(cx, |editor, cx| {
context_editor.editor.update(cx, |editor, cx| {
editor.move_to_end(&Default::default(), window, cx);
});
text_thread_editor.paste(&Default::default(), window, cx);
context_editor.paste(&Default::default(), window, cx);
text_thread_editor.editor.update(cx, |editor, cx| {
context_editor.editor.update(cx, |editor, cx| {
assert_eq!(editor.text(cx), expected_text);
});
});
}
fn create_text_thread_with_messages(
fn create_context_with_messages(
mut messages: Vec<(Role, &str)>,
cx: &mut TestAppContext,
) -> Entity<TextThread> {
) -> Entity<AssistantContext> {
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
cx.new(|cx| {
let mut text_thread = TextThread::local(
let mut context = AssistantContext::local(
registry,
None,
None,
@@ -3187,33 +3188,33 @@ mod tests {
Arc::new(SlashCommandWorkingSet::default()),
cx,
);
let mut message_1 = text_thread.messages(cx).next().unwrap();
let mut message_1 = context.messages(cx).next().unwrap();
let (role, text) = messages.remove(0);
loop {
if role == message_1.role {
text_thread.buffer().update(cx, |buffer, cx| {
context.buffer().update(cx, |buffer, cx| {
buffer.edit([(message_1.offset_range, text)], None, cx);
});
break;
}
let mut ids = HashSet::default();
ids.insert(message_1.id);
text_thread.cycle_message_roles(ids, cx);
message_1 = text_thread.messages(cx).next().unwrap();
context.cycle_message_roles(ids, cx);
message_1 = context.messages(cx).next().unwrap();
}
let mut last_message_id = message_1.id;
for (role, text) in messages {
text_thread.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
let message = text_thread.messages(cx).last().unwrap();
context.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
let message = context.messages(cx).last().unwrap();
last_message_id = message.id;
text_thread.buffer().update(cx, |buffer, cx| {
context.buffer().update(cx, |buffer, cx| {
buffer.edit([(message.offset_range, text)], None, cx);
})
}
text_thread
context
})
}

View File

@@ -18,7 +18,7 @@ impl BurnModeTooltip {
}
impl Render for BurnModeTooltip {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (icon, color) = if self.selected {
(IconName::ZedBurnModeOn, Color::Error)
} else {
@@ -45,7 +45,8 @@ impl Render for BurnModeTooltip {
.child(Label::new("Burn Mode"))
.when(self.selected, |title| title.child(turned_on));
let keybinding = KeyBinding::for_action(&ToggleBurnMode, cx).size(rems_from_px(12.));
let keybinding = KeyBinding::for_action(&ToggleBurnMode, window, cx)
.map(|kb| kb.size(rems_from_px(12.)));
tooltip_container(cx, |this, _| {
this
@@ -53,7 +54,7 @@ impl Render for BurnModeTooltip {
h_flex()
.justify_between()
.child(title)
.child(keybinding)
.children(keybinding)
)
.child(
div()

View File

@@ -244,8 +244,8 @@ impl RenderOnce for ContextPill {
.truncate(),
),
)
.tooltip(|_window, cx| {
Tooltip::with_meta("Suggested Context", None, "Click to add it", cx)
.tooltip(|window, cx| {
Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)
})
.when_some(on_click.as_ref(), |element, on_click| {
let on_click = on_click.clone();
@@ -497,9 +497,9 @@ impl AddedContext {
icon_path: None,
status: ContextStatus::Ready,
render_hover: {
let text_thread = handle.text_thread.clone();
let context = handle.context.clone();
Some(Rc::new(move |_, cx| {
let text = text_thread.read(cx).to_xml(cx);
let text = context.read(cx).to_xml(cx);
ContextPillHover::new_text(text.into(), cx).into()
}))
},

View File

@@ -84,32 +84,10 @@ impl ZedAiOnboarding {
self
}
fn render_dismiss_button(&self) -> Option<AnyElement> {
self.dismiss_onboarding.as_ref().map(|dismiss_callback| {
let callback = dismiss_callback.clone();
h_flex()
.absolute()
.top_0()
.right_0()
.child(
IconButton::new("dismiss_onboarding", IconName::Close)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Dismiss"))
.on_click(move |_, window, cx| {
telemetry::event!("Banner Dismissed", source = "AI Onboarding",);
callback(window, cx)
}),
)
.into_any_element()
})
}
fn render_sign_in_disclaimer(&self, _cx: &mut App) -> AnyElement {
let signing_in = matches!(self.sign_in_status, SignInStatus::SigningIn);
v_flex()
.relative()
.gap_1()
.child(Headline::new("Welcome to Zed AI"))
.child(
@@ -131,7 +109,6 @@ impl ZedAiOnboarding {
}
}),
)
.children(self.render_dismiss_button())
.into_any_element()
}
@@ -203,7 +180,27 @@ impl ZedAiOnboarding {
)
.child(PlanDefinitions.free_plan(is_v2)),
)
.children(self.render_dismiss_button())
.when_some(
self.dismiss_onboarding.as_ref(),
|this, dismiss_callback| {
let callback = dismiss_callback.clone();
this.child(
h_flex().absolute().top_0().right_0().child(
IconButton::new("dismiss_onboarding", IconName::Close)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Dismiss"))
.on_click(move |_, window, cx| {
telemetry::event!(
"Banner Dismissed",
source = "AI Onboarding",
);
callback(window, cx)
}),
),
)
},
)
.child(
v_flex()
.mt_2()
@@ -248,7 +245,26 @@ impl ZedAiOnboarding {
.mb_2(),
)
.child(PlanDefinitions.pro_trial(is_v2, false))
.children(self.render_dismiss_button())
.when_some(
self.dismiss_onboarding.as_ref(),
|this, dismiss_callback| {
let callback = dismiss_callback.clone();
this.child(
h_flex().absolute().top_0().right_0().child(
IconButton::new("dismiss_onboarding", IconName::Close)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Dismiss"))
.on_click(move |_, window, cx| {
telemetry::event!(
"Banner Dismissed",
source = "AI Onboarding",
);
callback(window, cx)
}),
),
)
},
)
.into_any_element()
}
@@ -262,7 +278,26 @@ impl ZedAiOnboarding {
.mb_2(),
)
.child(PlanDefinitions.pro_plan(is_v2, false))
.children(self.render_dismiss_button())
.when_some(
self.dismiss_onboarding.as_ref(),
|this, dismiss_callback| {
let callback = dismiss_callback.clone();
this.child(
h_flex().absolute().top_0().right_0().child(
IconButton::new("dismiss_onboarding", IconName::Close)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Dismiss"))
.on_click(move |_, window, cx| {
telemetry::event!(
"Banner Dismissed",
source = "AI Onboarding",
);
callback(window, cx)
}),
),
)
},
)
.into_any_element()
}
}

View File

@@ -1,5 +1,5 @@
[package]
name = "assistant_text_thread"
name = "assistant_context"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
@@ -9,7 +9,7 @@ license = "GPL-3.0-or-later"
workspace = true
[lib]
path = "src/assistant_text_thread.rs"
path = "src/assistant_context.rs"
[features]
test-support = []

View File

@@ -1,3 +1,7 @@
#[cfg(test)]
mod assistant_context_tests;
mod context_store;
use agent_settings::{AgentSettings, SUMMARIZE_THREAD_PROMPT};
use anyhow::{Context as _, Result, bail};
use assistant_slash_command::{
@@ -5,7 +9,7 @@ use assistant_slash_command::{
SlashCommandResult, SlashCommandWorkingSet,
};
use assistant_slash_commands::FileCommandMetadata;
use client::{self, ModelRequestUsage, RequestUsage, proto, telemetry::Telemetry};
use client::{self, Client, ModelRequestUsage, RequestUsage, proto, telemetry::Telemetry};
use clock::ReplicaId;
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
use collections::{HashMap, HashSet};
@@ -23,7 +27,7 @@ use language_model::{
report_assistant_event,
};
use open_ai::Model as OpenAiModel;
use paths::text_threads_dir;
use paths::contexts_dir;
use project::Project;
use prompt_store::PromptBuilder;
use serde::{Deserialize, Serialize};
@@ -44,10 +48,16 @@ use ui::IconName;
use util::{ResultExt, TryFutureExt, post_inc};
use uuid::Uuid;
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct TextThreadId(String);
pub use crate::context_store::*;
impl TextThreadId {
pub fn init(client: Arc<Client>, _: &mut App) {
context_store::init(&client.into());
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ContextId(String);
impl ContextId {
pub fn new() -> Self {
Self(Uuid::new_v4().to_string())
}
@@ -120,7 +130,7 @@ impl MessageStatus {
}
#[derive(Clone, Debug)]
pub enum TextThreadOperation {
pub enum ContextOperation {
InsertMessage {
anchor: MessageAnchor,
metadata: MessageMetadata,
@@ -132,7 +142,7 @@ pub enum TextThreadOperation {
version: clock::Global,
},
UpdateSummary {
summary: TextThreadSummaryContent,
summary: ContextSummaryContent,
version: clock::Global,
},
SlashCommandStarted {
@@ -160,7 +170,7 @@ pub enum TextThreadOperation {
BufferOperation(language::Operation),
}
impl TextThreadOperation {
impl ContextOperation {
pub fn from_proto(op: proto::ContextOperation) -> Result<Self> {
match op.variant.context("invalid variant")? {
proto::context_operation::Variant::InsertMessage(insert) => {
@@ -202,7 +212,7 @@ impl TextThreadOperation {
version: language::proto::deserialize_version(&update.version),
}),
proto::context_operation::Variant::UpdateSummary(update) => Ok(Self::UpdateSummary {
summary: TextThreadSummaryContent {
summary: ContextSummaryContent {
text: update.summary,
done: update.done,
timestamp: language::proto::deserialize_timestamp(
@@ -443,7 +453,7 @@ impl TextThreadOperation {
}
#[derive(Debug, Clone)]
pub enum TextThreadEvent {
pub enum ContextEvent {
ShowAssistError(SharedString),
ShowPaymentRequiredError,
MessagesEdited,
@@ -466,24 +476,24 @@ pub enum TextThreadEvent {
SlashCommandOutputSectionAdded {
section: SlashCommandOutputSection<language::Anchor>,
},
Operation(TextThreadOperation),
Operation(ContextOperation),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TextThreadSummary {
pub enum ContextSummary {
Pending,
Content(TextThreadSummaryContent),
Content(ContextSummaryContent),
Error,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TextThreadSummaryContent {
#[derive(Default, Clone, Debug, Eq, PartialEq)]
pub struct ContextSummaryContent {
pub text: String,
pub done: bool,
pub timestamp: clock::Lamport,
}
impl TextThreadSummary {
impl ContextSummary {
pub const DEFAULT: &str = "New Text Thread";
pub fn or_default(&self) -> SharedString {
@@ -495,48 +505,44 @@ impl TextThreadSummary {
.map_or_else(|| message.into(), |content| content.text.clone().into())
}
pub fn content(&self) -> Option<&TextThreadSummaryContent> {
pub fn content(&self) -> Option<&ContextSummaryContent> {
match self {
TextThreadSummary::Content(content) => Some(content),
TextThreadSummary::Pending | TextThreadSummary::Error => None,
ContextSummary::Content(content) => Some(content),
ContextSummary::Pending | ContextSummary::Error => None,
}
}
fn content_as_mut(&mut self) -> Option<&mut TextThreadSummaryContent> {
fn content_as_mut(&mut self) -> Option<&mut ContextSummaryContent> {
match self {
TextThreadSummary::Content(content) => Some(content),
TextThreadSummary::Pending | TextThreadSummary::Error => None,
ContextSummary::Content(content) => Some(content),
ContextSummary::Pending | ContextSummary::Error => None,
}
}
fn content_or_set_empty(&mut self) -> &mut TextThreadSummaryContent {
fn content_or_set_empty(&mut self) -> &mut ContextSummaryContent {
match self {
TextThreadSummary::Content(content) => content,
TextThreadSummary::Pending | TextThreadSummary::Error => {
let content = TextThreadSummaryContent {
text: "".to_string(),
done: false,
timestamp: clock::Lamport::MIN,
};
*self = TextThreadSummary::Content(content);
ContextSummary::Content(content) => content,
ContextSummary::Pending | ContextSummary::Error => {
let content = ContextSummaryContent::default();
*self = ContextSummary::Content(content);
self.content_as_mut().unwrap()
}
}
}
pub fn is_pending(&self) -> bool {
matches!(self, TextThreadSummary::Pending)
matches!(self, ContextSummary::Pending)
}
fn timestamp(&self) -> Option<clock::Lamport> {
match self {
TextThreadSummary::Content(content) => Some(content.timestamp),
TextThreadSummary::Pending | TextThreadSummary::Error => None,
ContextSummary::Content(content) => Some(content.timestamp),
ContextSummary::Pending | ContextSummary::Error => None,
}
}
}
impl PartialOrd for TextThreadSummary {
impl PartialOrd for ContextSummary {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.timestamp().partial_cmp(&other.timestamp())
}
@@ -658,27 +664,27 @@ struct PendingCompletion {
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct InvokedSlashCommandId(clock::Lamport);
pub struct TextThread {
id: TextThreadId,
pub struct AssistantContext {
id: ContextId,
timestamp: clock::Lamport,
version: clock::Global,
pub(crate) pending_ops: Vec<TextThreadOperation>,
operations: Vec<TextThreadOperation>,
pending_ops: Vec<ContextOperation>,
operations: Vec<ContextOperation>,
buffer: Entity<Buffer>,
pub(crate) parsed_slash_commands: Vec<ParsedSlashCommand>,
parsed_slash_commands: Vec<ParsedSlashCommand>,
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
edits_since_last_parse: language::Subscription,
slash_commands: Arc<SlashCommandWorkingSet>,
pub(crate) slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
thought_process_output_sections: Vec<ThoughtProcessOutputSection<language::Anchor>>,
pub(crate) message_anchors: Vec<MessageAnchor>,
message_anchors: Vec<MessageAnchor>,
contents: Vec<Content>,
pub(crate) messages_metadata: HashMap<MessageId, MessageMetadata>,
summary: TextThreadSummary,
messages_metadata: HashMap<MessageId, MessageMetadata>,
summary: ContextSummary,
summary_task: Task<Option<()>>,
completion_count: usize,
pending_completions: Vec<PendingCompletion>,
pub(crate) token_count: Option<u64>,
token_count: Option<u64>,
pending_token_count: Task<Option<()>>,
pending_save: Task<Result<()>>,
pending_cache_warming_task: Task<Option<()>>,
@@ -701,9 +707,9 @@ impl ContextAnnotation for ParsedSlashCommand {
}
}
impl EventEmitter<TextThreadEvent> for TextThread {}
impl EventEmitter<ContextEvent> for AssistantContext {}
impl TextThread {
impl AssistantContext {
pub fn local(
language_registry: Arc<LanguageRegistry>,
project: Option<Entity<Project>>,
@@ -713,7 +719,7 @@ impl TextThread {
cx: &mut Context<Self>,
) -> Self {
Self::new(
TextThreadId::new(),
ContextId::new(),
ReplicaId::default(),
language::Capability::ReadWrite,
language_registry,
@@ -734,7 +740,7 @@ impl TextThread {
}
pub fn new(
id: TextThreadId,
id: ContextId,
replica_id: ReplicaId,
capability: language::Capability,
language_registry: Arc<LanguageRegistry>,
@@ -770,7 +776,7 @@ impl TextThread {
slash_command_output_sections: Vec::new(),
thought_process_output_sections: Vec::new(),
edits_since_last_parse: edits_since_last_slash_command_parse,
summary: TextThreadSummary::Pending,
summary: ContextSummary::Pending,
summary_task: Task::ready(None),
completion_count: Default::default(),
pending_completions: Default::default(),
@@ -790,7 +796,7 @@ impl TextThread {
};
let first_message_id = MessageId(clock::Lamport {
replica_id: ReplicaId::LOCAL,
replica_id: 0,
value: 0,
});
let message = MessageAnchor {
@@ -813,12 +819,12 @@ impl TextThread {
this
}
pub(crate) fn serialize(&self, cx: &App) -> SavedTextThread {
pub(crate) fn serialize(&self, cx: &App) -> SavedContext {
let buffer = self.buffer.read(cx);
SavedTextThread {
SavedContext {
id: Some(self.id.clone()),
zed: "context".into(),
version: SavedTextThread::VERSION.into(),
version: SavedContext::VERSION.into(),
text: buffer.text(),
messages: self
.messages(cx)
@@ -866,7 +872,7 @@ impl TextThread {
}
pub fn deserialize(
saved_context: SavedTextThread,
saved_context: SavedContext,
path: Arc<Path>,
language_registry: Arc<LanguageRegistry>,
prompt_builder: Arc<PromptBuilder>,
@@ -875,7 +881,7 @@ impl TextThread {
telemetry: Option<Arc<Telemetry>>,
cx: &mut Context<Self>,
) -> Self {
let id = saved_context.id.clone().unwrap_or_else(TextThreadId::new);
let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
let mut this = Self::new(
id,
ReplicaId::default(),
@@ -896,7 +902,7 @@ impl TextThread {
this
}
pub fn id(&self) -> &TextThreadId {
pub fn id(&self) -> &ContextId {
&self.id
}
@@ -904,9 +910,9 @@ impl TextThread {
self.timestamp.replica_id
}
pub fn version(&self, cx: &App) -> TextThreadVersion {
TextThreadVersion {
text_thread: self.version.clone(),
pub fn version(&self, cx: &App) -> ContextVersion {
ContextVersion {
context: self.version.clone(),
buffer: self.buffer.read(cx).version(),
}
}
@@ -928,7 +934,7 @@ impl TextThread {
pub fn serialize_ops(
&self,
since: &TextThreadVersion,
since: &ContextVersion,
cx: &App,
) -> Task<Vec<proto::ContextOperation>> {
let buffer_ops = self
@@ -939,7 +945,7 @@ impl TextThread {
let mut context_ops = self
.operations
.iter()
.filter(|op| !since.text_thread.observed(op.timestamp()))
.filter(|op| !since.context.observed(op.timestamp()))
.cloned()
.collect::<Vec<_>>();
context_ops.extend(self.pending_ops.iter().cloned());
@@ -963,13 +969,13 @@ impl TextThread {
pub fn apply_ops(
&mut self,
ops: impl IntoIterator<Item = TextThreadOperation>,
ops: impl IntoIterator<Item = ContextOperation>,
cx: &mut Context<Self>,
) {
let mut buffer_ops = Vec::new();
for op in ops {
match op {
TextThreadOperation::BufferOperation(buffer_op) => buffer_ops.push(buffer_op),
ContextOperation::BufferOperation(buffer_op) => buffer_ops.push(buffer_op),
op @ _ => self.pending_ops.push(op),
}
}
@@ -978,7 +984,7 @@ impl TextThread {
self.flush_ops(cx);
}
fn flush_ops(&mut self, cx: &mut Context<TextThread>) {
fn flush_ops(&mut self, cx: &mut Context<AssistantContext>) {
let mut changed_messages = HashSet::default();
let mut summary_generated = false;
@@ -991,7 +997,7 @@ impl TextThread {
let timestamp = op.timestamp();
match op.clone() {
TextThreadOperation::InsertMessage {
ContextOperation::InsertMessage {
anchor, metadata, ..
} => {
if self.messages_metadata.contains_key(&anchor.id) {
@@ -1001,7 +1007,7 @@ impl TextThread {
self.insert_message(anchor, metadata, cx);
}
}
TextThreadOperation::UpdateMessage {
ContextOperation::UpdateMessage {
message_id,
metadata: new_metadata,
..
@@ -1012,7 +1018,7 @@ impl TextThread {
changed_messages.insert(message_id);
}
}
TextThreadOperation::UpdateSummary {
ContextOperation::UpdateSummary {
summary: new_summary,
..
} => {
@@ -1021,11 +1027,11 @@ impl TextThread {
.timestamp()
.is_none_or(|current_timestamp| new_summary.timestamp > current_timestamp)
{
self.summary = TextThreadSummary::Content(new_summary);
self.summary = ContextSummary::Content(new_summary);
summary_generated = true;
}
}
TextThreadOperation::SlashCommandStarted {
ContextOperation::SlashCommandStarted {
id,
output_range,
name,
@@ -1042,9 +1048,9 @@ impl TextThread {
timestamp: id.0,
},
);
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id: id });
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id: id });
}
TextThreadOperation::SlashCommandOutputSectionAdded { section, .. } => {
ContextOperation::SlashCommandOutputSectionAdded { section, .. } => {
let buffer = self.buffer.read(cx);
if let Err(ix) = self
.slash_command_output_sections
@@ -1052,10 +1058,10 @@ impl TextThread {
{
self.slash_command_output_sections
.insert(ix, section.clone());
cx.emit(TextThreadEvent::SlashCommandOutputSectionAdded { section });
cx.emit(ContextEvent::SlashCommandOutputSectionAdded { section });
}
}
TextThreadOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
ContextOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
let buffer = self.buffer.read(cx);
if let Err(ix) = self
.thought_process_output_sections
@@ -1065,7 +1071,7 @@ impl TextThread {
.insert(ix, section.clone());
}
}
TextThreadOperation::SlashCommandFinished {
ContextOperation::SlashCommandFinished {
id,
error_message,
timestamp,
@@ -1084,10 +1090,10 @@ impl TextThread {
slash_command.status = InvokedSlashCommandStatus::Finished;
}
}
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id: id });
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id: id });
}
}
TextThreadOperation::BufferOperation(_) => unreachable!(),
ContextOperation::BufferOperation(_) => unreachable!(),
}
self.version.observe(timestamp);
@@ -1097,43 +1103,43 @@ impl TextThread {
if !changed_messages.is_empty() {
self.message_roles_updated(changed_messages, cx);
cx.emit(TextThreadEvent::MessagesEdited);
cx.emit(ContextEvent::MessagesEdited);
cx.notify();
}
if summary_generated {
cx.emit(TextThreadEvent::SummaryChanged);
cx.emit(TextThreadEvent::SummaryGenerated);
cx.emit(ContextEvent::SummaryChanged);
cx.emit(ContextEvent::SummaryGenerated);
cx.notify();
}
}
fn can_apply_op(&self, op: &TextThreadOperation, cx: &App) -> bool {
fn can_apply_op(&self, op: &ContextOperation, cx: &App) -> bool {
if !self.version.observed_all(op.version()) {
return false;
}
match op {
TextThreadOperation::InsertMessage { anchor, .. } => self
ContextOperation::InsertMessage { anchor, .. } => self
.buffer
.read(cx)
.version
.observed(anchor.start.timestamp),
TextThreadOperation::UpdateMessage { message_id, .. } => {
ContextOperation::UpdateMessage { message_id, .. } => {
self.messages_metadata.contains_key(message_id)
}
TextThreadOperation::UpdateSummary { .. } => true,
TextThreadOperation::SlashCommandStarted { output_range, .. } => {
ContextOperation::UpdateSummary { .. } => true,
ContextOperation::SlashCommandStarted { output_range, .. } => {
self.has_received_operations_for_anchor_range(output_range.clone(), cx)
}
TextThreadOperation::SlashCommandOutputSectionAdded { section, .. } => {
ContextOperation::SlashCommandOutputSectionAdded { section, .. } => {
self.has_received_operations_for_anchor_range(section.range.clone(), cx)
}
TextThreadOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
ContextOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
self.has_received_operations_for_anchor_range(section.range.clone(), cx)
}
TextThreadOperation::SlashCommandFinished { .. } => true,
TextThreadOperation::BufferOperation(_) => {
ContextOperation::SlashCommandFinished { .. } => true,
ContextOperation::BufferOperation(_) => {
panic!("buffer operations should always be applied")
}
}
@@ -1154,9 +1160,9 @@ impl TextThread {
observed_start && observed_end
}
fn push_op(&mut self, op: TextThreadOperation, cx: &mut Context<Self>) {
fn push_op(&mut self, op: ContextOperation, cx: &mut Context<Self>) {
self.operations.push(op.clone());
cx.emit(TextThreadEvent::Operation(op));
cx.emit(ContextEvent::Operation(op));
}
pub fn buffer(&self) -> &Entity<Buffer> {
@@ -1179,7 +1185,7 @@ impl TextThread {
self.path.as_ref()
}
pub fn summary(&self) -> &TextThreadSummary {
pub fn summary(&self) -> &ContextSummary {
&self.summary
}
@@ -1240,13 +1246,13 @@ impl TextThread {
language::BufferEvent::Operation {
operation,
is_local: true,
} => cx.emit(TextThreadEvent::Operation(
TextThreadOperation::BufferOperation(operation.clone()),
)),
} => cx.emit(ContextEvent::Operation(ContextOperation::BufferOperation(
operation.clone(),
))),
language::BufferEvent::Edited => {
self.count_remaining_tokens(cx);
self.reparse(cx);
cx.emit(TextThreadEvent::MessagesEdited);
cx.emit(ContextEvent::MessagesEdited);
}
_ => {}
}
@@ -1512,7 +1518,7 @@ impl TextThread {
if !updated_parsed_slash_commands.is_empty()
|| !removed_parsed_slash_command_ranges.is_empty()
{
cx.emit(TextThreadEvent::ParsedSlashCommandsUpdated {
cx.emit(ContextEvent::ParsedSlashCommandsUpdated {
removed: removed_parsed_slash_command_ranges,
updated: updated_parsed_slash_commands,
});
@@ -1586,7 +1592,7 @@ impl TextThread {
&& (!command.range.start.is_valid(buffer) || !command.range.end.is_valid(buffer))
{
command.status = InvokedSlashCommandStatus::Finished;
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
invalidated_command_ids.push(command_id);
}
}
@@ -1595,7 +1601,7 @@ impl TextThread {
let version = self.version.clone();
let timestamp = self.next_timestamp();
self.push_op(
TextThreadOperation::SlashCommandFinished {
ContextOperation::SlashCommandFinished {
id: command_id,
timestamp,
error_message: None,
@@ -1900,9 +1906,9 @@ impl TextThread {
}
}
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
this.push_op(
TextThreadOperation::SlashCommandFinished {
ContextOperation::SlashCommandFinished {
id: command_id,
timestamp,
error_message,
@@ -1925,9 +1931,9 @@ impl TextThread {
timestamp: command_id.0,
},
);
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
self.push_op(
TextThreadOperation::SlashCommandStarted {
ContextOperation::SlashCommandStarted {
id: command_id,
output_range: command_range,
name: name.to_string(),
@@ -1951,13 +1957,13 @@ impl TextThread {
};
self.slash_command_output_sections
.insert(insertion_ix, section.clone());
cx.emit(TextThreadEvent::SlashCommandOutputSectionAdded {
cx.emit(ContextEvent::SlashCommandOutputSectionAdded {
section: section.clone(),
});
let version = self.version.clone();
let timestamp = self.next_timestamp();
self.push_op(
TextThreadOperation::SlashCommandOutputSectionAdded {
ContextOperation::SlashCommandOutputSectionAdded {
timestamp,
section,
version,
@@ -1986,7 +1992,7 @@ impl TextThread {
let version = self.version.clone();
let timestamp = self.next_timestamp();
self.push_op(
TextThreadOperation::ThoughtProcessOutputSectionAdded {
ContextOperation::ThoughtProcessOutputSectionAdded {
timestamp,
section,
version,
@@ -2105,7 +2111,7 @@ impl TextThread {
let end = buffer
.anchor_before(message_old_end_offset + chunk_len);
context_event = Some(
TextThreadEvent::StartedThoughtProcess(start..end),
ContextEvent::StartedThoughtProcess(start..end),
);
} else {
// This ensures that all the thinking chunks are inserted inside the thinking tag
@@ -2123,7 +2129,7 @@ impl TextThread {
if let Some(start) = thought_process_stack.pop() {
let end = buffer.anchor_before(message_old_end_offset);
context_event =
Some(TextThreadEvent::EndedThoughtProcess(end));
Some(ContextEvent::EndedThoughtProcess(end));
thought_process_output_section =
Some(ThoughtProcessOutputSection {
range: start..end,
@@ -2153,7 +2159,7 @@ impl TextThread {
cx.emit(context_event);
}
cx.emit(TextThreadEvent::StreamedCompletion);
cx.emit(ContextEvent::StreamedCompletion);
Some(())
})?;
@@ -2174,7 +2180,7 @@ impl TextThread {
this.update(cx, |this, cx| {
let error_message = if let Some(error) = result.as_ref().err() {
if error.is::<PaymentRequiredError>() {
cx.emit(TextThreadEvent::ShowPaymentRequiredError);
cx.emit(ContextEvent::ShowPaymentRequiredError);
this.update_metadata(assistant_message_id, cx, |metadata| {
metadata.status = MessageStatus::Canceled;
});
@@ -2185,7 +2191,7 @@ impl TextThread {
.map(|err| err.to_string())
.collect::<Vec<_>>()
.join("\n");
cx.emit(TextThreadEvent::ShowAssistError(SharedString::from(
cx.emit(ContextEvent::ShowAssistError(SharedString::from(
error_message.clone(),
)));
this.update_metadata(assistant_message_id, cx, |metadata| {
@@ -2402,13 +2408,13 @@ impl TextThread {
if let Some(metadata) = self.messages_metadata.get_mut(&id) {
f(metadata);
metadata.timestamp = timestamp;
let operation = TextThreadOperation::UpdateMessage {
let operation = ContextOperation::UpdateMessage {
message_id: id,
metadata: metadata.clone(),
version,
};
self.push_op(operation, cx);
cx.emit(TextThreadEvent::MessagesEdited);
cx.emit(ContextEvent::MessagesEdited);
cx.notify();
}
}
@@ -2472,7 +2478,7 @@ impl TextThread {
};
self.insert_message(anchor.clone(), metadata.clone(), cx);
self.push_op(
TextThreadOperation::InsertMessage {
ContextOperation::InsertMessage {
anchor: anchor.clone(),
metadata,
version,
@@ -2495,7 +2501,7 @@ impl TextThread {
Err(ix) => ix,
};
self.contents.insert(insertion_ix, content);
cx.emit(TextThreadEvent::MessagesEdited);
cx.emit(ContextEvent::MessagesEdited);
}
pub fn contents<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = Content> {
@@ -2570,7 +2576,7 @@ impl TextThread {
};
self.insert_message(suffix.clone(), suffix_metadata.clone(), cx);
self.push_op(
TextThreadOperation::InsertMessage {
ContextOperation::InsertMessage {
anchor: suffix.clone(),
metadata: suffix_metadata,
version,
@@ -2620,7 +2626,7 @@ impl TextThread {
};
self.insert_message(selection.clone(), selection_metadata.clone(), cx);
self.push_op(
TextThreadOperation::InsertMessage {
ContextOperation::InsertMessage {
anchor: selection.clone(),
metadata: selection_metadata,
version,
@@ -2632,7 +2638,7 @@ impl TextThread {
};
if !edited_buffer {
cx.emit(TextThreadEvent::MessagesEdited);
cx.emit(ContextEvent::MessagesEdited);
}
new_messages
} else {
@@ -2646,7 +2652,7 @@ impl TextThread {
new_metadata: MessageMetadata,
cx: &mut Context<Self>,
) {
cx.emit(TextThreadEvent::MessagesEdited);
cx.emit(ContextEvent::MessagesEdited);
self.messages_metadata.insert(new_anchor.id, new_metadata);
@@ -2682,15 +2688,15 @@ impl TextThread {
// If there is no summary, it is set with `done: false` so that "Loading Summary…" can
// be displayed.
match self.summary {
TextThreadSummary::Pending | TextThreadSummary::Error => {
self.summary = TextThreadSummary::Content(TextThreadSummaryContent {
ContextSummary::Pending | ContextSummary::Error => {
self.summary = ContextSummary::Content(ContextSummaryContent {
text: "".to_string(),
done: false,
timestamp: clock::Lamport::MIN,
timestamp: clock::Lamport::default(),
});
replace_old = true;
}
TextThreadSummary::Content(_) => {}
ContextSummary::Content(_) => {}
}
self.summary_task = cx.spawn(async move |this, cx| {
@@ -2712,13 +2718,13 @@ impl TextThread {
}
summary.text.extend(lines.next());
summary.timestamp = timestamp;
let operation = TextThreadOperation::UpdateSummary {
let operation = ContextOperation::UpdateSummary {
summary: summary.clone(),
version,
};
this.push_op(operation, cx);
cx.emit(TextThreadEvent::SummaryChanged);
cx.emit(TextThreadEvent::SummaryGenerated);
cx.emit(ContextEvent::SummaryChanged);
cx.emit(ContextEvent::SummaryGenerated);
})?;
// Stop if the LLM generated multiple lines.
@@ -2742,13 +2748,13 @@ impl TextThread {
if let Some(summary) = this.summary.content_as_mut() {
summary.done = true;
summary.timestamp = timestamp;
let operation = TextThreadOperation::UpdateSummary {
let operation = ContextOperation::UpdateSummary {
summary: summary.clone(),
version,
};
this.push_op(operation, cx);
cx.emit(TextThreadEvent::SummaryChanged);
cx.emit(TextThreadEvent::SummaryGenerated);
cx.emit(ContextEvent::SummaryChanged);
cx.emit(ContextEvent::SummaryGenerated);
}
})?;
@@ -2758,8 +2764,8 @@ impl TextThread {
if let Err(err) = result {
this.update(cx, |this, cx| {
this.summary = TextThreadSummary::Error;
cx.emit(TextThreadEvent::SummaryChanged);
this.summary = ContextSummary::Error;
cx.emit(ContextEvent::SummaryChanged);
})
.log_err();
log::error!("Error generating context summary: {}", err);
@@ -2865,7 +2871,7 @@ impl TextThread {
&mut self,
debounce: Option<Duration>,
fs: Arc<dyn Fs>,
cx: &mut Context<TextThread>,
cx: &mut Context<AssistantContext>,
) {
if self.replica_id() != ReplicaId::default() {
// Prevent saving a remote context for now.
@@ -2896,7 +2902,7 @@ impl TextThread {
let mut discriminant = 1;
let mut new_path;
loop {
new_path = text_threads_dir().join(&format!(
new_path = contexts_dir().join(&format!(
"{} - {}.zed.json",
summary.trim(),
discriminant
@@ -2908,7 +2914,7 @@ impl TextThread {
}
}
fs.create_dir(text_threads_dir().as_ref()).await?;
fs.create_dir(contexts_dir().as_ref()).await?;
// rename before write ensures that only one file exists
if let Some(old_path) = old_path.as_ref()
@@ -2930,7 +2936,7 @@ impl TextThread {
let new_path: Arc<Path> = new_path.clone().into();
move |this, cx| {
this.path = Some(new_path.clone());
cx.emit(TextThreadEvent::PathChanged { old_path, new_path });
cx.emit(ContextEvent::PathChanged { old_path, new_path });
}
})
.ok();
@@ -2949,7 +2955,7 @@ impl TextThread {
summary.timestamp = timestamp;
summary.done = true;
summary.text = custom_summary;
cx.emit(TextThreadEvent::SummaryChanged);
cx.emit(ContextEvent::SummaryChanged);
}
fn update_model_request_usage(&self, amount: u32, limit: UsageLimit, cx: &mut App) {
@@ -2969,23 +2975,23 @@ impl TextThread {
}
#[derive(Debug, Default)]
pub struct TextThreadVersion {
text_thread: clock::Global,
pub struct ContextVersion {
context: clock::Global,
buffer: clock::Global,
}
impl TextThreadVersion {
impl ContextVersion {
pub fn from_proto(proto: &proto::ContextVersion) -> Self {
Self {
text_thread: language::proto::deserialize_version(&proto.context_version),
context: language::proto::deserialize_version(&proto.context_version),
buffer: language::proto::deserialize_version(&proto.buffer_version),
}
}
pub fn to_proto(&self, context_id: TextThreadId) -> proto::ContextVersion {
pub fn to_proto(&self, context_id: ContextId) -> proto::ContextVersion {
proto::ContextVersion {
context_id: context_id.to_proto(),
context_version: language::proto::serialize_version(&self.text_thread),
context_version: language::proto::serialize_version(&self.context),
buffer_version: language::proto::serialize_version(&self.buffer),
}
}
@@ -3053,8 +3059,8 @@ pub struct SavedMessage {
}
#[derive(Serialize, Deserialize)]
pub struct SavedTextThread {
pub id: Option<TextThreadId>,
pub struct SavedContext {
pub id: Option<ContextId>,
pub zed: String,
pub version: String,
pub text: String,
@@ -3066,7 +3072,7 @@ pub struct SavedTextThread {
pub thought_process_output_sections: Vec<ThoughtProcessOutputSection<usize>>,
}
impl SavedTextThread {
impl SavedContext {
pub const VERSION: &'static str = "0.4.0";
pub fn from_json(json: &str) -> Result<Self> {
@@ -3076,9 +3082,9 @@ impl SavedTextThread {
.context("version not found")?
{
serde_json::Value::String(version) => match version.as_str() {
SavedTextThread::VERSION => Ok(serde_json::from_value::<SavedTextThread>(
saved_context_json,
)?),
SavedContext::VERSION => {
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
}
SavedContextV0_3_0::VERSION => {
let saved_context =
serde_json::from_value::<SavedContextV0_3_0>(saved_context_json)?;
@@ -3103,18 +3109,18 @@ impl SavedTextThread {
fn into_ops(
self,
buffer: &Entity<Buffer>,
cx: &mut Context<TextThread>,
) -> Vec<TextThreadOperation> {
cx: &mut Context<AssistantContext>,
) -> Vec<ContextOperation> {
let mut operations = Vec::new();
let mut version = clock::Global::new();
let mut next_timestamp = clock::Lamport::new(ReplicaId::default());
let mut first_message_metadata = None;
for message in self.messages {
if message.id == MessageId(clock::Lamport::MIN) {
if message.id == MessageId(clock::Lamport::default()) {
first_message_metadata = Some(message.metadata);
} else {
operations.push(TextThreadOperation::InsertMessage {
operations.push(ContextOperation::InsertMessage {
anchor: MessageAnchor {
id: message.id,
start: buffer.read(cx).anchor_before(message.start),
@@ -3134,8 +3140,8 @@ impl SavedTextThread {
if let Some(metadata) = first_message_metadata {
let timestamp = next_timestamp.tick();
operations.push(TextThreadOperation::UpdateMessage {
message_id: MessageId(clock::Lamport::MIN),
operations.push(ContextOperation::UpdateMessage {
message_id: MessageId(clock::Lamport::default()),
metadata: MessageMetadata {
role: metadata.role,
status: metadata.status,
@@ -3150,7 +3156,7 @@ impl SavedTextThread {
let buffer = buffer.read(cx);
for section in self.slash_command_output_sections {
let timestamp = next_timestamp.tick();
operations.push(TextThreadOperation::SlashCommandOutputSectionAdded {
operations.push(ContextOperation::SlashCommandOutputSectionAdded {
timestamp,
section: SlashCommandOutputSection {
range: buffer.anchor_after(section.range.start)
@@ -3167,7 +3173,7 @@ impl SavedTextThread {
for section in self.thought_process_output_sections {
let timestamp = next_timestamp.tick();
operations.push(TextThreadOperation::ThoughtProcessOutputSectionAdded {
operations.push(ContextOperation::ThoughtProcessOutputSectionAdded {
timestamp,
section: ThoughtProcessOutputSection {
range: buffer.anchor_after(section.range.start)
@@ -3180,8 +3186,8 @@ impl SavedTextThread {
}
let timestamp = next_timestamp.tick();
operations.push(TextThreadOperation::UpdateSummary {
summary: TextThreadSummaryContent {
operations.push(ContextOperation::UpdateSummary {
summary: ContextSummaryContent {
text: self.summary,
done: true,
timestamp,
@@ -3211,7 +3217,7 @@ struct SavedMessageMetadataPreV0_4_0 {
#[derive(Serialize, Deserialize)]
struct SavedContextV0_3_0 {
id: Option<TextThreadId>,
id: Option<ContextId>,
zed: String,
version: String,
text: String,
@@ -3224,11 +3230,11 @@ struct SavedContextV0_3_0 {
impl SavedContextV0_3_0 {
const VERSION: &'static str = "0.3.0";
fn upgrade(self) -> SavedTextThread {
SavedTextThread {
fn upgrade(self) -> SavedContext {
SavedContext {
id: self.id,
zed: self.zed,
version: SavedTextThread::VERSION.into(),
version: SavedContext::VERSION.into(),
text: self.text,
messages: self
.messages
@@ -3260,7 +3266,7 @@ impl SavedContextV0_3_0 {
#[derive(Serialize, Deserialize)]
struct SavedContextV0_2_0 {
id: Option<TextThreadId>,
id: Option<ContextId>,
zed: String,
version: String,
text: String,
@@ -3272,7 +3278,7 @@ struct SavedContextV0_2_0 {
impl SavedContextV0_2_0 {
const VERSION: &'static str = "0.2.0";
fn upgrade(self) -> SavedTextThread {
fn upgrade(self) -> SavedContext {
SavedContextV0_3_0 {
id: self.id,
zed: self.zed,
@@ -3289,7 +3295,7 @@ impl SavedContextV0_2_0 {
#[derive(Serialize, Deserialize)]
struct SavedContextV0_1_0 {
id: Option<TextThreadId>,
id: Option<ContextId>,
zed: String,
version: String,
text: String,
@@ -3303,7 +3309,7 @@ struct SavedContextV0_1_0 {
impl SavedContextV0_1_0 {
const VERSION: &'static str = "0.1.0";
fn upgrade(self) -> SavedTextThread {
fn upgrade(self) -> SavedContext {
SavedContextV0_2_0 {
id: self.id,
zed: self.zed,
@@ -3318,7 +3324,7 @@ impl SavedContextV0_1_0 {
}
#[derive(Debug, Clone)]
pub struct SavedTextThreadMetadata {
pub struct SavedContextMetadata {
pub title: SharedString,
pub path: Arc<Path>,
pub mtime: chrono::DateTime<chrono::Local>,

View File

@@ -1,6 +1,6 @@
use crate::{
SavedTextThread, SavedTextThreadMetadata, TextThread, TextThreadEvent, TextThreadId,
TextThreadOperation, TextThreadVersion,
AssistantContext, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
SavedContextMetadata,
};
use anyhow::{Context as _, Result};
use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
@@ -11,9 +11,9 @@ use context_server::ContextServerId;
use fs::{Fs, RemoveOptions};
use futures::StreamExt;
use fuzzy::StringMatchCandidate;
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, Task, WeakEntity};
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
use language::LanguageRegistry;
use paths::text_threads_dir;
use paths::contexts_dir;
use project::{
Project,
context_server_store::{ContextServerStatus, ContextServerStore},
@@ -27,24 +27,24 @@ use util::{ResultExt, TryFutureExt};
use zed_env_vars::ZED_STATELESS;
pub(crate) fn init(client: &AnyProtoClient) {
client.add_entity_message_handler(TextThreadStore::handle_advertise_contexts);
client.add_entity_request_handler(TextThreadStore::handle_open_context);
client.add_entity_request_handler(TextThreadStore::handle_create_context);
client.add_entity_message_handler(TextThreadStore::handle_update_context);
client.add_entity_request_handler(TextThreadStore::handle_synchronize_contexts);
client.add_entity_message_handler(ContextStore::handle_advertise_contexts);
client.add_entity_request_handler(ContextStore::handle_open_context);
client.add_entity_request_handler(ContextStore::handle_create_context);
client.add_entity_message_handler(ContextStore::handle_update_context);
client.add_entity_request_handler(ContextStore::handle_synchronize_contexts);
}
#[derive(Clone)]
pub struct RemoteTextThreadMetadata {
pub id: TextThreadId,
pub struct RemoteContextMetadata {
pub id: ContextId,
pub summary: Option<String>,
}
pub struct TextThreadStore {
text_threads: Vec<TextThreadHandle>,
text_threads_metadata: Vec<SavedTextThreadMetadata>,
pub struct ContextStore {
contexts: Vec<ContextHandle>,
contexts_metadata: Vec<SavedContextMetadata>,
context_server_slash_command_ids: HashMap<ContextServerId, Vec<SlashCommandId>>,
host_text_threads: Vec<RemoteTextThreadMetadata>,
host_contexts: Vec<RemoteContextMetadata>,
fs: Arc<dyn Fs>,
languages: Arc<LanguageRegistry>,
slash_commands: Arc<SlashCommandWorkingSet>,
@@ -58,28 +58,34 @@ pub struct TextThreadStore {
prompt_builder: Arc<PromptBuilder>,
}
enum TextThreadHandle {
Weak(WeakEntity<TextThread>),
Strong(Entity<TextThread>),
pub enum ContextStoreEvent {
ContextCreated(ContextId),
}
impl TextThreadHandle {
fn upgrade(&self) -> Option<Entity<TextThread>> {
impl EventEmitter<ContextStoreEvent> for ContextStore {}
enum ContextHandle {
Weak(WeakEntity<AssistantContext>),
Strong(Entity<AssistantContext>),
}
impl ContextHandle {
fn upgrade(&self) -> Option<Entity<AssistantContext>> {
match self {
TextThreadHandle::Weak(weak) => weak.upgrade(),
TextThreadHandle::Strong(strong) => Some(strong.clone()),
ContextHandle::Weak(weak) => weak.upgrade(),
ContextHandle::Strong(strong) => Some(strong.clone()),
}
}
fn downgrade(&self) -> WeakEntity<TextThread> {
fn downgrade(&self) -> WeakEntity<AssistantContext> {
match self {
TextThreadHandle::Weak(weak) => weak.clone(),
TextThreadHandle::Strong(strong) => strong.downgrade(),
ContextHandle::Weak(weak) => weak.clone(),
ContextHandle::Strong(strong) => strong.downgrade(),
}
}
}
impl TextThreadStore {
impl ContextStore {
pub fn new(
project: Entity<Project>,
prompt_builder: Arc<PromptBuilder>,
@@ -91,14 +97,14 @@ impl TextThreadStore {
let telemetry = project.read(cx).client().telemetry().clone();
cx.spawn(async move |cx| {
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
let (mut events, _) = fs.watch(text_threads_dir(), CONTEXT_WATCH_DURATION).await;
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
let this = cx.new(|cx: &mut Context<Self>| {
let mut this = Self {
text_threads: Vec::new(),
text_threads_metadata: Vec::new(),
contexts: Vec::new(),
contexts_metadata: Vec::new(),
context_server_slash_command_ids: HashMap::default(),
host_text_threads: Vec::new(),
host_contexts: Vec::new(),
fs,
languages,
slash_commands,
@@ -136,10 +142,10 @@ impl TextThreadStore {
#[cfg(any(test, feature = "test-support"))]
pub fn fake(project: Entity<Project>, cx: &mut Context<Self>) -> Self {
Self {
text_threads: Default::default(),
text_threads_metadata: Default::default(),
contexts: Default::default(),
contexts_metadata: Default::default(),
context_server_slash_command_ids: Default::default(),
host_text_threads: Default::default(),
host_contexts: Default::default(),
fs: project.read(cx).fs().clone(),
languages: project.read(cx).languages().clone(),
slash_commands: Arc::default(),
@@ -160,13 +166,13 @@ impl TextThreadStore {
mut cx: AsyncApp,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
this.host_text_threads = envelope
this.host_contexts = envelope
.payload
.contexts
.into_iter()
.map(|text_thread| RemoteTextThreadMetadata {
id: TextThreadId::from_proto(text_thread.context_id),
summary: text_thread.summary,
.map(|context| RemoteContextMetadata {
id: ContextId::from_proto(context.context_id),
summary: context.summary,
})
.collect();
cx.notify();
@@ -178,25 +184,25 @@ impl TextThreadStore {
envelope: TypedEnvelope<proto::OpenContext>,
mut cx: AsyncApp,
) -> Result<proto::OpenContextResponse> {
let context_id = TextThreadId::from_proto(envelope.payload.context_id);
let context_id = ContextId::from_proto(envelope.payload.context_id);
let operations = this.update(&mut cx, |this, cx| {
anyhow::ensure!(
!this.project.read(cx).is_via_collab(),
"only the host contexts can be opened"
);
let text_thread = this
.loaded_text_thread_for_id(&context_id, cx)
let context = this
.loaded_context_for_id(&context_id, cx)
.context("context not found")?;
anyhow::ensure!(
text_thread.read(cx).replica_id() == ReplicaId::default(),
context.read(cx).replica_id() == ReplicaId::default(),
"context must be opened via the host"
);
anyhow::Ok(
text_thread
context
.read(cx)
.serialize_ops(&TextThreadVersion::default(), cx),
.serialize_ops(&ContextVersion::default(), cx),
)
})??;
let operations = operations.await;
@@ -216,14 +222,15 @@ impl TextThreadStore {
"can only create contexts as the host"
);
let text_thread = this.create(cx);
let context_id = text_thread.read(cx).id().clone();
let context = this.create(cx);
let context_id = context.read(cx).id().clone();
cx.emit(ContextStoreEvent::ContextCreated(context_id.clone()));
anyhow::Ok((
context_id,
text_thread
context
.read(cx)
.serialize_ops(&TextThreadVersion::default(), cx),
.serialize_ops(&ContextVersion::default(), cx),
))
})??;
let operations = operations.await;
@@ -239,11 +246,11 @@ impl TextThreadStore {
mut cx: AsyncApp,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
let context_id = TextThreadId::from_proto(envelope.payload.context_id);
if let Some(text_thread) = this.loaded_text_thread_for_id(&context_id, cx) {
let context_id = ContextId::from_proto(envelope.payload.context_id);
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
let operation_proto = envelope.payload.operation.context("invalid operation")?;
let operation = TextThreadOperation::from_proto(operation_proto)?;
text_thread.update(cx, |text_thread, cx| text_thread.apply_ops([operation], cx));
let operation = ContextOperation::from_proto(operation_proto)?;
context.update(cx, |context, cx| context.apply_ops([operation], cx));
}
Ok(())
})?
@@ -262,12 +269,12 @@ impl TextThreadStore {
let mut local_versions = Vec::new();
for remote_version_proto in envelope.payload.contexts {
let remote_version = TextThreadVersion::from_proto(&remote_version_proto);
let context_id = TextThreadId::from_proto(remote_version_proto.context_id);
if let Some(text_thread) = this.loaded_text_thread_for_id(&context_id, cx) {
let text_thread = text_thread.read(cx);
let operations = text_thread.serialize_ops(&remote_version, cx);
local_versions.push(text_thread.version(cx).to_proto(context_id.clone()));
let remote_version = ContextVersion::from_proto(&remote_version_proto);
let context_id = ContextId::from_proto(remote_version_proto.context_id);
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
let context = context.read(cx);
let operations = context.serialize_ops(&remote_version, cx);
local_versions.push(context.version(cx).to_proto(context_id.clone()));
let client = this.client.clone();
let project_id = envelope.payload.project_id;
cx.background_spawn(async move {
@@ -301,9 +308,9 @@ impl TextThreadStore {
}
if is_shared {
self.text_threads.retain_mut(|text_thread| {
if let Some(strong_context) = text_thread.upgrade() {
*text_thread = TextThreadHandle::Strong(strong_context);
self.contexts.retain_mut(|context| {
if let Some(strong_context) = context.upgrade() {
*context = ContextHandle::Strong(strong_context);
true
} else {
false
@@ -338,12 +345,12 @@ impl TextThreadStore {
self.synchronize_contexts(cx);
}
project::Event::DisconnectedFromHost => {
self.text_threads.retain_mut(|text_thread| {
if let Some(strong_context) = text_thread.upgrade() {
*text_thread = TextThreadHandle::Weak(text_thread.downgrade());
strong_context.update(cx, |text_thread, cx| {
if text_thread.replica_id() != ReplicaId::default() {
text_thread.set_capability(language::Capability::ReadOnly, cx);
self.contexts.retain_mut(|context| {
if let Some(strong_context) = context.upgrade() {
*context = ContextHandle::Weak(context.downgrade());
strong_context.update(cx, |context, cx| {
if context.replica_id() != ReplicaId::default() {
context.set_capability(language::Capability::ReadOnly, cx);
}
});
true
@@ -351,24 +358,20 @@ impl TextThreadStore {
false
}
});
self.host_text_threads.clear();
self.host_contexts.clear();
cx.notify();
}
_ => {}
}
}
pub fn unordered_text_threads(&self) -> impl Iterator<Item = &SavedTextThreadMetadata> {
self.text_threads_metadata.iter()
pub fn unordered_contexts(&self) -> impl Iterator<Item = &SavedContextMetadata> {
self.contexts_metadata.iter()
}
pub fn host_text_threads(&self) -> impl Iterator<Item = &RemoteTextThreadMetadata> {
self.host_text_threads.iter()
}
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<TextThread> {
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
let context = cx.new(|cx| {
TextThread::local(
AssistantContext::local(
self.languages.clone(),
Some(self.project.clone()),
Some(self.telemetry.clone()),
@@ -377,11 +380,14 @@ impl TextThreadStore {
cx,
)
});
self.register_text_thread(&context, cx);
self.register_context(&context, cx);
context
}
pub fn create_remote(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<TextThread>>> {
pub fn create_remote_context(
&mut self,
cx: &mut Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
let project = self.project.read(cx);
let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
@@ -397,10 +403,10 @@ impl TextThreadStore {
let request = self.client.request(proto::CreateContext { project_id });
cx.spawn(async move |this, cx| {
let response = request.await?;
let context_id = TextThreadId::from_proto(response.context_id);
let context_id = ContextId::from_proto(response.context_id);
let context_proto = response.context.context("invalid context")?;
let text_thread = cx.new(|cx| {
TextThread::new(
let context = cx.new(|cx| {
AssistantContext::new(
context_id.clone(),
replica_id,
capability,
@@ -417,29 +423,29 @@ impl TextThreadStore {
context_proto
.operations
.into_iter()
.map(TextThreadOperation::from_proto)
.map(ContextOperation::from_proto)
.collect::<Result<Vec<_>>>()
})
.await?;
text_thread.update(cx, |context, cx| context.apply_ops(operations, cx))?;
context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
this.update(cx, |this, cx| {
if let Some(existing_context) = this.loaded_text_thread_for_id(&context_id, cx) {
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
existing_context
} else {
this.register_text_thread(&text_thread, cx);
this.register_context(&context, cx);
this.synchronize_contexts(cx);
text_thread
context
}
})
})
}
pub fn open_local(
pub fn open_local_context(
&mut self,
path: Arc<Path>,
cx: &Context<Self>,
) -> Task<Result<Entity<TextThread>>> {
if let Some(existing_context) = self.loaded_text_thread_for_path(&path, cx) {
) -> Task<Result<Entity<AssistantContext>>> {
if let Some(existing_context) = self.loaded_context_for_path(&path, cx) {
return Task::ready(Ok(existing_context));
}
@@ -451,7 +457,7 @@ impl TextThreadStore {
let path = path.clone();
async move {
let saved_context = fs.load(&path).await?;
SavedTextThread::from_json(&saved_context)
SavedContext::from_json(&saved_context)
}
});
let prompt_builder = self.prompt_builder.clone();
@@ -460,7 +466,7 @@ impl TextThreadStore {
cx.spawn(async move |this, cx| {
let saved_context = load.await?;
let context = cx.new(|cx| {
TextThread::deserialize(
AssistantContext::deserialize(
saved_context,
path.clone(),
languages,
@@ -472,17 +478,21 @@ impl TextThreadStore {
)
})?;
this.update(cx, |this, cx| {
if let Some(existing_context) = this.loaded_text_thread_for_path(&path, cx) {
if let Some(existing_context) = this.loaded_context_for_path(&path, cx) {
existing_context
} else {
this.register_text_thread(&context, cx);
this.register_context(&context, cx);
context
}
})
})
}
pub fn delete_local(&mut self, path: Arc<Path>, cx: &mut Context<Self>) -> Task<Result<()>> {
pub fn delete_local_context(
&mut self,
path: Arc<Path>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let fs = self.fs.clone();
cx.spawn(async move |this, cx| {
@@ -496,57 +506,57 @@ impl TextThreadStore {
.await?;
this.update(cx, |this, cx| {
this.text_threads.retain(|text_thread| {
text_thread
this.contexts.retain(|context| {
context
.upgrade()
.and_then(|text_thread| text_thread.read(cx).path())
.and_then(|context| context.read(cx).path())
!= Some(&path)
});
this.text_threads_metadata
.retain(|text_thread| text_thread.path.as_ref() != path.as_ref());
this.contexts_metadata
.retain(|context| context.path.as_ref() != path.as_ref());
})?;
Ok(())
})
}
fn loaded_text_thread_for_path(&self, path: &Path, cx: &App) -> Option<Entity<TextThread>> {
self.text_threads.iter().find_map(|text_thread| {
let text_thread = text_thread.upgrade()?;
if text_thread.read(cx).path().map(Arc::as_ref) == Some(path) {
Some(text_thread)
fn loaded_context_for_path(&self, path: &Path, cx: &App) -> Option<Entity<AssistantContext>> {
self.contexts.iter().find_map(|context| {
let context = context.upgrade()?;
if context.read(cx).path().map(Arc::as_ref) == Some(path) {
Some(context)
} else {
None
}
})
}
pub fn loaded_text_thread_for_id(
pub fn loaded_context_for_id(
&self,
id: &TextThreadId,
id: &ContextId,
cx: &App,
) -> Option<Entity<TextThread>> {
self.text_threads.iter().find_map(|text_thread| {
let text_thread = text_thread.upgrade()?;
if text_thread.read(cx).id() == id {
Some(text_thread)
) -> Option<Entity<AssistantContext>> {
self.contexts.iter().find_map(|context| {
let context = context.upgrade()?;
if context.read(cx).id() == id {
Some(context)
} else {
None
}
})
}
pub fn open_remote(
pub fn open_remote_context(
&mut self,
text_thread_id: TextThreadId,
context_id: ContextId,
cx: &mut Context<Self>,
) -> Task<Result<Entity<TextThread>>> {
) -> Task<Result<Entity<AssistantContext>>> {
let project = self.project.read(cx);
let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
};
if let Some(context) = self.loaded_text_thread_for_id(&text_thread_id, cx) {
if let Some(context) = self.loaded_context_for_id(&context_id, cx) {
return Task::ready(Ok(context));
}
@@ -557,16 +567,16 @@ impl TextThreadStore {
let telemetry = self.telemetry.clone();
let request = self.client.request(proto::OpenContext {
project_id,
context_id: text_thread_id.to_proto(),
context_id: context_id.to_proto(),
});
let prompt_builder = self.prompt_builder.clone();
let slash_commands = self.slash_commands.clone();
cx.spawn(async move |this, cx| {
let response = request.await?;
let context_proto = response.context.context("invalid context")?;
let text_thread = cx.new(|cx| {
TextThread::new(
text_thread_id.clone(),
let context = cx.new(|cx| {
AssistantContext::new(
context_id.clone(),
replica_id,
capability,
language_registry,
@@ -582,40 +592,38 @@ impl TextThreadStore {
context_proto
.operations
.into_iter()
.map(TextThreadOperation::from_proto)
.map(ContextOperation::from_proto)
.collect::<Result<Vec<_>>>()
})
.await?;
text_thread.update(cx, |context, cx| context.apply_ops(operations, cx))?;
context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
this.update(cx, |this, cx| {
if let Some(existing_context) = this.loaded_text_thread_for_id(&text_thread_id, cx)
{
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
existing_context
} else {
this.register_text_thread(&text_thread, cx);
this.register_context(&context, cx);
this.synchronize_contexts(cx);
text_thread
context
}
})
})
}
fn register_text_thread(&mut self, text_thread: &Entity<TextThread>, cx: &mut Context<Self>) {
fn register_context(&mut self, context: &Entity<AssistantContext>, cx: &mut Context<Self>) {
let handle = if self.project_is_shared {
TextThreadHandle::Strong(text_thread.clone())
ContextHandle::Strong(context.clone())
} else {
TextThreadHandle::Weak(text_thread.downgrade())
ContextHandle::Weak(context.downgrade())
};
self.text_threads.push(handle);
self.contexts.push(handle);
self.advertise_contexts(cx);
cx.subscribe(text_thread, Self::handle_context_event)
.detach();
cx.subscribe(context, Self::handle_context_event).detach();
}
fn handle_context_event(
&mut self,
text_thread: Entity<TextThread>,
event: &TextThreadEvent,
context: Entity<AssistantContext>,
event: &ContextEvent,
cx: &mut Context<Self>,
) {
let Some(project_id) = self.project.read(cx).remote_id() else {
@@ -623,12 +631,12 @@ impl TextThreadStore {
};
match event {
TextThreadEvent::SummaryChanged => {
ContextEvent::SummaryChanged => {
self.advertise_contexts(cx);
}
TextThreadEvent::PathChanged { old_path, new_path } => {
ContextEvent::PathChanged { old_path, new_path } => {
if let Some(old_path) = old_path.as_ref() {
for metadata in &mut self.text_threads_metadata {
for metadata in &mut self.contexts_metadata {
if &metadata.path == old_path {
metadata.path = new_path.clone();
break;
@@ -636,8 +644,8 @@ impl TextThreadStore {
}
}
}
TextThreadEvent::Operation(operation) => {
let context_id = text_thread.read(cx).id().to_proto();
ContextEvent::Operation(operation) => {
let context_id = context.read(cx).id().to_proto();
let operation = operation.to_proto();
self.client
.send(proto::UpdateContext {
@@ -662,15 +670,15 @@ impl TextThreadStore {
}
let contexts = self
.text_threads
.contexts
.iter()
.rev()
.filter_map(|text_thread| {
let text_thread = text_thread.upgrade()?.read(cx);
if text_thread.replica_id() == ReplicaId::default() {
.filter_map(|context| {
let context = context.upgrade()?.read(cx);
if context.replica_id() == ReplicaId::default() {
Some(proto::ContextMetadata {
context_id: text_thread.id().to_proto(),
summary: text_thread
context_id: context.id().to_proto(),
summary: context
.summary()
.content()
.map(|summary| summary.text.clone()),
@@ -693,13 +701,13 @@ impl TextThreadStore {
return;
};
let text_threads = self
.text_threads
let contexts = self
.contexts
.iter()
.filter_map(|text_thread| {
let text_thread = text_thread.upgrade()?.read(cx);
if text_thread.replica_id() != ReplicaId::default() {
Some(text_thread.version(cx).to_proto(text_thread.id().clone()))
.filter_map(|context| {
let context = context.upgrade()?.read(cx);
if context.replica_id() != ReplicaId::default() {
Some(context.version(cx).to_proto(context.id().clone()))
} else {
None
}
@@ -709,27 +717,26 @@ impl TextThreadStore {
let client = self.client.clone();
let request = self.client.request(proto::SynchronizeContexts {
project_id,
contexts: text_threads,
contexts,
});
cx.spawn(async move |this, cx| {
let response = request.await?;
let mut text_thread_ids = Vec::new();
let mut context_ids = Vec::new();
let mut operations = Vec::new();
this.read_with(cx, |this, cx| {
for context_version_proto in response.contexts {
let text_thread_version = TextThreadVersion::from_proto(&context_version_proto);
let text_thread_id = TextThreadId::from_proto(context_version_proto.context_id);
if let Some(text_thread) = this.loaded_text_thread_for_id(&text_thread_id, cx) {
text_thread_ids.push(text_thread_id);
operations
.push(text_thread.read(cx).serialize_ops(&text_thread_version, cx));
let context_version = ContextVersion::from_proto(&context_version_proto);
let context_id = ContextId::from_proto(context_version_proto.context_id);
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
context_ids.push(context_id);
operations.push(context.read(cx).serialize_ops(&context_version, cx));
}
}
})?;
let operations = futures::future::join_all(operations).await;
for (context_id, operations) in text_thread_ids.into_iter().zip(operations) {
for (context_id, operations) in context_ids.into_iter().zip(operations) {
for operation in operations {
client.send(proto::UpdateContext {
project_id,
@@ -744,8 +751,8 @@ impl TextThreadStore {
.detach_and_log_err(cx);
}
pub fn search(&self, query: String, cx: &App) -> Task<Vec<SavedTextThreadMetadata>> {
let metadata = self.text_threads_metadata.clone();
pub fn search(&self, query: String, cx: &App) -> Task<Vec<SavedContextMetadata>> {
let metadata = self.contexts_metadata.clone();
let executor = cx.background_executor().clone();
cx.background_spawn(async move {
if query.is_empty() {
@@ -775,16 +782,20 @@ impl TextThreadStore {
})
}
pub fn host_contexts(&self) -> &[RemoteContextMetadata] {
&self.host_contexts
}
fn reload(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let fs = self.fs.clone();
cx.spawn(async move |this, cx| {
if *ZED_STATELESS {
return Ok(());
}
fs.create_dir(text_threads_dir()).await?;
fs.create_dir(contexts_dir()).await?;
let mut paths = fs.read_dir(text_threads_dir()).await?;
let mut contexts = Vec::<SavedTextThreadMetadata>::new();
let mut paths = fs.read_dir(contexts_dir()).await?;
let mut contexts = Vec::<SavedContextMetadata>::new();
while let Some(path) = paths.next().await {
let path = path?;
if path.extension() != Some(OsStr::new("json")) {
@@ -810,7 +821,7 @@ impl TextThreadStore {
.lines()
.next()
{
contexts.push(SavedTextThreadMetadata {
contexts.push(SavedContextMetadata {
title: title.to_string().into(),
path: path.into(),
mtime: metadata.mtime.timestamp_for_user().into(),
@@ -818,10 +829,10 @@ impl TextThreadStore {
}
}
}
contexts.sort_unstable_by_key(|text_thread| Reverse(text_thread.mtime));
contexts.sort_unstable_by_key(|context| Reverse(context.mtime));
this.update(cx, |this, cx| {
this.text_threads_metadata = contexts;
this.contexts_metadata = contexts;
cx.notify();
})
})

View File

@@ -1,15 +0,0 @@
#[cfg(test)]
mod assistant_text_thread_tests;
mod text_thread;
mod text_thread_store;
pub use crate::text_thread::*;
pub use crate::text_thread_store::*;
use client::Client;
use gpui::App;
use std::sync::Arc;
pub fn init(client: Arc<Client>, _: &mut App) {
text_thread_store::init(&client.into());
}

View File

@@ -433,7 +433,7 @@ where
/// Stores already emitted samples, once its full we call the callback.
buffer: [Sample; N],
/// Next free element in buffer. If this is equal to the buffer length
/// we have no more free elements.
/// we have no more free lements.
free: usize,
}

View File

@@ -119,19 +119,21 @@ impl Render for Breadcrumbs {
}
}
})
.tooltip(move |_window, cx| {
.tooltip(move |window, cx| {
if let Some(editor) = editor.upgrade() {
let focus_handle = editor.read(cx).focus_handle(cx);
Tooltip::for_action_in(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
&focus_handle,
window,
cx,
)
} else {
Tooltip::for_action(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
window,
cx,
)
}

View File

@@ -85,7 +85,7 @@ struct PendingHunk {
new_status: DiffHunkSecondaryStatus,
}
#[derive(Debug, Clone)]
#[derive(Debug, Default, Clone)]
pub struct DiffHunkSummary {
buffer_range: Range<Anchor>,
}
@@ -114,9 +114,7 @@ impl sum_tree::Summary for DiffHunkSummary {
type Context<'a> = &'a text::BufferSnapshot;
fn zero(_cx: Self::Context<'_>) -> Self {
DiffHunkSummary {
buffer_range: Anchor::MIN..Anchor::MIN,
}
Default::default()
}
fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
@@ -939,9 +937,7 @@ impl BufferDiff {
pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
if self.secondary_diff.is_some() {
self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
buffer_range: Anchor::MIN..Anchor::MIN,
});
self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary::default());
cx.emit(BufferDiffEvent::DiffChanged {
changed_range: Some(Anchor::MIN..Anchor::MAX),
});
@@ -1372,7 +1368,7 @@ mod tests {
use gpui::TestAppContext;
use pretty_assertions::{assert_eq, assert_ne};
use rand::{Rng as _, rngs::StdRng};
use text::{Buffer, BufferId, ReplicaId, Rope};
use text::{Buffer, BufferId, Rope};
use unindent::Unindent as _;
use util::test::marked_text_ranges;
@@ -1397,7 +1393,7 @@ mod tests {
"
.unindent();
let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
assert_hunks(
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
@@ -1471,7 +1467,7 @@ mod tests {
"
.unindent();
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
let mut uncommitted_diff =
BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
@@ -1540,7 +1536,7 @@ mod tests {
"
.unindent();
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let diff = cx
.update(|cx| {
BufferDiffSnapshot::new_with_base_text(
@@ -1803,7 +1799,7 @@ mod tests {
for example in table {
let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let hunk_range =
buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
@@ -1876,11 +1872,7 @@ mod tests {
"
.unindent();
let buffer = Buffer::new(
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
buffer_text.clone(),
);
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text.clone());
let unstaged = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
let uncommitted = BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
let unstaged_diff = cx.new(|cx| {
@@ -1953,7 +1945,7 @@ mod tests {
"
.unindent();
let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1);
let empty_diff = cx.update(|cx| BufferDiffSnapshot::empty(&buffer, cx));
let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);

View File

@@ -9,7 +9,7 @@ use rpc::{
proto::{self, PeerId},
};
use std::{sync::Arc, time::Duration};
use text::{BufferId, ReplicaId};
use text::BufferId;
use util::ResultExt;
pub const ACKNOWLEDGE_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(250);
@@ -65,12 +65,7 @@ impl ChannelBuffer {
let buffer = cx.new(|cx| {
let capability = channel_store.read(cx).channel_capability(channel.id);
language::Buffer::remote(
buffer_id,
ReplicaId::new(response.replica_id as u16),
capability,
base_text,
)
language::Buffer::remote(buffer_id, response.replica_id as u16, capability, base_text)
})?;
buffer.update(cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
@@ -277,7 +272,7 @@ impl ChannelBuffer {
self.connected
}
pub fn replica_id(&self, cx: &App) -> ReplicaId {
pub fn replica_id(&self, cx: &App) -> u16 {
self.buffer.read(cx).replica_id()
}
}

View File

@@ -17,7 +17,6 @@ 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>,
},

View File

@@ -62,14 +62,11 @@ struct Args {
#[arg(short, long)]
wait: bool,
/// Add files to the currently open workspace
#[arg(short, long, overrides_with_all = ["new", "reuse"])]
#[arg(short, long, overrides_with = "new")]
add: bool,
/// Create a new workspace
#[arg(short, long, overrides_with_all = ["add", "reuse"])]
#[arg(short, long, overrides_with = "add")]
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`.")]
@@ -377,7 +374,6 @@ fn main() -> Result<()> {
wsl,
wait: args.wait,
open_new_workspace,
reuse: args.reuse,
env,
user_data_dir: user_data_dir_for_thread,
})?;

View File

@@ -23,7 +23,7 @@ pub(super) struct Socks5Authorization<'a> {
/// Socks Proxy Protocol Version
///
/// V4 allows identification using a user_id
/// V4 allows idenfication using a user_id
/// V5 allows authorization using a username and password
pub(super) enum SocksVersion<'a> {
V4 {

View File

@@ -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: ReplicaId::new(message.replica_id as u16),
replica_id: message.replica_id as ReplicaId,
user_id: message.user_id as UserId,
is_host: message.is_host,
committer_name: message.committer_name,

View File

@@ -4,73 +4,33 @@ use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::{
cmp::{self, Ordering},
fmt,
fmt, iter,
};
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.
#[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)
}
}
}
pub type ReplicaId = u16;
/// 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, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct Lamport {
pub replica_id: ReplicaId,
pub value: Seq,
}
/// A [version vector](https://en.wikipedia.org/wiki/Version_vector).
/// A [vector clock](https://en.wikipedia.org/wiki/Vector_clock).
#[derive(Clone, Default, Hash, Eq, PartialEq)]
pub struct Global {
// 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]>,
values: SmallVec<[u32; 8]>,
local_branch_value: u32,
}
impl Global {
@@ -78,31 +38,30 @@ impl Global {
Self::default()
}
/// Fetches the sequence number for the given replica ID.
pub fn get(&self, replica_id: ReplicaId) -> 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 {
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);
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
}
}
pub fn observe(&mut self, timestamp: Lamport) {
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);
}
}
}
/// 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);
@@ -111,36 +70,34 @@ 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).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),
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);
}
if *left != 0 {
new_len = ix + 1;
}
}
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);
}
self.values.resize(new_len, 0);
self.local_branch_value = cmp::min(self.local_branch_value, other.local_branch_value);
}
pub fn observed(&self, timestamp: Lamport) -> bool {
@@ -148,18 +105,20 @@ impl Global {
}
pub fn observed_any(&self, other: &Self) -> bool {
self.iter()
.zip(other.iter())
.any(|(left, right)| right.value > 0 && left.value >= right.value)
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)
}
pub fn observed_all(&self, other: &Self) -> bool {
if self.values.len() < other.values.len() {
return false;
}
self.iter()
.zip(other.iter())
.all(|(left, right)| left.value >= right.value)
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
}
pub fn changed_since(&self, other: &Self) -> bool {
@@ -169,21 +128,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: ReplicaId(replica_id as u16),
replica_id: replica_id as ReplicaId,
value: *seq,
})
.chain((self.local_branch_value > 0).then_some(Lamport {
replica_id: LOCAL_BRANCH_REPLICA_ID,
value: self.local_branch_value,
}))
}
}
@@ -214,12 +173,12 @@ impl PartialOrd for Lamport {
impl Lamport {
pub const MIN: Self = Self {
replica_id: ReplicaId(u16::MIN),
replica_id: ReplicaId::MIN,
value: Seq::MIN,
};
pub const MAX: Self = Self {
replica_id: ReplicaId(u16::MAX),
replica_id: ReplicaId::MAX,
value: Seq::MAX,
};
@@ -231,7 +190,7 @@ impl Lamport {
}
pub fn as_u64(self) -> u64 {
((self.value as u64) << 32) | (self.replica_id.0 as u64)
((self.value as u64) << 32) | (self.replica_id as u64)
}
pub fn tick(&mut self) -> Self {
@@ -252,7 +211,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)
}
}
}
@@ -261,10 +220,16 @@ 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 > 0 {
if timestamp.replica_id > 0 {
write!(f, ", ")?;
}
write!(f, "{:?}: {}", timestamp.replica_id, timestamp.value)?;
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, "}}")
}

View File

@@ -47,9 +47,7 @@ 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
@@ -73,7 +71,7 @@ uuid.workspace = true
[dev-dependencies]
agent_settings.workspace = true
assistant_text_thread.workspace = true
assistant_context.workspace = true
assistant_slash_command.workspace = true
async-trait.workspace = true
audio.workspace = true

View File

@@ -62,9 +62,9 @@ impl Database {
.iter()
.map(|c| c.replica_id)
.collect::<HashSet<_>>();
let mut replica_id = ReplicaId(clock::ReplicaId::FIRST_COLLAB_ID.as_u16() as i32);
let mut replica_id = ReplicaId(0);
while replica_ids.contains(&replica_id) {
replica_id = ReplicaId(replica_id.0 + 1);
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: clock::ReplicaId::new(row.replica_id as u16),
replica_id: row.replica_id as u16,
value: row.lamport_timestamp as u32,
};
server_version.observe(timestamp);
@@ -701,11 +701,7 @@ impl Database {
return Ok(());
}
let mut text_buffer = text::Buffer::new(
clock::ReplicaId::LOCAL,
text::BufferId::new(1).unwrap(),
base_text,
);
let mut text_buffer = text::Buffer::new(0, 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();
@@ -938,7 +934,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: clock::ReplicaId::new(edit.replica_id as u16),
replica_id: edit.replica_id as text::ReplicaId,
value: edit.lamport_timestamp,
},
version: version_from_wire(&edit.version),
@@ -953,7 +949,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: clock::ReplicaId::new(undo.replica_id as u16),
replica_id: undo.replica_id as text::ReplicaId,
value: undo.lamport_timestamp,
},
version: version_from_wire(&undo.version),
@@ -963,7 +959,7 @@ pub fn operation_from_wire(operation: proto::Operation) -> Option<text::Operatio
.map(|c| {
(
clock::Lamport {
replica_id: clock::ReplicaId::new(c.replica_id as u16),
replica_id: c.replica_id as text::ReplicaId,
value: c.lamport_timestamp,
},
c.count,
@@ -979,7 +975,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: clock::ReplicaId::new(entry.replica_id as u16),
replica_id: entry.replica_id as text::ReplicaId,
value: entry.timestamp,
});
}
@@ -990,7 +986,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_u16() as u32,
replica_id: entry.replica_id as u32,
timestamp: entry.value,
});
}

View File

@@ -91,18 +91,14 @@ impl Database {
.await?;
}
let replica_id = if is_ssh_project {
clock::ReplicaId::REMOTE_SERVER
} else {
clock::ReplicaId::LOCAL
};
let replica_id = if is_ssh_project { 1 } else { 0 };
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.as_u16() as i32)),
replica_id: ActiveValue::set(ReplicaId(replica_id)),
is_host: ActiveValue::set(true),
id: ActiveValue::NotSet,
committer_name: ActiveValue::Set(None),
@@ -845,7 +841,7 @@ impl Database {
.iter()
.map(|c| c.replica_id)
.collect::<HashSet<_>>();
let mut replica_id = ReplicaId(clock::ReplicaId::FIRST_COLLAB_ID.as_u16() as i32);
let mut replica_id = ReplicaId(1);
while replica_ids.contains(&replica_id) {
replica_id.0 += 1;
}

View File

@@ -1,7 +1,7 @@
use super::*;
use crate::test_both_dbs;
use language::proto::{self, serialize_version};
use text::{Buffer, ReplicaId};
use text::Buffer;
test_both_dbs!(
test_channel_buffers,
@@ -70,11 +70,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
.await
.unwrap();
let mut buffer_a = Buffer::new(
ReplicaId::new(0),
text::BufferId::new(1).unwrap(),
"".to_string(),
);
let mut buffer_a = Buffer::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")]),
@@ -99,7 +95,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
.unwrap();
let mut buffer_b = Buffer::new(
ReplicaId::new(0),
0,
text::BufferId::new(1).unwrap(),
buffer_response_b.base_text,
);
@@ -128,7 +124,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: ReplicaId::FIRST_COLLAB_ID.as_u16() as u32,
replica_id: 0,
is_host: false,
committer_name: None,
committer_email: None,
@@ -136,7 +132,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: ReplicaId::FIRST_COLLAB_ID.as_u16() as u32 + 1,
replica_id: 1,
is_host: false,
committer_name: None,
committer_email: None,
@@ -232,8 +228,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
.await
.unwrap();
let res = db
.join_channel_buffer(channel, user_id, connection_id)
db.join_channel_buffer(channel, user_id, connection_id)
.await
.unwrap();
@@ -244,7 +239,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
);
text_buffers.push(Buffer::new(
ReplicaId::new(res.replica_id as u16),
0,
text::BufferId::new(1).unwrap(),
"".to_string(),
));
@@ -281,12 +276,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
db.join_channel_buffer(buffers[1].channel_id, user_id, connection_id)
.await
.unwrap();
let replica_id = text_buffers[1].replica_id();
text_buffers[1] = Buffer::new(
replica_id,
text::BufferId::new(1).unwrap(),
"def".to_string(),
);
text_buffers[1] = Buffer::new(1, text::BufferId::new(1).unwrap(), "def".to_string());
update_buffer(
buffers[1].channel_id,
user_id,
@@ -314,32 +304,20 @@ 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())
.into_iter()
.filter(
|vector| vector.replica_id == text_buffers[0].replica_id().as_u16() as u32
)
.collect::<Vec<_>>(),
version: serialize_version(&text_buffers[0].version()),
},
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_u16() as u32
)
.filter(|vector| vector.replica_id == text_buffers[1].replica_id() as u32)
.collect::<Vec<_>>(),
},
rpc::proto::ChannelBufferVersion {
channel_id: buffers[2].channel_id.to_proto(),
epoch: 0,
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<_>>(),
version: serialize_version(&text_buffers[2].version()),
},
]
);

View File

@@ -343,6 +343,7 @@ 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>)

View File

@@ -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 placeholders
// no snippet placehodlers
new_text: "third_method".to_string(),
range: lsp::Range::new(
lsp::Position::new(1, 32),
@@ -1849,40 +1849,10 @@ 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()
},
);
@@ -1924,20 +1894,61 @@ 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 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 _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 fake_language_server = fake_language_servers.next().await.unwrap();
let editor_a = file_a.await.unwrap().downcast::<Editor>().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();
executor.run_until_parked();
let initial_edit = edits_made.load(atomic::Ordering::Acquire);
editor_a.update(cx_a, |editor, cx| {
editor_a.update(cx_a, |editor, _| {
assert_eq!(
vec![initial_edit.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
"Host should get its first hints when opens an editor"
);
});
@@ -1952,10 +1963,10 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.unwrap();
executor.run_until_parked();
editor_b.update(cx_b, |editor, cx| {
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec![initial_edit.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
"Client should get its first hints when opens an editor"
);
});
@@ -1970,16 +1981,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, cx| {
editor_a.update(cx_a, |editor, _| {
assert_eq!(
vec![after_client_edit.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
);
});
editor_b.update(cx_b, |editor, cx| {
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec![after_client_edit.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
);
});
@@ -1993,16 +2004,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, cx| {
editor_a.update(cx_a, |editor, _| {
assert_eq!(
vec![after_host_edit.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
);
});
editor_b.update(cx_b, |editor, cx| {
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec![after_host_edit.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
);
});
@@ -2014,22 +2025,26 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.expect("inlay refresh request failed");
executor.run_until_parked();
editor_a.update(cx_a, |editor, cx| {
editor_a.update(cx_a, |editor, _| {
assert_eq!(
vec![after_special_edit_for_refresh.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
"Host should react to /refresh LSP request"
);
});
editor_b.update(cx_b, |editor, cx| {
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec![after_special_edit_for_refresh.to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
"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,
@@ -2191,18 +2206,18 @@ async fn test_inlay_hint_refresh_is_forwarded(
executor.finish_waiting();
executor.run_until_parked();
editor_a.update(cx_a, |editor, cx| {
editor_a.update(cx_a, |editor, _| {
assert!(
extract_hint_labels(editor, cx).is_empty(),
extract_hint_labels(editor).is_empty(),
"Host should get no hints due to them turned off"
);
});
executor.run_until_parked();
editor_b.update(cx_b, |editor, cx| {
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec!["initial hint".to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
"Client should get its first hints when opens an editor"
);
});
@@ -2214,18 +2229,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, cx| {
editor_a.update(cx_a, |editor, _| {
assert!(
extract_hint_labels(editor, cx).is_empty(),
extract_hint_labels(editor).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, cx| {
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec!["other hint".to_string()],
extract_hint_labels(editor, cx),
extract_hint_labels(editor),
"Guest should get a /refresh LSP request propagated by host despite host hints are off"
);
});
@@ -4202,35 +4217,15 @@ fn tab_undo_assert(
cx_b.assert_editor_state(expected_initial);
}
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());
});
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!(),
}
}
assert!(
all_fetched_hints.is_empty(),
"Did not expect background hints fetch tasks, but got {} of them",
all_fetched_hints.len()
);
all_cached_labels
labels
}
#[track_caller]

View File

@@ -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 text_thread_store_a = cx_a
let context_store_a = cx_a
.update(|cx| {
TextThreadStore::new(
ContextStore::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 text_thread_store_b = cx_b
let context_store_b = cx_b
.update(|cx| {
TextThreadStore::new(
ContextStore::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 text_thread_a = text_thread_store_a.update(cx_a, |store, cx| store.create(cx));
let context_a = context_store_a.update(cx_a, |store, cx| store.create(cx));
executor.run_until_parked();
// Client B retrieves host's contexts and joins one.
let text_thread_b = text_thread_store_b
let context_b = context_store_b
.update(cx_b, |store, 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)
let host_contexts = store.host_contexts().to_vec();
assert_eq!(host_contexts.len(), 1);
store.open_remote_context(host_contexts[0].id.clone(), cx)
})
.await
.unwrap();
// Host and guest make changes
text_thread_a.update(cx_a, |text_thread, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
context_a.update(cx_a, |context, cx| {
context.buffer().update(cx, |buffer, cx| {
buffer.edit([(0..0, "Host change\n")], None, cx)
})
});
text_thread_b.update(cx_b, |text_thread, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
context_b.update(cx_b, |context, cx| {
context.buffer().update(cx, |buffer, cx| {
buffer.edit([(0..0, "Guest change\n")], None, cx)
})
});
executor.run_until_parked();
assert_eq!(
text_thread_a.read_with(cx_a, |text_thread, cx| text_thread.buffer().read(cx).text()),
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
"Guest change\nHost change\n"
);
assert_eq!(
text_thread_b.read_with(cx_b, |text_thread, cx| text_thread.buffer().read(cx).text()),
context_b.read_with(cx_b, |context, cx| context.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();
text_thread_a.update(cx_a, |text_thread, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
context_a.update(cx_a, |context, cx| {
context.buffer().update(cx, |buffer, cx| {
buffer.edit([(0..0, "Host offline change\n")], None, cx)
})
});
text_thread_b.update(cx_b, |text_thread, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
context_b.update(cx_b, |context, cx| {
context.buffer().update(cx, |buffer, cx| {
buffer.edit([(0..0, "Guest offline change\n")], None, cx)
})
});
executor.run_until_parked();
assert_eq!(
text_thread_a.read_with(cx_a, |text_thread, cx| text_thread.buffer().read(cx).text()),
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
"Host offline change\nGuest change\nHost change\n"
);
assert_eq!(
text_thread_b.read_with(cx_b, |text_thread, cx| text_thread.buffer().read(cx).text()),
context_b.read_with(cx_b, |context, cx| context.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!(
text_thread_a.read_with(cx_a, |text_thread, cx| text_thread.buffer().read(cx).text()),
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
"Guest offline change\nHost offline change\nGuest change\nHost change\n"
);
assert_eq!(
text_thread_b.read_with(cx_b, |text_thread, cx| text_thread.buffer().read(cx).text()),
context_b.read_with(cx_b, |context, cx| context.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);
text_thread_b.read_with(cx_b, |text_thread, cx| {
assert!(text_thread.buffer().read(cx).read_only());
context_b.read_with(cx_b, |context, cx| {
assert!(context.buffer().read(cx).read_only());
});
}

View File

@@ -358,7 +358,7 @@ impl TestServer {
settings::KeymapFile::load_asset_allow_partial_failure(os_keymap, cx).unwrap(),
);
language_model::LanguageModelRegistry::test(cx);
assistant_text_thread::init(client.clone(), cx);
assistant_context::init(client.clone(), cx);
agent_settings::init(cx);
});

View File

@@ -3037,10 +3037,6 @@ impl Panel for CollabPanel {
"CollabPanel"
}
fn panel_key() -> &'static str {
COLLABORATION_PANEL_KEY
}
fn activation_priority(&self) -> u32 {
6
}

View File

@@ -612,10 +612,6 @@ 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
}
@@ -738,17 +734,19 @@ 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,
)
}

View File

@@ -443,7 +443,7 @@ impl PickerDelegate for CommandPaletteDelegate {
&self,
ix: usize,
selected: bool,
_: &mut Window,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let matching_command = self.matches.get(ix)?;
@@ -462,9 +462,10 @@ impl PickerDelegate for CommandPaletteDelegate {
command.name.clone(),
matching_command.positions.clone(),
))
.child(KeyBinding::for_action_in(
.children(KeyBinding::for_action_in(
&*command.action,
&self.previous_focus_handle,
window,
cx,
)),
),

View File

@@ -9,7 +9,6 @@ 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

View File

@@ -33,31 +33,17 @@ 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) {
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));
}
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 exe = env::current_exe().expect("unable to find ourselves");
@@ -286,11 +272,6 @@ 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>()

View File

@@ -43,8 +43,6 @@ use workspace::{
};
use zed_actions::ToggleFocus;
const DEBUG_PANEL_KEY: &str = "DebugPanel";
pub struct DebugPanel {
size: Pixels,
active_session: Option<Entity<DebugSession>>,
@@ -616,11 +614,12 @@ 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,
)
}
@@ -693,11 +692,12 @@ 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,11 +717,12 @@ 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,
)
}
@@ -741,11 +742,12 @@ 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,
)
}
@@ -766,11 +768,12 @@ 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,
)
}
@@ -788,11 +791,12 @@ 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,
)
}
@@ -810,11 +814,12 @@ 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,
)
}
@@ -854,11 +859,12 @@ impl DebugPanel {
} else {
"Terminate All Threads"
};
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
label,
&Stop,
&focus_handle,
window,
cx,
)
}
@@ -885,11 +891,12 @@ 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,
)
}
@@ -1407,10 +1414,6 @@ 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()
}

View File

@@ -96,9 +96,7 @@ impl NewProcessModal {
let debug_picker = cx.new(|cx| {
let delegate =
DebugDelegate::new(debug_panel.downgrade(), task_store.clone());
Picker::list(delegate, window, cx)
.modal(false)
.list_measure_all()
Picker::uniform_list(delegate, window, cx).modal(false)
});
let configure_mode = ConfigureMode::new(window, cx);
@@ -747,15 +745,22 @@ impl Render for NewProcessModal {
== 0;
let secondary_action = menu::SecondaryConfirm.boxed_clone();
container
.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(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(
h_flex()
.child(div().child(self.adapter_drop_down_menu(window, cx))),
@@ -1048,7 +1053,7 @@ impl DebugDelegate {
Some(TaskSourceKind::Lsp { language_name, .. }) => {
Some(format!("LSP: {language_name}"))
}
Some(TaskSourceKind::Language { name }) => Some(format!("Lang: {name}")),
Some(TaskSourceKind::Language { .. }) => None,
_ => context.clone().and_then(|ctx| {
ctx.task_context
.task_variables
@@ -1442,48 +1447,56 @@ impl PickerDelegate for DebugDelegate {
.justify_between()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.child({
.children({
let action = menu::SecondaryConfirm.boxed_clone();
if self.matches.is_empty() {
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);
}))
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);
})),
)
} else {
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)
})
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)
})
})
}
})
.map(|this| {
if (current_modifiers.alt || self.matches.is_empty()) && !self.prompt.is_empty() {
let action = picker::ConfirmInput { secondary: false }.boxed_clone();
this.child({
this.children(KeyBinding::for_action(&*action, window, cx).map(|keybind| {
Button::new("launch-custom", "Launch Custom")
.key_binding(KeyBinding::for_action(&*action, cx))
.key_binding(keybind)
.on_click(move |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx)
})
})
}))
} else {
this.child({
let is_recent_selected = self.divider_index >= Some(self.selected_index);
let run_entry_label = if is_recent_selected { "Rerun" } else { "Spawn" };
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" };
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);
})
})
Button::new("spawn", run_entry_label)
.key_binding(keybind)
.on_click(|_, window, cx| {
window.dispatch_action(menu::Confirm.boxed_clone(), cx);
})
},
))
}
});
Some(footer.into_any_element())

View File

@@ -386,7 +386,6 @@ pub(crate) fn new_debugger_pane(
Default::default(),
None,
NoAction.boxed_clone(),
true,
window,
cx,
);
@@ -566,13 +565,14 @@ 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,
)
}

View File

@@ -607,12 +607,13 @@ 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,
)
}
@@ -633,12 +634,13 @@ 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,
)
}
@@ -817,7 +819,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"
@@ -826,6 +828,7 @@ impl LineBreakpoint {
},
&ToggleEnableBreakpoint,
&focus_handle,
window,
cx,
)
}
@@ -977,7 +980,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"
@@ -986,6 +989,7 @@ impl DataBreakpoint {
},
&ToggleEnableBreakpoint,
&focus_handle,
window,
cx,
)
}
@@ -1081,7 +1085,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"
@@ -1090,6 +1094,7 @@ impl ExceptionBreakpoint {
},
&ToggleEnableBreakpoint,
&focus_handle,
window,
cx,
)
}
@@ -1397,11 +1402,12 @@ 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,
)
}),
@@ -1432,11 +1438,12 @@ 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,
)
}),
@@ -1467,11 +1474,12 @@ 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,
)
}),

View File

@@ -484,11 +484,12 @@ 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,
)
}

View File

@@ -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, cx)
.tooltip(move |window, cx| {
Tooltip::for_action(tooltip_title, &ToggleUserFrames, window, cx)
})
.toggle_state(self.list_filter == StackFrameFilter::OnlyUserFrames)
.icon_size(IconSize::Small)

View File

@@ -1306,8 +1306,14 @@ impl VariableList {
.ok();
}
})
.tooltip(move |_window, cx| {
Tooltip::for_action_in("Remove Watch", &RemoveWatch, &focus_handle, cx)
.tooltip(move |window, cx| {
Tooltip::for_action_in(
"Remove Watch",
&RemoveWatch,
&focus_handle,
window,
cx,
)
})
.icon_size(ui::IconSize::Indicator),
),

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