diff --git a/.github/CODEOWNERS.hold b/.github/CODEOWNERS.hold
index 3b7cbc644768f82646591619e49c4b6a0d6de200..073a543b881052f3c2e0c2a9a1054261af40dba5 100644
--- a/.github/CODEOWNERS.hold
+++ b/.github/CODEOWNERS.hold
@@ -32,9 +32,6 @@
/crates/agent_ui/ @zed-industries/ai-team
/crates/ai_onboarding/ @zed-industries/ai-team
/crates/anthropic/ @zed-industries/ai-team
-/crates/assistant_slash_command/ @zed-industries/ai-team
-/crates/assistant_slash_commands/ @zed-industries/ai-team
-/crates/assistant_text_thread/ @zed-industries/ai-team
/crates/bedrock/ @zed-industries/ai-team
/crates/cloud_llm_client/ @zed-industries/ai-team
/crates/codestral/ @zed-industries/ai-team
diff --git a/Cargo.lock b/Cargo.lock
index 81d82dcd46c85293f67a927405251bbc87df4967..b8e4c549314fa30614cdf5afb0f62af1b4922fdb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -335,9 +335,6 @@ dependencies = [
"agent_settings",
"ai_onboarding",
"anyhow",
- "assistant_slash_command",
- "assistant_slash_commands",
- "assistant_text_thread",
"audio",
"base64 0.22.1",
"buffer_diff",
@@ -393,7 +390,6 @@ dependencies = [
"rope",
"rules_library",
"schemars",
- "search",
"semver",
"serde",
"serde_json",
@@ -783,108 +779,6 @@ dependencies = [
"rust-embed",
]
-[[package]]
-name = "assistant_slash_command"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "async-trait",
- "collections",
- "derive_more",
- "extension",
- "futures 0.3.31",
- "gpui",
- "language",
- "language_model",
- "parking_lot",
- "pretty_assertions",
- "serde",
- "serde_json",
- "ui",
- "util",
- "workspace",
-]
-
-[[package]]
-name = "assistant_slash_commands"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "assistant_slash_command",
- "chrono",
- "collections",
- "editor",
- "feature_flags",
- "fs",
- "futures 0.3.31",
- "fuzzy",
- "gpui",
- "html_to_markdown",
- "http_client",
- "language",
- "multi_buffer",
- "pretty_assertions",
- "project",
- "prompt_store",
- "rope",
- "serde",
- "serde_json",
- "settings",
- "smol",
- "text",
- "ui",
- "util",
- "workspace",
- "worktree",
- "zlog",
-]
-
-[[package]]
-name = "assistant_text_thread"
-version = "0.1.0"
-dependencies = [
- "agent_settings",
- "anyhow",
- "assistant_slash_command",
- "assistant_slash_commands",
- "chrono",
- "client",
- "clock",
- "collections",
- "context_server",
- "fs",
- "futures 0.3.31",
- "fuzzy",
- "gpui",
- "itertools 0.14.0",
- "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",
- "text",
- "ui",
- "unindent",
- "util",
- "uuid",
- "workspace",
- "zed_env_vars",
-]
-
[[package]]
name = "async-attributes"
version = "1.1.2"
@@ -3183,8 +3077,6 @@ version = "0.44.0"
dependencies = [
"agent",
"anyhow",
- "assistant_slash_command",
- "assistant_text_thread",
"async-trait",
"async-tungstenite",
"aws-config",
@@ -9446,7 +9338,6 @@ dependencies = [
"open_ai",
"open_router",
"parking_lot",
- "proto",
"schemars",
"serde",
"serde_json",
@@ -15965,7 +15856,6 @@ dependencies = [
"agent_settings",
"agent_ui",
"anyhow",
- "assistant_text_thread",
"chrono",
"collections",
"editor",
@@ -17510,7 +17400,6 @@ name = "terminal_view"
version = "0.1.0"
dependencies = [
"anyhow",
- "assistant_slash_command",
"async-recursion",
"breadcrumbs",
"collections",
diff --git a/Cargo.toml b/Cargo.toml
index f3056b87fbdcc1ccc380b9fc0059df8a94b0c1f3..ebe14d332ad477a58a44305145c51f6b18ea72dd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,9 +13,6 @@ members = [
"crates/anthropic",
"crates/askpass",
"crates/assets",
- "crates/assistant_slash_command",
- "crates/assistant_slash_commands",
- "crates/assistant_text_thread",
"crates/audio",
"crates/auto_update",
"crates/auto_update_helper",
@@ -271,9 +268,6 @@ 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_slash_command = { path = "crates/assistant_slash_command" }
-assistant_slash_commands = { path = "crates/assistant_slash_commands" }
audio = { path = "crates/audio" }
auto_update = { path = "crates/auto_update" }
auto_update_ui = { path = "crates/auto_update_ui" }
diff --git a/assets/icons/text_thread.svg b/assets/icons/text_thread.svg
deleted file mode 100644
index aa078c72a2f35d2b82e90f2be64d23fcda3418a5..0000000000000000000000000000000000000000
--- a/assets/icons/text_thread.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json
index 84b814e27a702232f5c54a6745b31f42935ca7a5..523a961d6964e2c6e08d03b75a3e1eb1890fc586 100644
--- a/assets/keymaps/default-linux.json
+++ b/assets/keymaps/default-linux.json
@@ -148,7 +148,6 @@
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
"ctrl->": "agent::AddSelectionToThread",
- "ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
"ctrl-shift-alt-backspace": "editor::GoToNextChange",
@@ -185,7 +184,7 @@
},
},
{
- "context": "Editor && jupyter && !ContextEditor",
+ "context": "Editor && jupyter",
"bindings": {
"ctrl-shift-enter": "repl::Run",
"ctrl-alt-enter": "repl::RunInPlace",
@@ -221,29 +220,10 @@
"shift-alt-z": "agent::RejectAll",
},
},
- {
- "context": "ContextEditor > Editor",
- "bindings": {
- "ctrl-enter": "assistant::Assist",
- "save": "workspace::Save",
- "ctrl-s": "workspace::Save",
- "ctrl-<": "assistant::InsertIntoEditor",
- "shift-enter": "assistant::Split",
- "ctrl-r": "assistant::CycleMessageRole",
- "enter": "assistant::ConfirmCommand",
- "alt-enter": "editor::Newline",
- "ctrl-k c": "assistant::CopyCode",
- "ctrl-g": "search::SelectNextMatch",
- "ctrl-shift-g": "search::SelectPreviousMatch",
- "ctrl-k l": "agent::OpenRulesLibrary",
- "ctrl-shift-v": "agent::PasteRaw",
- },
- },
{
"context": "AgentPanel",
"bindings": {
"ctrl-n": "agent::NewThread",
- "ctrl-alt-n": "agent::NewTextThread",
"ctrl-shift-h": "agent::OpenHistory",
"ctrl-alt-c": "agent::OpenSettings",
"ctrl-alt-p": "agent::ManageProfiles",
@@ -278,13 +258,6 @@
"ctrl-c": "markdown::CopyAsMarkdown",
},
},
- {
- "context": "AgentPanel && text_thread",
- "bindings": {
- "ctrl-n": "agent::NewTextThread",
- "ctrl-alt-t": "agent::NewThread",
- },
- },
{
"context": "AgentPanel && acp_thread",
"use_key_equivalents": true,
@@ -719,8 +692,8 @@
"context": "ThreadSwitcher",
"bindings": {
"ctrl-tab": "agents_sidebar::ToggleThreadSwitcher",
- "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }]
- }
+ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }],
+ },
},
{
"context": "Workspace && debugger_running",
@@ -858,7 +831,7 @@
},
},
{
- "context": "!ContextEditor && !AcpThread > Editor && mode == full",
+ "context": "!AcpThread > Editor && mode == full",
"bindings": {
"alt-enter": "editor::OpenExcerpts",
"shift-enter": "editor::ExpandExcerpts",
diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json
index 1ffe011a25b74c6e7fdb30282f86b0667fc54793..9ca71aa9be3a99b1b52ab8490a6fe841956ecf50 100644
--- a/assets/keymaps/default-macos.json
+++ b/assets/keymaps/default-macos.json
@@ -173,7 +173,6 @@
"cmd-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
"cmd-e": ["buffer_search::Deploy", { "focus": false }],
"cmd->": "agent::AddSelectionToThread",
- "cmd-<": "assistant::InsertIntoEditor",
"cmd-alt-e": "editor::SelectEnclosingSymbol",
"alt-enter": "editor::OpenSelectionsInMultibuffer",
},
@@ -220,7 +219,7 @@
},
},
{
- "context": "Editor && jupyter && !ContextEditor",
+ "context": "Editor && jupyter",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-enter": "repl::Run",
@@ -260,31 +259,11 @@
"shift-ctrl-r": "agent::OpenAgentDiff",
},
},
- {
- "context": "ContextEditor > Editor",
- "use_key_equivalents": true,
- "bindings": {
- "cmd-enter": "assistant::Assist",
- "cmd-s": "workspace::Save",
- "cmd-<": "assistant::InsertIntoEditor",
- "shift-enter": "assistant::Split",
- "ctrl-r": "assistant::CycleMessageRole",
- "enter": "assistant::ConfirmCommand",
- "alt-enter": "editor::Newline",
- "cmd-k c": "assistant::CopyCode",
- "cmd-g": "search::SelectNextMatch",
- "cmd-shift-g": "search::SelectPreviousMatch",
- "cmd-k l": "agent::OpenRulesLibrary",
- "alt-tab": "agent::CycleFavoriteModels",
- "cmd-shift-v": "agent::PasteRaw",
- },
- },
{
"context": "AgentPanel",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "agent::NewThread",
- "cmd-alt-n": "agent::NewTextThread",
"cmd-shift-h": "agent::OpenHistory",
"cmd-alt-c": "agent::OpenSettings",
"cmd-alt-l": "agent::OpenRulesLibrary",
@@ -315,14 +294,6 @@
"cmd-c": "markdown::CopyAsMarkdown",
},
},
- {
- "context": "AgentPanel && text_thread",
- "use_key_equivalents": true,
- "bindings": {
- "cmd-n": "agent::NewTextThread",
- "cmd-alt-n": "agent::NewExternalAgentThread",
- },
- },
{
"context": "AgentPanel && acp_thread",
"use_key_equivalents": true,
@@ -784,8 +755,8 @@
"context": "ThreadSwitcher",
"bindings": {
"ctrl-tab": "agents_sidebar::ToggleThreadSwitcher",
- "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }]
- }
+ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }],
+ },
},
{
"context": "Workspace && debugger_running",
@@ -918,7 +889,7 @@
},
},
{
- "context": "!ContextEditor && !AcpThread > Editor && mode == full",
+ "context": "!AcpThread > Editor && mode == full",
"use_key_equivalents": true,
"bindings": {
"alt-enter": "editor::OpenExcerpts",
diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json
index 8ceec7e7f494c352b65a7a1f5aa1ad608eb5ff96..1883d0df0b3ff44ad8dceefb997198cb203a9b8d 100644
--- a/assets/keymaps/default-windows.json
+++ b/assets/keymaps/default-windows.json
@@ -143,7 +143,6 @@
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
"ctrl-shift-.": "agent::AddSelectionToThread",
- "ctrl-shift-,": "assistant::InsertIntoEditor",
"shift-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
"ctrl-shift-alt-backspace": "editor::GoToNextChange",
@@ -182,7 +181,7 @@
},
},
{
- "context": "Editor && jupyter && !ContextEditor",
+ "context": "Editor && jupyter",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-enter": "repl::Run",
@@ -221,30 +220,11 @@
"shift-alt-z": "agent::RejectAll",
},
},
- {
- "context": "ContextEditor > Editor",
- "use_key_equivalents": true,
- "bindings": {
- "ctrl-i": "assistant::Assist",
- "ctrl-s": "workspace::Save",
- "ctrl-shift-,": "assistant::InsertIntoEditor",
- "shift-enter": "assistant::Split",
- "ctrl-r": "assistant::CycleMessageRole",
- "enter": "assistant::ConfirmCommand",
- "alt-enter": "editor::Newline",
- "ctrl-k c": "assistant::CopyCode",
- "ctrl-g": "search::SelectNextMatch",
- "ctrl-shift-g": "search::SelectPreviousMatch",
- "ctrl-k l": "agent::OpenRulesLibrary",
- "ctrl-shift-v": "agent::PasteRaw",
- },
- },
{
"context": "AgentPanel",
"use_key_equivalents": true,
"bindings": {
"ctrl-n": "agent::NewThread",
- "shift-alt-n": "agent::NewTextThread",
"ctrl-shift-h": "agent::OpenHistory",
"shift-alt-c": "agent::OpenSettings",
"shift-alt-l": "agent::OpenRulesLibrary",
@@ -278,14 +258,6 @@
"ctrl-c": "markdown::CopyAsMarkdown",
},
},
- {
- "context": "AgentPanel && text_thread",
- "use_key_equivalents": true,
- "bindings": {
- "ctrl-n": "agent::NewTextThread",
- "ctrl-alt-t": "agent::NewThread",
- },
- },
{
"context": "AgentPanel && acp_thread",
"use_key_equivalents": true,
@@ -721,8 +693,8 @@
"context": "ThreadSwitcher",
"bindings": {
"ctrl-tab": "agents_sidebar::ToggleThreadSwitcher",
- "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }]
- }
+ "ctrl-shift-tab": ["agents_sidebar::ToggleThreadSwitcher", { "select_last": true }],
+ },
},
{
"context": "ApplicationMenu",
@@ -858,7 +830,7 @@
},
},
{
- "context": "!ContextEditor && !AcpThread > Editor && mode == full",
+ "context": "!AcpThread > Editor && mode == full",
"use_key_equivalents": true,
"bindings": {
"alt-enter": "editor::OpenExcerpts",
diff --git a/assets/keymaps/linux/cursor.json b/assets/keymaps/linux/cursor.json
index e1eeade9db16d178fb2ce0ec4b2ec03f0ac2c221..8d5f7b5a76cb09a6c1be2638019f9cd6cf9942de 100644
--- a/assets/keymaps/linux/cursor.json
+++ b/assets/keymaps/linux/cursor.json
@@ -20,7 +20,6 @@
"ctrl-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
"ctrl-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
"ctrl-k": "assistant::InlineAssist",
- "ctrl-shift-k": "assistant::InsertIntoEditor",
},
},
{
@@ -34,7 +33,7 @@
},
},
{
- "context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
+ "context": "AgentPanel || (MessageEditor > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-i": "workspace::ToggleRightDock",
@@ -47,7 +46,6 @@
"ctrl-shift-backspace": "editor::Cancel",
"ctrl-r": "agent::NewThread",
"ctrl-shift-v": "editor::Paste",
- "ctrl-shift-k": "assistant::InsertIntoEditor",
// "escape": "agent::ToggleFocus"
///// Enable when Zed supports multiple thread tabs
// "ctrl-t": // new thread tab
diff --git a/assets/keymaps/macos/cursor.json b/assets/keymaps/macos/cursor.json
index 2824575a445ad0c870a59cb516441dc6f1421f31..f7cab89fb6118777ea07268cdeef2cf440c7b077 100644
--- a/assets/keymaps/macos/cursor.json
+++ b/assets/keymaps/macos/cursor.json
@@ -20,7 +20,6 @@
"cmd-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
"cmd-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
"cmd-k": "assistant::InlineAssist",
- "cmd-shift-k": "assistant::InsertIntoEditor",
},
},
{
@@ -35,7 +34,7 @@
},
},
{
- "context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
+ "context": "AgentPanel || (MessageEditor > Editor)",
"use_key_equivalents": true,
"bindings": {
"cmd-i": "workspace::ToggleRightDock",
@@ -48,7 +47,6 @@
"cmd-shift-backspace": "editor::Cancel",
"cmd-r": "agent::NewThread",
"cmd-shift-v": "editor::Paste",
- "cmd-shift-k": "assistant::InsertIntoEditor",
// "escape": "agent::ToggleFocus"
///// Enable when Zed supports multiple thread tabs
// "cmd-t": // new thread tab
diff --git a/assets/settings/default.json b/assets/settings/default.json
index d3defb428c68120e89c6bc6cc82488f03a06b320..57bad245474b9469a0a9b9d5674c692059f039af 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -960,8 +960,6 @@
"default_width": 640,
// Default height when the agent panel is docked to the bottom.
"default_height": 320,
- // The view to use by default (thread, or text_thread)
- "default_view": "thread",
// The default model to use when creating new threads.
"default_model": {
// The provider to use.
@@ -1614,9 +1612,6 @@
"prompt_format": "infer",
"max_output_tokens": 64,
},
- // Whether edit predictions are enabled when editing text threads in the agent panel.
- // This setting has no effect if globally disabled.
- "enabled_in_text_threads": true,
},
// Settings specific to journaling
"journal": {
diff --git a/crates/acp_thread/src/mention.rs b/crates/acp_thread/src/mention.rs
index 43dfe7610e34a0399a27a1d28858b938acfc2e0f..753838d3b98ed60dc02c3d9383c28fe4f848a29e 100644
--- a/crates/acp_thread/src/mention.rs
+++ b/crates/acp_thread/src/mention.rs
@@ -32,10 +32,6 @@ pub enum MentionUri {
id: acp::SessionId,
name: String,
},
- TextThread {
- path: PathBuf,
- name: String,
- },
Rule {
id: PromptId,
name: String,
@@ -137,12 +133,6 @@ impl MentionUri {
id: acp::SessionId::new(thread_id),
name,
})
- } else if let Some(path) = path.strip_prefix("/agent/text-thread/") {
- let name = single_query_param(&url, "name")?.context("Missing thread name")?;
- Ok(Self::TextThread {
- path: path.into(),
- name,
- })
} else if let Some(rule_id) = path.strip_prefix("/agent/rule/") {
let name = single_query_param(&url, "name")?.context("Missing rule name")?;
let rule_id = UserPromptId(rule_id.parse()?);
@@ -240,7 +230,6 @@ impl MentionUri {
MentionUri::PastedImage => "Image".to_string(),
MentionUri::Symbol { name, .. } => name.clone(),
MentionUri::Thread { name, .. } => name.clone(),
- MentionUri::TextThread { name, .. } => name.clone(),
MentionUri::Rule { name, .. } => name.clone(),
MentionUri::Diagnostics { .. } => "Diagnostics".to_string(),
MentionUri::TerminalSelection { line_count } => {
@@ -312,7 +301,6 @@ impl MentionUri {
.unwrap_or_else(|| IconName::Folder.path().into()),
MentionUri::Symbol { .. } => IconName::Code.path().into(),
MentionUri::Thread { .. } => IconName::Thread.path().into(),
- MentionUri::TextThread { .. } => IconName::Thread.path().into(),
MentionUri::Rule { .. } => IconName::Reader.path().into(),
MentionUri::Diagnostics { .. } => IconName::Warning.path().into(),
MentionUri::TerminalSelection { .. } => IconName::Terminal.path().into(),
@@ -381,15 +369,6 @@ impl MentionUri {
url.query_pairs_mut().append_pair("name", name);
url
}
- MentionUri::TextThread { path, name } => {
- let mut url = Url::parse("zed:///").unwrap();
- url.set_path(&format!(
- "/agent/text-thread/{}",
- path.to_string_lossy().trim_start_matches('/')
- ));
- url.query_pairs_mut().append_pair("name", name);
- url
- }
MentionUri::Rule { name, id } => {
let mut url = Url::parse("zed:///").unwrap();
url.set_path(&format!("/agent/rule/{id}"));
diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs
index 627fb37b4d2559e5cda573d849fd0df306c1cc7d..b61df1b8af84d312d7f186fb85e5a1d04ab59dfd 100644
--- a/crates/agent/src/thread.rs
+++ b/crates/agent/src/thread.rs
@@ -295,9 +295,6 @@ impl UserMessage {
MentionUri::Thread { .. } => {
write!(&mut thread_context, "\n{}\n", content).ok();
}
- MentionUri::TextThread { .. } => {
- write!(&mut thread_context, "\n{}\n", content).ok();
- }
MentionUri::Rule { .. } => {
write!(
&mut rules_context,
diff --git a/crates/agent/src/tool_permissions.rs b/crates/agent/src/tool_permissions.rs
index 73b3ff842ab6961b22815c902ce9ae79e60cd2e3..e74b6e4c5ce34383ad7ea702f1ba3a0cfd028455 100644
--- a/crates/agent/src/tool_permissions.rs
+++ b/crates/agent/src/tool_permissions.rs
@@ -563,7 +563,7 @@ mod tests {
use crate::tools::{DeletePathTool, EditFileTool, FetchTool, TerminalTool};
use agent_settings::{AgentProfileId, CompiledRegex, InvalidRegexPattern, ToolRules};
use gpui::px;
- use settings::{DefaultAgentView, DockPosition, NotifyWhenAgentWaiting};
+ use settings::{DockPosition, NotifyWhenAgentWaiting};
use std::sync::Arc;
fn test_agent_settings(tool_permissions: ToolPermissions) -> AgentSettings {
@@ -582,7 +582,6 @@ mod tests {
inline_alternatives: vec![],
favorite_models: vec![],
default_profile: AgentProfileId::default(),
- default_view: DefaultAgentView::Thread,
profiles: Default::default(),
notify_when_agent_waiting: NotifyWhenAgentWaiting::default(),
play_sound_when_agent_done: false,
diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs
index 7f51bd8ea5b9b8864663fbf9dc95beedb643d480..2ef65fe33641cdeca1a77642251523275511e81f 100644
--- a/crates/agent_settings/src/agent_settings.rs
+++ b/crates/agent_settings/src/agent_settings.rs
@@ -12,9 +12,9 @@ use project::DisableAiSettings;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{
- DefaultAgentView, DockPosition, DockSide, LanguageModelParameters, LanguageModelSelection,
- NewThreadLocation, NotifyWhenAgentWaiting, RegisterSetting, Settings, SettingsContent,
- SettingsStore, SidebarDockPosition, SidebarSide, ThinkingBlockDisplay, ToolPermissionMode,
+ DockPosition, DockSide, LanguageModelParameters, LanguageModelSelection, NewThreadLocation,
+ NotifyWhenAgentWaiting, RegisterSetting, Settings, SettingsContent, SettingsStore,
+ SidebarDockPosition, SidebarSide, ThinkingBlockDisplay, ToolPermissionMode,
update_settings_file,
};
@@ -162,7 +162,6 @@ pub struct AgentSettings {
pub inline_alternatives: Vec,
pub favorite_models: Vec,
pub default_profile: AgentProfileId,
- pub default_view: DefaultAgentView,
pub profiles: IndexMap,
pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
@@ -611,7 +610,6 @@ impl Settings for AgentSettings {
inline_alternatives: agent.inline_alternatives.unwrap_or_default(),
favorite_models: agent.favorite_models,
default_profile: AgentProfileId(agent.default_profile.unwrap()),
- default_view: agent.default_view.unwrap(),
profiles: agent
.profiles
.unwrap()
diff --git a/crates/agent_ui/Cargo.toml b/crates/agent_ui/Cargo.toml
index 6c045d4dd2114834605da278aad111fab174d4c6..e505a124b6898953db9751ddfc8ab98cb7f496f0 100644
--- a/crates/agent_ui/Cargo.toml
+++ b/crates/agent_ui/Cargo.toml
@@ -14,7 +14,6 @@ doctest = false
[features]
test-support = [
- "assistant_text_thread/test-support",
"acp_thread/test-support",
"eval_utils",
"gpui/test-support",
@@ -36,9 +35,6 @@ agent_settings.workspace = true
ai_onboarding.workspace = true
anyhow.workspace = true
heapless.workspace = true
-assistant_text_thread.workspace = true
-assistant_slash_command.workspace = true
-assistant_slash_commands.workspace = true
audio = { workspace = true, optional = true }
base64.workspace = true
buffer_diff.workspace = true
@@ -89,7 +85,6 @@ release_channel.workspace = true
rope.workspace = true
rules_library.workspace = true
schemars.workspace = true
-search.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
@@ -119,7 +114,6 @@ reqwest_client = { workspace = true, optional = true }
[dev-dependencies]
acp_thread = { workspace = true, features = ["test-support"] }
agent = { workspace = true, features = ["test-support"] }
-assistant_text_thread = { workspace = true, features = ["test-support"] }
buffer_diff = { workspace = true, features = ["test-support"] }
db = { workspace = true, features = ["test-support"] }
diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs
index 17eb0e966fe51580a7d756fa6f5524ecaa96a640..e6ef267a95110e745534010bae32b1b1fd6c0f0c 100644
--- a/crates/agent_ui/src/agent_panel.rs
+++ b/crates/agent_ui/src/agent_panel.rs
@@ -1,6 +1,5 @@
use std::{
- ops::Range,
- path::{Path, PathBuf},
+ path::PathBuf,
rc::Rc,
sync::{
Arc,
@@ -22,19 +21,17 @@ use settings::{LanguageModelProviderSetting, LanguageModelSelection};
use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt as _};
use zed_actions::agent::{
- ConflictContent, OpenClaudeAgentOnboardingModal, ReauthenticateAgent,
+ AddSelectionToThread, ConflictContent, OpenClaudeAgentOnboardingModal, ReauthenticateAgent,
ResolveConflictedFilesWithAgent, ResolveConflictsWithAgent, ReviewBranchDiff,
};
use crate::{
AddContextServer, AgentDiffPane, ConversationView, CopyThreadToClipboard, CycleStartThreadIn,
- Follow, InlineAssistant, LoadThreadFromClipboard, NewTextThread, NewThread,
- OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell,
- StartThreadIn, ToggleNavigationMenu, ToggleNewThreadMenu, ToggleOptionsMenu,
+ Follow, InlineAssistant, LoadThreadFromClipboard, NewThread, OpenActiveThreadAsMarkdown,
+ OpenAgentDiff, OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell, StartThreadIn,
+ ToggleNavigationMenu, ToggleNewThreadMenu, ToggleOptionsMenu,
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
conversation_view::{AcpThreadViewEvent, ThreadView},
- slash_command::SlashCommandCompletionProvider,
- text_thread_editor::{AgentPanelDelegate, TextThreadEditor, make_lsp_adapter_delegate},
ui::EndTrialUpsell,
};
use crate::{
@@ -45,21 +42,16 @@ use crate::{
DEFAULT_THREAD_TITLE,
ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal, HoldForDefault},
};
-use crate::{
- ExpandMessageEditor, ThreadHistoryView,
- text_thread_history::{TextThreadHistory, TextThreadHistoryEvent},
-};
+use crate::{ExpandMessageEditor, ThreadHistoryView};
use crate::{ManageProfiles, ThreadHistoryViewEvent};
use crate::{ThreadHistory, agent_connection_store::AgentConnectionStore};
use agent_settings::AgentSettings;
use ai_onboarding::AgentPanelOnboarding;
use anyhow::{Context as _, Result, anyhow};
-use assistant_slash_command::SlashCommandWorkingSet;
-use assistant_text_thread::{TextThread, TextThreadEvent, TextThreadSummary};
use client::UserStore;
use cloud_api_types::Plan;
use collections::HashMap;
-use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
+use editor::Editor;
use extension::ExtensionEvents;
use extension_host::ExtensionStore;
use fs::Fs;
@@ -69,23 +61,21 @@ use gpui::{
Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
};
use language::LanguageRegistry;
-use language_model::{ConfigurationError, LanguageModelRegistry};
+use language_model::LanguageModelRegistry;
use project::project_settings::ProjectSettings;
use project::{Project, ProjectPath, Worktree};
-use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
+use prompt_store::{PromptStore, UserPromptId};
use rules_library::{RulesLibrary, open_rules_library};
-use search::{BufferSearchBar, buffer_search};
use settings::{Settings, update_settings_file};
use theme_settings::ThemeSettings;
use ui::{
Button, Callout, CommonAnimationExt, ContextMenu, ContextMenuEntry, DocumentationSide,
- KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip, prelude::*, utils::WithRemSize,
+ PopoverMenu, PopoverMenuHandle, Tab, Tooltip, prelude::*, utils::WithRemSize,
};
use util::{ResultExt as _, debug_panic};
use workspace::{
CollaboratorId, DraggedSelection, DraggedTab, OpenMode, OpenResult, PathList,
- SerializedPathList, ToggleWorkspaceSidebar, ToggleZoom, ToolbarItemView, Workspace,
- WorkspaceId,
+ SerializedPathList, ToggleWorkspaceSidebar, ToggleZoom, Workspace, WorkspaceId,
dock::{DockPosition, Panel, PanelEvent},
};
use zed_actions::{
@@ -132,7 +122,7 @@ fn read_legacy_serialized_panel(kvp: &KeyValueStore) -> Option,
+ selected_agent: Option,
#[serde(default)]
last_active_thread: Option,
#[serde(default)]
@@ -142,7 +132,7 @@ struct SerializedAgentPanel {
#[derive(Serialize, Deserialize, Debug)]
struct SerializedActiveThread {
session_id: String,
- agent_type: AgentType,
+ agent_type: Agent,
title: Option,
work_dirs: Option,
}
@@ -185,14 +175,6 @@ pub fn init(cx: &mut App) {
panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
}
})
- .register_action(|workspace, _: &NewTextThread, window, cx| {
- if let Some(panel) = workspace.panel::(cx) {
- workspace.focus_panel::(window, cx);
- panel.update(cx, |panel, cx| {
- panel.new_text_thread(window, cx);
- });
- }
- })
.register_action(|workspace, action: &NewExternalAgentThread, window, cx| {
if let Some(panel) = workspace.panel::(cx) {
workspace.focus_panel::(window, cx);
@@ -419,7 +401,28 @@ pub fn init(cx: &mut App) {
panel.cycle_start_thread_in(window, cx);
});
}
- });
+ })
+ .register_action(
+ |workspace: &mut Workspace, _: &AddSelectionToThread, window, cx| {
+ let Some(panel) = workspace.panel::(cx) else {
+ return;
+ };
+
+ if !panel.focus_handle(cx).contains_focused(window, cx) {
+ workspace.toggle_panel_focus::(window, cx);
+ }
+
+ panel.update(cx, |_, cx| {
+ cx.defer_in(window, move |panel, window, cx| {
+ if let Some(conversation_view) = panel.active_conversation_view() {
+ conversation_view.update(cx, |conversation_view, cx| {
+ conversation_view.insert_selections(window, cx);
+ });
+ }
+ });
+ });
+ },
+ );
},
)
.detach();
@@ -532,76 +535,22 @@ fn build_conflicted_files_resolution_prompt(
content
}
-#[derive(Clone, Debug, PartialEq, Eq)]
-enum History {
- AgentThreads { view: Entity },
- TextThreads,
-}
-
enum ActiveView {
Uninitialized,
AgentThread {
conversation_view: Entity,
},
- TextThread {
- text_thread_editor: Entity,
- title_editor: Entity,
- buffer_search_bar: Entity,
- _subscriptions: Vec,
- },
History {
- history: History,
+ view: Entity,
},
Configuration,
}
enum WhichFontSize {
AgentFont,
- BufferFont,
None,
}
-// TODO unify this with ExternalAgent
-#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
-pub enum AgentType {
- #[default]
- NativeAgent,
- TextThread,
- Custom {
- #[serde(rename = "name")]
- id: AgentId,
- },
-}
-
-impl AgentType {
- pub fn is_native(&self) -> bool {
- matches!(self, Self::NativeAgent)
- }
-
- fn label(&self) -> SharedString {
- match self {
- Self::NativeAgent | Self::TextThread => "Zed Agent".into(),
- Self::Custom { id, .. } => id.0.clone(),
- }
- }
-
- fn icon(&self) -> Option {
- match self {
- Self::NativeAgent | Self::TextThread => None,
- Self::Custom { .. } => Some(IconName::Sparkle),
- }
- }
-}
-
-impl From for AgentType {
- fn from(value: Agent) -> Self {
- match value {
- Agent::Custom { id } => Self::Custom { id },
- Agent::NativeAgent => Self::NativeAgent,
- }
- }
-}
-
impl StartThreadIn {
fn label(&self) -> SharedString {
match self {
@@ -624,97 +573,9 @@ impl ActiveView {
ActiveView::Uninitialized
| ActiveView::AgentThread { .. }
| ActiveView::History { .. } => WhichFontSize::AgentFont,
- ActiveView::TextThread { .. } => WhichFontSize::BufferFont,
ActiveView::Configuration => WhichFontSize::None,
}
}
-
- pub fn text_thread(
- text_thread_editor: Entity,
- language_registry: Arc,
- window: &mut Window,
- cx: &mut App,
- ) -> Self {
- let title = text_thread_editor.read(cx).title(cx).to_string();
-
- let editor = cx.new(|cx| {
- let mut editor = Editor::single_line(window, cx);
- editor.set_text(title, window, cx);
- editor
- });
-
- // This is a workaround for `editor.set_text` emitting a `BufferEdited` event, which would
- // cause a custom summary to be set. The presence of this custom summary would cause
- // summarization to not happen.
- let mut suppress_first_edit = true;
-
- let subscriptions = vec![
- window.subscribe(&editor, cx, {
- {
- let text_thread_editor = text_thread_editor.clone();
- move |editor, event, window, cx| match event {
- EditorEvent::BufferEdited => {
- if suppress_first_edit {
- suppress_first_edit = false;
- return;
- }
- 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);
- })
- })
- }
- EditorEvent::Blurred => {
- if editor.read(cx).text(cx).is_empty() {
- let summary = text_thread_editor
- .read(cx)
- .text_thread()
- .read(cx)
- .summary()
- .or_default();
-
- editor.update(cx, |editor, cx| {
- editor.set_text(summary, window, cx);
- });
- }
- }
- _ => {}
- }
- }
- }),
- window.subscribe(&text_thread_editor.read(cx).text_thread().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();
-
- editor.update(cx, |editor, cx| {
- editor.set_text(summary, window, cx);
- })
- }
- TextThreadEvent::PathChanged { .. } => {}
- _ => {}
- }
- }),
- ];
-
- 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)
- });
-
- Self::TextThread {
- text_thread_editor,
- title_editor: editor,
- buffer_search_bar,
- _subscriptions: subscriptions,
- }
- }
}
pub struct AgentPanel {
@@ -725,9 +586,7 @@ pub struct AgentPanel {
project: Entity,
fs: Arc,
language_registry: Arc,
- text_thread_history: Entity,
thread_store: Entity,
- text_thread_store: Entity,
prompt_store: Option>,
connection_store: Entity,
context_server_registry: Entity,
@@ -747,14 +606,13 @@ pub struct AgentPanel {
zoomed: bool,
pending_serialization: Option>>,
onboarding: Entity,
- selected_agent_type: AgentType,
+ selected_agent: Agent,
start_thread_in: StartThreadIn,
worktree_creation_status: Option,
_thread_view_subscription: Option,
_active_thread_focus_subscription: Option,
_worktree_creation_task: Option>,
show_trust_workspace_message: bool,
- last_configuration_error_telemetry: Option,
on_boarding_upsell_dismissed: AtomicBool,
_active_view_observation: Option,
}
@@ -765,7 +623,7 @@ impl AgentPanel {
return;
};
- let selected_agent_type = self.selected_agent_type.clone();
+ let selected_agent = self.selected_agent.clone();
let start_thread_in = Some(self.start_thread_in);
let last_active_thread = self.active_agent_thread(cx).map(|thread| {
@@ -774,7 +632,7 @@ impl AgentPanel {
let work_dirs = thread.work_dirs().cloned();
SerializedActiveThread {
session_id: thread.session_id().0.to_string(),
- agent_type: self.selected_agent_type.clone(),
+ agent_type: self.selected_agent.clone(),
title: title.map(|t| t.to_string()),
work_dirs: work_dirs.map(|dirs| dirs.serialize()),
}
@@ -785,7 +643,7 @@ impl AgentPanel {
save_serialized_panel(
workspace_id,
SerializedAgentPanel {
- selected_agent: Some(selected_agent_type),
+ selected_agent: Some(selected_agent),
last_active_thread,
start_thread_in,
},
@@ -798,7 +656,6 @@ impl AgentPanel {
pub fn load(
workspace: WeakEntity,
- prompt_builder: Arc,
mut cx: AsyncWindowContext,
) -> Task>> {
let prompt_store = cx.update(|_window, cx| PromptStore::global(cx));
@@ -823,19 +680,6 @@ impl AgentPanel {
})
.await;
- let slash_commands = Arc::new(SlashCommandWorkingSet::default());
- let text_thread_store = workspace
- .update(cx, |workspace, cx| {
- let project = workspace.project().clone();
- assistant_text_thread::TextThreadStore::new(
- project,
- prompt_builder,
- slash_commands,
- cx,
- )
- })?
- .await?;
-
let last_active_thread = if let Some(thread_info) = serialized_panel
.as_ref()
.and_then(|p| p.last_active_thread.as_ref())
@@ -869,12 +713,12 @@ impl AgentPanel {
let panel = workspace.update_in(cx, |workspace, window, cx| {
let panel =
- cx.new(|cx| Self::new(workspace, text_thread_store, prompt_store, window, cx));
+ cx.new(|cx| Self::new(workspace, prompt_store, window, cx));
if let Some(serialized_panel) = &serialized_panel {
panel.update(cx, |panel, cx| {
if let Some(selected_agent) = serialized_panel.selected_agent.clone() {
- panel.selected_agent_type = selected_agent;
+ panel.selected_agent = selected_agent;
}
if let Some(start_thread_in) = serialized_panel.start_thread_in {
let is_worktree_flag_enabled =
@@ -900,20 +744,18 @@ impl AgentPanel {
}
if let Some(thread_info) = last_active_thread {
- let agent_type = thread_info.agent_type.clone();
+ let agent = thread_info.agent_type.clone();
panel.update(cx, |panel, cx| {
- panel.selected_agent_type = agent_type;
- if let Some(agent) = panel.selected_agent() {
- panel.load_agent_thread(
- agent,
- thread_info.session_id.clone().into(),
- thread_info.work_dirs.as_ref().map(|dirs| PathList::deserialize(dirs)),
- thread_info.title.as_ref().map(|t| t.clone().into()),
- false,
- window,
- cx,
- );
- }
+ panel.selected_agent = agent.clone();
+ panel.load_agent_thread(
+ agent,
+ thread_info.session_id.clone().into(),
+ thread_info.work_dirs.as_ref().map(|dirs| PathList::deserialize(dirs)),
+ thread_info.title.as_ref().map(|t| t.clone().into()),
+ false,
+ window,
+ cx,
+ );
});
}
panel
@@ -925,7 +767,6 @@ impl AgentPanel {
pub(crate) fn new(
workspace: &Workspace,
- text_thread_store: Entity,
prompt_store: Option>,
window: &mut Window,
cx: &mut Context,
@@ -942,20 +783,6 @@ impl AgentPanel {
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let thread_store = ThreadStore::global(cx);
- let text_thread_history =
- cx.new(|cx| TextThreadHistory::new(text_thread_store.clone(), window, cx));
-
- cx.subscribe_in(
- &text_thread_history,
- window,
- |this, _, event, window, cx| match event {
- TextThreadHistoryEvent::Open(thread) => {
- this.open_saved_text_thread(thread.path.clone(), window, cx)
- .detach_and_log_err(cx);
- }
- },
- )
- .detach();
let active_view = ActiveView::Uninitialized;
@@ -969,14 +796,10 @@ impl AgentPanel {
if let Some(history) = panel
.update(cx, |panel, cx| panel.history_for_selected_agent(window, cx))
{
- let view_all_label = match history {
- History::AgentThreads { .. } => "View All",
- History::TextThreads => "View All Text Threads",
- };
menu = Self::populate_recently_updated_menu_section(
menu, panel, history, cx,
);
- menu = menu.action(view_all_label, Box::new(OpenHistory));
+ menu = menu.action("View All", Box::new(OpenHistory));
}
}
@@ -1070,7 +893,6 @@ impl AgentPanel {
project: project.clone(),
fs: fs.clone(),
language_registry,
- text_thread_store,
prompt_store,
connection_store,
configuration: None,
@@ -1089,16 +911,14 @@ impl AgentPanel {
zoomed: false,
pending_serialization: None,
onboarding,
- text_thread_history,
thread_store,
- selected_agent_type: AgentType::default(),
+ selected_agent: Agent::default(),
start_thread_in: StartThreadIn::default(),
worktree_creation_status: None,
_thread_view_subscription: None,
_active_thread_focus_subscription: None,
_worktree_creation_task: None,
show_trust_workspace_message: false,
- last_configuration_error_telemetry: None,
on_boarding_upsell_dismissed: AtomicBool::new(OnboardingUpsell::dismissed(cx)),
_active_view_observation: None,
};
@@ -1240,49 +1060,6 @@ impl AgentPanel {
.detach_and_log_err(cx);
}
- fn new_text_thread(&mut self, window: &mut Window, cx: &mut Context) {
- telemetry::event!("Agent Thread Started", agent = "zed-text");
-
- let context = self
- .text_thread_store
- .update(cx, |context_store, cx| context_store.create(cx));
- let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
- .log_err()
- .flatten();
-
- let text_thread_editor = cx.new(|cx| {
- let mut editor = TextThreadEditor::for_text_thread(
- context,
- self.fs.clone(),
- self.workspace.clone(),
- self.project.clone(),
- lsp_adapter_delegate,
- window,
- cx,
- );
- editor.insert_default_prompt(window, cx);
- editor
- });
-
- if self.selected_agent_type != AgentType::TextThread {
- self.selected_agent_type = AgentType::TextThread;
- self.serialize(cx);
- }
-
- self.set_active_view(
- ActiveView::text_thread(
- text_thread_editor.clone(),
- self.language_registry.clone(),
- window,
- cx,
- ),
- true,
- window,
- cx,
- );
- text_thread_editor.focus_handle(cx).focus(window, cx);
- }
-
fn external_thread(
&mut self,
agent_choice: Option,
@@ -1387,13 +1164,6 @@ impl AgentPanel {
open_rules_library(
self.language_registry.clone(),
Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
- Rc::new(|| {
- Rc::new(SlashCommandCompletionProvider::new(
- Arc::new(SlashCommandWorkingSet::default()),
- None,
- None,
- ))
- }),
action
.prompt_to_select
.map(|uuid| UserPromptId(uuid).into()),
@@ -1418,15 +1188,13 @@ impl AgentPanel {
}
fn has_history_for_selected_agent(&self, cx: &App) -> bool {
- match &self.selected_agent_type {
- AgentType::TextThread | AgentType::NativeAgent => true,
- AgentType::Custom { id } => {
- let agent = Agent::Custom { id: id.clone() };
- self.connection_store
- .read(cx)
- .entry(&agent)
- .map_or(false, |entry| entry.read(cx).history().is_some())
- }
+ match &self.selected_agent {
+ Agent::NativeAgent => true,
+ Agent::Custom { .. } => self
+ .connection_store
+ .read(cx)
+ .entry(&self.selected_agent)
+ .map_or(false, |entry| entry.read(cx).history().is_some()),
}
}
@@ -1434,36 +1202,16 @@ impl AgentPanel {
&self,
window: &mut Window,
cx: &mut Context,
- ) -> Option {
- match &self.selected_agent_type {
- AgentType::TextThread => Some(History::TextThreads),
- AgentType::NativeAgent => {
- let history = self
- .connection_store
- .read(cx)
- .entry(&Agent::NativeAgent)?
- .read(cx)
- .history()?
- .clone();
-
- Some(History::AgentThreads {
- view: self.create_thread_history_view(Agent::NativeAgent, history, window, cx),
- })
- }
- AgentType::Custom { id, .. } => {
- let agent = Agent::Custom { id: id.clone() };
- let history = self
- .connection_store
- .read(cx)
- .entry(&agent)?
- .read(cx)
- .history()?
- .clone();
- Some(History::AgentThreads {
- view: self.create_thread_history_view(agent, history, window, cx),
- })
- }
- }
+ ) -> Option> {
+ let agent = self.selected_agent.clone();
+ let history = self
+ .connection_store
+ .read(cx)
+ .entry(&agent)?
+ .read(cx)
+ .history()?
+ .clone();
+ Some(self.create_thread_history_view(agent, history, window, cx))
}
fn create_thread_history_view(
@@ -1496,15 +1244,12 @@ impl AgentPanel {
}
fn open_history(&mut self, window: &mut Window, cx: &mut Context) {
- let Some(history) = self.history_for_selected_agent(window, cx) else {
+ let Some(view) = self.history_for_selected_agent(window, cx) else {
return;
};
- if let ActiveView::History {
- history: active_history,
- } = &self.active_view
- {
- if active_history == &history {
+ if let ActiveView::History { view: active_view } = &self.active_view {
+ if active_view == &view {
if let Some(previous_view) = self.previous_view.take() {
self.set_active_view(previous_view, true, window, cx);
}
@@ -1512,61 +1257,10 @@ impl AgentPanel {
}
}
- self.set_active_view(ActiveView::History { history }, true, window, cx);
+ self.set_active_view(ActiveView::History { view }, true, window, cx);
cx.notify();
}
- pub(crate) fn open_saved_text_thread(
- &mut self,
- path: Arc,
- window: &mut Window,
- cx: &mut Context,
- ) -> Task> {
- let text_thread_task = self
- .text_thread_store
- .update(cx, |store, cx| store.open_local(path, cx));
- cx.spawn_in(window, async move |this, cx| {
- let text_thread = text_thread_task.await?;
- this.update_in(cx, |this, window, cx| {
- this.open_text_thread(text_thread, window, cx);
- })
- })
- }
-
- pub(crate) fn open_text_thread(
- &mut self,
- text_thread: Entity,
- window: &mut Window,
- cx: &mut Context,
- ) {
- let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project.clone(), cx)
- .log_err()
- .flatten();
- let editor = cx.new(|cx| {
- TextThreadEditor::for_text_thread(
- text_thread,
- self.fs.clone(),
- self.workspace.clone(),
- self.project.clone(),
- lsp_adapter_delegate,
- window,
- cx,
- )
- });
-
- if self.selected_agent_type != AgentType::TextThread {
- self.selected_agent_type = AgentType::TextThread;
- self.serialize(cx);
- }
-
- self.set_active_view(
- ActiveView::text_thread(editor, self.language_registry.clone(), window, cx),
- true,
- window,
- cx,
- );
- }
-
pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context) {
match self.active_view {
ActiveView::Configuration | ActiveView::History { .. } => {
@@ -1650,11 +1344,6 @@ impl AgentPanel {
theme_settings::adjust_agent_buffer_font_size(cx, |size| size + delta);
}
}
- WhichFontSize::BufferFont => {
- // Prompt editor uses the buffer font size, so allow the action to propagate to the
- // default handler that changes that font size.
- cx.propagate();
- }
WhichFontSize::None => {}
}
}
@@ -2065,15 +1754,6 @@ impl AgentPanel {
}
}
- pub(crate) fn active_text_thread_editor(&self) -> Option> {
- match &self.active_view {
- ActiveView::TextThread {
- text_thread_editor, ..
- } => Some(text_thread_editor.clone()),
- _ => None,
- }
- }
-
fn set_active_view(
&mut self,
new_view: ActiveView,
@@ -2081,12 +1761,7 @@ impl AgentPanel {
window: &mut Window,
cx: &mut Context,
) {
- let was_in_agent_history = matches!(
- self.active_view,
- ActiveView::History {
- history: History::AgentThreads { .. }
- }
- );
+ let was_in_agent_history = matches!(self.active_view, ActiveView::History { .. });
let current_is_uninitialized = matches!(self.active_view, ActiveView::Uninitialized);
let current_is_history = matches!(self.active_view, ActiveView::History { .. });
let new_is_history = matches!(new_view, ActiveView::History { .. });
@@ -2144,8 +1819,8 @@ impl AgentPanel {
}
};
- if let ActiveView::History { history } = &self.active_view {
- if !was_in_agent_history && let History::AgentThreads { view } = history {
+ if let ActiveView::History { view } = &self.active_view {
+ if !was_in_agent_history {
view.update(cx, |view, cx| {
view.history()
.update(cx, |history, cx| history.refresh_full_history(cx))
@@ -2162,97 +1837,55 @@ impl AgentPanel {
fn populate_recently_updated_menu_section(
mut menu: ContextMenu,
panel: Entity,
- history: History,
+ view: Entity,
cx: &mut Context,
) -> ContextMenu {
- match history {
- History::AgentThreads { view } => {
- let entries = view
- .read(cx)
- .history()
- .read(cx)
- .sessions()
- .iter()
- .take(RECENTLY_UPDATED_MENU_LIMIT)
- .cloned()
- .collect::>();
-
- if entries.is_empty() {
- return menu;
- }
-
- menu = menu.header("Recently Updated");
-
- for entry in entries {
- let title = entry
- .title
- .as_ref()
- .filter(|title| !title.is_empty())
- .cloned()
- .unwrap_or_else(|| SharedString::new_static(DEFAULT_THREAD_TITLE));
-
- menu = menu.entry(title, None, {
- let panel = panel.downgrade();
- let entry = entry.clone();
- move |window, cx| {
- let entry = entry.clone();
- panel
- .update(cx, move |this, cx| {
- if let Some(agent) = this.selected_agent() {
- this.load_agent_thread(
- agent,
- entry.session_id.clone(),
- entry.work_dirs.clone(),
- entry.title.clone(),
- true,
- window,
- cx,
- );
- }
- })
- .ok();
- }
- });
- }
- }
- History::TextThreads => {
- let entries = panel
- .read(cx)
- .text_thread_store
- .read(cx)
- .ordered_text_threads()
- .take(RECENTLY_UPDATED_MENU_LIMIT)
- .cloned()
- .collect::>();
+ let entries = view
+ .read(cx)
+ .history()
+ .read(cx)
+ .sessions()
+ .iter()
+ .take(RECENTLY_UPDATED_MENU_LIMIT)
+ .cloned()
+ .collect::>();
- if entries.is_empty() {
- return menu;
- }
+ if entries.is_empty() {
+ return menu;
+ }
- menu = menu.header("Recent Text Threads");
+ menu = menu.header("Recently Updated");
- for entry in entries {
- let title = if entry.title.is_empty() {
- SharedString::new_static(DEFAULT_THREAD_TITLE)
- } else {
- entry.title.clone()
- };
+ for entry in entries {
+ let title = entry
+ .title
+ .as_ref()
+ .filter(|title| !title.is_empty())
+ .cloned()
+ .unwrap_or_else(|| SharedString::new_static(DEFAULT_THREAD_TITLE));
- menu = menu.entry(title, None, {
- let panel = panel.downgrade();
- let entry = entry.clone();
- move |window, cx| {
- let path = entry.path.clone();
- panel
- .update(cx, move |this, cx| {
- this.open_saved_text_thread(path.clone(), window, cx)
- .detach_and_log_err(cx);
- })
- .ok();
- }
- });
+ menu = menu.entry(title, None, {
+ let panel = panel.downgrade();
+ let entry = entry.clone();
+ move |window, cx| {
+ let entry = entry.clone();
+ panel
+ .update(cx, move |this, cx| {
+ if let Some(agent) = this.selected_agent() {
+ this.load_agent_thread(
+ agent,
+ entry.session_id.clone(),
+ entry.work_dirs.clone(),
+ entry.title.clone(),
+ true,
+ window,
+ cx,
+ );
+ }
+ })
+ .ok();
}
- }
+ });
}
menu.separator()
@@ -2347,11 +1980,7 @@ impl AgentPanel {
}
pub(crate) fn selected_agent(&self) -> Option {
- match &self.selected_agent_type {
- AgentType::NativeAgent => Some(Agent::NativeAgent),
- AgentType::Custom { id } => Some(Agent::Custom { id: id.clone() }),
- AgentType::TextThread => None,
- }
+ Some(self.selected_agent.clone())
}
fn sync_agent_servers_from_extensions(&mut self, cx: &mut Context) {
@@ -2397,48 +2026,19 @@ impl AgentPanel {
);
}
- pub fn new_agent_thread(
- &mut self,
- agent: AgentType,
- window: &mut Window,
- cx: &mut Context,
- ) {
+ pub fn new_agent_thread(&mut self, agent: Agent, window: &mut Window, cx: &mut Context) {
self.reset_start_thread_in_to_default(cx);
self.new_agent_thread_inner(agent, true, window, cx);
}
fn new_agent_thread_inner(
&mut self,
- agent: AgentType,
+ agent: Agent,
focus: bool,
window: &mut Window,
cx: &mut Context,
) {
- match agent {
- AgentType::TextThread => {
- window.dispatch_action(NewTextThread.boxed_clone(), cx);
- }
- AgentType::NativeAgent => self.external_thread(
- Some(crate::Agent::NativeAgent),
- None,
- None,
- None,
- None,
- focus,
- window,
- cx,
- ),
- AgentType::Custom { id } => self.external_thread(
- Some(crate::Agent::Custom { id }),
- None,
- None,
- None,
- None,
- focus,
- window,
- cx,
- ),
- }
+ self.external_thread(Some(agent), None, None, None, None, focus, window, cx);
}
pub fn load_agent_thread(
@@ -2512,9 +2112,8 @@ impl AgentPanel {
window: &mut Window,
cx: &mut Context,
) {
- let selected_agent = AgentType::from(ext_agent.clone());
- if self.selected_agent_type != selected_agent {
- self.selected_agent_type = selected_agent;
+ if self.selected_agent != ext_agent {
+ self.selected_agent = ext_agent.clone();
self.serialize(cx);
}
let thread_store = server
@@ -2775,8 +2374,8 @@ impl AgentPanel {
) {
self.worktree_creation_status = Some(WorktreeCreationStatus::Error(message));
if matches!(self.active_view, ActiveView::Uninitialized) {
- let selected_agent_type = self.selected_agent_type.clone();
- self.new_agent_thread(selected_agent_type, window, cx);
+ let selected_agent = self.selected_agent.clone();
+ self.new_agent_thread(selected_agent, window, cx);
}
cx.notify();
}
@@ -3136,13 +2735,7 @@ impl Focusable for AgentPanel {
ActiveView::AgentThread {
conversation_view, ..
} => conversation_view.focus_handle(cx),
- ActiveView::History { history: kind } => match kind {
- History::AgentThreads { view } => view.read(cx).focus_handle(cx),
- History::TextThreads => self.text_thread_history.focus_handle(cx),
- },
- ActiveView::TextThread {
- text_thread_editor, ..
- } => text_thread_editor.focus_handle(cx),
+ ActiveView::History { view } => view.read(cx).focus_handle(cx),
ActiveView::Configuration => {
if let Some(configuration) = self.configuration.as_ref() {
configuration.focus_handle(cx)
@@ -3227,8 +2820,8 @@ impl Panel for AgentPanel {
Some(WorktreeCreationStatus::Creating)
)
{
- let selected_agent_type = self.selected_agent_type.clone();
- self.new_agent_thread_inner(selected_agent_type, false, window, cx);
+ let selected_agent = self.selected_agent.clone();
+ self.new_agent_thread_inner(selected_agent, false, window, cx);
}
}
@@ -3272,8 +2865,6 @@ impl Panel for AgentPanel {
impl AgentPanel {
fn render_title_view(&self, _window: &mut Window, cx: &Context) -> AnyElement {
- const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
-
let content = match &self.active_view {
ActiveView::AgentThread { conversation_view } => {
let server_view_ref = conversation_view.read(cx);
@@ -3327,70 +2918,7 @@ impl AgentPanel {
.into_any_element()
}
}
- ActiveView::TextThread {
- title_editor,
- text_thread_editor,
- ..
- } => {
- let summary = text_thread_editor.read(cx).text_thread().read(cx).summary();
-
- match summary {
- TextThreadSummary::Pending => Label::new(TextThreadSummary::DEFAULT)
- .color(Color::Muted)
- .truncate()
- .into_any_element(),
- TextThreadSummary::Content(summary) => {
- if summary.done {
- div()
- .w_full()
- .child(title_editor.clone())
- .into_any_element()
- } else {
- Label::new(LOADING_SUMMARY_PLACEHOLDER)
- .truncate()
- .color(Color::Muted)
- .with_animation(
- "generating_title",
- Animation::new(Duration::from_secs(2))
- .repeat()
- .with_easing(pulsating_between(0.4, 0.8)),
- |label, delta| label.alpha(delta),
- )
- .into_any_element()
- }
- }
- TextThreadSummary::Error => h_flex()
- .w_full()
- .child(title_editor.clone())
- .child(
- IconButton::new("retry-summary-generation", IconName::RotateCcw)
- .icon_size(IconSize::Small)
- .on_click({
- let text_thread_editor = text_thread_editor.clone();
- move |_, _window, cx| {
- text_thread_editor.update(cx, |text_thread_editor, cx| {
- text_thread_editor.regenerate_summary(cx);
- });
- }
- })
- .tooltip(move |_window, cx| {
- cx.new(|_| {
- Tooltip::new("Failed to generate title")
- .meta("Click to try again")
- })
- .into()
- }),
- )
- .into_any_element(),
- }
- }
- ActiveView::History { history: kind } => {
- let title = match kind {
- History::AgentThreads { .. } => "History",
- History::TextThreads => "Text Thread History",
- };
- Label::new(title).truncate().into_any_element()
- }
+ ActiveView::History { .. } => Label::new("History").truncate().into_any_element(),
ActiveView::Configuration => Label::new("Settings").truncate().into_any_element(),
ActiveView::Uninitialized => Label::new("Agent").truncate().into_any_element(),
};
@@ -3416,15 +2944,6 @@ impl AgentPanel {
});
}
- fn handle_regenerate_text_thread_title(
- text_thread_editor: Entity,
- cx: &mut App,
- ) {
- text_thread_editor.update(cx, |text_thread_editor, cx| {
- text_thread_editor.regenerate_summary(cx);
- });
- }
-
fn render_panel_options_menu(
&self,
window: &mut Window,
@@ -3438,24 +2957,6 @@ impl AgentPanel {
"Enable Full Screen"
};
- let text_thread_view = match &self.active_view {
- ActiveView::TextThread {
- text_thread_editor, ..
- } => Some(text_thread_editor.clone()),
- _ => None,
- };
- let text_thread_with_messages = match &self.active_view {
- ActiveView::TextThread {
- text_thread_editor, ..
- } => text_thread_editor
- .read(cx)
- .text_thread()
- .read(cx)
- .messages(cx)
- .any(|message| message.role == language_model::Role::Assistant),
- _ => false,
- };
-
let conversation_view = match &self.active_view {
ActiveView::AgentThread { conversation_view } => Some(conversation_view.clone()),
_ => None,
@@ -3496,23 +2997,9 @@ impl AgentPanel {
Some(ContextMenu::build(window, cx, |mut menu, _window, _| {
menu = menu.context(focus_handle.clone());
- if thread_with_messages | text_thread_with_messages {
+ if thread_with_messages {
menu = menu.header("Current Thread");
- if let Some(text_thread_view) = text_thread_view.as_ref() {
- menu = menu
- .entry("Regenerate Thread Title", None, {
- let text_thread_view = text_thread_view.clone();
- move |_, cx| {
- Self::handle_regenerate_text_thread_title(
- text_thread_view.clone(),
- cx,
- );
- }
- })
- .separator();
- }
-
if let Some(conversation_view) = conversation_view.as_ref() {
menu = menu
.entry("Regenerate Thread Title", None, {
@@ -3764,33 +3251,32 @@ impl AgentPanel {
let focus_handle = self.focus_handle(cx);
let (selected_agent_custom_icon, selected_agent_label) =
- if let AgentType::Custom { id, .. } = &self.selected_agent_type {
+ if let Agent::Custom { id, .. } = &self.selected_agent {
let store = agent_server_store.read(cx);
let icon = store.agent_icon(&id);
let label = store
.agent_display_name(&id)
- .unwrap_or_else(|| self.selected_agent_type.label());
+ .unwrap_or_else(|| self.selected_agent.label());
(icon, label)
} else {
- (None, self.selected_agent_type.label())
+ (None, self.selected_agent.label())
};
let active_thread = match &self.active_view {
ActiveView::AgentThread { conversation_view } => {
conversation_view.read(cx).as_native_thread(cx)
}
- ActiveView::Uninitialized
- | ActiveView::TextThread { .. }
- | ActiveView::History { .. }
- | ActiveView::Configuration => None,
+ ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {
+ None
+ }
};
let new_thread_menu_builder: Rc<
dyn Fn(&mut Window, &mut App) -> Option>,
> = {
- let selected_agent = self.selected_agent_type.clone();
- let is_agent_selected = move |agent_type: AgentType| selected_agent == agent_type;
+ let selected_agent = self.selected_agent.clone();
+ let is_agent_selected = move |agent: Agent| selected_agent == agent;
let workspace = self.workspace.clone();
let is_via_collab = workspace
@@ -3832,15 +3318,9 @@ impl AgentPanel {
})
.item(
ContextMenuEntry::new("Zed Agent")
- .when(
- is_agent_selected(AgentType::NativeAgent)
- | is_agent_selected(AgentType::TextThread),
- |this| {
- this.action(Box::new(NewExternalAgentThread {
- agent: None,
- }))
- },
- )
+ .when(is_agent_selected(Agent::NativeAgent), |this| {
+ this.action(Box::new(NewExternalAgentThread { agent: None }))
+ })
.icon(IconName::ZedAgent)
.icon_color(Color::Muted)
.handler({
@@ -3853,33 +3333,7 @@ impl AgentPanel {
{
panel.update(cx, |panel, cx| {
panel.new_agent_thread(
- AgentType::NativeAgent,
- window,
- cx,
- );
- });
- }
- });
- }
- }
- }),
- )
- .item(
- ContextMenuEntry::new("Text Thread")
- .action(NewTextThread.boxed_clone())
- .icon(IconName::TextThread)
- .icon_color(Color::Muted)
- .handler({
- let workspace = workspace.clone();
- move |window, cx| {
- if let Some(workspace) = workspace.upgrade() {
- workspace.update(cx, |workspace, cx| {
- if let Some(panel) =
- workspace.panel::(cx)
- {
- panel.update(cx, |panel, cx| {
- panel.new_agent_thread(
- AgentType::TextThread,
+ Agent::NativeAgent,
window,
cx,
);
@@ -3942,7 +3396,7 @@ impl AgentPanel {
entry = entry
.when(
- is_agent_selected(AgentType::Custom {
+ is_agent_selected(Agent::Custom {
id: item.id.clone(),
}),
|this| {
@@ -3964,7 +3418,7 @@ impl AgentPanel {
{
panel.update(cx, |panel, cx| {
panel.new_agent_thread(
- AgentType::Custom {
+ Agent::Custom {
id: agent_id.clone(),
},
window,
@@ -4005,7 +3459,7 @@ impl AgentPanel {
let has_custom_icon = selected_agent_custom_icon.is_some();
let selected_agent_custom_icon_for_button = selected_agent_custom_icon.clone();
- let selected_agent_builtin_icon = self.selected_agent_type.icon();
+ let selected_agent_builtin_icon = self.selected_agent.icon();
let selected_agent_label_for_tooltip = selected_agent_label.clone();
let selected_agent = div()
@@ -4015,7 +3469,7 @@ impl AgentPanel {
.child(Icon::from_external_svg(icon_path).color(Color::Muted))
})
.when(!has_custom_icon, |this| {
- this.when_some(self.selected_agent_type.icon(), |this, icon| {
+ this.when_some(selected_agent_builtin_icon, |this, icon| {
this.px_1().child(Icon::new(icon).color(Color::Muted))
})
})
@@ -4051,12 +3505,9 @@ impl AgentPanel {
ActiveView::History { .. } | ActiveView::Configuration
);
- let is_text_thread = matches!(&self.active_view, ActiveView::TextThread { .. });
-
let is_full_screen = self.is_zoomed(window, cx);
- let use_v2_empty_toolbar =
- has_v2_flag && is_empty_state && !is_in_history_or_config && !is_text_thread;
+ let use_v2_empty_toolbar = has_v2_flag && is_empty_state && !is_in_history_or_config;
let base_container = h_flex()
.id("agent-panel-toolbar")
@@ -4270,7 +3721,7 @@ impl AgentPanel {
}
match &self.active_view {
- ActiveView::TextThread { .. } => {
+ ActiveView::AgentThread { .. } => {
if LanguageModelRegistry::global(cx)
.read(cx)
.default_model()
@@ -4281,10 +3732,9 @@ impl AgentPanel {
return false;
}
}
- ActiveView::Uninitialized
- | ActiveView::AgentThread { .. }
- | ActiveView::History { .. }
- | ActiveView::Configuration => return false,
+ ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {
+ return false;
+ }
}
let plan = self.user_store.read(cx).plan();
@@ -4334,10 +3784,6 @@ impl AgentPanel {
.is_none_or(|h| h.read(cx).is_empty());
history_is_empty || !has_configured_non_zed_providers
}
- ActiveView::TextThread { .. } => {
- let history_is_empty = self.text_thread_history.read(cx).is_empty();
- history_is_empty || !has_configured_non_zed_providers
- }
}
}
@@ -4350,15 +3796,7 @@ impl AgentPanel {
return None;
}
- let text_thread_view = matches!(&self.active_view, ActiveView::TextThread { .. });
-
- Some(
- div()
- .when(text_thread_view, |this| {
- this.bg(cx.theme().colors().editor_background)
- })
- .child(self.onboarding.clone()),
- )
+ Some(div().child(self.onboarding.clone()))
}
fn render_trial_end_upsell(
@@ -4390,142 +3828,6 @@ impl AgentPanel {
)
}
- fn emit_configuration_error_telemetry_if_needed(
- &mut self,
- configuration_error: Option<&ConfigurationError>,
- ) {
- let error_kind = configuration_error.map(|err| match err {
- ConfigurationError::NoProvider => "no_provider",
- ConfigurationError::ModelNotFound => "model_not_found",
- ConfigurationError::ProviderNotAuthenticated(_) => "provider_not_authenticated",
- });
-
- let error_kind_string = error_kind.map(String::from);
-
- if self.last_configuration_error_telemetry == error_kind_string {
- return;
- }
-
- self.last_configuration_error_telemetry = error_kind_string;
-
- if let Some(kind) = error_kind {
- let message = configuration_error
- .map(|err| err.to_string())
- .unwrap_or_default();
-
- telemetry::event!("Agent Panel Error Shown", kind = kind, message = message,);
- }
- }
-
- fn render_configuration_error(
- &self,
- border_bottom: bool,
- configuration_error: &ConfigurationError,
- focus_handle: &FocusHandle,
- cx: &mut App,
- ) -> impl IntoElement {
- let zed_provider_configured = AgentSettings::get_global(cx)
- .default_model
- .as_ref()
- .is_some_and(|selection| selection.provider.0.as_str() == "zed.dev");
-
- let callout = if zed_provider_configured {
- Callout::new()
- .icon(IconName::Warning)
- .severity(Severity::Warning)
- .when(border_bottom, |this| {
- this.border_position(ui::BorderPosition::Bottom)
- })
- .title("Sign in to continue using Zed as your LLM provider.")
- .actions_slot(
- Button::new("sign_in", "Sign In")
- .style(ButtonStyle::Tinted(ui::TintColor::Warning))
- .label_size(LabelSize::Small)
- .on_click({
- let workspace = self.workspace.clone();
- move |_, _, cx| {
- let Ok(client) =
- workspace.update(cx, |workspace, _| workspace.client().clone())
- else {
- return;
- };
-
- cx.spawn(async move |cx| {
- client.sign_in_with_optional_connect(true, cx).await
- })
- .detach_and_log_err(cx);
- }
- }),
- )
- } else {
- Callout::new()
- .icon(IconName::Warning)
- .severity(Severity::Warning)
- .when(border_bottom, |this| {
- this.border_position(ui::BorderPosition::Bottom)
- })
- .title(configuration_error.to_string())
- .actions_slot(
- Button::new("settings", "Configure")
- .style(ButtonStyle::Tinted(ui::TintColor::Warning))
- .label_size(LabelSize::Small)
- .key_binding(
- KeyBinding::for_action_in(&OpenSettings, focus_handle, cx)
- .map(|kb| kb.size(rems_from_px(12.))),
- )
- .on_click(|_event, window, cx| {
- window.dispatch_action(OpenSettings.boxed_clone(), cx)
- }),
- )
- };
-
- match configuration_error {
- ConfigurationError::ModelNotFound
- | ConfigurationError::ProviderNotAuthenticated(_)
- | ConfigurationError::NoProvider => callout.into_any_element(),
- }
- }
-
- fn render_text_thread(
- &self,
- text_thread_editor: &Entity,
- buffer_search_bar: &Entity,
- window: &mut Window,
- cx: &mut Context,
- ) -> Div {
- let mut registrar = buffer_search::DivRegistrar::new(
- |this, _, _cx| match &this.active_view {
- ActiveView::TextThread {
- buffer_search_bar, ..
- } => Some(buffer_search_bar.clone()),
- _ => None,
- },
- cx,
- );
- BufferSearchBar::register(&mut registrar);
- registrar
- .into_div()
- .size_full()
- .relative()
- .map(|parent| {
- buffer_search_bar.update(cx, |buffer_search_bar, cx| {
- if buffer_search_bar.is_dismissed() {
- return parent;
- }
- parent.child(
- div()
- .p(DynamicSpacing::Base08.rems(cx))
- .border_b_1()
- .border_color(cx.theme().colors().border_variant)
- .bg(cx.theme().colors().editor_background)
- .child(buffer_search_bar.render(window, cx)),
- )
- })
- })
- .child(text_thread_editor.clone())
- .child(self.render_drag_target(cx))
- }
-
fn render_drag_target(&self, cx: &Context) -> Div {
let is_local = self.project.read(cx).is_local();
div()
@@ -4598,19 +3900,6 @@ impl AgentPanel {
conversation_view.insert_dragged_files(paths, added_worktrees, window, cx);
});
}
- ActiveView::TextThread {
- text_thread_editor, ..
- } => {
- text_thread_editor.update(cx, |text_thread_editor, cx| {
- TextThreadEditor::insert_dragged_files(
- text_thread_editor,
- paths,
- added_worktrees,
- window,
- cx,
- );
- });
- }
ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {}
}
}
@@ -4652,7 +3941,6 @@ impl AgentPanel {
key_context.add("AgentPanel");
match &self.active_view {
ActiveView::AgentThread { .. } => key_context.add("acp_thread"),
- ActiveView::TextThread { .. } => key_context.add("text_thread"),
ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {}
}
key_context
@@ -4703,59 +3991,15 @@ impl Render for AgentPanel {
.child(self.render_toolbar(window, cx))
.children(self.render_workspace_trust_message(cx))
.children(self.render_onboarding(window, cx))
- .map(|parent| {
- // Emit configuration error telemetry before entering the match to avoid borrow conflicts
- if matches!(&self.active_view, ActiveView::TextThread { .. }) {
- let model_registry = LanguageModelRegistry::read_global(cx);
- let configuration_error =
- model_registry.configuration_error(model_registry.default_model(), cx);
- self.emit_configuration_error_telemetry_if_needed(configuration_error.as_ref());
- }
-
- match &self.active_view {
- ActiveView::Uninitialized => parent,
- ActiveView::AgentThread {
- conversation_view, ..
- } => parent
- .child(conversation_view.clone())
- .child(self.render_drag_target(cx)),
- ActiveView::History { history: kind } => match kind {
- History::AgentThreads { view } => parent.child(view.clone()),
- History::TextThreads => parent.child(self.text_thread_history.clone()),
- },
- ActiveView::TextThread {
- text_thread_editor,
- buffer_search_bar,
- ..
- } => {
- let model_registry = LanguageModelRegistry::read_global(cx);
- let configuration_error =
- model_registry.configuration_error(model_registry.default_model(), cx);
-
- parent
- .map(|this| {
- if !self.should_render_onboarding(cx)
- && let Some(err) = configuration_error.as_ref()
- {
- this.child(self.render_configuration_error(
- true,
- err,
- &self.focus_handle(cx),
- cx,
- ))
- } else {
- this
- }
- })
- .child(self.render_text_thread(
- text_thread_editor,
- buffer_search_bar,
- window,
- cx,
- ))
- }
- ActiveView::Configuration => parent.children(self.configuration.clone()),
- }
+ .map(|parent| match &self.active_view {
+ ActiveView::Uninitialized => parent,
+ ActiveView::AgentThread {
+ conversation_view, ..
+ } => parent
+ .child(conversation_view.clone())
+ .child(self.render_drag_target(cx)),
+ ActiveView::History { view } => parent.child(view.clone()),
+ ActiveView::Configuration => parent.children(self.configuration.clone()),
})
.children(self.render_worktree_creation_status(cx))
.children(self.render_trial_end_upsell(window, cx));
@@ -4831,117 +4075,6 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
}
}
-pub struct ConcreteAssistantPanelDelegate;
-
-impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
- fn active_text_thread_editor(
- &self,
- workspace: &mut Workspace,
- _window: &mut Window,
- cx: &mut Context,
- ) -> Option> {
- let panel = workspace.panel::(cx)?;
- panel.read(cx).active_text_thread_editor()
- }
-
- fn open_local_text_thread(
- &self,
- workspace: &mut Workspace,
- path: Arc,
- window: &mut Window,
- cx: &mut Context,
- ) -> Task> {
- let Some(panel) = workspace.panel::(cx) else {
- return Task::ready(Err(anyhow!("Agent panel not found")));
- };
-
- panel.update(cx, |panel, cx| {
- panel.open_saved_text_thread(path, window, cx)
- })
- }
-
- fn open_remote_text_thread(
- &self,
- _workspace: &mut Workspace,
- _text_thread_id: assistant_text_thread::TextThreadId,
- _window: &mut Window,
- _cx: &mut Context,
- ) -> Task>> {
- Task::ready(Err(anyhow!("opening remote context not implemented")))
- }
-
- fn quote_selection(
- &self,
- workspace: &mut Workspace,
- selection_ranges: Vec>,
- buffer: Entity,
- window: &mut Window,
- cx: &mut Context,
- ) {
- let Some(panel) = workspace.panel::(cx) else {
- return;
- };
-
- if !panel.focus_handle(cx).contains_focused(window, cx) {
- workspace.toggle_panel_focus::(window, cx);
- }
-
- panel.update(cx, |_, cx| {
- // Wait to create a new context until the workspace is no longer
- // being updated.
- cx.defer_in(window, move |panel, window, cx| {
- if let Some(conversation_view) = panel.active_conversation_view() {
- conversation_view.update(cx, |conversation_view, cx| {
- conversation_view.insert_selections(window, cx);
- });
- } else if let Some(text_thread_editor) = panel.active_text_thread_editor() {
- let snapshot = buffer.read(cx).snapshot(cx);
- let selection_ranges = selection_ranges
- .into_iter()
- .map(|range| range.to_point(&snapshot))
- .collect::>();
-
- text_thread_editor.update(cx, |text_thread_editor, cx| {
- text_thread_editor.quote_ranges(selection_ranges, snapshot, window, cx)
- });
- }
- });
- });
- }
-
- fn quote_terminal_text(
- &self,
- workspace: &mut Workspace,
- text: String,
- window: &mut Window,
- cx: &mut Context,
- ) {
- let Some(panel) = workspace.panel::(cx) else {
- return;
- };
-
- if !panel.focus_handle(cx).contains_focused(window, cx) {
- workspace.toggle_panel_focus::(window, cx);
- }
-
- panel.update(cx, |_, cx| {
- // Wait to create a new context until the workspace is no longer
- // being updated.
- cx.defer_in(window, move |panel, window, cx| {
- if let Some(conversation_view) = panel.active_conversation_view() {
- conversation_view.update(cx, |conversation_view, cx| {
- conversation_view.insert_terminal_text(text, window, cx);
- });
- } else if let Some(text_thread_editor) = panel.active_text_thread_editor() {
- text_thread_editor.update(cx, |text_thread_editor, cx| {
- text_thread_editor.quote_terminal_text(text, window, cx)
- });
- }
- });
- });
- }
-}
-
struct OnboardingUpsell;
impl Dismissable for OnboardingUpsell {
@@ -4957,13 +4090,8 @@ impl Dismissable for TrialEndUpsell {
/// Test-only helper methods
#[cfg(any(test, feature = "test-support"))]
impl AgentPanel {
- pub fn test_new(
- workspace: &Workspace,
- text_thread_store: Entity,
- window: &mut Window,
- cx: &mut Context,
- ) -> Self {
- Self::new(workspace, text_thread_store, None, window, cx)
+ pub fn test_new(workspace: &Workspace, window: &mut Window, cx: &mut Context) -> Self {
+ Self::new(workspace, None, window, cx)
}
/// Opens an external thread using an arbitrary AgentServer.
@@ -5063,12 +4191,12 @@ mod tests {
};
use acp_thread::{StubAgentConnection, ThreadStatus};
use agent_servers::CODEX_ID;
- use assistant_text_thread::TextThreadStore;
use feature_flags::FeatureFlagAppExt;
use fs::FakeFs;
use gpui::{TestAppContext, VisualTestContext};
use project::Project;
use serde_json::json;
+ use std::path::Path;
use std::time::Instant;
use workspace::MultiWorkspace;
@@ -5112,8 +4240,7 @@ mod tests {
// --- Set up workspace A: with an active thread ---
let panel_a = workspace_a.update_in(cx, |workspace, window, cx| {
- let text_thread_store = cx.new(|cx| TextThreadStore::fake(project_a.clone(), cx));
- cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx))
+ cx.new(|cx| AgentPanel::new(workspace, None, window, cx))
});
panel_a.update_in(cx, |panel, window, cx| {
@@ -5133,16 +4260,15 @@ mod tests {
);
});
- let agent_type_a = panel_a.read_with(cx, |panel, _cx| panel.selected_agent_type.clone());
+ let agent_type_a = panel_a.read_with(cx, |panel, _cx| panel.selected_agent.clone());
// --- Set up workspace B: ClaudeCode, no active thread ---
let panel_b = workspace_b.update_in(cx, |workspace, window, cx| {
- let text_thread_store = cx.new(|cx| TextThreadStore::fake(project_b.clone(), cx));
- cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx))
+ cx.new(|cx| AgentPanel::new(workspace, None, window, cx))
});
panel_b.update(cx, |panel, _cx| {
- panel.selected_agent_type = AgentType::Custom {
+ panel.selected_agent = Agent::Custom {
id: "claude-acp".into(),
};
});
@@ -5153,16 +4279,14 @@ mod tests {
cx.run_until_parked();
// --- Load fresh panels for each workspace and verify independent state ---
- let prompt_builder = Arc::new(prompt_store::PromptBuilder::new(None).unwrap());
-
let async_cx = cx.update(|window, cx| window.to_async(cx));
- let loaded_a = AgentPanel::load(workspace_a.downgrade(), prompt_builder.clone(), async_cx)
+ let loaded_a = AgentPanel::load(workspace_a.downgrade(), async_cx)
.await
.expect("panel A load should succeed");
cx.run_until_parked();
let async_cx = cx.update(|window, cx| window.to_async(cx));
- let loaded_b = AgentPanel::load(workspace_b.downgrade(), prompt_builder.clone(), async_cx)
+ let loaded_b = AgentPanel::load(workspace_b.downgrade(), async_cx)
.await
.expect("panel B load should succeed");
cx.run_until_parked();
@@ -5170,7 +4294,7 @@ mod tests {
// Workspace A should restore its thread and agent type
loaded_a.read_with(cx, |panel, _cx| {
assert_eq!(
- panel.selected_agent_type, agent_type_a,
+ panel.selected_agent, agent_type_a,
"workspace A agent type should be restored"
);
assert!(
@@ -5182,8 +4306,8 @@ mod tests {
// Workspace B should restore its own agent type, with no thread
loaded_b.read_with(cx, |panel, _cx| {
assert_eq!(
- panel.selected_agent_type,
- AgentType::Custom {
+ panel.selected_agent,
+ Agent::Custom {
id: "claude-acp".into()
},
"workspace B agent type should be restored"
@@ -5195,53 +4319,6 @@ mod tests {
});
}
- // Simple regression test
- #[gpui::test]
- async fn test_new_text_thread_action_handler(cx: &mut TestAppContext) {
- init_test(cx);
-
- let fs = FakeFs::new(cx.executor());
-
- cx.update(|cx| {
- cx.update_flags(true, vec!["agent-v2".to_string()]);
- agent::ThreadStore::init_global(cx);
- language_model::LanguageModelRegistry::test(cx);
- let slash_command_registry =
- assistant_slash_command::SlashCommandRegistry::default_global(cx);
- slash_command_registry
- .register_command(assistant_slash_commands::DefaultSlashCommand, false);
- ::set_global(fs.clone(), cx);
- });
-
- let project = Project::test(fs.clone(), [], cx).await;
-
- let multi_workspace =
- cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
-
- let workspace_a = multi_workspace
- .read_with(cx, |multi_workspace, _cx| {
- multi_workspace.workspace().clone()
- })
- .unwrap();
-
- let cx = &mut VisualTestContext::from_window(multi_workspace.into(), cx);
-
- workspace_a.update_in(cx, |workspace, window, cx| {
- let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
- let panel =
- cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx));
- workspace.add_panel(panel, window, cx);
- });
-
- cx.run_until_parked();
-
- workspace_a.update_in(cx, |_, window, cx| {
- window.dispatch_action(NewTextThread.boxed_clone(), cx);
- });
-
- cx.run_until_parked();
- }
-
/// Extracts the text from a Text content block, panicking if it's not Text.
fn expect_text_block(block: &acp::ContentBlock) -> &str {
match block {
@@ -5578,8 +4655,7 @@ mod tests {
let mut cx = VisualTestContext::from_window(multi_workspace.into(), cx);
let panel = workspace.update_in(&mut cx, |workspace, window, cx| {
- let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
- cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx))
+ cx.new(|cx| AgentPanel::new(workspace, None, window, cx))
});
(panel, cx)
@@ -5923,9 +4999,7 @@ mod tests {
cx.run_until_parked();
let panel = workspace.update_in(cx, |workspace, window, cx| {
- let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
- let panel =
- cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx));
+ let panel = cx.new(|cx| AgentPanel::new(workspace, None, window, cx));
workspace.add_panel(panel.clone(), window, cx);
panel
});
@@ -6033,9 +5107,7 @@ mod tests {
cx.run_until_parked();
let panel = workspace.update_in(cx, |workspace, window, cx| {
- let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
- let panel =
- cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx));
+ let panel = cx.new(|cx| AgentPanel::new(workspace, None, window, cx));
workspace.add_panel(panel.clone(), window, cx);
panel
});
@@ -6064,12 +5136,10 @@ mod tests {
cx.run_until_parked();
// Load a fresh panel from the serialized data.
- let prompt_builder = Arc::new(prompt_store::PromptBuilder::new(None).unwrap());
let async_cx = cx.update(|window, cx| window.to_async(cx));
- let loaded_panel =
- AgentPanel::load(workspace.downgrade(), prompt_builder.clone(), async_cx)
- .await
- .expect("panel load should succeed");
+ let loaded_panel = AgentPanel::load(workspace.downgrade(), async_cx)
+ .await
+ .expect("panel load should succeed");
cx.run_until_parked();
loaded_panel.read_with(cx, |panel, _cx| {
@@ -6118,9 +5188,7 @@ mod tests {
let cx = &mut VisualTestContext::from_window(multi_workspace.into(), cx);
let panel = workspace.update_in(cx, |workspace, window, cx| {
- let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
- let panel =
- cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx));
+ let panel = cx.new(|cx| AgentPanel::new(workspace, None, window, cx));
workspace.add_panel(panel.clone(), window, cx);
panel
});
@@ -6163,21 +5231,49 @@ mod tests {
}
#[test]
- fn test_deserialize_agent_type_variants() {
+ fn test_deserialize_agent_variants() {
+ // PascalCase (legacy AgentType format, persisted in panel state)
+ assert_eq!(
+ serde_json::from_str::(r#""NativeAgent""#).unwrap(),
+ Agent::NativeAgent,
+ );
+ assert_eq!(
+ serde_json::from_str::(r#"{"Custom":{"name":"my-agent"}}"#).unwrap(),
+ Agent::Custom {
+ id: "my-agent".into(),
+ },
+ );
+
+ // Legacy TextThread variant deserializes to NativeAgent
assert_eq!(
- serde_json::from_str::(r#""NativeAgent""#).unwrap(),
- AgentType::NativeAgent,
+ serde_json::from_str::(r#""TextThread""#).unwrap(),
+ Agent::NativeAgent,
);
+
+ // snake_case (canonical format)
assert_eq!(
- serde_json::from_str::(r#""TextThread""#).unwrap(),
- AgentType::TextThread,
+ serde_json::from_str::(r#""native_agent""#).unwrap(),
+ Agent::NativeAgent,
);
assert_eq!(
- serde_json::from_str::(r#"{"Custom":{"name":"my-agent"}}"#).unwrap(),
- AgentType::Custom {
+ serde_json::from_str::(r#"{"custom":{"name":"my-agent"}}"#).unwrap(),
+ Agent::Custom {
id: "my-agent".into(),
},
);
+
+ // Serialization uses snake_case
+ assert_eq!(
+ serde_json::to_string(&Agent::NativeAgent).unwrap(),
+ r#""native_agent""#,
+ );
+ assert_eq!(
+ serde_json::to_string(&Agent::Custom {
+ id: "my-agent".into()
+ })
+ .unwrap(),
+ r#"{"custom":{"name":"my-agent"}}"#,
+ );
}
#[gpui::test]
@@ -6229,12 +5325,7 @@ mod tests {
window: Option<&mut Window>,
cx: &mut Context| {
if let Some(window) = window {
- let project = workspace.project().clone();
- let text_thread_store =
- cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
- let panel = cx.new(|cx| {
- AgentPanel::new(workspace, text_thread_store, None, window, cx)
- });
+ let panel = cx.new(|cx| AgentPanel::new(workspace, None, window, cx));
workspace.add_panel(panel, window, cx);
}
},
@@ -6248,9 +5339,7 @@ mod tests {
cx.run_until_parked();
let panel = workspace.update_in(cx, |workspace, window, cx| {
- let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
- let panel =
- cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx));
+ let panel = cx.new(|cx| AgentPanel::new(workspace, None, window, cx));
workspace.add_panel(panel.clone(), window, cx);
panel
});
@@ -6270,9 +5359,9 @@ mod tests {
// Set the selected agent to Codex (a custom agent) and start_thread_in
// to NewWorktree. We do this AFTER opening the thread because
- // open_external_thread_with_server overrides selected_agent_type.
+ // open_external_thread_with_server overrides selected_agent.
panel.update_in(cx, |panel, window, cx| {
- panel.selected_agent_type = AgentType::Custom {
+ panel.selected_agent = Agent::Custom {
id: CODEX_ID.into(),
};
panel.set_start_thread_in(&StartThreadIn::NewWorktree, window, cx);
@@ -6281,8 +5370,8 @@ mod tests {
// Verify the panel has the Codex agent selected.
panel.read_with(cx, |panel, _cx| {
assert_eq!(
- panel.selected_agent_type,
- AgentType::Custom {
+ panel.selected_agent,
+ Agent::Custom {
id: CODEX_ID.into()
},
);
@@ -6321,13 +5410,13 @@ mod tests {
.panel::(cx)
.expect("new workspace should have an AgentPanel");
- new_panel.read(cx).selected_agent_type.clone()
+ new_panel.read(cx).selected_agent.clone()
})
.unwrap();
assert_eq!(
found_codex,
- AgentType::Custom {
+ Agent::Custom {
id: CODEX_ID.into()
},
"the new worktree workspace should use the same agent (Codex) that was selected in the original panel",
@@ -6359,8 +5448,7 @@ mod tests {
let mut cx = VisualTestContext::from_window(multi_workspace.into(), cx);
let panel = workspace.update_in(&mut cx, |workspace, window, cx| {
- let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
- cx.new(|cx| AgentPanel::new(workspace, text_thread_store, None, window, cx))
+ cx.new(|cx| AgentPanel::new(workspace, None, window, cx))
});
// Open thread A and send a message. With empty next_prompt_updates it
diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs
index 02050a0e11d45dbb0c7c5cfeea3a200409a266ad..98715056ccec43fb91cc4dc9307cf41d84719fc0 100644
--- a/crates/agent_ui/src/agent_ui.rs
+++ b/crates/agent_ui/src/agent_ui.rs
@@ -11,6 +11,7 @@ mod config_options;
mod context;
mod context_server_configuration;
pub(crate) mod conversation_view;
+mod diagnostics;
mod entry_view_state;
mod external_source_prompt;
mod favorite_models;
@@ -23,14 +24,10 @@ mod mode_selector;
mod model_selector;
mod model_selector_popover;
mod profile_selector;
-mod slash_command;
-mod slash_command_picker;
mod terminal_codegen;
mod terminal_inline_assistant;
#[cfg(any(test, feature = "test-support"))]
pub mod test_support;
-mod text_thread_editor;
-mod text_thread_history;
mod thread_history;
mod thread_history_view;
mod thread_import;
@@ -41,10 +38,9 @@ mod ui;
use std::rc::Rc;
use std::sync::Arc;
+use ::ui::IconName;
use agent_client_protocol as acp;
use agent_settings::{AgentProfileId, AgentSettings};
-use assistant_slash_command::SlashCommandRegistry;
-use client::Client;
use command_palette_hooks::CommandPaletteFilter;
use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt as _};
use fs::Fs;
@@ -65,9 +61,7 @@ use std::any::TypeId;
use workspace::Workspace;
use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
-pub use crate::agent_panel::{
- AgentPanel, AgentPanelEvent, ConcreteAssistantPanelDelegate, WorktreeCreationStatus,
-};
+pub use crate::agent_panel::{AgentPanel, AgentPanelEvent, WorktreeCreationStatus};
use crate::agent_registry_ui::AgentRegistryPage;
pub use crate::inline_assistant::InlineAssistant;
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
@@ -76,7 +70,6 @@ pub use external_source_prompt::ExternalSourcePrompt;
pub(crate) use mode_selector::ModeSelector;
pub(crate) use model_selector::ModelSelector;
pub(crate) use model_selector_popover::ModelSelectorPopover;
-pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
pub(crate) use thread_history::ThreadHistory;
pub(crate) use thread_history_view::*;
pub use thread_import::{AcpThreadImportOnboarding, ThreadImportModal};
@@ -88,8 +81,6 @@ const PARALLEL_AGENT_LAYOUT_BACKFILL_KEY: &str = "parallel_agent_layout_backfill
actions!(
agent,
[
- /// Creates a new text-based conversation thread.
- NewTextThread,
/// Toggles the menu to create new agent threads.
ToggleNewThreadMenu,
/// Cycles through the options for where new threads start (current project or new worktree).
@@ -244,11 +235,13 @@ pub struct NewNativeAgentThreadFromSummary {
from_session_id: agent_client_protocol::SessionId,
}
-// TODO unify this with AgentType
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
+#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Agent {
+ #[default]
+ #[serde(alias = "NativeAgent", alias = "TextThread")]
NativeAgent,
+ #[serde(alias = "Custom")]
Custom {
#[serde(rename = "name")]
id: AgentId,
@@ -273,6 +266,24 @@ impl Agent {
}
}
+ pub fn is_native(&self) -> bool {
+ matches!(self, Self::NativeAgent)
+ }
+
+ pub fn label(&self) -> SharedString {
+ match self {
+ Self::NativeAgent => "Zed Agent".into(),
+ Self::Custom { id, .. } => id.0.clone(),
+ }
+ }
+
+ pub fn icon(&self) -> Option {
+ match self {
+ Self::NativeAgent => None,
+ Self::Custom { .. } => Some(IconName::Sparkle),
+ }
+ }
+
pub fn server(
&self,
fs: Arc,
@@ -350,10 +361,39 @@ impl ModelUsageContext {
}
}
+pub(crate) fn humanize_token_count(count: u64) -> String {
+ match count {
+ 0..=999 => count.to_string(),
+ 1000..=9999 => {
+ let thousands = count / 1000;
+ let hundreds = (count % 1000 + 50) / 100;
+ if hundreds == 0 {
+ format!("{}k", thousands)
+ } else if hundreds == 10 {
+ format!("{}k", thousands + 1)
+ } else {
+ format!("{}.{}k", thousands, hundreds)
+ }
+ }
+ 10_000..=999_999 => format!("{}k", (count + 500) / 1000),
+ 1_000_000..=9_999_999 => {
+ let millions = count / 1_000_000;
+ let hundred_thousands = (count % 1_000_000 + 50_000) / 100_000;
+ if hundred_thousands == 0 {
+ format!("{}M", millions)
+ } else if hundred_thousands == 10 {
+ format!("{}M", millions + 1)
+ } else {
+ format!("{}.{}M", millions, hundred_thousands)
+ }
+ }
+ 10_000_000.. => format!("{}M", (count + 500_000) / 1_000_000),
+ }
+}
+
/// Initializes the `agent` crate.
pub fn init(
fs: Arc,
- client: Arc,
prompt_builder: Arc,
language_registry: Arc,
is_new_install: bool,
@@ -361,20 +401,16 @@ pub fn init(
cx: &mut App,
) {
agent::ThreadStore::init_global(cx);
- assistant_text_thread::init(client, 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
// we're not running inside of the eval.
init_language_model_settings(cx);
}
- assistant_slash_command::init(cx);
agent_panel::init(cx);
context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
- TextThreadEditor::init(cx);
thread_metadata_store::init(cx);
- register_slash_commands(cx);
inline_assistant::init(fs.clone(), prompt_builder.clone(), cx);
terminal_inline_assistant::init(fs.clone(), prompt_builder, cx);
cx.observe_new(move |workspace, window, cx| {
@@ -628,34 +664,6 @@ fn update_active_language_model_from_settings(cx: &mut App) {
});
}
-fn register_slash_commands(cx: &mut App) {
- let slash_command_registry = SlashCommandRegistry::global(cx);
-
- slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
- slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
- slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
- slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
- slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
- slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
- slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
- slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
- slash_command_registry
- .register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
- slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
-
- cx.observe_flag::({
- move |is_enabled, _cx| {
- if is_enabled {
- slash_command_registry.register_command(
- assistant_slash_commands::StreamingExampleSlashCommand,
- false,
- );
- }
- }
- })
- .detach();
-}
-
#[cfg(test)]
mod tests {
use super::*;
@@ -666,9 +674,7 @@ mod tests {
use feature_flags::FeatureFlagAppExt;
use gpui::{BorrowAppContext, TestAppContext, px};
use project::DisableAiSettings;
- use settings::{
- DefaultAgentView, DockPosition, NotifyWhenAgentWaiting, Settings, SettingsStore,
- };
+ use settings::{DockPosition, NotifyWhenAgentWaiting, Settings, SettingsStore};
#[gpui::test]
fn test_agent_command_palette_visibility(cx: &mut TestAppContext) {
@@ -697,7 +703,6 @@ mod tests {
inline_alternatives: vec![],
favorite_models: vec![],
default_profile: AgentProfileId::default(),
- default_view: DefaultAgentView::Thread,
profiles: Default::default(),
notify_when_agent_waiting: NotifyWhenAgentWaiting::default(),
play_sound_when_agent_done: false,
@@ -731,10 +736,6 @@ mod tests {
!filter.is_hidden(&NewThread),
"NewThread should be visible by default"
);
- assert!(
- !filter.is_hidden(&text_thread_editor::CopyCode),
- "CopyCode should be visible when agent is enabled"
- );
});
// Disable agent
@@ -754,10 +755,6 @@ mod tests {
filter.is_hidden(&NewThread),
"NewThread should be hidden when agent is disabled"
);
- assert!(
- filter.is_hidden(&text_thread_editor::CopyCode),
- "CopyCode should be hidden when agent is disabled"
- );
});
// Test EditPredictionProvider
@@ -903,11 +900,11 @@ mod tests {
#[test]
fn test_deserialize_external_agent_variants() {
assert_eq!(
- serde_json::from_str::(r#""native_agent""#).unwrap(),
+ serde_json::from_str::(r#""NativeAgent""#).unwrap(),
Agent::NativeAgent,
);
assert_eq!(
- serde_json::from_str::(r#"{"custom":{"name":"my-agent"}}"#).unwrap(),
+ serde_json::from_str::(r#"{"Custom":{"name":"my-agent"}}"#).unwrap(),
Agent::Custom {
id: "my-agent".into(),
},
diff --git a/crates/agent_ui/src/conversation_view.rs b/crates/agent_ui/src/conversation_view.rs
index bdc30d49a85a4a76f18f4f9c65b2e50ebbc13d85..c13b6d29f92c273b36b948c90f5f5c6f1b659970 100644
--- a/crates/agent_ui/src/conversation_view.rs
+++ b/crates/agent_ui/src/conversation_view.rs
@@ -2524,22 +2524,6 @@ impl ConversationView {
}
}
- /// Inserts terminal text as a crease into the message editor.
- pub(crate) fn insert_terminal_text(
- &self,
- text: String,
- window: &mut Window,
- cx: &mut Context,
- ) {
- if let Some(active_thread) = self.active_thread() {
- active_thread.update(cx, |thread, cx| {
- thread.message_editor.update(cx, |editor, cx| {
- editor.insert_terminal_crease(text, window, cx);
- })
- });
- }
- }
-
fn current_model_name(&self, cx: &App) -> SharedString {
// For native agent (Zed Agent), use the specific model name (e.g., "Claude 3.5 Sonnet")
// For ACP agents, use the agent name (e.g., "Claude Agent", "Gemini CLI")
@@ -2751,7 +2735,6 @@ pub(crate) mod tests {
use action_log::ActionLog;
use agent::{AgentTool, EditFileTool, FetchTool, TerminalTool, ToolPermissionContext};
use agent_client_protocol::SessionId;
- use assistant_text_thread::TextThreadStore;
use editor::MultiBufferOffset;
use fs::FakeFs;
use gpui::{EventEmitter, TestAppContext, VisualTestContext};
@@ -3309,10 +3292,7 @@ pub(crate) mod tests {
let cx = &mut VisualTestContext::from_window(multi_workspace_handle.into(), cx);
workspace1.update_in(cx, |workspace, window, cx| {
- let text_thread_store =
- cx.new(|cx| TextThreadStore::fake(workspace.project().clone(), cx));
- let panel =
- cx.new(|cx| crate::AgentPanel::new(workspace, text_thread_store, None, window, cx));
+ let panel = cx.new(|cx| crate::AgentPanel::new(workspace, None, window, cx));
workspace.add_panel(panel, window, cx);
// Open the dock and activate the agent panel so it's visible
diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs
index afd508e47366d3217df6e5f1eb4cd0128c479895..63aa8b8529655a26b99ba74062f8d0a6a4812c5f 100644
--- a/crates/agent_ui/src/conversation_view/thread_view.rs
+++ b/crates/agent_ui/src/conversation_view/thread_view.rs
@@ -3419,12 +3419,10 @@ impl ThreadView {
}
};
- let used = crate::text_thread_editor::humanize_token_count(usage.used_tokens);
- let max = crate::text_thread_editor::humanize_token_count(usage.max_tokens);
- let input_tokens_label =
- crate::text_thread_editor::humanize_token_count(usage.input_tokens);
- let output_tokens_label =
- crate::text_thread_editor::humanize_token_count(usage.output_tokens);
+ let used = crate::humanize_token_count(usage.used_tokens);
+ let max = crate::humanize_token_count(usage.max_tokens);
+ let input_tokens_label = crate::humanize_token_count(usage.input_tokens);
+ let output_tokens_label = crate::humanize_token_count(usage.output_tokens);
let progress_ratio = if usage.max_tokens > 0 {
usage.used_tokens as f32 / usage.max_tokens as f32
@@ -3468,10 +3466,9 @@ impl ThreadView {
.and_then(|thread| thread.read(cx).model())
.and_then(|model| model.max_output_tokens())
.unwrap_or(0);
- let input_max_label = crate::text_thread_editor::humanize_token_count(
- usage.max_tokens.saturating_sub(max_output_tokens),
- );
- let output_max_label = crate::text_thread_editor::humanize_token_count(max_output_tokens);
+ let input_max_label =
+ crate::humanize_token_count(usage.max_tokens.saturating_sub(max_output_tokens));
+ let output_max_label = crate::humanize_token_count(max_output_tokens);
let build_tooltip = {
move |_window: &mut Window, cx: &mut App| {
@@ -4808,12 +4805,9 @@ impl ThreadView {
.last_turn_tokens
.filter(|&tokens| tokens > TOKEN_THRESHOLD)
.map(|tokens| {
- Label::new(format!(
- "{} tokens",
- crate::text_thread_editor::humanize_token_count(tokens)
- ))
- .size(LabelSize::Small)
- .color(Color::Muted)
+ Label::new(format!("{} tokens", crate::humanize_token_count(tokens)))
+ .size(LabelSize::Small)
+ .color(Color::Muted)
})
})
.flatten();
@@ -5096,7 +5090,7 @@ impl ThreadView {
self.turn_fields
.turn_tokens
.filter(|&tokens| tokens > TOKEN_THRESHOLD)
- .map(|tokens| crate::text_thread_editor::humanize_token_count(tokens))
+ .map(|tokens| crate::humanize_token_count(tokens))
})
.flatten();
@@ -8775,15 +8769,6 @@ pub(crate) fn open_link(
});
}
}
- MentionUri::TextThread { path, .. } => {
- if let Some(panel) = workspace.panel::(cx) {
- panel.update(cx, |panel, cx| {
- panel
- .open_saved_text_thread(path.as_path().into(), window, cx)
- .detach_and_log_err(cx);
- });
- }
- }
MentionUri::Rule { id, .. } => {
let PromptId::User { uuid } = id else {
return;
diff --git a/crates/agent_ui/src/diagnostics.rs b/crates/agent_ui/src/diagnostics.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5a2423cae65aad960e73a0f50b11d7eb87323c90
--- /dev/null
+++ b/crates/agent_ui/src/diagnostics.rs
@@ -0,0 +1,252 @@
+use anyhow::Result;
+use gpui::{App, AppContext as _, Entity, Task};
+use language::{Anchor, BufferSnapshot, DiagnosticEntryRef, DiagnosticSeverity, ToOffset};
+use project::{DiagnosticSummary, Project};
+use rope::Point;
+use std::{fmt::Write, ops::RangeInclusive, path::Path};
+use text::OffsetRangeExt;
+use util::ResultExt;
+use util::paths::PathMatcher;
+
+pub fn codeblock_fence_for_path(
+ path: Option<&str>,
+ row_range: Option>,
+) -> String {
+ let mut text = String::new();
+ write!(text, "```").unwrap();
+
+ if let Some(path) = path {
+ if let Some(extension) = Path::new(path).extension().and_then(|ext| ext.to_str()) {
+ write!(text, "{} ", extension).unwrap();
+ }
+
+ write!(text, "{path}").unwrap();
+ } else {
+ write!(text, "untitled").unwrap();
+ }
+
+ if let Some(row_range) = row_range {
+ write!(text, ":{}-{}", row_range.start() + 1, row_range.end() + 1).unwrap();
+ }
+
+ text.push('\n');
+ text
+}
+
+pub struct DiagnosticsOptions {
+ pub include_errors: bool,
+ pub include_warnings: bool,
+ pub path_matcher: Option,
+}
+
+/// Collects project diagnostics into a formatted string.
+///
+/// Returns `None` if no matching diagnostics were found.
+pub fn collect_diagnostics(
+ project: Entity,
+ options: DiagnosticsOptions,
+ cx: &mut App,
+) -> Task>> {
+ let path_style = project.read(cx).path_style(cx);
+ let glob_is_exact_file_match = if let Some(path) = options
+ .path_matcher
+ .as_ref()
+ .and_then(|pm| pm.sources().next())
+ {
+ project
+ .read(cx)
+ .find_project_path(Path::new(path), cx)
+ .is_some()
+ } else {
+ false
+ };
+
+ let project_handle = project.downgrade();
+ let diagnostic_summaries: Vec<_> = project
+ .read(cx)
+ .diagnostic_summaries(false, cx)
+ .flat_map(|(path, _, summary)| {
+ let worktree = project.read(cx).worktree_for_id(path.worktree_id, cx)?;
+ let full_path = worktree.read(cx).root_name().join(&path.path);
+ Some((path, full_path, summary))
+ })
+ .collect();
+
+ cx.spawn(async move |cx| {
+ let error_source = if let Some(path_matcher) = &options.path_matcher {
+ debug_assert_eq!(path_matcher.sources().count(), 1);
+ Some(path_matcher.sources().next().unwrap_or_default())
+ } else {
+ None
+ };
+
+ let mut text = String::new();
+ if let Some(error_source) = error_source.as_ref() {
+ writeln!(text, "diagnostics: {}", error_source).unwrap();
+ } else {
+ writeln!(text, "diagnostics").unwrap();
+ }
+
+ let mut found_any_diagnostics = false;
+ let mut project_summary = DiagnosticSummary::default();
+ for (project_path, path, summary) in diagnostic_summaries {
+ if let Some(path_matcher) = &options.path_matcher
+ && !path_matcher.is_match(&path)
+ {
+ continue;
+ }
+
+ let has_errors = options.include_errors && summary.error_count > 0;
+ let has_warnings = options.include_warnings && summary.warning_count > 0;
+ if !has_errors && !has_warnings {
+ continue;
+ }
+
+ if options.include_errors {
+ project_summary.error_count += summary.error_count;
+ }
+ if options.include_warnings {
+ project_summary.warning_count += summary.warning_count;
+ }
+
+ let file_path = path.display(path_style).to_string();
+ if !glob_is_exact_file_match {
+ writeln!(&mut text, "{file_path}").unwrap();
+ }
+
+ if let Some(buffer) = project_handle
+ .update(cx, |project, cx| project.open_buffer(project_path, cx))?
+ .await
+ .log_err()
+ {
+ let snapshot = cx.read_entity(&buffer, |buffer, _| buffer.snapshot());
+ if collect_buffer_diagnostics(
+ &mut text,
+ &snapshot,
+ options.include_warnings,
+ options.include_errors,
+ ) {
+ found_any_diagnostics = true;
+ }
+ }
+ }
+
+ if !found_any_diagnostics {
+ return Ok(None);
+ }
+
+ let mut label = String::new();
+ label.push_str("Diagnostics");
+ if let Some(source) = error_source {
+ write!(label, " ({})", source).unwrap();
+ }
+
+ if project_summary.error_count > 0 || project_summary.warning_count > 0 {
+ label.push(':');
+
+ if project_summary.error_count > 0 {
+ write!(label, " {} errors", project_summary.error_count).unwrap();
+ if project_summary.warning_count > 0 {
+ label.push(',');
+ }
+ }
+
+ if project_summary.warning_count > 0 {
+ write!(label, " {} warnings", project_summary.warning_count).unwrap();
+ }
+ }
+
+ // Prepend the summary label to the output.
+ text.insert_str(0, &format!("{label}\n"));
+
+ Ok(Some(text))
+ })
+}
+
+/// Collects diagnostics from a buffer snapshot into the text output.
+///
+/// Returns `true` if any diagnostics were written.
+fn collect_buffer_diagnostics(
+ text: &mut String,
+ snapshot: &BufferSnapshot,
+ include_warnings: bool,
+ include_errors: bool,
+) -> bool {
+ let mut found_any = false;
+ for (_, group) in snapshot.diagnostic_groups(None) {
+ let entry = &group.entries[group.primary_ix];
+ if collect_diagnostic(text, entry, snapshot, include_warnings, include_errors) {
+ found_any = true;
+ }
+ }
+ found_any
+}
+
+/// Formats a single diagnostic entry as a code excerpt with the diagnostic message.
+///
+/// Returns `true` if the diagnostic was written (i.e. it matched severity filters).
+fn collect_diagnostic(
+ text: &mut String,
+ entry: &DiagnosticEntryRef<'_, Anchor>,
+ snapshot: &BufferSnapshot,
+ include_warnings: bool,
+ include_errors: bool,
+) -> bool {
+ const EXCERPT_EXPANSION_SIZE: u32 = 2;
+ const MAX_MESSAGE_LENGTH: usize = 2000;
+
+ let ty = match entry.diagnostic.severity {
+ DiagnosticSeverity::WARNING => {
+ if !include_warnings {
+ return false;
+ }
+ "warning"
+ }
+ DiagnosticSeverity::ERROR => {
+ if !include_errors {
+ return false;
+ }
+ "error"
+ }
+ _ => return false,
+ };
+
+ let range = entry.range.to_point(snapshot);
+ let diagnostic_row_number = range.start.row + 1;
+
+ let start_row = range.start.row.saturating_sub(EXCERPT_EXPANSION_SIZE);
+ let end_row = (range.end.row + EXCERPT_EXPANSION_SIZE).min(snapshot.max_point().row) + 1;
+ let excerpt_range =
+ Point::new(start_row, 0).to_offset(snapshot)..Point::new(end_row, 0).to_offset(snapshot);
+
+ text.push_str("```");
+ if let Some(language_name) = snapshot.language().map(|l| l.code_fence_block_name()) {
+ text.push_str(&language_name);
+ }
+ text.push('\n');
+
+ let mut buffer_text = String::new();
+ for chunk in snapshot.text_for_range(excerpt_range) {
+ buffer_text.push_str(chunk);
+ }
+
+ for (i, line) in buffer_text.lines().enumerate() {
+ let line_number = start_row + i as u32 + 1;
+ writeln!(text, "{}", line).unwrap();
+
+ if line_number == diagnostic_row_number {
+ text.push_str("//");
+ let marker_start = text.len();
+ write!(text, " {}: ", ty).unwrap();
+ let padding = text.len() - marker_start;
+
+ let message = util::truncate(&entry.diagnostic.message, MAX_MESSAGE_LENGTH)
+ .replace('\n', format!("\n//{:padding$}", "").as_str());
+
+ writeln!(text, "{message}").unwrap();
+ }
+ }
+
+ writeln!(text, "```").unwrap();
+ true
+}
diff --git a/crates/agent_ui/src/inline_assistant.rs b/crates/agent_ui/src/inline_assistant.rs
index 9a2b95519b5977cb9937d2a37c8ef8c133e57976..3b98e496d4732deaf54be9b4e14da380285f467f 100644
--- a/crates/agent_ui/src/inline_assistant.rs
+++ b/crates/agent_ui/src/inline_assistant.rs
@@ -257,12 +257,8 @@ impl InlineAssistant {
return;
}
- let Some(inline_assist_target) = Self::resolve_inline_assist_target(
- workspace,
- workspace.panel::(cx),
- window,
- cx,
- ) else {
+ let Some(inline_assist_target) = Self::resolve_inline_assist_target(workspace, window, cx)
+ else {
return;
};
@@ -1570,7 +1566,6 @@ impl InlineAssistant {
fn resolve_inline_assist_target(
workspace: &mut Workspace,
- agent_panel: Option>,
window: &mut Window,
cx: &mut App,
) -> Option {
@@ -1588,20 +1583,7 @@ impl InlineAssistant {
return Some(InlineAssistTarget::Terminal(terminal_view));
}
- let text_thread_editor = agent_panel
- .and_then(|panel| panel.read(cx).active_text_thread_editor())
- .and_then(|editor| {
- let editor = &editor.read(cx).editor().clone();
- if editor.read(cx).is_focused(window) {
- Some(editor.clone())
- } else {
- None
- }
- });
-
- if let Some(text_thread_editor) = text_thread_editor {
- Some(InlineAssistTarget::Editor(text_thread_editor))
- } else if let Some(workspace_editor) = workspace
+ if let Some(workspace_editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::(cx))
{
diff --git a/crates/agent_ui/src/mention_set.rs b/crates/agent_ui/src/mention_set.rs
index b8e16de99f13d9eb6925e5618ccca81c742f8d12..2559edc566d4467eaaab180e0a16f4af5fae7ab9 100644
--- a/crates/agent_ui/src/mention_set.rs
+++ b/crates/agent_ui/src/mention_set.rs
@@ -1,9 +1,9 @@
+use crate::diagnostics::{DiagnosticsOptions, codeblock_fence_for_path, collect_diagnostics};
use acp_thread::{MentionUri, selection_name};
use agent::{ThreadStore, outline};
use agent_client_protocol as acp;
use agent_servers::{AgentServer, AgentServerDelegate};
use anyhow::{Context as _, Result, anyhow};
-use assistant_slash_commands::{codeblock_fence_for_path, collect_diagnostics_output};
use collections::{HashMap, HashSet};
use editor::{
Anchor, Editor, EditorSnapshot, ExcerptId, FoldPlaceholder, ToOffset,
@@ -131,9 +131,6 @@ impl MentionSet {
MentionUri::Fetch { url } => self.confirm_mention_for_fetch(url, http_client, cx),
MentionUri::Directory { .. } => Task::ready(Ok(Mention::Link)),
MentionUri::Thread { id, .. } => self.confirm_mention_for_thread(id, cx),
- MentionUri::TextThread { .. } => {
- Task::ready(Err(anyhow!("Text thread mentions are no longer supported")))
- }
MentionUri::File { abs_path } => {
self.confirm_mention_for_file(abs_path, supports_images, cx)
}
@@ -276,9 +273,6 @@ impl MentionSet {
}
MentionUri::Directory { .. } => Task::ready(Ok(Mention::Link)),
MentionUri::Thread { id, .. } => self.confirm_mention_for_thread(id, cx),
- MentionUri::TextThread { .. } => {
- Task::ready(Err(anyhow!("Text thread mentions are no longer supported")))
- }
MentionUri::File { abs_path } => {
self.confirm_mention_for_file(abs_path, supports_images, cx)
}
@@ -589,9 +583,9 @@ impl MentionSet {
return Task::ready(Err(anyhow!("project not found")));
};
- let diagnostics_task = collect_diagnostics_output(
+ let diagnostics_task = collect_diagnostics(
project,
- assistant_slash_commands::Options {
+ DiagnosticsOptions {
include_errors,
include_warnings,
path_matcher: None,
@@ -599,9 +593,8 @@ impl MentionSet {
cx,
);
cx.spawn(async move |_, _| {
- let output = diagnostics_task.await?;
- let content = output
- .map(|output| output.text)
+ let content = diagnostics_task
+ .await?
.unwrap_or_else(|| "No diagnostics found.".into());
Ok(Mention::Text {
content,
diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs
index 44a816f894f791f8b9f3b4753deef7028fae20ab..df36f38899c9abea165d0ff5a01834a2bb84c82f 100644
--- a/crates/agent_ui/src/message_editor.rs
+++ b/crates/agent_ui/src/message_editor.rs
@@ -1308,62 +1308,6 @@ impl MessageEditor {
}
}
- pub fn insert_terminal_crease(
- &mut self,
- text: String,
- window: &mut Window,
- cx: &mut Context,
- ) {
- let line_count = text.lines().count() as u32;
- let mention_uri = MentionUri::TerminalSelection { line_count };
- let mention_text = mention_uri.as_link().to_string();
-
- let (excerpt_id, text_anchor, content_len) = self.editor.update(cx, |editor, cx| {
- let buffer = editor.buffer().read(cx);
- let snapshot = buffer.snapshot(cx);
- let (excerpt_id, _, buffer_snapshot) = snapshot.as_singleton().unwrap();
- let text_anchor = editor
- .selections
- .newest_anchor()
- .start
- .text_anchor
- .bias_left(&buffer_snapshot);
-
- editor.insert(&mention_text, window, cx);
- editor.insert(" ", window, cx);
-
- (excerpt_id, text_anchor, mention_text.len())
- });
-
- let Some((crease_id, tx)) = insert_crease_for_mention(
- excerpt_id,
- text_anchor,
- content_len,
- mention_uri.name().into(),
- mention_uri.icon_path(cx),
- mention_uri.tooltip_text(),
- Some(mention_uri.clone()),
- Some(self.workspace.clone()),
- None,
- self.editor.clone(),
- window,
- cx,
- ) else {
- return;
- };
- drop(tx);
-
- let mention_task = Task::ready(Ok(Mention::Text {
- content: text,
- tracked_buffers: vec![],
- }))
- .shared();
-
- self.mention_set.update(cx, |mention_set, _| {
- mention_set.insert_mention(crease_id, mention_uri, mention_task);
- });
- }
-
pub fn insert_branch_diff_crease(&mut self, window: &mut Window, cx: &mut Context) {
let Some(workspace) = self.workspace.upgrade() else {
return;
diff --git a/crates/agent_ui/src/slash_command.rs b/crates/agent_ui/src/slash_command.rs
deleted file mode 100644
index e328ef6725e5e789bd402667da91417ad69a372d..0000000000000000000000000000000000000000
--- a/crates/agent_ui/src/slash_command.rs
+++ /dev/null
@@ -1,360 +0,0 @@
-use crate::text_thread_editor::TextThreadEditor;
-use anyhow::Result;
-pub use assistant_slash_command::SlashCommand;
-use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet};
-use editor::{CompletionProvider, Editor, ExcerptId};
-use fuzzy::{StringMatchCandidate, match_strings};
-use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity, Window};
-use language::{Anchor, Buffer, ToPoint};
-use parking_lot::Mutex;
-use project::{
- CompletionDisplayOptions, CompletionIntent, CompletionSource,
- lsp_store::CompletionDocumentation,
-};
-use rope::Point;
-use std::{
- ops::Range,
- sync::{
- Arc,
- atomic::{AtomicBool, Ordering::SeqCst},
- },
-};
-use workspace::Workspace;
-
-pub struct SlashCommandCompletionProvider {
- cancel_flag: Mutex>,
- slash_commands: Arc,
- editor: Option>,
- workspace: Option>,
-}
-
-impl SlashCommandCompletionProvider {
- pub fn new(
- slash_commands: Arc,
- editor: Option>,
- workspace: Option>,
- ) -> Self {
- Self {
- cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
- slash_commands,
- editor,
- workspace,
- }
- }
-
- fn complete_command_name(
- &self,
- command_name: &str,
- command_range: Range,
- name_range: Range,
- window: &mut Window,
- cx: &mut App,
- ) -> Task>> {
- let slash_commands = self.slash_commands.clone();
- let candidates = slash_commands
- .command_names(cx)
- .into_iter()
- .enumerate()
- .map(|(ix, def)| StringMatchCandidate::new(ix, &def))
- .collect::>();
- let command_name = command_name.to_string();
- let editor = self.editor.clone();
- let workspace = self.workspace.clone();
- window.spawn(cx, async move |cx| {
- let matches = match_strings(
- &candidates,
- &command_name,
- true,
- true,
- usize::MAX,
- &Default::default(),
- cx.background_executor().clone(),
- )
- .await;
-
- cx.update(|_, cx| {
- let completions = matches
- .into_iter()
- .filter_map(|mat| {
- let command = slash_commands.command(&mat.string, cx)?;
- let mut new_text = mat.string.clone();
- let requires_argument = command.requires_argument();
- let accepts_arguments = command.accepts_arguments();
- if requires_argument || accepts_arguments {
- new_text.push(' ');
- }
-
- let confirm =
- editor
- .clone()
- .zip(workspace.clone())
- .map(|(editor, workspace)| {
- let command_name = mat.string.clone();
- let command_range = command_range.clone();
- Arc::new(
- move |intent: CompletionIntent,
- window: &mut Window,
- cx: &mut App| {
- if !requires_argument
- && (!accepts_arguments || intent.is_complete())
- {
- editor
- .update(cx, |editor, cx| {
- editor.run_command(
- command_range.clone(),
- &command_name,
- &[],
- true,
- workspace.clone(),
- window,
- cx,
- );
- })
- .ok();
- false
- } else {
- requires_argument || accepts_arguments
- }
- },
- ) as Arc<_>
- });
-
- Some(project::Completion {
- replace_range: name_range.clone(),
- documentation: Some(CompletionDocumentation::SingleLine(
- command.description().into(),
- )),
- new_text,
- label: command.label(cx),
- icon_path: None,
- match_start: None,
- snippet_deduplication_key: None,
- insert_text_mode: None,
- confirm,
- source: CompletionSource::Custom,
- })
- })
- .collect();
-
- vec![project::CompletionResponse {
- completions,
- display_options: CompletionDisplayOptions::default(),
- is_incomplete: false,
- }]
- })
- })
- }
-
- fn complete_command_argument(
- &self,
- command_name: &str,
- arguments: &[String],
- command_range: Range,
- argument_range: Range,
- last_argument_range: Range,
- window: &mut Window,
- cx: &mut App,
- ) -> Task>> {
- let new_cancel_flag = Arc::new(AtomicBool::new(false));
- let mut flag = self.cancel_flag.lock();
- flag.store(true, SeqCst);
- *flag = new_cancel_flag.clone();
- if let Some(command) = self.slash_commands.command(command_name, cx) {
- let completions = command.complete_argument(
- arguments,
- new_cancel_flag,
- self.workspace.clone(),
- window,
- cx,
- );
- let command_name: Arc = command_name.into();
- let editor = self.editor.clone();
- let workspace = self.workspace.clone();
- let arguments = arguments.to_vec();
- cx.background_spawn(async move {
- let completions = completions
- .await?
- .into_iter()
- .map(|new_argument| {
- let confirm =
- editor
- .clone()
- .zip(workspace.clone())
- .map(|(editor, workspace)| {
- Arc::new({
- let mut completed_arguments = arguments.clone();
- if new_argument.replace_previous_arguments {
- completed_arguments.clear();
- } else {
- completed_arguments.pop();
- }
- completed_arguments.push(new_argument.new_text.clone());
-
- let command_range = command_range.clone();
- let command_name = command_name.clone();
- move |intent: CompletionIntent,
- window: &mut Window,
- cx: &mut App| {
- if new_argument.after_completion.run()
- || intent.is_complete()
- {
- editor
- .update(cx, |editor, cx| {
- editor.run_command(
- command_range.clone(),
- &command_name,
- &completed_arguments,
- true,
- workspace.clone(),
- window,
- cx,
- );
- })
- .ok();
- false
- } else {
- !new_argument.after_completion.run()
- }
- }
- }) as Arc<_>
- });
-
- let mut new_text = new_argument.new_text.clone();
- if new_argument.after_completion == AfterCompletion::Continue {
- new_text.push(' ');
- }
-
- project::Completion {
- replace_range: if new_argument.replace_previous_arguments {
- argument_range.clone()
- } else {
- last_argument_range.clone()
- },
- label: new_argument.label,
- icon_path: None,
- new_text,
- documentation: None,
- match_start: None,
- snippet_deduplication_key: None,
- confirm,
- insert_text_mode: None,
- source: CompletionSource::Custom,
- }
- })
- .collect();
-
- Ok(vec![project::CompletionResponse {
- completions,
- display_options: CompletionDisplayOptions::default(),
- // TODO: Could have slash commands indicate whether their completions are incomplete.
- is_incomplete: true,
- }])
- })
- } else {
- Task::ready(Ok(vec![project::CompletionResponse {
- completions: Vec::new(),
- display_options: CompletionDisplayOptions::default(),
- is_incomplete: true,
- }]))
- }
- }
-}
-
-impl CompletionProvider for SlashCommandCompletionProvider {
- fn completions(
- &self,
- _excerpt_id: ExcerptId,
- buffer: &Entity,
- buffer_position: Anchor,
- _: editor::CompletionContext,
- window: &mut Window,
- cx: &mut Context,
- ) -> Task>> {
- let Some((name, arguments, command_range, last_argument_range)) =
- buffer.update(cx, |buffer, _cx| {
- let position = buffer_position.to_point(buffer);
- let line_start = Point::new(position.row, 0);
- let mut lines = buffer.text_for_range(line_start..position).lines();
- let line = lines.next()?;
- let call = SlashCommandLine::parse(line)?;
-
- let command_range_start = Point::new(position.row, call.name.start as u32 - 1);
- let command_range_end = Point::new(
- position.row,
- call.arguments.last().map_or(call.name.end, |arg| arg.end) as u32,
- );
- let command_range = buffer.anchor_before(command_range_start)
- ..buffer.anchor_after(command_range_end);
-
- let name = line[call.name.clone()].to_string();
- let (arguments, last_argument_range) = if let Some(argument) = call.arguments.last()
- {
- let last_arg_start =
- buffer.anchor_before(Point::new(position.row, argument.start as u32));
- let first_arg_start = call.arguments.first().expect("we have the last element");
- let first_arg_start = buffer
- .anchor_before(Point::new(position.row, first_arg_start.start as u32));
- let arguments = call
- .arguments
- .into_iter()
- .filter_map(|argument| Some(line.get(argument)?.to_string()))
- .collect::>();
- let argument_range = first_arg_start..buffer_position;
- (
- Some((arguments, argument_range)),
- last_arg_start..buffer_position,
- )
- } else {
- let start =
- buffer.anchor_before(Point::new(position.row, call.name.start as u32));
- (None, start..buffer_position)
- };
-
- Some((name, arguments, command_range, last_argument_range))
- })
- else {
- return Task::ready(Ok(vec![project::CompletionResponse {
- completions: Vec::new(),
- display_options: CompletionDisplayOptions::default(),
- is_incomplete: false,
- }]));
- };
-
- if let Some((arguments, argument_range)) = arguments {
- self.complete_command_argument(
- &name,
- &arguments,
- command_range,
- argument_range,
- last_argument_range,
- window,
- cx,
- )
- } else {
- self.complete_command_name(&name, command_range, last_argument_range, window, cx)
- }
- }
-
- fn is_completion_trigger(
- &self,
- buffer: &Entity,
- position: language::Anchor,
- _text: &str,
- _trigger_in_words: bool,
- cx: &mut Context,
- ) -> bool {
- let buffer = buffer.read(cx);
- let position = position.to_point(buffer);
- let line_start = Point::new(position.row, 0);
- let mut lines = buffer.text_for_range(line_start..position).lines();
- if let Some(line) = lines.next() {
- SlashCommandLine::parse(line).is_some()
- } else {
- false
- }
- }
-
- fn sort_completions(&self) -> bool {
- false
- }
-}
diff --git a/crates/agent_ui/src/slash_command_picker.rs b/crates/agent_ui/src/slash_command_picker.rs
deleted file mode 100644
index 0c3cf37599887fe8e97dcdc67bb0bd7e28a744a7..0000000000000000000000000000000000000000
--- a/crates/agent_ui/src/slash_command_picker.rs
+++ /dev/null
@@ -1,348 +0,0 @@
-use crate::text_thread_editor::TextThreadEditor;
-use assistant_slash_command::SlashCommandWorkingSet;
-use gpui::{AnyElement, AnyView, DismissEvent, SharedString, Task, WeakEntity};
-use picker::{Picker, PickerDelegate, PickerEditorPosition};
-use std::sync::Arc;
-use ui::{ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip, prelude::*};
-
-#[derive(IntoElement)]
-pub(super) struct SlashCommandSelector
-where
- T: PopoverTrigger + ButtonCommon,
- TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
-{
- working_set: Arc,
- active_context_editor: WeakEntity,
- trigger: T,
- tooltip: TT,
-}
-
-#[derive(Clone)]
-struct SlashCommandInfo {
- name: SharedString,
- description: SharedString,
- args: Option,
- icon: IconName,
-}
-
-#[derive(Clone)]
-enum SlashCommandEntry {
- Info(SlashCommandInfo),
- Advert {
- name: SharedString,
- renderer: fn(&mut Window, &mut App) -> AnyElement,
- on_confirm: fn(&mut Window, &mut App),
- },
-}
-
-impl AsRef for SlashCommandEntry {
- fn as_ref(&self) -> &str {
- match self {
- SlashCommandEntry::Info(SlashCommandInfo { name, .. })
- | SlashCommandEntry::Advert { name, .. } => name,
- }
- }
-}
-
-pub(crate) struct SlashCommandDelegate {
- all_commands: Vec,
- filtered_commands: Vec,
- active_context_editor: WeakEntity,
- selected_index: usize,
-}
-
-impl SlashCommandSelector
-where
- T: PopoverTrigger + ButtonCommon,
- TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
-{
- pub(crate) fn new(
- working_set: Arc,
- active_context_editor: WeakEntity,
- trigger: T,
- tooltip: TT,
- ) -> Self {
- SlashCommandSelector {
- working_set,
- active_context_editor,
- trigger,
- tooltip,
- }
- }
-}
-
-impl PickerDelegate for SlashCommandDelegate {
- type ListItem = ListItem;
-
- fn match_count(&self) -> usize {
- self.filtered_commands.len()
- }
-
- fn selected_index(&self) -> usize {
- self.selected_index
- }
-
- fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context>) {
- self.selected_index = ix.min(self.filtered_commands.len().saturating_sub(1));
- cx.notify();
- }
-
- fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc {
- "Select a command...".into()
- }
-
- fn update_matches(
- &mut self,
- query: String,
- window: &mut Window,
- cx: &mut Context>,
- ) -> Task<()> {
- let all_commands = self.all_commands.clone();
- cx.spawn_in(window, async move |this, cx| {
- let filtered_commands = cx
- .background_spawn(async move {
- if query.is_empty() {
- all_commands
- } else {
- all_commands
- .into_iter()
- .filter(|model_info| {
- model_info
- .as_ref()
- .to_lowercase()
- .contains(&query.to_lowercase())
- })
- .collect()
- }
- })
- .await;
-
- this.update_in(cx, |this, window, cx| {
- this.delegate.filtered_commands = filtered_commands;
- this.delegate.set_selected_index(0, window, cx);
- cx.notify();
- })
- .ok();
- })
- }
-
- fn separators_after_indices(&self) -> Vec {
- let mut ret = vec![];
- let mut previous_is_advert = false;
-
- for (index, command) in self.filtered_commands.iter().enumerate() {
- if previous_is_advert {
- if let SlashCommandEntry::Info(_) = command {
- previous_is_advert = false;
- debug_assert_ne!(
- index, 0,
- "index cannot be zero, as we can never have a separator at 0th position"
- );
- ret.push(index - 1);
- }
- } else if let SlashCommandEntry::Advert { .. } = command {
- previous_is_advert = true;
- if index != 0 {
- ret.push(index - 1);
- }
- }
- }
- ret
- }
-
- fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context>) {
- if let Some(command) = self.filtered_commands.get(self.selected_index) {
- match command {
- SlashCommandEntry::Info(info) => {
- self.active_context_editor
- .update(cx, |text_thread_editor, cx| {
- text_thread_editor.insert_command(&info.name, window, cx)
- })
- .ok();
- }
- SlashCommandEntry::Advert { on_confirm, .. } => {
- on_confirm(window, cx);
- }
- }
- cx.emit(DismissEvent);
- }
- }
-
- fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context>) {}
-
- fn editor_position(&self) -> PickerEditorPosition {
- PickerEditorPosition::End
- }
-
- fn render_match(
- &self,
- ix: usize,
- selected: bool,
- window: &mut Window,
- cx: &mut Context>,
- ) -> Option {
- let command_info = self.filtered_commands.get(ix)?;
-
- match command_info {
- SlashCommandEntry::Info(info) => Some(
- ListItem::new(ix)
- .inset(true)
- .spacing(ListItemSpacing::Dense)
- .toggle_state(selected)
- .tooltip({
- let description = info.description.clone();
- move |_, cx| cx.new(|_| Tooltip::new(description.clone())).into()
- })
- .child(
- v_flex()
- .group(format!("command-entry-label-{ix}"))
- .w_full()
- .py_0p5()
- .min_w(px(250.))
- .max_w(px(400.))
- .child(
- h_flex()
- .gap_1p5()
- .child(
- Icon::new(info.icon)
- .size(IconSize::XSmall)
- .color(Color::Muted),
- )
- .child({
- let mut label = format!("{}", info.name);
- if let Some(args) = info.args.as_ref().filter(|_| selected)
- {
- label.push_str(args);
- }
- Label::new(label)
- .single_line()
- .size(LabelSize::Small)
- .buffer_font(cx)
- })
- .children(info.args.clone().filter(|_| !selected).map(
- |args| {
- div()
- .child(
- Label::new(args)
- .single_line()
- .size(LabelSize::Small)
- .color(Color::Muted)
- .buffer_font(cx),
- )
- .visible_on_hover(format!(
- "command-entry-label-{ix}"
- ))
- },
- )),
- )
- .child(
- Label::new(info.description.clone())
- .size(LabelSize::Small)
- .color(Color::Muted)
- .truncate(),
- ),
- ),
- ),
- SlashCommandEntry::Advert { renderer, .. } => Some(
- ListItem::new(ix)
- .inset(true)
- .spacing(ListItemSpacing::Dense)
- .toggle_state(selected)
- .child(renderer(window, cx)),
- ),
- }
- }
-}
-
-impl RenderOnce for SlashCommandSelector
-where
- T: PopoverTrigger + ButtonCommon,
- TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
-{
- fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
- let all_models = self
- .working_set
- .featured_command_names(cx)
- .into_iter()
- .filter_map(|command_name| {
- let command = self.working_set.command(&command_name, cx)?;
- let menu_text = SharedString::from(Arc::from(command.menu_text()));
- let label = command.label(cx);
- let args = label.filter_range.end.ne(&label.text.len()).then(|| {
- SharedString::from(
- label.text[label.filter_range.end..label.text.len()].to_owned(),
- )
- });
- Some(SlashCommandEntry::Info(SlashCommandInfo {
- name: command_name.into(),
- description: menu_text,
- args,
- icon: command.icon(),
- }))
- })
- .chain([SlashCommandEntry::Advert {
- name: "create-your-command".into(),
- renderer: |_, cx| {
- v_flex()
- .w_full()
- .child(
- h_flex()
- .w_full()
- .font_buffer(cx)
- .items_center()
- .justify_between()
- .child(
- h_flex()
- .items_center()
- .gap_1p5()
- .child(Icon::new(IconName::Plus).size(IconSize::XSmall))
- .child(
- Label::new("create-your-command")
- .size(LabelSize::Small)
- .buffer_font(cx),
- ),
- )
- .child(
- Icon::new(IconName::ArrowUpRight)
- .size(IconSize::Small)
- .color(Color::Muted),
- ),
- )
- .child(
- Label::new("Create your custom command")
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- .into_any_element()
- },
- on_confirm: |_, cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"),
- }])
- .collect::>();
-
- let delegate = SlashCommandDelegate {
- all_commands: all_models.clone(),
- active_context_editor: self.active_context_editor.clone(),
- filtered_commands: all_models,
- selected_index: 0,
- };
-
- let picker_view = cx.new(|cx| {
- Picker::uniform_list(delegate, window, cx).max_height(Some(rems(20.).into()))
- });
-
- let handle = self
- .active_context_editor
- .read_with(cx, |this, _| this.slash_menu_handle.clone())
- .ok();
- PopoverMenu::new("model-switcher")
- .menu(move |_window, _cx| Some(picker_view.clone()))
- .trigger_with_tooltip(self.trigger, self.tooltip)
- .attach(gpui::Corner::TopLeft)
- .anchor(gpui::Corner::BottomLeft)
- .offset(gpui::Point {
- x: px(0.0),
- y: px(-2.0),
- })
- .when_some(handle, |this, handle| this.with_handle(handle))
- }
-}
diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs
deleted file mode 100644
index 180a31edde29b7ef78ee263a437458abd5affafc..0000000000000000000000000000000000000000
--- a/crates/agent_ui/src/text_thread_editor.rs
+++ /dev/null
@@ -1,3471 +0,0 @@
-use crate::{
- language_model_selector::{LanguageModelSelector, language_model_selector},
- mention_set::load_external_image_from_path,
- ui::ModelSelectorTooltip,
-};
-use anyhow::Result;
-use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};
-use assistant_slash_commands::{DefaultSlashCommand, FileSlashCommand, selections_creases};
-use client::{proto, zed_urls};
-use collections::{BTreeSet, HashMap, HashSet, hash_map};
-use editor::{
- Anchor, Editor, EditorEvent, MenuEditPredictionsPolicy, MultiBuffer, MultiBufferOffset,
- MultiBufferSnapshot, RowExt, ToOffset as _, ToPoint as _,
- actions::{MoveToEndOfLine, Newline, ShowCompletions},
- display_map::{
- BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId,
- RenderBlock, ToDisplayPoint,
- },
- scroll::ScrollOffset,
-};
-use editor::{FoldPlaceholder, display_map::CreaseId};
-use fs::Fs;
-use futures::FutureExt;
-use gpui::{
- Action, Animation, AnimationExt, AnyElement, App, ClipboardEntry, ClipboardItem, Empty, Entity,
- EventEmitter, FocusHandle, Focusable, FontWeight, Global, InteractiveElement, IntoElement,
- ParentElement, Pixels, Render, RenderImage, SharedString, Size, StatefulInteractiveElement,
- Styled, Subscription, Task, WeakEntity, actions, div, img, point, prelude::*,
- pulsating_between, size,
-};
-use language::{
- BufferSnapshot, LspAdapterDelegate, ToOffset,
- language_settings::{SoftWrap, all_language_settings},
-};
-use language_model::{
- ConfigurationError, IconOrSvg, LanguageModelImage, LanguageModelRegistry, Role,
-};
-use multi_buffer::MultiBufferRow;
-use picker::{Picker, popover_menu::PickerPopoverMenu};
-use project::{Project, Worktree};
-use project::{ProjectPath, lsp_store::LocalLspAdapterDelegate};
-use rope::Point;
-use serde::{Deserialize, Serialize};
-use settings::{
- LanguageModelProviderSetting, LanguageModelSelection, Settings, SettingsStore,
- update_settings_file,
-};
-use std::{
- any::{Any, TypeId},
- cmp,
- ops::Range,
- path::{Path, PathBuf},
- rc::Rc,
- sync::Arc,
- time::Duration,
-};
-use text::SelectionGoal;
-use ui::{
- ButtonLike, CommonAnimationExt, Disclosure, ElevationIndex, KeyBinding, PopoverMenuHandle,
- TintColor, Tooltip, prelude::*,
-};
-use util::{ResultExt, maybe};
-use workspace::{
- CollaboratorId,
- searchable::{Direction, SearchToken, SearchableItemHandle},
-};
-
-use workspace::{
- Save, Toast, Workspace,
- item::{self, FollowableItem, Item},
- notifications::NotificationId,
- pane,
- searchable::{SearchEvent, SearchableItem},
-};
-use zed_actions::agent::{AddSelectionToThread, PasteRaw, ToggleModelSelector};
-
-use crate::CycleFavoriteModels;
-
-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,
-};
-
-actions!(
- assistant,
- [
- /// Sends the current message to the assistant.
- Assist,
- /// Confirms and executes the entered slash command.
- ConfirmCommand,
- /// Copies code from the assistant's response to the clipboard.
- CopyCode,
- /// Cycles between user and assistant message roles.
- CycleMessageRole,
- /// Inserts the selected text into the active editor.
- InsertIntoEditor,
- /// Splits the conversation at the current cursor position.
- Split,
- ]
-);
-
-/// Inserts files that were dragged and dropped into the assistant conversation.
-#[derive(PartialEq, Clone, Action)]
-#[action(namespace = assistant, no_json, no_register)]
-pub enum InsertDraggedFiles {
- ProjectPaths(Vec),
- ExternalFiles(Vec),
-}
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-struct ScrollPosition {
- offset_before_cursor: gpui::Point,
- cursor: Anchor,
-}
-
-type MessageHeader = MessageMetadata;
-
-#[derive(Clone)]
-enum AssistError {
- PaymentRequired,
- Message(SharedString),
-}
-
-pub enum ThoughtProcessStatus {
- Pending,
- Completed,
-}
-
-pub trait AgentPanelDelegate {
- fn active_text_thread_editor(
- &self,
- workspace: &mut Workspace,
- window: &mut Window,
- cx: &mut Context,
- ) -> Option>;
-
- fn open_local_text_thread(
- &self,
- workspace: &mut Workspace,
- path: Arc,
- window: &mut Window,
- cx: &mut Context,
- ) -> Task>;
-
- fn open_remote_text_thread(
- &self,
- workspace: &mut Workspace,
- text_thread_id: TextThreadId,
- window: &mut Window,
- cx: &mut Context,
- ) -> Task>>;
-
- fn quote_selection(
- &self,
- workspace: &mut Workspace,
- selection_ranges: Vec>,
- buffer: Entity,
- window: &mut Window,
- cx: &mut Context,
- );
-
- fn quote_terminal_text(
- &self,
- workspace: &mut Workspace,
- text: String,
- window: &mut Window,
- cx: &mut Context,
- );
-}
-
-impl dyn AgentPanelDelegate {
- /// Returns the global [`AssistantPanelDelegate`], if it exists.
- pub fn try_global(cx: &App) -> Option> {
- cx.try_global::()
- .map(|global| global.0.clone())
- }
-
- /// Sets the global [`AssistantPanelDelegate`].
- pub fn set_global(delegate: Arc, cx: &mut App) {
- cx.set_global(GlobalAssistantPanelDelegate(delegate));
- }
-}
-
-struct GlobalAssistantPanelDelegate(Arc);
-
-impl Global for GlobalAssistantPanelDelegate {}
-
-pub struct TextThreadEditor {
- text_thread: Entity,
- fs: Arc,
- slash_commands: Arc,
- workspace: WeakEntity,
- project: Entity,
- lsp_adapter_delegate: Option>,
- editor: Entity,
- pending_thought_process: Option<(CreaseId, language::Anchor)>,
- blocks: HashMap,
- image_blocks: HashSet,
- scroll_position: Option,
- remote_id: Option,
- pending_slash_command_creases: HashMap, CreaseId>,
- invoked_slash_command_creases: HashMap,
- _subscriptions: Vec,
- last_error: Option,
- pub(crate) slash_menu_handle:
- PopoverMenuHandle>,
- // dragged_file_worktrees is used to keep references to worktrees that were added
- // when the user drag/dropped an external file onto the context editor. Since
- // the worktree is not part of the project panel, it would be dropped as soon as
- // the file is opened. In order to keep the worktree alive for the duration of the
- // context editor, we keep a reference here.
- dragged_file_worktrees: Vec>,
- language_model_selector: Entity,
- language_model_selector_menu_handle: PopoverMenuHandle,
-}
-
-const MAX_TAB_TITLE_LEN: usize = 16;
-
-impl TextThreadEditor {
- pub fn init(cx: &mut App) {
- workspace::FollowableViewRegistry::register::(cx);
-
- cx.observe_new(
- |workspace: &mut Workspace, _window, _cx: &mut Context| {
- workspace
- .register_action(TextThreadEditor::quote_selection)
- .register_action(TextThreadEditor::insert_selection)
- .register_action(TextThreadEditor::copy_code)
- .register_action(TextThreadEditor::handle_insert_dragged_files);
- },
- )
- .detach();
- }
-
- pub fn for_text_thread(
- text_thread: Entity,
- fs: Arc,
- workspace: WeakEntity,
- project: Entity,
- lsp_adapter_delegate: Option>,
- window: &mut Window,
- cx: &mut Context,
- ) -> Self {
- let completion_provider = SlashCommandCompletionProvider::new(
- text_thread.read(cx).slash_commands().clone(),
- Some(cx.entity().downgrade()),
- Some(workspace.clone()),
- );
-
- let editor = cx.new(|cx| {
- let mut editor =
- Editor::for_buffer(text_thread.read(cx).buffer().clone(), None, window, cx);
- editor.disable_scrollbars_and_minimap(window, cx);
- editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
- editor.set_show_line_numbers(false, cx);
- editor.set_show_git_diff_gutter(false, cx);
- editor.set_show_code_actions(false, cx);
- editor.set_show_runnables(false, cx);
- editor.set_show_breakpoints(false, cx);
- editor.set_show_wrap_guides(false, cx);
- editor.set_show_indent_guides(false, cx);
- editor.set_completion_provider(Some(Rc::new(completion_provider)));
- editor.set_menu_edit_predictions_policy(MenuEditPredictionsPolicy::Never);
- editor.set_collaboration_hub(Box::new(project.clone()));
-
- let show_edit_predictions = all_language_settings(None, cx)
- .edit_predictions
- .enabled_in_text_threads;
-
- editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
-
- editor
- });
-
- let _subscriptions = vec![
- cx.observe(&text_thread, |_, _, cx| cx.notify()),
- cx.subscribe_in(&text_thread, window, Self::handle_text_thread_event),
- cx.subscribe_in(&editor, window, Self::handle_editor_event),
- cx.subscribe_in(&editor, window, Self::handle_editor_search_event),
- cx.observe_global_in::(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 focus_handle = editor.read(cx).focus_handle(cx);
-
- let mut this = Self {
- text_thread,
- slash_commands,
- editor,
- lsp_adapter_delegate,
- blocks: Default::default(),
- image_blocks: Default::default(),
- scroll_position: None,
- remote_id: None,
- pending_thought_process: None,
- fs: fs.clone(),
- workspace,
- project,
- pending_slash_command_creases: HashMap::default(),
- invoked_slash_command_creases: HashMap::default(),
- _subscriptions,
- last_error: None,
- slash_menu_handle: Default::default(),
- dragged_file_worktrees: Vec::new(),
- language_model_selector: cx.new(|cx| {
- language_model_selector(
- |cx| LanguageModelRegistry::read_global(cx).default_model(),
- {
- let fs = fs.clone();
- move |model, cx| {
- update_settings_file(fs.clone(), cx, move |settings, _| {
- let provider = model.provider_id().0.to_string();
- let model_id = model.id().0.to_string();
- settings.agent.get_or_insert_default().set_model(
- LanguageModelSelection {
- provider: LanguageModelProviderSetting(provider),
- model: model_id,
- enable_thinking: model.supports_thinking(),
- effort: model
- .default_effort_level()
- .map(|effort| effort.value.to_string()),
- },
- )
- });
- }
- },
- {
- let fs = fs.clone();
- move |model, should_be_favorite, cx| {
- crate::favorite_models::toggle_in_settings(
- model,
- should_be_favorite,
- fs.clone(),
- cx,
- );
- }
- },
- true, // Use popover styles for picker
- focus_handle,
- window,
- cx,
- )
- }),
- language_model_selector_menu_handle: PopoverMenuHandle::default(),
- };
- this.update_message_headers(cx);
- this.update_image_blocks(cx);
- this.insert_slash_command_output_sections(slash_command_sections, false, window, cx);
- this.insert_thought_process_output_sections(
- thought_process_sections
- .into_iter()
- .map(|section| (section, ThoughtProcessStatus::Completed)),
- window,
- cx,
- );
- this
- }
-
- fn settings_changed(&mut self, window: &mut Window, cx: &mut Context) {
- self.editor.update(cx, |editor, cx| {
- let show_edit_predictions = all_language_settings(None, cx)
- .edit_predictions
- .enabled_in_text_threads;
-
- editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
- });
- }
-
- pub fn text_thread(&self) -> &Entity {
- &self.text_thread
- }
-
- pub fn editor(&self) -> &Entity {
- &self.editor
- }
-
- pub fn insert_default_prompt(&mut self, window: &mut Window, cx: &mut Context) {
- let command_name = DefaultSlashCommand.name();
- 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()
- });
- self.run_command(
- command.source_range,
- &command.name,
- &command.arguments,
- false,
- self.workspace.clone(),
- window,
- cx,
- );
- }
-
- fn assist(&mut self, _: &Assist, window: &mut Window, cx: &mut Context) {
- if self.sending_disabled(cx) {
- return;
- }
- telemetry::event!("Agent Message Sent", agent = "zed-text");
- self.send_to_model(window, cx);
- }
-
- fn send_to_model(&mut self, window: &mut Window, cx: &mut Context) {
- self.last_error = None;
- if let Some(user_message) = self
- .text_thread
- .update(cx, |text_thread, cx| text_thread.assist(cx))
- {
- let new_selection = {
- let cursor = user_message
- .start
- .to_offset(self.text_thread.read(cx).buffer().read(cx));
- MultiBufferOffset(cursor)..MultiBufferOffset(cursor)
- };
- self.editor.update(cx, |editor, cx| {
- editor.change_selections(Default::default(), window, cx, |selections| {
- selections.select_ranges([new_selection])
- });
- });
- // Avoid scrolling to the new cursor position so the assistant's output is stable.
- cx.defer_in(window, |this, _, _| this.scroll_position = None);
- }
-
- cx.notify();
- }
-
- fn cancel(
- &mut self,
- _: &editor::actions::Cancel,
- _window: &mut Window,
- cx: &mut Context,
- ) {
- self.last_error = None;
-
- if self
- .text_thread
- .update(cx, |text_thread, cx| text_thread.cancel_last_assist(cx))
- {
- return;
- }
-
- cx.propagate();
- }
-
- fn cycle_message_role(
- &mut self,
- _: &CycleMessageRole,
- _window: &mut Window,
- cx: &mut Context,
- ) {
- let cursors = self.cursors(cx);
- self.text_thread.update(cx, |text_thread, cx| {
- let messages = text_thread
- .messages_for_offsets(cursors.into_iter().map(|cursor| cursor.0), cx)
- .into_iter()
- .map(|message| message.id)
- .collect();
- text_thread.cycle_message_roles(messages, cx)
- });
- }
-
- fn cursors(&self, cx: &mut App) -> Vec {
- let selections = self.editor.update(cx, |editor, cx| {
- editor
- .selections
- .all::(&editor.display_snapshot(cx))
- });
- selections
- .into_iter()
- .map(|selection| selection.head())
- .collect()
- }
-
- pub fn insert_command(&mut self, name: &str, window: &mut Window, cx: &mut Context) {
- if let Some(command) = self.slash_commands.command(name, cx) {
- self.editor.update(cx, |editor, cx| {
- editor.transact(window, cx, |editor, window, cx| {
- editor.change_selections(Default::default(), window, cx, |s| s.try_cancel());
- let snapshot = editor.buffer().read(cx).snapshot(cx);
- let newest_cursor = editor
- .selections
- .newest::(&editor.display_snapshot(cx))
- .head();
- if newest_cursor.column > 0
- || snapshot
- .chars_at(newest_cursor)
- .next()
- .is_some_and(|ch| ch != '\n')
- {
- editor.move_to_end_of_line(
- &MoveToEndOfLine {
- stop_at_soft_wraps: false,
- },
- window,
- cx,
- );
- editor.newline(&Newline, window, cx);
- }
-
- editor.insert(&format!("/{name}"), window, cx);
- if command.accepts_arguments() {
- editor.insert(" ", window, cx);
- editor.show_completions(&ShowCompletions, window, cx);
- }
- });
- });
- if !command.requires_argument() {
- self.confirm_command(&ConfirmCommand, window, cx);
- }
- }
- }
-
- pub fn confirm_command(
- &mut self,
- _: &ConfirmCommand,
- window: &mut Window,
- cx: &mut Context,
- ) {
- if self.editor.read(cx).has_visible_completions_menu() {
- return;
- }
-
- 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);
- for selection in selections.iter() {
- if let Some(command) =
- text_thread.pending_command_for_position(selection.head().text_anchor, cx)
- {
- commands_by_range
- .entry(command.source_range.clone())
- .or_insert_with(|| command.clone());
- }
- }
- });
-
- if commands_by_range.is_empty() {
- cx.propagate();
- } else {
- for command in commands_by_range.into_values() {
- self.run_command(
- command.source_range,
- &command.name,
- &command.arguments,
- true,
- workspace.clone(),
- window,
- cx,
- );
- }
- cx.stop_propagation();
- }
- }
-
- pub fn run_command(
- &mut self,
- command_range: Range,
- name: &str,
- arguments: &[String],
- ensure_trailing_newline: bool,
- workspace: WeakEntity,
- window: &mut Window,
- cx: &mut Context,
- ) {
- if let Some(command) = self.slash_commands.command(name, cx) {
- let text_thread = self.text_thread.read(cx);
- let sections = text_thread
- .slash_command_output_sections()
- .iter()
- .filter(|section| section.is_valid(text_thread.buffer().read(cx)))
- .cloned()
- .collect::>();
- let snapshot = text_thread.buffer().read(cx).snapshot();
- let output = command.run(
- arguments,
- §ions,
- snapshot,
- workspace,
- self.lsp_adapter_delegate.clone(),
- window,
- cx,
- );
- self.text_thread.update(cx, |text_thread, cx| {
- text_thread.insert_command_output(
- command_range,
- name,
- output,
- ensure_trailing_newline,
- cx,
- )
- });
- }
- }
-
- fn handle_text_thread_event(
- &mut self,
- _: &Entity,
- event: &TextThreadEvent,
- window: &mut Window,
- cx: &mut Context,
- ) {
- let text_thread_editor = cx.entity().downgrade();
-
- match event {
- TextThreadEvent::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);
- });
- }
- TextThreadEvent::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);
- });
- }
- TextThreadEvent::SummaryGenerated => {}
- TextThreadEvent::PathChanged { .. } => {}
- TextThreadEvent::StartedThoughtProcess(range) => {
- let creases = self.insert_thought_process_output_sections(
- [(
- ThoughtProcessOutputSection {
- range: range.clone(),
- },
- ThoughtProcessStatus::Pending,
- )],
- window,
- cx,
- );
- self.pending_thought_process = Some((creases[0], range.start));
- }
- TextThreadEvent::EndedThoughtProcess(end) => {
- if let Some((crease_id, start)) = self.pending_thought_process.take() {
- self.editor.update(cx, |editor, cx| {
- let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
- let start_anchor =
- multi_buffer_snapshot.as_singleton_anchor(start).unwrap();
-
- editor.display_map.update(cx, |display_map, cx| {
- display_map.unfold_intersecting(
- vec![start_anchor..start_anchor],
- true,
- cx,
- );
- });
- editor.remove_creases(vec![crease_id], cx);
- });
- self.insert_thought_process_output_sections(
- [(
- ThoughtProcessOutputSection { range: start..*end },
- ThoughtProcessStatus::Completed,
- )],
- window,
- cx,
- );
- }
- }
- TextThreadEvent::StreamedCompletion => {
- self.editor.update(cx, |editor, cx| {
- if let Some(scroll_position) = self.scroll_position {
- let snapshot = editor.snapshot(window, cx);
- let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
- let scroll_top =
- cursor_point.row().as_f64() - scroll_position.offset_before_cursor.y;
- editor.set_scroll_position(
- point(scroll_position.offset_before_cursor.x, scroll_top),
- window,
- cx,
- );
- }
- });
- }
- TextThreadEvent::ParsedSlashCommandsUpdated { removed, updated } => {
- self.editor.update(cx, |editor, cx| {
- let buffer = editor.buffer().read(cx).snapshot(cx);
- let (excerpt_id, _, _) = buffer.as_singleton().unwrap();
-
- editor.remove_creases(
- removed
- .iter()
- .filter_map(|range| self.pending_slash_command_creases.remove(range)),
- cx,
- );
-
- let crease_ids = editor.insert_creases(
- updated.iter().map(|command| {
- let workspace = self.workspace.clone();
- let confirm_command = Arc::new({
- let text_thread_editor = text_thread_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(
- command.source_range.clone(),
- &command.name,
- &command.arguments,
- false,
- workspace.clone(),
- window,
- cx,
- );
- })
- .ok();
- }
- });
- let placeholder = FoldPlaceholder {
- render: Arc::new(move |_, _, _| Empty.into_any()),
- ..Default::default()
- };
- let render_toggle = {
- let confirm_command = confirm_command.clone();
- let command = command.clone();
- move |row, _, _, _window: &mut Window, _cx: &mut App| {
- render_pending_slash_command_gutter_decoration(
- row,
- &command.status,
- confirm_command.clone(),
- )
- }
- };
- let render_trailer = {
- move |_row, _unfold, _window: &mut Window, _cx: &mut App| {
- Empty.into_any()
- }
- };
-
- let range = buffer
- .anchor_range_in_excerpt(excerpt_id, command.source_range.clone())
- .unwrap();
- Crease::inline(range, placeholder, render_toggle, render_trailer)
- }),
- cx,
- );
-
- self.pending_slash_command_creases.extend(
- updated
- .iter()
- .map(|command| command.source_range.clone())
- .zip(crease_ids),
- );
- })
- }
- TextThreadEvent::InvokedSlashCommandChanged { command_id } => {
- self.update_invoked_slash_command(*command_id, window, cx);
- }
- TextThreadEvent::SlashCommandOutputSectionAdded { section } => {
- self.insert_slash_command_output_sections([section.clone()], false, window, cx);
- }
- TextThreadEvent::Operation(_) => {}
- TextThreadEvent::ShowAssistError(error_message) => {
- self.last_error = Some(AssistError::Message(error_message.clone()));
- }
- TextThreadEvent::ShowPaymentRequiredError => {
- self.last_error = Some(AssistError::PaymentRequired);
- }
- }
- }
-
- fn update_invoked_slash_command(
- &mut self,
- command_id: InvokedSlashCommandId,
- window: &mut Window,
- cx: &mut Context,
- ) {
- if let Some(invoked_slash_command) =
- self.text_thread.read(cx).invoked_slash_command(&command_id)
- && let InvokedSlashCommandStatus::Finished = invoked_slash_command.status
- {
- let run_commands_in_ranges = invoked_slash_command.run_commands_in_ranges.clone();
- for range in run_commands_in_ranges {
- let commands = self.text_thread.update(cx, |text_thread, cx| {
- text_thread.reparse(cx);
- text_thread
- .pending_commands_for_range(range.clone(), cx)
- .to_vec()
- });
-
- for command in commands {
- self.run_command(
- command.source_range,
- &command.name,
- &command.arguments,
- false,
- self.workspace.clone(),
- window,
- cx,
- );
- }
- }
- }
-
- self.editor.update(cx, |editor, cx| {
- if let Some(invoked_slash_command) =
- self.text_thread.read(cx).invoked_slash_command(&command_id)
- {
- if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status {
- let buffer = editor.buffer().read(cx).snapshot(cx);
- let (excerpt_id, _buffer_id, _buffer_snapshot) = buffer.as_singleton().unwrap();
-
- let range = buffer
- .anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
- .unwrap();
- editor.remove_folds_with_type(
- &[range],
- TypeId::of::(),
- false,
- cx,
- );
-
- editor.remove_creases(
- HashSet::from_iter(self.invoked_slash_command_creases.remove(&command_id)),
- cx,
- );
- } else if let hash_map::Entry::Vacant(entry) =
- self.invoked_slash_command_creases.entry(command_id)
- {
- 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 range = buffer
- .anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
- .unwrap();
- let crease = Crease::inline(
- range,
- invoked_slash_command_fold_placeholder(command_id, context),
- fold_toggle("invoked-slash-command"),
- |_row, _folded, _window, _cx| Empty.into_any(),
- );
- let crease_ids = editor.insert_creases([crease.clone()], cx);
- editor.fold_creases(vec![crease], false, window, cx);
- entry.insert(crease_ids[0]);
- } else {
- cx.notify()
- }
- } else {
- editor.remove_creases(
- HashSet::from_iter(self.invoked_slash_command_creases.remove(&command_id)),
- cx,
- );
- cx.notify();
- };
- });
- }
-
- fn insert_thought_process_output_sections(
- &mut self,
- sections: impl IntoIterator<
- Item = (
- ThoughtProcessOutputSection,
- ThoughtProcessStatus,
- ),
- >,
- window: &mut Window,
- cx: &mut Context,
- ) -> Vec {
- self.editor.update(cx, |editor, cx| {
- let buffer = editor.buffer().read(cx).snapshot(cx);
- let excerpt_id = buffer.as_singleton().unwrap().0;
- let mut buffer_rows_to_fold = BTreeSet::new();
- let mut creases = Vec::new();
- for (section, status) in sections {
- let range = buffer
- .anchor_range_in_excerpt(excerpt_id, section.range)
- .unwrap();
- let buffer_row = MultiBufferRow(range.start.to_point(&buffer).row);
- buffer_rows_to_fold.insert(buffer_row);
- creases.push(
- Crease::inline(
- range,
- FoldPlaceholder {
- render: render_thought_process_fold_icon_button(
- cx.entity().downgrade(),
- status,
- ),
- merge_adjacent: false,
- ..Default::default()
- },
- render_slash_command_output_toggle,
- |_, _, _, _| Empty.into_any_element(),
- )
- .with_metadata(CreaseMetadata {
- icon_path: SharedString::from(IconName::ZedAgent.path()),
- label: "Thinking Process".into(),
- }),
- );
- }
-
- let creases = editor.insert_creases(creases, cx);
-
- for buffer_row in buffer_rows_to_fold.into_iter().rev() {
- editor.fold_at(buffer_row, window, cx);
- }
-
- creases
- })
- }
-
- fn insert_slash_command_output_sections(
- &mut self,
- sections: impl IntoIterator- >,
- expand_result: bool,
- window: &mut Window,
- cx: &mut Context,
- ) {
- self.editor.update(cx, |editor, cx| {
- let buffer = editor.buffer().read(cx).snapshot(cx);
- let excerpt_id = buffer.as_singleton().unwrap().0;
- let mut buffer_rows_to_fold = BTreeSet::new();
- let mut creases = Vec::new();
- for section in sections {
- let range = buffer
- .anchor_range_in_excerpt(excerpt_id, section.range)
- .unwrap();
- let buffer_row = MultiBufferRow(range.start.to_point(&buffer).row);
- buffer_rows_to_fold.insert(buffer_row);
- creases.push(
- Crease::inline(
- range,
- FoldPlaceholder {
- render: render_fold_icon_button(
- cx.entity().downgrade(),
- section.icon.path().into(),
- section.label.clone(),
- ),
- merge_adjacent: false,
- ..Default::default()
- },
- render_slash_command_output_toggle,
- |_, _, _, _| Empty.into_any_element(),
- )
- .with_metadata(CreaseMetadata {
- icon_path: section.icon.path().into(),
- label: section.label,
- }),
- );
- }
-
- editor.insert_creases(creases, cx);
-
- if expand_result {
- buffer_rows_to_fold.clear();
- }
- for buffer_row in buffer_rows_to_fold.into_iter().rev() {
- editor.fold_at(buffer_row, window, cx);
- }
- });
- }
-
- fn handle_editor_event(
- &mut self,
- _: &Entity