Compare commits

..

2 Commits

Author SHA1 Message Date
Anthony
1c567baa8e More proof of concept 2025-10-21 02:58:56 -04:00
Anthony
f623ea55b0 Basic proof of concept 2025-10-21 02:50:33 -04:00
391 changed files with 12330 additions and 19550 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

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

261
Cargo.lock generated
View File

@@ -142,7 +142,7 @@ dependencies = [
"agent_servers",
"agent_settings",
"anyhow",
"assistant_text_thread",
"assistant_context",
"chrono",
"client",
"clock",
@@ -315,9 +315,9 @@ dependencies = [
"ai_onboarding",
"anyhow",
"arrayvec",
"assistant_context",
"assistant_slash_command",
"assistant_slash_commands",
"assistant_text_thread",
"audio",
"buffer_diff",
"chrono",
@@ -802,6 +802,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"
@@ -859,53 +906,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 +1184,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",
@@ -3324,8 +3324,8 @@ version = "0.44.0"
dependencies = [
"agent_settings",
"anyhow",
"assistant_context",
"assistant_slash_command",
"assistant_text_thread",
"async-trait",
"async-tungstenite",
"audio",
@@ -3393,7 +3393,6 @@ dependencies = [
"rpc",
"scrypt",
"sea-orm",
"sea-orm-macros",
"semantic_version",
"semver",
"serde",
@@ -4132,7 +4131,6 @@ dependencies = [
"bincode 1.3.3",
"cfg-if",
"crash-handler",
"extension_host",
"log",
"mach2 0.5.0",
"minidumper",
@@ -4532,6 +4530,7 @@ dependencies = [
"paths",
"serde",
"serde_json",
"shlex",
"smol",
"task",
"util",
@@ -4757,6 +4756,7 @@ dependencies = [
"serde_json",
"serde_json_lenient",
"settings",
"shlex",
"sysinfo 0.37.2",
"task",
"tasks_ui",
@@ -5295,12 +5295,10 @@ name = "edit_prediction_context"
version = "0.1.0"
dependencies = [
"anyhow",
"arraydeque",
"arrayvec",
"clap",
"cloud_llm_client",
"collections",
"criterion",
"futures 0.3.31",
"gpui",
"hashbrown 0.15.5",
@@ -5312,13 +5310,11 @@ dependencies = [
"postage",
"pretty_assertions",
"project",
"rand 0.9.2",
"regex",
"serde",
"serde_json",
"settings",
"slotmap",
"smallvec",
"strum 0.27.2",
"text",
"tree-sitter",
@@ -5371,7 +5367,6 @@ dependencies = [
"rand 0.9.2",
"regex",
"release_channel",
"rope",
"rpc",
"schemars 1.0.4",
"serde",
@@ -5695,61 +5690,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"
@@ -5888,7 +5828,6 @@ dependencies = [
"fs",
"futures 0.3.31",
"gpui",
"gpui_tokio",
"http_client",
"language",
"language_extension",
@@ -7073,6 +7012,7 @@ dependencies = [
"notifications",
"panel",
"picker",
"postage",
"pretty_assertions",
"project",
"schemars 1.0.4",
@@ -7218,7 +7158,7 @@ dependencies = [
[[package]]
name = "gpui"
version = "0.2.2"
version = "0.2.1"
dependencies = [
"anyhow",
"as-raw-xcb-connection",
@@ -10849,6 +10789,7 @@ dependencies = [
"telemetry",
"theme",
"ui",
"ui_input",
"util",
"vim_mode_setting",
"workspace",
@@ -11446,7 +11387,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",
@@ -11483,7 +11424,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",
@@ -11502,7 +11443,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",
@@ -11517,7 +11458,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",
@@ -11533,7 +11474,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",
@@ -11542,7 +11483,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",
@@ -11555,7 +11496,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",
@@ -11573,7 +11514,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",
@@ -11586,7 +11527,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",
@@ -11599,7 +11540,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",
@@ -11612,7 +11553,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",
@@ -11625,7 +11566,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",
@@ -11638,7 +11579,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",
@@ -11651,7 +11592,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",
@@ -11663,7 +11604,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",
@@ -11684,7 +11625,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",
@@ -11702,7 +11643,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",
@@ -11719,7 +11660,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",
@@ -11733,7 +11674,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",
@@ -11748,7 +11689,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",
@@ -11760,7 +11701,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",
@@ -11772,7 +11713,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",
@@ -11785,7 +11726,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",
@@ -11803,7 +11744,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",
@@ -12928,6 +12869,7 @@ dependencies = [
"settings",
"sha2",
"shellexpand 2.1.2",
"shlex",
"smallvec",
"smol",
"snippet",
@@ -12948,6 +12890,23 @@ dependencies = [
"zlog",
]
[[package]]
name = "project_benchmarks"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"client",
"futures 0.3.31",
"gpui",
"http_client",
"language",
"node_runtime",
"project",
"settings",
"watch",
]
[[package]]
name = "project_panel"
version = "0.1.0"
@@ -13840,6 +13799,7 @@ dependencies = [
"serde",
"serde_json",
"settings",
"shlex",
"smol",
"tempfile",
"thiserror 2.0.17",
@@ -14226,7 +14186,6 @@ dependencies = [
"log",
"rand 0.9.2",
"rayon",
"regex",
"smallvec",
"sum_tree",
"unicode-segmentation",
@@ -14957,7 +14916,6 @@ dependencies = [
"futures 0.3.31",
"gpui",
"language",
"lsp",
"menu",
"project",
"schemars 1.0.4",
@@ -15331,13 +15289,12 @@ dependencies = [
"menu",
"node_runtime",
"paths",
"picker",
"pretty_assertions",
"project",
"release_channel",
"schemars 1.0.4",
"search",
"serde",
"serde_json",
"session",
"settings",
"strum 0.27.2",
@@ -17034,6 +16991,7 @@ dependencies = [
"parking_lot",
"postage",
"rand 0.9.2",
"regex",
"rope",
"smallvec",
"sum_tree",
@@ -18252,8 +18210,10 @@ version = "0.1.0"
dependencies = [
"component",
"editor",
"fuzzy",
"gpui",
"menu",
"picker",
"settings",
"theme",
"ui",
@@ -20648,6 +20608,16 @@ dependencies = [
"zlog",
]
[[package]]
name = "worktree_benchmarks"
version = "0.1.0"
dependencies = [
"fs",
"gpui",
"settings",
"worktree",
]
[[package]]
name = "writeable"
version = "0.6.1"
@@ -20990,7 +20960,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.211.0"
version = "0.210.0"
dependencies = [
"acp_tools",
"activity_indicator",
@@ -21564,7 +21534,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",
@@ -126,6 +126,7 @@ members = [
"crates/picker",
"crates/prettier",
"crates/project",
"crates/project_benchmarks",
"crates/project_panel",
"crates/project_symbols",
"crates/prompt_store",
@@ -194,6 +195,7 @@ members = [
"crates/web_search_providers",
"crates/workspace",
"crates/worktree",
"crates/worktree_benchmarks",
"crates/x_ai",
"crates/zed",
"crates/zed_actions",
@@ -246,7 +248,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" }
@@ -443,7 +445,6 @@ aho-corasick = "1.1"
alacritty_terminal = "0.25.1-rc1"
any_vec = "0.14"
anyhow = "1.0.86"
arraydeque = "0.5.1"
arrayvec = { version = "0.7.4", features = ["serde"] }
ashpd = { version = "0.11", default-features = false, features = ["async-std"] }
async-compat = "0.2.1"
@@ -453,7 +454,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"
@@ -581,14 +582,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 +902,4 @@ ignored = [
"serde",
"component",
"documented",
"sea-orm-macros",
]

View File

@@ -1,106 +0,0 @@
; This file contains a list of people who're interested in reviewing pull requests
; to certain parts of the code-base.
;
; This is mostly used internally for PR assignment, and may change over time.
;
; If you have permission to merge PRs (mostly equivalent to "do you work at Zed Industries"),
; we strongly encourage you to put your name in the "all" bucket, but you can also add yourself
; to other areas too.
<all>
= @ConradIrwin
= @maxdeviant
= @SomeoneToIgnore
= @probably-neb
= @danilo-leal
= @Veykril
= @kubkon
= @p1n3appl3
= @dinocosta
= @smitbarmase
= @cole-miller
vim
= @ConradIrwin
= @probably-neb
= @p1n3appl3
= @dinocosta
gpui
= @mikayla-maki
git
= @cole-miller
= @danilo-leal
linux
= @dvdsk
= @smitbarmase
= @p1n3appl3
= @cole-miller
windows
= @reflectronic
= @localcc
pickers
= @p1n3appl3
= @dvdsk
= @SomeoneToIgnore
audio
= @dvdsk
helix
= @kubkon
terminal
= @kubkon
= @Veykril
debugger
= @kubkon
= @osiewicz
= @Anthony-Eid
extension
= @kubkon
settings_ui
= @probably-neb
= @danilo-leal
= @Anthony-Eid
crashes
= @p1n3appl3
= @Veykril
ai
= @danilo-leal
= @benbrandt
design
= @danilo-leal
multi_buffer
= @Veykril
= @SomeoneToIgnore
lsp
= @osiewicz
= @Veykril
= @smitbarmase
= @SomeoneToIgnore
languages
= @osiewicz
= @Veykril
= @smitbarmase
= @SomeoneToIgnore
project_panel
= @smitbarmase
tasks
= @SomeoneToIgnore
= @Veykril

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"
}
},
@@ -1094,7 +1094,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."],
@@ -1290,13 +1290,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"
}
},
@@ -1168,7 +1168,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
@@ -1396,13 +1396,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"
}
},
@@ -1120,7 +1120,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."],
@@ -1319,13 +1319,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

@@ -1741,7 +1741,7 @@
}
},
"Kotlin": {
"language_servers": ["!kotlin-language-server", "kotlin-lsp", "..."]
"language_servers": ["kotlin-language-server", "!kotlin-lsp", "..."]
},
"LaTeX": {
"formatter": "language_server",
@@ -1772,9 +1772,6 @@
"allow_rewrap": "anywhere"
},
"Python": {
"code_actions_on_format": {
"source.organizeImports.ruff": true
},
"formatter": {
"language_server": {
"name": "ruff"
@@ -1823,7 +1820,6 @@
},
"SystemVerilog": {
"format_on_save": "off",
"language_servers": ["!slang", "..."],
"use_on_type_format": false
},
"Vue.js": {

View File

@@ -1421,18 +1421,15 @@ impl AcpThread {
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 should_ignore = if let Some(agent_location) = project.agent_location() {
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
agent_location.buffer == location.buffer
// 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
};

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

@@ -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
@@ -1483,27 +1483,16 @@ impl EditAgentTest {
fs.insert_tree("/root", json!({})).await;
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
let agent_model = SelectedModel::from_str(
&std::env::var("ZED_AGENT_MODEL").unwrap_or("anthropic/claude-sonnet-4-latest".into()),
&std::env::var("ZED_AGENT_MODEL").unwrap_or("anthropic/claude-4-sonnet-latest".into()),
)
.unwrap();
let judge_model = SelectedModel::from_str(
&std::env::var("ZED_JUDGE_MODEL").unwrap_or("anthropic/claude-sonnet-4-latest".into()),
&std::env::var("ZED_JUDGE_MODEL").unwrap_or("anthropic/claude-4-sonnet-latest".into()),
)
.unwrap();
let authenticate_provider_tasks = cx.update(|cx| {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry
.providers()
.iter()
.map(|p| p.authenticate(cx))
.collect::<Vec<_>>()
})
});
let (agent_model, judge_model) = cx
.update(|cx| {
cx.spawn(async move |cx| {
futures::future::join_all(authenticate_provider_tasks).await;
let agent_model = Self::load_model(&agent_model, cx).await;
let judge_model = Self::load_model(&judge_model, cx).await;
(agent_model.unwrap(), judge_model.unwrap())
@@ -1547,7 +1536,7 @@ impl EditAgentTest {
model.provider_id() == selected_model.provider
&& model.id() == selected_model.model
})
.unwrap_or_else(|| panic!("Model {} not found", selected_model.model.0));
.expect("Model not found");
model
})
}

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

@@ -81,7 +81,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 +116,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")]
@@ -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

@@ -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

@@ -1259,7 +1259,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(())
})
@@ -2158,6 +2157,7 @@ impl AcpThreadView {
options,
entry_ix,
tool_call.id.clone(),
window,
cx,
))
.into_any(),
@@ -2558,6 +2558,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 +2615,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 +2796,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 +3102,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 +3131,7 @@ impl AcpThreadView {
KeyBinding::for_action_in(
&OpenHistory,
&self.focus_handle(cx),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -3456,6 +3459,7 @@ impl AcpThreadView {
&changed_buffers,
self.edits_expanded,
pending_edits,
window,
cx,
))
.when(self.edits_expanded, |parent| {
@@ -3615,6 +3619,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 +3695,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 +3718,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 +3738,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 +3968,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 +4198,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 +4221,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 +4282,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 +5079,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 +5090,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 +5283,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 +5314,7 @@ impl AcpThreadView {
KeyBinding::for_action_in(
&ContinueWithBurnMode,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
@@ -5318,8 +5338,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 +5439,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 +5520,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 +5551,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 +5576,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 +5761,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 +5924,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| {
@@ -6172,10 +6196,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

@@ -6,11 +6,8 @@ use std::sync::Arc;
use acp_thread::AcpThread;
use agent::{ContextServerRegistry, DbThreadMetadata, HistoryEntry, HistoryStore};
use db::kvp::{Dismissable, KEY_VALUE_STORE};
use project::{
ExternalAgentServerName,
agent_server_store::{
AgentServerCommand, AllAgentServersSettings, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME,
},
use project::agent_server_store::{
AgentServerCommand, AllAgentServersSettings, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME,
};
use serde::{Deserialize, Serialize};
use settings::{
@@ -39,13 +36,11 @@ use crate::{
use agent_settings::AgentSettings;
use ai_onboarding::AgentPanelOnboarding;
use anyhow::{Result, anyhow};
use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
use assistant_slash_command::SlashCommandWorkingSet;
use assistant_text_thread::{TextThread, TextThreadEvent, TextThreadSummary};
use client::{UserStore, zed_urls};
use cloud_llm_client::{Plan, PlanV1, PlanV2, UsageLimit};
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
use extension::ExtensionEvents;
use extension_host::ExtensionStore;
use fs::Fs;
use gpui::{
Action, AnyElement, App, AsyncWindowContext, Corner, DismissEvent, Entity, EventEmitter,
@@ -72,9 +67,7 @@ use workspace::{
};
use zed_actions::{
DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,
agent::{
OpenAcpOnboardingModal, OpenOnboardingModal, OpenSettings, ResetAgentZoom, ResetOnboarding,
},
agent::{OpenAcpOnboardingModal, OpenOnboardingModal, OpenSettings, ResetOnboarding},
assistant::{OpenRulesLibrary, ToggleFocus},
};
@@ -195,13 +188,6 @@ pub fn init(cx: &mut App) {
})
.register_action(|_workspace, _: &ResetTrialEndUpsell, _window, cx| {
TrialEndUpsell::set_dismissed(false, cx);
})
.register_action(|workspace, _: &ResetAgentZoom, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
panel.update(cx, |panel, cx| {
panel.reset_agent_zoom(window, cx);
});
}
});
},
)
@@ -213,7 +199,7 @@ enum ActiveView {
thread_view: Entity<AcpThreadView>,
},
TextThread {
text_thread_editor: Entity<TextThreadEditor>,
context_editor: Entity<TextThreadEditor>,
title_editor: Entity<Editor>,
buffer_search_bar: Entity<BufferSearchBar>,
_subscriptions: Vec<gpui::Subscription>,
@@ -315,13 +301,13 @@ impl ActiveView {
}
pub fn text_thread(
text_thread_editor: Entity<TextThreadEditor>,
context_editor: Entity<TextThreadEditor>,
acp_history_store: Entity<agent::HistoryStore>,
language_registry: Arc<LanguageRegistry>,
window: &mut Window,
cx: &mut App,
) -> Self {
let title = text_thread_editor.read(cx).title(cx).to_string();
let title = context_editor.read(cx).title(cx).to_string();
let editor = cx.new(|cx| {
let mut editor = Editor::single_line(window, cx);
@@ -337,7 +323,7 @@ impl ActiveView {
let subscriptions = vec![
window.subscribe(&editor, cx, {
{
let text_thread_editor = text_thread_editor.clone();
let context_editor = context_editor.clone();
move |editor, event, window, cx| match event {
EditorEvent::BufferEdited => {
if suppress_first_edit {
@@ -346,19 +332,19 @@ impl ActiveView {
}
let new_summary = editor.read(cx).text(cx);
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor
.text_thread()
.update(cx, |text_thread, cx| {
text_thread.set_custom_summary(new_summary, cx);
context_editor.update(cx, |context_editor, cx| {
context_editor
.context()
.update(cx, |assistant_context, cx| {
assistant_context.set_custom_summary(new_summary, cx);
})
})
}
EditorEvent::Blurred => {
if editor.read(cx).text(cx).is_empty() {
let summary = text_thread_editor
let summary = context_editor
.read(cx)
.text_thread()
.context()
.read(cx)
.summary()
.or_default();
@@ -372,17 +358,17 @@ impl ActiveView {
}
}
}),
window.subscribe(&text_thread_editor.read(cx).text_thread().clone(), cx, {
window.subscribe(&context_editor.read(cx).context().clone(), cx, {
let editor = editor.clone();
move |text_thread, event, window, cx| match event {
TextThreadEvent::SummaryGenerated => {
let summary = text_thread.read(cx).summary().or_default();
move |assistant_context, event, window, cx| match event {
ContextEvent::SummaryGenerated => {
let summary = assistant_context.read(cx).summary().or_default();
editor.update(cx, |editor, cx| {
editor.set_text(summary, window, cx);
})
}
TextThreadEvent::PathChanged { old_path, new_path } => {
ContextEvent::PathChanged { old_path, new_path } => {
acp_history_store.update(cx, |history_store, cx| {
if let Some(old_path) = old_path {
history_store
@@ -403,11 +389,11 @@ impl ActiveView {
let buffer_search_bar =
cx.new(|cx| BufferSearchBar::new(Some(language_registry), window, cx));
buffer_search_bar.update(cx, |buffer_search_bar, cx| {
buffer_search_bar.set_active_pane_item(Some(&text_thread_editor), window, cx)
buffer_search_bar.set_active_pane_item(Some(&context_editor), window, cx)
});
Self::TextThread {
text_thread_editor,
context_editor,
title_editor: editor,
buffer_search_bar,
_subscriptions: subscriptions,
@@ -424,7 +410,7 @@ pub struct AgentPanel {
language_registry: Arc<LanguageRegistry>,
acp_history: Entity<AcpThreadHistory>,
history_store: Entity<agent::HistoryStore>,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
text_thread_store: Entity<assistant_context::ContextStore>,
prompt_store: Option<Entity<PromptStore>>,
context_server_registry: Entity<ContextServerRegistry>,
inline_assist_context_store: Entity<ContextStore>,
@@ -436,7 +422,6 @@ pub struct AgentPanel {
agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
agent_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
agent_navigation_menu: Option<Entity<ContextMenu>>,
_extension_subscription: Option<Subscription>,
width: Option<Pixels>,
height: Option<Pixels>,
zoomed: bool,
@@ -489,7 +474,7 @@ impl AgentPanel {
let text_thread_store = workspace
.update(cx, |workspace, cx| {
let project = workspace.project().clone();
assistant_text_thread::TextThreadStore::new(
assistant_context::ContextStore::new(
project,
prompt_builder,
slash_commands,
@@ -527,7 +512,7 @@ impl AgentPanel {
fn new(
workspace: &Workspace,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
text_thread_store: Entity<assistant_context::ContextStore>,
prompt_store: Option<Entity<PromptStore>>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -580,8 +565,8 @@ impl AgentPanel {
DefaultView::TextThread => {
let context = text_thread_store.update(cx, |store, cx| store.create(cx));
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project.clone(), cx).unwrap();
let text_thread_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_text_thread(
let context_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_context(
context,
fs.clone(),
workspace.clone(),
@@ -594,7 +579,7 @@ impl AgentPanel {
editor
});
ActiveView::text_thread(
text_thread_editor,
context_editor,
history_store.clone(),
language_registry.clone(),
window,
@@ -647,24 +632,7 @@ impl AgentPanel {
)
});
// Subscribe to extension events to sync agent servers when extensions change
let extension_subscription = if let Some(extension_events) = ExtensionEvents::try_global(cx)
{
Some(
cx.subscribe(&extension_events, |this, _source, event, cx| match event {
extension::Event::ExtensionInstalled(_)
| extension::Event::ExtensionUninstalled(_)
| extension::Event::ExtensionsInstalledChanged => {
this.sync_agent_servers_from_extensions(cx);
}
_ => {}
}),
)
} else {
None
};
let mut panel = Self {
Self {
active_view,
workspace,
user_store,
@@ -682,7 +650,6 @@ impl AgentPanel {
agent_panel_menu_handle: PopoverMenuHandle::default(),
agent_navigation_menu_handle: PopoverMenuHandle::default(),
agent_navigation_menu: None,
_extension_subscription: extension_subscription,
width: None,
height: None,
zoomed: false,
@@ -692,11 +659,7 @@ impl AgentPanel {
history_store,
selected_agent: AgentType::default(),
loading: false,
};
// Initial sync of agent servers from extensions
panel.sync_agent_servers_from_extensions(cx);
panel
}
}
pub fn toggle_focus(
@@ -773,8 +736,8 @@ impl AgentPanel {
.log_err()
.flatten();
let text_thread_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_text_thread(
let context_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_context(
context,
self.fs.clone(),
self.workspace.clone(),
@@ -794,7 +757,7 @@ impl AgentPanel {
self.set_active_view(
ActiveView::text_thread(
text_thread_editor.clone(),
context_editor.clone(),
self.history_store.clone(),
self.language_registry.clone(),
window,
@@ -803,7 +766,7 @@ impl AgentPanel {
window,
cx,
);
text_thread_editor.focus_handle(cx).focus(window);
context_editor.focus_handle(cx).focus(window);
}
fn external_thread(
@@ -942,20 +905,20 @@ impl AgentPanel {
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let text_thread_task = self
let context = self
.history_store
.update(cx, |store, cx| store.load_text_thread(path, cx));
cx.spawn_in(window, async move |this, cx| {
let text_thread = text_thread_task.await?;
let context = context.await?;
this.update_in(cx, |this, window, cx| {
this.open_text_thread(text_thread, window, cx);
this.open_text_thread(context, window, cx);
})
})
}
pub(crate) fn open_text_thread(
&mut self,
text_thread: Entity<TextThread>,
context: Entity<AssistantContext>,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -963,8 +926,8 @@ impl AgentPanel {
.log_err()
.flatten();
let editor = cx.new(|cx| {
TextThreadEditor::for_text_thread(
text_thread,
TextThreadEditor::for_context(
context,
self.fs.clone(),
self.workspace.clone(),
self.project.clone(),
@@ -1002,10 +965,8 @@ impl AgentPanel {
ActiveView::ExternalAgentThread { thread_view } => {
thread_view.focus_handle(cx).focus(window);
}
ActiveView::TextThread {
text_thread_editor, ..
} => {
text_thread_editor.focus_handle(cx).focus(window);
ActiveView::TextThread { context_editor, .. } => {
context_editor.focus_handle(cx).focus(window);
}
ActiveView::History | ActiveView::Configuration => {}
}
@@ -1068,21 +1029,13 @@ impl AgentPanel {
update_settings_file(self.fs.clone(), cx, move |settings, cx| {
let agent_ui_font_size =
ThemeSettings::get_global(cx).agent_ui_font_size(cx) + delta;
let agent_buffer_font_size =
ThemeSettings::get_global(cx).agent_buffer_font_size(cx) + delta;
let _ = settings
.theme
.agent_ui_font_size
.insert(theme::clamp_font_size(agent_ui_font_size).into());
let _ = settings
.theme
.agent_buffer_font_size
.insert(theme::clamp_font_size(agent_buffer_font_size).into());
});
} else {
theme::adjust_agent_ui_font_size(cx, |size| size + delta);
theme::adjust_agent_buffer_font_size(cx, |size| size + delta);
}
}
WhichFontSize::BufferFont => {
@@ -1103,19 +1056,12 @@ impl AgentPanel {
if action.persist {
update_settings_file(self.fs.clone(), cx, move |settings, _| {
settings.theme.agent_ui_font_size = None;
settings.theme.agent_buffer_font_size = None;
});
} else {
theme::reset_agent_ui_font_size(cx);
theme::reset_agent_buffer_font_size(cx);
}
}
pub fn reset_agent_zoom(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
theme::reset_agent_ui_font_size(cx);
theme::reset_agent_buffer_font_size(cx);
}
pub fn toggle_zoom(&mut self, _: &ToggleZoom, window: &mut Window, cx: &mut Context<Self>) {
if self.zoomed {
cx.emit(PanelEvent::ZoomOut);
@@ -1237,11 +1183,9 @@ impl AgentPanel {
}
}
pub(crate) fn active_text_thread_editor(&self) -> Option<Entity<TextThreadEditor>> {
pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
match &self.active_view {
ActiveView::TextThread {
text_thread_editor, ..
} => Some(text_thread_editor.clone()),
ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
_ => None,
}
}
@@ -1262,16 +1206,16 @@ impl AgentPanel {
let new_is_special = new_is_history || new_is_config;
match &new_view {
ActiveView::TextThread {
text_thread_editor, ..
} => self.history_store.update(cx, |store, cx| {
if let Some(path) = text_thread_editor.read(cx).text_thread().read(cx).path() {
store.push_recently_opened_entry(
agent::HistoryEntryId::TextThread(path.clone()),
cx,
)
}
}),
ActiveView::TextThread { context_editor, .. } => {
self.history_store.update(cx, |store, cx| {
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
store.push_recently_opened_entry(
agent::HistoryEntryId::TextThread(path.clone()),
cx,
)
}
})
}
ActiveView::ExternalAgentThread { .. } => {}
ActiveView::History | ActiveView::Configuration => {}
}
@@ -1361,31 +1305,6 @@ impl AgentPanel {
self.selected_agent.clone()
}
fn sync_agent_servers_from_extensions(&mut self, cx: &mut Context<Self>) {
if let Some(extension_store) = ExtensionStore::try_global(cx) {
let (manifests, extensions_dir) = {
let store = extension_store.read(cx);
let installed = store.installed_extensions();
let manifests: Vec<_> = installed
.iter()
.map(|(id, entry)| (id.clone(), entry.manifest.clone()))
.collect();
let extensions_dir = paths::extensions_dir().join("installed");
(manifests, extensions_dir)
};
self.project.update(cx, |project, cx| {
project.agent_server_store().update(cx, |store, cx| {
let manifest_refs: Vec<_> = manifests
.iter()
.map(|(id, manifest)| (id.as_ref(), manifest.as_ref()))
.collect();
store.sync_extension_agents(manifest_refs, extensions_dir, cx);
});
});
}
}
pub fn new_agent_thread(
&mut self,
agent: AgentType,
@@ -1453,9 +1372,7 @@ impl Focusable for AgentPanel {
match &self.active_view {
ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
ActiveView::History => self.acp_history.focus_handle(cx),
ActiveView::TextThread {
text_thread_editor, ..
} => text_thread_editor.focus_handle(cx),
ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
ActiveView::Configuration => {
if let Some(configuration) = self.configuration.as_ref() {
configuration.focus_handle(cx)
@@ -1590,17 +1507,17 @@ impl AgentPanel {
}
ActiveView::TextThread {
title_editor,
text_thread_editor,
context_editor,
..
} => {
let summary = text_thread_editor.read(cx).text_thread().read(cx).summary();
let summary = context_editor.read(cx).context().read(cx).summary();
match summary {
TextThreadSummary::Pending => Label::new(TextThreadSummary::DEFAULT)
ContextSummary::Pending => Label::new(ContextSummary::DEFAULT)
.color(Color::Muted)
.truncate()
.into_any_element(),
TextThreadSummary::Content(summary) => {
ContextSummary::Content(summary) => {
if summary.done {
div()
.w_full()
@@ -1613,17 +1530,17 @@ impl AgentPanel {
.into_any_element()
}
}
TextThreadSummary::Error => h_flex()
ContextSummary::Error => h_flex()
.w_full()
.child(title_editor.clone())
.child(
IconButton::new("retry-summary-generation", IconName::RotateCcw)
.icon_size(IconSize::Small)
.on_click({
let text_thread_editor = text_thread_editor.clone();
let context_editor = context_editor.clone();
move |_, _window, cx| {
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor.regenerate_summary(cx);
context_editor.update(cx, |context_editor, cx| {
context_editor.regenerate_summary(cx);
});
}
})
@@ -1678,11 +1595,12 @@ impl AgentPanel {
.icon_size(IconSize::Small),
{
let focus_handle = focus_handle.clone();
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Toggle Agent Menu",
&ToggleOptionsMenu,
&focus_handle,
window,
cx,
)
}
@@ -1746,7 +1664,7 @@ impl AgentPanel {
.separator();
menu = menu
.action("Rules", Box::new(OpenRulesLibrary::default()))
.action("Rules", Box::new(OpenRulesLibrary::default()))
.action("Settings", Box::new(OpenSettings))
.separator()
.action(full_screen_label, Box::new(ToggleZoom));
@@ -1773,11 +1691,12 @@ impl AgentPanel {
.trigger_with_tooltip(
IconButton::new("agent-nav-menu", icon).icon_size(IconSize::Small),
{
move |_window, cx| {
move |window, cx| {
Tooltip::for_action_in(
"Toggle Recent Threads",
&ToggleNavigationMenu,
&focus_handle,
window,
cx,
)
}
@@ -1811,8 +1730,8 @@ impl AgentPanel {
this.go_back(&workspace::GoBack, window, cx);
}))
.tooltip({
move |_window, cx| {
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, window, cx)
}
})
}
@@ -1821,16 +1740,6 @@ impl AgentPanel {
let agent_server_store = self.project.read(cx).agent_server_store().clone();
let focus_handle = self.focus_handle(cx);
// Get custom icon path for selected agent before building menu (to avoid borrow issues)
let selected_agent_custom_icon =
if let AgentType::Custom { name, .. } = &self.selected_agent {
agent_server_store
.read(cx)
.agent_icon(&ExternalAgentServerName(name.clone()))
} else {
None
};
let active_thread = match &self.active_view {
ActiveView::ExternalAgentThread { thread_view } => {
thread_view.read(cx).as_native_thread(cx)
@@ -1843,8 +1752,14 @@ impl AgentPanel {
IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
{
let focus_handle = focus_handle.clone();
move |_window, cx| {
Tooltip::for_action_in("New…", &ToggleNewThreadMenu, &focus_handle, cx)
move |window, cx| {
Tooltip::for_action_in(
"New…",
&ToggleNewThreadMenu,
&focus_handle,
window,
cx,
)
}
},
)
@@ -1863,7 +1778,8 @@ impl AgentPanel {
let active_thread = active_thread.clone();
Some(ContextMenu::build(window, cx, |menu, _window, cx| {
menu.context(focus_handle.clone())
menu
.context(focus_handle.clone())
.header("Zed Agent")
.when_some(active_thread, |this, active_thread| {
let thread = active_thread.read(cx);
@@ -2020,110 +1936,83 @@ impl AgentPanel {
}),
)
.map(|mut menu| {
let agent_server_store_read = agent_server_store.read(cx);
let agent_names = agent_server_store_read
let agent_names = agent_server_store
.read(cx)
.external_agents()
.filter(|name| {
name.0 != GEMINI_NAME
&& name.0 != CLAUDE_CODE_NAME
&& name.0 != CODEX_NAME
name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME && name.0 != CODEX_NAME
})
.cloned()
.collect::<Vec<_>>();
let custom_settings = cx
.global::<SettingsStore>()
.get::<AllAgentServersSettings>(None)
.custom
.clone();
let custom_settings = cx.global::<SettingsStore>().get::<AllAgentServersSettings>(None).custom.clone();
for agent_name in agent_names {
let icon_path = agent_server_store_read.agent_icon(&agent_name);
let mut entry =
ContextMenuEntry::new(format!("New {} Thread", agent_name));
if let Some(icon_path) = icon_path {
entry = entry.custom_icon_path(icon_path);
} else {
entry = entry.icon(IconName::Terminal);
}
entry = entry
.icon_color(Color::Muted)
.disabled(is_via_collab)
.handler({
let workspace = workspace.clone();
let agent_name = agent_name.clone();
let custom_settings = custom_settings.clone();
move |window, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(panel) =
workspace.panel::<AgentPanel>(cx)
{
panel.update(cx, |panel, cx| {
panel.new_agent_thread(
AgentType::Custom {
name: agent_name
.clone()
.into(),
command: custom_settings
.get(&agent_name.0)
.map(|settings| {
settings
.command
.clone()
})
.unwrap_or(
placeholder_command(
),
),
},
window,
cx,
);
});
}
});
menu = menu.item(
ContextMenuEntry::new(format!("New {} Thread", agent_name))
.icon(IconName::Terminal)
.icon_color(Color::Muted)
.disabled(is_via_collab)
.handler({
let workspace = workspace.clone();
let agent_name = agent_name.clone();
let custom_settings = custom_settings.clone();
move |window, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(panel) =
workspace.panel::<AgentPanel>(cx)
{
panel.update(cx, |panel, cx| {
panel.new_agent_thread(
AgentType::Custom {
name: agent_name.clone().into(),
command: custom_settings
.get(&agent_name.0)
.map(|settings| {
settings.command.clone()
})
.unwrap_or(placeholder_command()),
},
window,
cx,
);
});
}
});
}
}
}
});
menu = menu.item(entry);
}),
);
}
menu
})
.separator()
.link(
"Add Other Agents",
OpenBrowser {
url: zed_urls::external_agents_docs(cx),
}
.boxed_clone(),
)
.separator().link(
"Add Other Agents",
OpenBrowser {
url: zed_urls::external_agents_docs(cx),
}
.boxed_clone(),
)
}))
}
});
let selected_agent_label = self.selected_agent.label();
let has_custom_icon = selected_agent_custom_icon.is_some();
let selected_agent = div()
.id("selected_agent_icon")
.when_some(selected_agent_custom_icon, |this, icon_path| {
let label = selected_agent_label.clone();
.when_some(self.selected_agent.icon(), |this, icon| {
this.px(DynamicSpacing::Base02.rems(cx))
.child(Icon::from_path(icon_path).color(Color::Muted))
.tooltip(move |_window, cx| {
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
.child(Icon::new(icon).color(Color::Muted))
.tooltip(move |window, cx| {
Tooltip::with_meta(
selected_agent_label.clone(),
None,
"Selected Agent",
window,
cx,
)
})
})
.when(!has_custom_icon, |this| {
this.when_some(self.selected_agent.icon(), |this, icon| {
let label = selected_agent_label.clone();
this.px(DynamicSpacing::Base02.rems(cx))
.child(Icon::new(icon).color(Color::Muted))
.tooltip(move |_window, cx| {
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
})
})
})
.into_any_element();
h_flex()
@@ -2297,6 +2186,7 @@ impl AgentPanel {
border_bottom: bool,
configuration_error: &ConfigurationError,
focus_handle: &FocusHandle,
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
let zed_provider_configured = AgentSettings::get_global(cx)
@@ -2345,7 +2235,7 @@ impl AgentPanel {
.style(ButtonStyle::Tinted(ui::TintColor::Warning))
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(&OpenSettings, focus_handle, cx)
KeyBinding::for_action_in(&OpenSettings, focus_handle, window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(|_event, window, cx| {
@@ -2363,7 +2253,7 @@ impl AgentPanel {
fn render_text_thread(
&self,
text_thread_editor: &Entity<TextThreadEditor>,
context_editor: &Entity<TextThreadEditor>,
buffer_search_bar: &Entity<BufferSearchBar>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -2397,7 +2287,7 @@ impl AgentPanel {
)
})
})
.child(text_thread_editor.clone())
.child(context_editor.clone())
.child(self.render_drag_target(cx))
}
@@ -2473,12 +2363,10 @@ impl AgentPanel {
thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
});
}
ActiveView::TextThread {
text_thread_editor, ..
} => {
text_thread_editor.update(cx, |text_thread_editor, cx| {
ActiveView::TextThread { context_editor, .. } => {
context_editor.update(cx, |context_editor, cx| {
TextThreadEditor::insert_dragged_files(
text_thread_editor,
context_editor,
paths,
added_worktrees,
window,
@@ -2549,7 +2437,7 @@ impl Render for AgentPanel {
.child(self.render_drag_target(cx)),
ActiveView::History => parent.child(self.acp_history.clone()),
ActiveView::TextThread {
text_thread_editor,
context_editor,
buffer_search_bar,
..
} => {
@@ -2565,6 +2453,7 @@ impl Render for AgentPanel {
true,
err,
&self.focus_handle(cx),
window,
cx,
))
} else {
@@ -2572,7 +2461,7 @@ impl Render for AgentPanel {
}
})
.child(self.render_text_thread(
text_thread_editor,
context_editor,
buffer_search_bar,
window,
cx,
@@ -2650,17 +2539,17 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
pub struct ConcreteAssistantPanelDelegate;
impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
fn active_text_thread_editor(
fn active_context_editor(
&self,
workspace: &mut Workspace,
_window: &mut Window,
cx: &mut Context<Workspace>,
) -> Option<Entity<TextThreadEditor>> {
let panel = workspace.panel::<AgentPanel>(cx)?;
panel.read(cx).active_text_thread_editor()
panel.read(cx).active_context_editor()
}
fn open_local_text_thread(
fn open_saved_context(
&self,
workspace: &mut Workspace,
path: Arc<Path>,
@@ -2676,10 +2565,10 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
})
}
fn open_remote_text_thread(
fn open_remote_context(
&self,
_workspace: &mut Workspace,
_text_thread_id: assistant_text_thread::TextThreadId,
_context_id: assistant_context::ContextId,
_window: &mut Window,
_cx: &mut Context<Workspace>,
) -> Task<Result<Entity<TextThreadEditor>>> {
@@ -2710,15 +2599,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

@@ -20,7 +20,7 @@ use futures::{
};
use gpui::{AsyncApp, BackgroundExecutor, Task};
use smol::fs;
use util::{ResultExt as _, debug_panic, maybe, paths::PathExt, shell::ShellKind};
use util::{ResultExt as _, debug_panic, maybe, paths::PathExt};
/// Path to the program used for askpass
///
@@ -199,15 +199,9 @@ impl PasswordProxy {
let current_exec =
std::env::current_exe().context("Failed to determine current zed executable path.")?;
// TODO: inferred from the use of powershell.exe in askpass_helper_script
let shell_kind = if cfg!(windows) {
ShellKind::PowerShell
} else {
ShellKind::Posix
};
let askpass_program = ASKPASS_PROGRAM
.get_or_init(|| current_exec)
.try_shell_safe(shell_kind)
.try_shell_safe()
.context("Failed to shell-escape Askpass program path.")?
.to_string();
// Create an askpass script that communicates back to this process.
@@ -349,7 +343,7 @@ fn generate_askpass_script(askpass_program: &str, askpass_socket: &std::path::Pa
format!(
r#"
$ErrorActionPreference = 'Stop';
($args -join [char]0) | & {askpass_program} --askpass={askpass_socket} 2> $null
($args -join [char]0) | & "{askpass_program}" --askpass={askpass_socket} 2> $null
"#,
askpass_socket = askpass_socket.display(),
)

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 {
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,48 @@ 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 {
ContextSummary::Content(content) => content,
ContextSummary::Pending | ContextSummary::Error => {
let content = ContextSummaryContent {
text: "".to_string(),
done: false,
timestamp: clock::Lamport::MIN,
};
*self = TextThreadSummary::Content(content);
*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 +668,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 +711,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 +723,7 @@ impl TextThread {
cx: &mut Context<Self>,
) -> Self {
Self::new(
TextThreadId::new(),
ContextId::new(),
ReplicaId::default(),
language::Capability::ReadWrite,
language_registry,
@@ -734,7 +744,7 @@ impl TextThread {
}
pub fn new(
id: TextThreadId,
id: ContextId,
replica_id: ReplicaId,
capability: language::Capability,
language_registry: Arc<LanguageRegistry>,
@@ -770,7 +780,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(),
@@ -813,12 +823,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 +876,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 +885,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 +906,7 @@ impl TextThread {
this
}
pub fn id(&self) -> &TextThreadId {
pub fn id(&self) -> &ContextId {
&self.id
}
@@ -904,9 +914,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 +938,7 @@ impl TextThread {
pub fn serialize_ops(
&self,
since: &TextThreadVersion,
since: &ContextVersion,
cx: &App,
) -> Task<Vec<proto::ContextOperation>> {
let buffer_ops = self
@@ -939,7 +949,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 +973,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 +988,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 +1001,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 +1011,7 @@ impl TextThread {
self.insert_message(anchor, metadata, cx);
}
}
TextThreadOperation::UpdateMessage {
ContextOperation::UpdateMessage {
message_id,
metadata: new_metadata,
..
@@ -1012,7 +1022,7 @@ impl TextThread {
changed_messages.insert(message_id);
}
}
TextThreadOperation::UpdateSummary {
ContextOperation::UpdateSummary {
summary: new_summary,
..
} => {
@@ -1021,11 +1031,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 +1052,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 +1062,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 +1075,7 @@ impl TextThread {
.insert(ix, section.clone());
}
}
TextThreadOperation::SlashCommandFinished {
ContextOperation::SlashCommandFinished {
id,
error_message,
timestamp,
@@ -1084,10 +1094,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 +1107,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 +1164,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 +1189,7 @@ impl TextThread {
self.path.as_ref()
}
pub fn summary(&self) -> &TextThreadSummary {
pub fn summary(&self) -> &ContextSummary {
&self.summary
}
@@ -1240,13 +1250,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 +1522,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 +1596,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 +1605,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 +1910,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 +1935,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 +1961,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 +1996,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 +2115,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 +2133,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 +2163,7 @@ impl TextThread {
cx.emit(context_event);
}
cx.emit(TextThreadEvent::StreamedCompletion);
cx.emit(ContextEvent::StreamedCompletion);
Some(())
})?;
@@ -2174,7 +2184,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 +2195,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 +2412,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 +2482,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 +2505,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 +2580,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 +2630,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 +2642,7 @@ impl TextThread {
};
if !edited_buffer {
cx.emit(TextThreadEvent::MessagesEdited);
cx.emit(ContextEvent::MessagesEdited);
}
new_messages
} else {
@@ -2646,7 +2656,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 +2692,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,
});
replace_old = true;
}
TextThreadSummary::Content(_) => {}
ContextSummary::Content(_) => {}
}
self.summary_task = cx.spawn(async move |this, cx| {
@@ -2712,13 +2722,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 +2752,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 +2768,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 +2875,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 +2906,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 +2918,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 +2940,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 +2959,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 +2979,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 +3063,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 +3076,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 +3086,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,8 +3113,8 @@ 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());
@@ -3114,7 +3124,7 @@ impl SavedTextThread {
if message.id == MessageId(clock::Lamport::MIN) {
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,7 +3144,7 @@ impl SavedTextThread {
if let Some(metadata) = first_message_metadata {
let timestamp = next_timestamp.tick();
operations.push(TextThreadOperation::UpdateMessage {
operations.push(ContextOperation::UpdateMessage {
message_id: MessageId(clock::Lamport::MIN),
metadata: MessageMetadata {
role: metadata.role,
@@ -3150,7 +3160,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 +3177,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 +3190,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 +3221,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 +3234,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 +3270,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 +3282,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 +3299,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 +3313,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 +3328,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

@@ -1162,22 +1162,34 @@ impl BufferDiff {
self.hunks_intersecting_range(start..end, buffer, cx)
}
pub fn set_base_text_buffer(
&mut self,
base_buffer: Entity<language::Buffer>,
buffer: text::BufferSnapshot,
cx: &mut Context<Self>,
) -> oneshot::Receiver<()> {
let base_buffer = base_buffer.read(cx);
let language_registry = base_buffer.language_registry();
let base_buffer = base_buffer.snapshot();
self.set_base_text(base_buffer, language_registry, buffer, cx)
}
/// Used in cases where the change set isn't derived from git.
pub fn set_base_text(
&mut self,
base_text: Option<Arc<String>>,
language: Option<Arc<Language>>,
base_buffer: language::BufferSnapshot,
language_registry: Option<Arc<LanguageRegistry>>,
buffer: text::BufferSnapshot,
cx: &mut Context<Self>,
) -> oneshot::Receiver<()> {
let (tx, rx) = oneshot::channel();
let this = cx.weak_entity();
let base_text = Arc::new(base_buffer.text());
let snapshot = BufferDiffSnapshot::new_with_base_text(
buffer.clone(),
base_text,
language,
Some(base_text),
base_buffer.language().cloned(),
language_registry,
cx,
);

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

@@ -83,7 +83,7 @@ impl Global {
self.values.get(replica_id.0 as usize).copied().unwrap_or(0) as Seq
}
/// Observe the lamport timestamp.
/// Observe the lamport timestampe.
///
/// 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) {

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

@@ -467,7 +467,6 @@ CREATE TABLE extension_versions (
provides_grammars BOOLEAN NOT NULL DEFAULT FALSE,
provides_language_servers BOOLEAN NOT NULL DEFAULT FALSE,
provides_context_servers BOOLEAN NOT NULL DEFAULT FALSE,
provides_agent_servers BOOLEAN NOT NULL DEFAULT FALSE,
provides_slash_commands BOOLEAN NOT NULL DEFAULT FALSE,
provides_indexed_docs_providers BOOLEAN NOT NULL DEFAULT FALSE,
provides_snippets BOOLEAN NOT NULL DEFAULT FALSE,

View File

@@ -1,2 +0,0 @@
alter table extension_versions
add column provides_agent_servers bool not null default false

View File

@@ -310,9 +310,6 @@ impl Database {
.provides
.contains(&ExtensionProvides::ContextServers),
),
provides_agent_servers: ActiveValue::Set(
version.provides.contains(&ExtensionProvides::AgentServers),
),
provides_slash_commands: ActiveValue::Set(
version.provides.contains(&ExtensionProvides::SlashCommands),
),
@@ -425,10 +422,6 @@ fn apply_provides_filter(
condition = condition.add(extension_version::Column::ProvidesContextServers.eq(true));
}
if provides_filter.contains(&ExtensionProvides::AgentServers) {
condition = condition.add(extension_version::Column::ProvidesAgentServers.eq(true));
}
if provides_filter.contains(&ExtensionProvides::SlashCommands) {
condition = condition.add(extension_version::Column::ProvidesSlashCommands.eq(true));
}

View File

@@ -24,7 +24,6 @@ pub struct Model {
pub provides_grammars: bool,
pub provides_language_servers: bool,
pub provides_context_servers: bool,
pub provides_agent_servers: bool,
pub provides_slash_commands: bool,
pub provides_indexed_docs_providers: bool,
pub provides_snippets: bool,
@@ -58,10 +57,6 @@ impl Model {
provides.insert(ExtensionProvides::ContextServers);
}
if self.provides_agent_servers {
provides.insert(ExtensionProvides::AgentServers);
}
if self.provides_slash_commands {
provides.insert(ExtensionProvides::SlashCommands);
}

View File

@@ -16,72 +16,6 @@ test_both_dbs!(
test_extensions_sqlite
);
test_both_dbs!(
test_agent_servers_filter,
test_agent_servers_filter_postgres,
test_agent_servers_filter_sqlite
);
async fn test_agent_servers_filter(db: &Arc<Database>) {
// No extensions initially
let versions = db.get_known_extension_versions().await.unwrap();
assert!(versions.is_empty());
// Shared timestamp
let t0 = time::OffsetDateTime::from_unix_timestamp_nanos(0).unwrap();
let t0 = time::PrimitiveDateTime::new(t0.date(), t0.time());
// Insert two extensions, only one provides AgentServers
db.insert_extension_versions(
&[
(
"ext_agent_servers",
vec![NewExtensionVersion {
name: "Agent Servers Provider".into(),
version: semver::Version::parse("1.0.0").unwrap(),
description: "has agent servers".into(),
authors: vec!["author".into()],
repository: "org/agent-servers".into(),
schema_version: 1,
wasm_api_version: None,
provides: BTreeSet::from_iter([ExtensionProvides::AgentServers]),
published_at: t0,
}],
),
(
"ext_plain",
vec![NewExtensionVersion {
name: "Plain Extension".into(),
version: semver::Version::parse("0.1.0").unwrap(),
description: "no agent servers".into(),
authors: vec!["author2".into()],
repository: "org/plain".into(),
schema_version: 1,
wasm_api_version: None,
provides: BTreeSet::default(),
published_at: t0,
}],
),
]
.into_iter()
.collect(),
)
.await
.unwrap();
// Filter by AgentServers provides
let provides_filter = BTreeSet::from_iter([ExtensionProvides::AgentServers]);
let filtered = db
.get_extensions(None, Some(&provides_filter), 1, 10)
.await
.unwrap();
// Expect only the extension that declared AgentServers
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].id.as_ref(), "ext_agent_servers");
}
async fn test_extensions(db: &Arc<Database>) {
let versions = db.get_known_extension_versions().await.unwrap();
assert!(versions.is_empty());

View File

@@ -343,11 +343,11 @@ 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>)
.add_request_handler(forward_read_only_project_request::<proto::GitGetBranches>)
.add_request_handler(forward_read_only_project_request::<proto::GetDefaultBranch>)
.add_request_handler(forward_read_only_project_request::<proto::OpenUnstagedDiff>)
.add_request_handler(forward_read_only_project_request::<proto::OpenUncommittedDiff>)
.add_request_handler(forward_read_only_project_request::<proto::LspExtExpandMacro>)
@@ -462,8 +462,6 @@ impl Server {
.add_message_handler(broadcast_project_message_from_host::<proto::BreakpointsForFile>)
.add_request_handler(forward_mutating_project_request::<proto::OpenCommitMessageBuffer>)
.add_request_handler(forward_mutating_project_request::<proto::GitDiff>)
.add_request_handler(forward_mutating_project_request::<proto::GetTreeDiff>)
.add_request_handler(forward_mutating_project_request::<proto::GetBlobContent>)
.add_request_handler(forward_mutating_project_request::<proto::GitCreateBranch>)
.add_request_handler(forward_mutating_project_request::<proto::GitChangeBranch>)
.add_request_handler(forward_mutating_project_request::<proto::CheckForPushedCommits>)

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

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

@@ -1,6 +1,5 @@
pub mod copilot_chat;
mod copilot_completion_provider;
pub mod copilot_responses;
pub mod request;
mod sign_in;

View File

@@ -15,8 +15,6 @@ use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
use itertools::Itertools;
use paths::home_dir;
use serde::{Deserialize, Serialize};
use crate::copilot_responses as responses;
use settings::watch_config_dir;
pub const COPILOT_OAUTH_ENV_VAR: &str = "GH_COPILOT_TOKEN";
@@ -44,14 +42,10 @@ impl CopilotChatConfiguration {
}
}
pub fn chat_completions_url_from_endpoint(&self, endpoint: &str) -> String {
pub fn api_url_from_endpoint(&self, endpoint: &str) -> String {
format!("{}/chat/completions", endpoint)
}
pub fn responses_url_from_endpoint(&self, endpoint: &str) -> String {
format!("{}/responses", endpoint)
}
pub fn models_url_from_endpoint(&self, endpoint: &str) -> String {
format!("{}/models", endpoint)
}
@@ -77,14 +71,6 @@ pub enum Role {
System,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub enum ModelSupportedEndpoint {
#[serde(rename = "/chat/completions")]
ChatCompletions,
#[serde(rename = "/responses")]
Responses,
}
#[derive(Deserialize)]
struct ModelSchema {
#[serde(deserialize_with = "deserialize_models_skip_errors")]
@@ -123,8 +109,6 @@ pub struct Model {
// reached. Zed does not currently implement this behaviour
is_chat_fallback: bool,
model_picker_enabled: bool,
#[serde(default)]
supported_endpoints: Vec<ModelSupportedEndpoint>,
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
@@ -240,16 +224,6 @@ impl Model {
pub fn tokenizer(&self) -> Option<&str> {
self.capabilities.tokenizer.as_deref()
}
pub fn supports_response(&self) -> bool {
self.supported_endpoints.len() > 0
&& !self
.supported_endpoints
.contains(&ModelSupportedEndpoint::ChatCompletions)
&& self
.supported_endpoints
.contains(&ModelSupportedEndpoint::Responses)
}
}
#[derive(Serialize, Deserialize)]
@@ -279,7 +253,7 @@ pub enum Tool {
Function { function: Function },
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ToolChoice {
Auto,
@@ -372,7 +346,7 @@ pub struct Usage {
#[derive(Debug, Deserialize)]
pub struct ResponseChoice {
pub index: Option<usize>,
pub index: usize,
pub finish_reason: Option<String>,
pub delta: Option<ResponseDelta>,
pub message: Option<ResponseDelta>,
@@ -385,9 +359,10 @@ pub struct ResponseDelta {
#[serde(default)]
pub tool_calls: Vec<ToolCallChunk>,
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct ToolCallChunk {
pub index: Option<usize>,
pub index: usize,
pub id: Option<String>,
pub function: Option<FunctionChunk>,
}
@@ -579,47 +554,13 @@ impl CopilotChat {
is_user_initiated: bool,
mut cx: AsyncApp,
) -> Result<BoxStream<'static, Result<ResponseEvent>>> {
let (client, token, configuration) = Self::get_auth_details(&mut cx).await?;
let api_url = configuration.chat_completions_url_from_endpoint(&token.api_endpoint);
stream_completion(
client.clone(),
token.api_key,
api_url.into(),
request,
is_user_initiated,
)
.await
}
pub async fn stream_response(
request: responses::Request,
is_user_initiated: bool,
mut cx: AsyncApp,
) -> Result<BoxStream<'static, Result<responses::StreamEvent>>> {
let (client, token, configuration) = Self::get_auth_details(&mut cx).await?;
let api_url = configuration.responses_url_from_endpoint(&token.api_endpoint);
responses::stream_response(
client.clone(),
token.api_key,
api_url,
request,
is_user_initiated,
)
.await
}
async fn get_auth_details(
cx: &mut AsyncApp,
) -> Result<(Arc<dyn HttpClient>, ApiToken, CopilotChatConfiguration)> {
let this = cx
.update(|cx| Self::global(cx))
.ok()
.flatten()
.context("Copilot chat is not enabled")?;
let (oauth_token, api_token, client, configuration) = this.read_with(cx, |this, _| {
let (oauth_token, api_token, client, configuration) = this.read_with(&cx, |this, _| {
(
this.oauth_token.clone(),
this.api_token.clone(),
@@ -631,12 +572,12 @@ impl CopilotChat {
let oauth_token = oauth_token.context("No OAuth token available")?;
let token = match api_token {
Some(api_token) if api_token.remaining_seconds() > 5 * 60 => api_token,
Some(api_token) if api_token.remaining_seconds() > 5 * 60 => api_token.clone(),
_ => {
let token_url = configuration.token_url();
let token =
request_api_token(&oauth_token, token_url.into(), client.clone()).await?;
this.update(cx, |this, cx| {
this.update(&mut cx, |this, cx| {
this.api_token = Some(token.clone());
cx.notify();
})?;
@@ -644,7 +585,15 @@ impl CopilotChat {
}
};
Ok((client, token, configuration))
let api_url = configuration.api_url_from_endpoint(&token.api_endpoint);
stream_completion(
client.clone(),
token.api_key,
api_url.into(),
request,
is_user_initiated,
)
.await
}
pub fn set_configuration(

View File

@@ -1,414 +0,0 @@
use super::*;
use anyhow::{Result, anyhow};
use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub use settings::OpenAiReasoningEffort as ReasoningEffort;
#[derive(Serialize, Debug)]
pub struct Request {
pub model: String,
pub input: Vec<ResponseInputItem>,
#[serde(default)]
pub stream: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<ToolDefinition>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<ReasoningConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include: Option<Vec<ResponseIncludable>>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum ResponseIncludable {
#[serde(rename = "reasoning.encrypted_content")]
ReasoningEncryptedContent,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ToolDefinition {
Function {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
parameters: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
strict: Option<bool>,
},
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum ToolChoice {
Auto,
Any,
None,
#[serde(untagged)]
Other(ToolDefinition),
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum ReasoningSummary {
Auto,
Concise,
Detailed,
}
#[derive(Serialize, Debug)]
pub struct ReasoningConfig {
pub effort: ReasoningEffort,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<ReasoningSummary>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "snake_case")]
pub enum ResponseImageDetail {
Low,
High,
#[default]
Auto,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResponseInputContent {
InputText {
text: String,
},
OutputText {
text: String,
},
InputImage {
#[serde(skip_serializing_if = "Option::is_none")]
image_url: Option<String>,
#[serde(default)]
detail: ResponseImageDetail,
},
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum ItemStatus {
InProgress,
Completed,
Incomplete,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum ResponseFunctionOutput {
Text(String),
Content(Vec<ResponseInputContent>),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResponseInputItem {
Message {
role: String,
#[serde(skip_serializing_if = "Option::is_none")]
content: Option<Vec<ResponseInputContent>>,
#[serde(skip_serializing_if = "Option::is_none")]
status: Option<String>,
},
FunctionCall {
call_id: String,
name: String,
arguments: String,
#[serde(skip_serializing_if = "Option::is_none")]
status: Option<ItemStatus>,
},
FunctionCallOutput {
call_id: String,
output: ResponseFunctionOutput,
#[serde(skip_serializing_if = "Option::is_none")]
status: Option<ItemStatus>,
},
Reasoning {
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
summary: Vec<ResponseReasoningItem>,
encrypted_content: String,
},
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum IncompleteReason {
#[serde(rename = "max_output_tokens")]
MaxOutputTokens,
#[serde(rename = "content_filter")]
ContentFilter,
}
#[derive(Deserialize, Debug, Clone)]
pub struct IncompleteDetails {
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<IncompleteReason>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ResponseReasoningItem {
#[serde(rename = "type")]
pub kind: String,
pub text: String,
}
#[derive(Deserialize, Debug)]
#[serde(tag = "type")]
pub enum StreamEvent {
#[serde(rename = "error")]
GenericError { error: ResponseError },
#[serde(rename = "response.created")]
Created { response: Response },
#[serde(rename = "response.output_item.added")]
OutputItemAdded {
output_index: usize,
#[serde(default)]
sequence_number: Option<u64>,
item: ResponseOutputItem,
},
#[serde(rename = "response.output_text.delta")]
OutputTextDelta {
item_id: String,
output_index: usize,
delta: String,
},
#[serde(rename = "response.output_item.done")]
OutputItemDone {
output_index: usize,
#[serde(default)]
sequence_number: Option<u64>,
item: ResponseOutputItem,
},
#[serde(rename = "response.incomplete")]
Incomplete { response: Response },
#[serde(rename = "response.completed")]
Completed { response: Response },
#[serde(rename = "response.failed")]
Failed { response: Response },
#[serde(other)]
Unknown,
}
#[derive(Deserialize, Debug, Clone)]
pub struct ResponseError {
pub code: String,
pub message: String,
}
#[derive(Deserialize, Debug, Default, Clone)]
pub struct Response {
pub id: Option<String>,
pub status: Option<String>,
pub usage: Option<ResponseUsage>,
pub output: Vec<ResponseOutputItem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub incomplete_details: Option<IncompleteDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<ResponseError>,
}
#[derive(Deserialize, Debug, Default, Clone)]
pub struct ResponseUsage {
pub input_tokens: Option<u64>,
pub output_tokens: Option<u64>,
pub total_tokens: Option<u64>,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResponseOutputItem {
Message {
id: String,
role: String,
#[serde(skip_serializing_if = "Option::is_none")]
content: Option<Vec<ResponseOutputContent>>,
},
FunctionCall {
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
call_id: String,
name: String,
arguments: String,
#[serde(skip_serializing_if = "Option::is_none")]
status: Option<ItemStatus>,
},
Reasoning {
id: String,
#[serde(skip_serializing_if = "Option::is_none")]
summary: Option<Vec<ResponseReasoningItem>>,
#[serde(skip_serializing_if = "Option::is_none")]
encrypted_content: Option<String>,
},
}
#[derive(Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResponseOutputContent {
OutputText { text: String },
Refusal { refusal: String },
}
pub async fn stream_response(
client: Arc<dyn HttpClient>,
api_key: String,
api_url: String,
request: Request,
is_user_initiated: bool,
) -> Result<BoxStream<'static, Result<StreamEvent>>> {
let is_vision_request = request.input.iter().any(|item| match item {
ResponseInputItem::Message {
content: Some(parts),
..
} => parts
.iter()
.any(|p| matches!(p, ResponseInputContent::InputImage { .. })),
_ => false,
});
let request_initiator = if is_user_initiated { "user" } else { "agent" };
let request_builder = HttpRequest::builder()
.method(Method::POST)
.uri(&api_url)
.header(
"Editor-Version",
format!(
"Zed/{}",
option_env!("CARGO_PKG_VERSION").unwrap_or("unknown")
),
)
.header("Authorization", format!("Bearer {}", api_key))
.header("Content-Type", "application/json")
.header("Copilot-Integration-Id", "vscode-chat")
.header("X-Initiator", request_initiator);
let request_builder = if is_vision_request {
request_builder.header("Copilot-Vision-Request", "true")
} else {
request_builder
};
let is_streaming = request.stream;
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
let mut response = client.send(request).await?;
if !response.status().is_success() {
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
anyhow::bail!("Failed to connect to API: {} {}", response.status(), body);
}
if is_streaming {
let reader = BufReader::new(response.into_body());
Ok(reader
.lines()
.filter_map(|line| async move {
match line {
Ok(line) => {
let line = line.strip_prefix("data: ")?;
if line.starts_with("[DONE]") || line.is_empty() {
return None;
}
match serde_json::from_str::<StreamEvent>(line) {
Ok(event) => Some(Ok(event)),
Err(error) => {
log::error!(
"Failed to parse Copilot responses stream event: `{}`\nResponse: `{}`",
error,
line,
);
Some(Err(anyhow!(error)))
}
}
}
Err(error) => Some(Err(anyhow!(error))),
}
})
.boxed())
} else {
// Simulate streaming this makes the mapping of this function return more straight-forward to handle if all callers assume it streams.
// Removes the need of having a method to map StreamEvent and another to map Response to a LanguageCompletionEvent
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
match serde_json::from_str::<Response>(&body) {
Ok(response) => {
let events = vec![StreamEvent::Created {
response: response.clone(),
}];
let mut all_events = events;
for (output_index, item) in response.output.iter().enumerate() {
all_events.push(StreamEvent::OutputItemAdded {
output_index,
sequence_number: None,
item: item.clone(),
});
if let ResponseOutputItem::Message {
id,
content: Some(content),
..
} = item
{
for part in content {
if let ResponseOutputContent::OutputText { text } = part {
all_events.push(StreamEvent::OutputTextDelta {
item_id: id.clone(),
output_index,
delta: text.clone(),
});
}
}
}
all_events.push(StreamEvent::OutputItemDone {
output_index,
sequence_number: None,
item: item.clone(),
});
}
let final_event = if response.error.is_some() {
StreamEvent::Failed { response }
} else if response.incomplete_details.is_some() {
StreamEvent::Incomplete { response }
} else {
StreamEvent::Completed { response }
};
all_events.push(final_event);
Ok(futures::stream::iter(all_events.into_iter().map(Ok)).boxed())
}
Err(error) => {
log::error!(
"Failed to parse Copilot non-streaming response: `{}`\nResponse: `{}`",
error,
body,
);
Err(anyhow!(error))
}
}
}
}

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

@@ -35,6 +35,7 @@ log.workspace = true
paths.workspace = true
serde.workspace = true
serde_json.workspace = true
shlex.workspace = true
smol.workspace = true
task.workspace = true
util.workspace = true

View File

@@ -6,7 +6,7 @@ use gpui::AsyncApp;
use serde_json::Value;
use std::{path::PathBuf, sync::OnceLock};
use task::DebugRequest;
use util::{ResultExt, maybe, shell::ShellKind};
use util::{ResultExt, maybe};
use crate::*;
@@ -67,7 +67,7 @@ impl JsDebugAdapter {
.get("type")
.filter(|value| value == &"node-terminal")?;
let command = configuration.get("command")?.as_str()?.to_owned();
let mut args = ShellKind::Posix.split(&command)?.into_iter();
let mut args = shlex::split(&command)?.into_iter();
let program = args.next()?;
configuration.insert("runtimeExecutable".to_owned(), program.into());
configuration.insert(

View File

@@ -60,6 +60,7 @@ serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
settings.workspace = true
shlex.workspace = true
sysinfo.workspace = true
task.workspace = true
tasks_ui.workspace = true

View File

@@ -9,7 +9,7 @@ use task::ZedDebugConfig;
use util::debug_panic;
use std::sync::Arc;
use sysinfo::{ProcessRefreshKind, RefreshKind, System, UpdateKind};
use sysinfo::System;
use ui::{Context, Tooltip, prelude::*};
use ui::{ListItem, ListItemSpacing};
use workspace::{ModalView, Workspace};
@@ -362,12 +362,7 @@ fn get_processes_for_project(project: &Entity<Project>, cx: &mut App) -> Task<Ar
Arc::from(processes.into_boxed_slice())
})
} else {
let refresh_kind = RefreshKind::nothing().with_processes(
ProcessRefreshKind::nothing()
.without_tasks()
.with_cmd(UpdateKind::Always),
);
let mut processes: Box<[_]> = System::new_with_specifics(refresh_kind)
let mut processes: Box<[_]> = System::new_all()
.processes()
.values()
.map(|process| {

View File

@@ -616,11 +616,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 +694,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 +719,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 +744,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 +770,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 +793,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 +816,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 +861,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 +893,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,
)
}

View File

@@ -32,7 +32,7 @@ use ui::{
SharedString, Styled, StyledExt, ToggleButton, ToggleState, Toggleable, Tooltip, Window, div,
h_flex, relative, rems, v_flex,
};
use util::{ResultExt, rel_path::RelPath, shell::ShellKind};
use util::{ResultExt, rel_path::RelPath};
use workspace::{ModalView, Workspace, notifications::DetachAndPromptErr, pane};
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
@@ -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))),
@@ -839,11 +844,7 @@ impl ConfigureMode {
};
}
let command = self.program.read(cx).text(cx);
let mut args = ShellKind::Posix
.split(&command)
.into_iter()
.flatten()
.peekable();
let mut args = shlex::split(&command).into_iter().flatten().peekable();
let mut env = FxHashMap::default();
while args.peek().is_some_and(|arg| arg.contains('=')) {
let arg = args.next().unwrap();
@@ -1052,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
@@ -1269,11 +1270,7 @@ impl PickerDelegate for DebugDelegate {
})
.unwrap_or_default();
let mut args = ShellKind::Posix
.split(&text)
.into_iter()
.flatten()
.peekable();
let mut args = shlex::split(&text).into_iter().flatten().peekable();
let mut env = HashMap::default();
while args.peek().is_some_and(|arg| arg.contains('=')) {
let arg = args.next().unwrap();
@@ -1450,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

@@ -566,13 +566,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),
),

View File

@@ -1,9 +1,9 @@
use super::*;
use collections::{HashMap, HashSet};
use editor::{
DisplayPoint, EditorSettings, Inlay,
DisplayPoint, EditorSettings,
actions::{GoToDiagnostic, GoToPreviousDiagnostic, Hover, MoveToBeginning},
display_map::DisplayRow,
display_map::{DisplayRow, Inlay},
test::{
editor_content_with_blocks, editor_lsp_test_context::EditorLspTestContext,
editor_test_context::EditorTestContext,

View File

@@ -67,10 +67,11 @@ impl Render for DiagnosticIndicator {
Some(
Button::new("diagnostic_message", SharedString::new(message))
.label_size(LabelSize::Small)
.tooltip(|_window, cx| {
.tooltip(|window, cx| {
Tooltip::for_action(
"Next Diagnostic",
&editor::actions::GoToDiagnostic::default(),
window,
cx,
)
})
@@ -86,8 +87,8 @@ impl Render for DiagnosticIndicator {
.child(
ButtonLike::new("diagnostic-indicator")
.child(diagnostic_indicator)
.tooltip(move |_window, cx| {
Tooltip::for_action("Project Diagnostics", &Deploy, cx)
.tooltip(|window, cx| {
Tooltip::for_action("Project Diagnostics", &Deploy, window, cx)
})
.on_click(cx.listener(|this, _, window, cx| {
if let Some(workspace) = this.workspace.upgrade() {

View File

@@ -203,10 +203,6 @@ fn template_big_table_of_actions(book: &mut Book) {
});
}
fn format_binding(binding: String) -> String {
binding.replace("\\", "\\\\")
}
fn template_and_validate_keybindings(book: &mut Book, errors: &mut HashSet<PreprocessorError>) {
let regex = Regex::new(r"\{#kb (.*?)\}").unwrap();
@@ -227,10 +223,7 @@ fn template_and_validate_keybindings(book: &mut Book, errors: &mut HashSet<Prepr
return "<div>No default binding</div>".to_string();
}
let formatted_macos_binding = format_binding(macos_binding);
let formatted_linux_binding = format_binding(linux_binding);
format!("<kbd class=\"keybinding\">{formatted_macos_binding}|{formatted_linux_binding}</kbd>")
format!("<kbd class=\"keybinding\">{macos_binding}|{linux_binding}</kbd>")
})
.into_owned()
});

View File

@@ -123,8 +123,8 @@ impl Render for EditPredictionButton {
});
}
}))
.tooltip(|_window, cx| {
Tooltip::for_action("GitHub Copilot", &ToggleMenu, cx)
.tooltip(|window, cx| {
Tooltip::for_action("GitHub Copilot", &ToggleMenu, window, cx)
}),
);
}
@@ -146,7 +146,9 @@ impl Render for EditPredictionButton {
.anchor(Corner::BottomRight)
.trigger_with_tooltip(
IconButton::new("copilot-icon", icon),
|_window, cx| Tooltip::for_action("GitHub Copilot", &ToggleMenu, cx),
|window, cx| {
Tooltip::for_action("GitHub Copilot", &ToggleMenu, window, cx)
},
)
.with_handle(self.popover_menu_handle.clone()),
)
@@ -218,7 +220,12 @@ impl Render for EditPredictionButton {
IconButton::new("supermaven-icon", icon),
move |window, cx| {
if has_menu {
Tooltip::for_action(tooltip_text.clone(), &ToggleMenu, cx)
Tooltip::for_action(
tooltip_text.clone(),
&ToggleMenu,
window,
cx,
)
} else {
Tooltip::text(tooltip_text.clone())(window, cx)
}
@@ -281,7 +288,9 @@ impl Render for EditPredictionButton {
cx.theme().colors().status_bar_background,
))
}),
move |_window, cx| Tooltip::for_action("Codestral", &ToggleMenu, cx),
move |window, cx| {
Tooltip::for_action("Codestral", &ToggleMenu, window, cx)
},
)
.with_handle(self.popover_menu_handle.clone()),
)
@@ -308,8 +317,14 @@ impl Render for EditPredictionButton {
.shape(IconButtonShape::Square)
.indicator(Indicator::dot().color(Color::Muted))
.indicator_border_color(Some(cx.theme().colors().status_bar_background))
.tooltip(move |_window, cx| {
Tooltip::with_meta("Edit Predictions", None, tooltip_meta, cx)
.tooltip(move |window, cx| {
Tooltip::with_meta(
"Edit Predictions",
None,
tooltip_meta,
window,
cx,
)
})
.on_click(cx.listener(move |_, _, window, cx| {
telemetry::event!(
@@ -350,15 +365,16 @@ impl Render for EditPredictionButton {
},
)
.when(!self.popover_menu_handle.is_deployed(), |element| {
element.tooltip(move |_window, cx| {
element.tooltip(move |window, cx| {
if enabled {
if show_editor_predictions {
Tooltip::for_action("Edit Prediction", &ToggleMenu, cx)
Tooltip::for_action("Edit Prediction", &ToggleMenu, window, cx)
} else {
Tooltip::with_meta(
"Edit Prediction",
Some(&ToggleMenu),
"Hidden For This File",
window,
cx,
)
}
@@ -367,6 +383,7 @@ impl Render for EditPredictionButton {
"Edit Prediction",
Some(&ToggleMenu),
"Disabled For This File",
window,
cx,
)
}

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