Move UI code from assistant_context_editor -> agent_ui (#33289)

Max Brunsfeld created

This breaks a transitive dependency of `agent` on UI crates. I've also
found and eliminated some dead code in assistant_context_editor.

Release Notes:

- N/A

Change summary

Cargo.lock                                                      |  23 
Cargo.toml                                                      |   4 
crates/agent/Cargo.toml                                         |   2 
crates/agent/src/context.rs                                     |   2 
crates/agent/src/context_store.rs                               |   2 
crates/agent/src/history_store.rs                               |   6 
crates/agent/src/thread_store.rs                                |   2 
crates/agent_ui/Cargo.toml                                      |   5 
crates/agent_ui/src/agent_model_selector.rs                     |  13 
crates/agent_ui/src/agent_panel.rs                              |  75 
crates/agent_ui/src/agent_ui.rs                                 |   8 
crates/agent_ui/src/context_picker/completion_provider.rs       |   2 
crates/agent_ui/src/inline_prompt_editor.rs                     |   2 
crates/agent_ui/src/language_model_selector.rs                  |   0 
crates/agent_ui/src/max_mode_tooltip.rs                         |   0 
crates/agent_ui/src/message_editor.rs                           |   2 
crates/agent_ui/src/slash_command.rs                            |   6 
crates/agent_ui/src/slash_command_picker.rs                     |  12 
crates/agent_ui/src/text_thread_editor.rs                       |  90 
crates/assistant_context/Cargo.toml                             |  15 
crates/assistant_context/LICENSE-GPL                            |   0 
crates/assistant_context/src/assistant_context.rs               |  11 
crates/assistant_context/src/assistant_context_tests.rs         |   0 
crates/assistant_context/src/context_store.rs                   |   0 
crates/assistant_context_editor/src/assistant_context_editor.rs |  36 
crates/assistant_context_editor/src/context_history.rs          | 271 ---
crates/collab/Cargo.toml                                        |   2 
crates/collab/src/tests/integration_tests.rs                    |   2 
crates/collab/src/tests/test_server.rs                          |   2 
crates/zed/Cargo.toml                                           |   1 
crates/zed/src/zed.rs                                           |   3 
31 files changed, 144 insertions(+), 455 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -55,7 +55,7 @@ version = "0.1.0"
 dependencies = [
  "agent_settings",
  "anyhow",
- "assistant_context_editor",
+ "assistant_context",
  "assistant_tool",
  "assistant_tools",
  "chrono",
@@ -138,7 +138,7 @@ dependencies = [
  "agent",
  "agent_settings",
  "anyhow",
- "assistant_context_editor",
+ "assistant_context",
  "assistant_slash_command",
  "assistant_slash_commands",
  "assistant_tool",
@@ -169,6 +169,7 @@ dependencies = [
  "jsonschema",
  "language",
  "language_model",
+ "languages",
  "log",
  "lsp",
  "markdown",
@@ -203,7 +204,9 @@ dependencies = [
  "theme",
  "time",
  "time_format",
+ "tree-sitter-md",
  "ui",
+ "unindent",
  "urlencoding",
  "util",
  "uuid",
@@ -557,7 +560,7 @@ dependencies = [
 ]
 
 [[package]]
-name = "assistant_context_editor"
+name = "assistant_context"
 version = "0.1.0"
 dependencies = [
  "agent_settings",
@@ -569,31 +572,23 @@ dependencies = [
  "clock",
  "collections",
  "context_server",
- "editor",
- "feature_flags",
  "fs",
  "futures 0.3.31",
  "fuzzy",
  "gpui",
- "indexed_docs",
  "indoc",
  "language",
  "language_model",
- "languages",
  "log",
- "multi_buffer",
  "open_ai",
- "ordered-float 2.10.1",
  "parking_lot",
  "paths",
- "picker",
  "pretty_assertions",
  "project",
  "prompt_store",
  "proto",
  "rand 0.8.5",
  "regex",
- "rope",
  "rpc",
  "serde",
  "serde_json",
@@ -602,15 +597,12 @@ dependencies = [
  "smol",
  "telemetry_events",
  "text",
- "theme",
- "tree-sitter-md",
  "ui",
  "unindent",
  "util",
  "uuid",
  "workspace",
  "workspace-hack",
- "zed_actions",
  "zed_llm_client",
 ]
 
@@ -3031,7 +3023,7 @@ version = "0.44.0"
 dependencies = [
  "agent_settings",
  "anyhow",
- "assistant_context_editor",
+ "assistant_context",
  "assistant_slash_command",
  "async-stripe",
  "async-trait",
@@ -19924,7 +19916,6 @@ dependencies = [
  "ashpd",
  "askpass",
  "assets",
- "assistant_context_editor",
  "assistant_tool",
  "assistant_tools",
  "audio",

Cargo.toml 🔗

@@ -8,7 +8,7 @@ members = [
     "crates/anthropic",
     "crates/askpass",
     "crates/assets",
-    "crates/assistant_context_editor",
+    "crates/assistant_context",
     "crates/assistant_slash_command",
     "crates/assistant_slash_commands",
     "crates/assistant_tool",
@@ -221,7 +221,7 @@ ai = { path = "crates/ai" }
 anthropic = { path = "crates/anthropic" }
 askpass = { path = "crates/askpass" }
 assets = { path = "crates/assets" }
-assistant_context_editor = { path = "crates/assistant_context_editor" }
+assistant_context = { path = "crates/assistant_context" }
 assistant_slash_command = { path = "crates/assistant_slash_command" }
 assistant_slash_commands = { path = "crates/assistant_slash_commands" }
 assistant_tool = { path = "crates/assistant_tool" }

crates/agent/Cargo.toml 🔗

@@ -21,7 +21,7 @@ test-support = [
 [dependencies]
 agent_settings.workspace = true
 anyhow.workspace = true
-assistant_context_editor.workspace = true
+assistant_context.workspace = true
 assistant_tool.workspace = true
 chrono.workspace = true
 client.workspace = true

crates/agent/src/context.rs 🔗

@@ -1,5 +1,5 @@
 use crate::thread::Thread;
-use assistant_context_editor::AssistantContext;
+use assistant_context::AssistantContext;
 use assistant_tool::outline;
 use collections::HashSet;
 use futures::future;

crates/agent/src/context_store.rs 🔗

@@ -8,7 +8,7 @@ use crate::{
     thread_store::ThreadStore,
 };
 use anyhow::{Context as _, Result, anyhow};
-use assistant_context_editor::AssistantContext;
+use assistant_context::AssistantContext;
 use collections::{HashSet, IndexSet};
 use futures::{self, FutureExt};
 use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};

crates/agent/src/history_store.rs 🔗

@@ -3,7 +3,7 @@ use crate::{
     thread_store::{SerializedThreadMetadata, ThreadStore},
 };
 use anyhow::{Context as _, Result};
-use assistant_context_editor::SavedContextMetadata;
+use assistant_context::SavedContextMetadata;
 use chrono::{DateTime, Utc};
 use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
 use itertools::Itertools;
@@ -62,7 +62,7 @@ enum SerializedRecentOpen {
 
 pub struct HistoryStore {
     thread_store: Entity<ThreadStore>,
-    context_store: Entity<assistant_context_editor::ContextStore>,
+    context_store: Entity<assistant_context::ContextStore>,
     recently_opened_entries: VecDeque<HistoryEntryId>,
     _subscriptions: Vec<gpui::Subscription>,
     _save_recently_opened_entries_task: Task<()>,
@@ -71,7 +71,7 @@ pub struct HistoryStore {
 impl HistoryStore {
     pub fn new(
         thread_store: Entity<ThreadStore>,
-        context_store: Entity<assistant_context_editor::ContextStore>,
+        context_store: Entity<assistant_context::ContextStore>,
         initial_recent_entries: impl IntoIterator<Item = HistoryEntryId>,
         cx: &mut Context<Self>,
     ) -> Self {

crates/agent/src/thread_store.rs 🔗

@@ -96,7 +96,7 @@ impl SharedProjectContext {
     }
 }
 
-pub type TextThreadStore = assistant_context_editor::ContextStore;
+pub type TextThreadStore = assistant_context::ContextStore;
 
 pub struct ThreadStore {
     project: Entity<Project>,

crates/agent_ui/Cargo.toml 🔗

@@ -22,7 +22,7 @@ test-support = [
 agent.workspace = true
 agent_settings.workspace = true
 anyhow.workspace = true
-assistant_context_editor.workspace = true
+assistant_context.workspace = true
 assistant_slash_command.workspace = true
 assistant_slash_commands.workspace = true
 assistant_tool.workspace = true
@@ -101,7 +101,10 @@ editor = { workspace = true, features = ["test-support"] }
 gpui = { workspace = true, "features" = ["test-support"] }
 indoc.workspace = true
 language = { workspace = true, "features" = ["test-support"] }
+languages = { workspace = true, features = ["test-support"] }
 language_model = { workspace = true, "features" = ["test-support"] }
 pretty_assertions.workspace = true
 project = { workspace = true, features = ["test-support"] }
 rand.workspace = true
+tree-sitter-md.workspace = true
+unindent.workspace = true

crates/agent_ui/src/agent_model_selector.rs 🔗

@@ -1,13 +1,14 @@
+use crate::{
+    ModelUsageContext,
+    language_model_selector::{
+        LanguageModelSelector, ToggleModelSelector, language_model_selector,
+    },
+};
 use agent_settings::AgentSettings;
 use fs::Fs;
 use gpui::{Entity, FocusHandle, SharedString};
-use picker::popover_menu::PickerPopoverMenu;
-
-use crate::ModelUsageContext;
-use assistant_context_editor::language_model_selector::{
-    LanguageModelSelector, ToggleModelSelector, language_model_selector,
-};
 use language_model::{ConfiguredModel, LanguageModelRegistry};
+use picker::popover_menu::PickerPopoverMenu;
 use settings::update_settings_file;
 use std::sync::Arc;
 use ui::{PopoverMenuHandle, Tooltip, prelude::*};

crates/agent_ui/src/agent_panel.rs 🔗

@@ -7,17 +7,35 @@ use std::time::Duration;
 use db::kvp::{Dismissable, KEY_VALUE_STORE};
 use serde::{Deserialize, Serialize};
 
+use crate::language_model_selector::ToggleModelSelector;
+use crate::{
+    AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
+    DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
+    NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
+    ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
+    active_thread::{self, ActiveThread, ActiveThreadEvent},
+    agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
+    agent_diff::AgentDiff,
+    message_editor::{MessageEditor, MessageEditorEvent},
+    slash_command::SlashCommandCompletionProvider,
+    text_thread_editor::{
+        AgentPanelDelegate, TextThreadEditor, humanize_token_count, make_lsp_adapter_delegate,
+        render_remaining_tokens,
+    },
+    thread_history::{HistoryEntryElement, ThreadHistory},
+    ui::AgentOnboardingModal,
+};
+use agent::{
+    Thread, ThreadError, ThreadEvent, ThreadId, ThreadSummary, TokenUsageRatio,
+    context_store::ContextStore,
+    history_store::{HistoryEntryId, HistoryStore},
+    thread_store::{TextThreadStore, ThreadStore},
+};
 use agent_settings::{AgentDockPosition, AgentSettings, CompletionMode, DefaultView};
 use anyhow::{Result, anyhow};
-use assistant_context_editor::{
-    AgentPanelDelegate, AssistantContext, ContextEditor, ContextEvent, ContextSummary,
-    SlashCommandCompletionProvider, humanize_token_count, make_lsp_adapter_delegate,
-    render_remaining_tokens,
-};
+use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
 use assistant_slash_command::SlashCommandWorkingSet;
 use assistant_tool::ToolWorkingSet;
-
-use assistant_context_editor::language_model_selector::ToggleModelSelector;
 use client::{UserStore, zed_urls};
 use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
 use fs::Fs;
@@ -56,25 +74,6 @@ use zed_actions::{
 };
 use zed_llm_client::{CompletionIntent, UsageLimit};
 
-use crate::{
-    AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
-    DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
-    NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
-    ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
-    active_thread::{self, ActiveThread, ActiveThreadEvent},
-    agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
-    agent_diff::AgentDiff,
-    message_editor::{MessageEditor, MessageEditorEvent},
-    thread_history::{HistoryEntryElement, ThreadHistory},
-    ui::AgentOnboardingModal,
-};
-use agent::{
-    Thread, ThreadError, ThreadEvent, ThreadId, ThreadSummary, TokenUsageRatio,
-    context_store::ContextStore,
-    history_store::{HistoryEntryId, HistoryStore},
-    thread_store::{TextThreadStore, ThreadStore},
-};
-
 const AGENT_PANEL_KEY: &str = "agent_panel";
 
 #[derive(Serialize, Deserialize)]
@@ -179,7 +178,7 @@ enum ActiveView {
         _subscriptions: Vec<gpui::Subscription>,
     },
     TextThread {
-        context_editor: Entity<ContextEditor>,
+        context_editor: Entity<TextThreadEditor>,
         title_editor: Entity<Editor>,
         buffer_search_bar: Entity<BufferSearchBar>,
         _subscriptions: Vec<gpui::Subscription>,
@@ -260,7 +259,7 @@ impl ActiveView {
     }
 
     pub fn prompt_editor(
-        context_editor: Entity<ContextEditor>,
+        context_editor: Entity<TextThreadEditor>,
         history_store: Entity<HistoryStore>,
         language_registry: Arc<LanguageRegistry>,
         window: &mut Window,
@@ -434,7 +433,7 @@ impl AgentPanel {
             let context_store = workspace
                 .update(cx, |workspace, cx| {
                     let project = workspace.project().clone();
-                    assistant_context_editor::ContextStore::new(
+                    assistant_context::ContextStore::new(
                         project,
                         prompt_builder.clone(),
                         slash_commands,
@@ -546,7 +545,7 @@ impl AgentPanel {
                     context_store.update(cx, |context_store, cx| context_store.create(cx));
                 let lsp_adapter_delegate = make_lsp_adapter_delegate(&project.clone(), cx).unwrap();
                 let context_editor = cx.new(|cx| {
-                    let mut editor = ContextEditor::for_context(
+                    let mut editor = TextThreadEditor::for_context(
                         context,
                         fs.clone(),
                         workspace.clone(),
@@ -841,7 +840,7 @@ impl AgentPanel {
             .flatten();
 
         let context_editor = cx.new(|cx| {
-            let mut editor = ContextEditor::for_context(
+            let mut editor = TextThreadEditor::for_context(
                 context,
                 self.fs.clone(),
                 self.workspace.clone(),
@@ -933,7 +932,7 @@ impl AgentPanel {
             .log_err()
             .flatten();
         let editor = cx.new(|cx| {
-            ContextEditor::for_context(
+            TextThreadEditor::for_context(
                 context,
                 self.fs.clone(),
                 self.workspace.clone(),
@@ -1321,7 +1320,7 @@ impl AgentPanel {
         });
     }
 
-    pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
+    pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
         match &self.active_view {
             ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
             _ => None,
@@ -2899,7 +2898,7 @@ impl AgentPanel {
 
     fn render_prompt_editor(
         &self,
-        context_editor: &Entity<ContextEditor>,
+        context_editor: &Entity<TextThreadEditor>,
         buffer_search_bar: &Entity<BufferSearchBar>,
         window: &mut Window,
         cx: &mut Context<Self>,
@@ -3026,7 +3025,7 @@ impl AgentPanel {
             }
             ActiveView::TextThread { context_editor, .. } => {
                 context_editor.update(cx, |context_editor, cx| {
-                    ContextEditor::insert_dragged_files(
+                    TextThreadEditor::insert_dragged_files(
                         context_editor,
                         paths,
                         added_worktrees,
@@ -3205,7 +3204,7 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
         workspace: &mut Workspace,
         _window: &mut Window,
         cx: &mut Context<Workspace>,
-    ) -> Option<Entity<ContextEditor>> {
+    ) -> Option<Entity<TextThreadEditor>> {
         let panel = workspace.panel::<AgentPanel>(cx)?;
         panel.read(cx).active_context_editor()
     }
@@ -3229,10 +3228,10 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
     fn open_remote_context(
         &self,
         _workspace: &mut Workspace,
-        _context_id: assistant_context_editor::ContextId,
+        _context_id: assistant_context::ContextId,
         _window: &mut Window,
         _cx: &mut Context<Workspace>,
-    ) -> Task<Result<Entity<ContextEditor>>> {
+    ) -> Task<Result<Entity<TextThreadEditor>>> {
         Task::ready(Err(anyhow!("opening remote context not implemented")))
     }
 

crates/agent_ui/src/agent_ui.rs 🔗

@@ -10,11 +10,16 @@ mod context_strip;
 mod debug;
 mod inline_assistant;
 mod inline_prompt_editor;
+mod language_model_selector;
+mod max_mode_tooltip;
 mod message_editor;
 mod profile_selector;
+mod slash_command;
+mod slash_command_picker;
 mod slash_command_settings;
 mod terminal_codegen;
 mod terminal_inline_assistant;
+mod text_thread_editor;
 mod thread_history;
 mod tool_compatibility;
 mod ui;
@@ -43,6 +48,7 @@ pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
 pub use crate::inline_assistant::InlineAssistant;
 use crate::slash_command_settings::SlashCommandSettings;
 pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
+pub use text_thread_editor::AgentPanelDelegate;
 pub use ui::preview::{all_agent_previews, get_agent_preview};
 
 actions!(
@@ -140,7 +146,7 @@ pub fn init(
     AgentSettings::register(cx);
     SlashCommandSettings::register(cx);
 
-    assistant_context_editor::init(client.clone(), cx);
+    assistant_context::init(client.clone(), cx);
     rules_library::init(cx);
     if !is_eval {
         // Initializing the language model from the user settings messes with the eval, so we only initialize them when

crates/agent_ui/src/context_picker/completion_provider.rs 🔗

@@ -73,7 +73,7 @@ fn search(
     recent_entries: Vec<RecentEntry>,
     prompt_store: Option<Entity<PromptStore>>,
     thread_store: Option<WeakEntity<ThreadStore>>,
-    text_thread_context_store: Option<WeakEntity<assistant_context_editor::ContextStore>>,
+    text_thread_context_store: Option<WeakEntity<assistant_context::ContextStore>>,
     workspace: Entity<Workspace>,
     cx: &mut App,
 ) -> Task<Vec<Match>> {

crates/agent_ui/src/inline_prompt_editor.rs 🔗

@@ -2,6 +2,7 @@ use crate::agent_model_selector::AgentModelSelector;
 use crate::buffer_codegen::BufferCodegen;
 use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider};
 use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
+use crate::language_model_selector::ToggleModelSelector;
 use crate::message_editor::{ContextCreasesAddon, extract_message_creases, insert_message_creases};
 use crate::terminal_codegen::TerminalCodegen;
 use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist, ModelUsageContext};
@@ -10,7 +11,6 @@ use agent::{
     context_store::ContextStore,
     thread_store::{TextThreadStore, ThreadStore},
 };
-use assistant_context_editor::language_model_selector::ToggleModelSelector;
 use client::ErrorExt;
 use collections::VecDeque;
 use db::kvp::Dismissable;

crates/agent_ui/src/message_editor.rs 🔗

@@ -3,6 +3,7 @@ use std::rc::Rc;
 use std::sync::Arc;
 
 use crate::agent_model_selector::AgentModelSelector;
+use crate::language_model_selector::ToggleModelSelector;
 use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip};
 use crate::ui::{
     MaxModeTooltip,
@@ -13,7 +14,6 @@ use agent::{
     context_store::ContextStoreEvent,
 };
 use agent_settings::{AgentSettings, CompletionMode};
-use assistant_context_editor::language_model_selector::ToggleModelSelector;
 use buffer_diff::BufferDiff;
 use client::UserStore;
 use collections::{HashMap, HashSet};

crates/assistant_context_editor/src/slash_command.rs → crates/agent_ui/src/slash_command.rs 🔗

@@ -1,4 +1,4 @@
-use crate::context_editor::ContextEditor;
+use crate::text_thread_editor::TextThreadEditor;
 use anyhow::Result;
 pub use assistant_slash_command::SlashCommand;
 use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet};
@@ -21,14 +21,14 @@ use workspace::Workspace;
 pub struct SlashCommandCompletionProvider {
     cancel_flag: Mutex<Arc<AtomicBool>>,
     slash_commands: Arc<SlashCommandWorkingSet>,
-    editor: Option<WeakEntity<ContextEditor>>,
+    editor: Option<WeakEntity<TextThreadEditor>>,
     workspace: Option<WeakEntity<Workspace>>,
 }
 
 impl SlashCommandCompletionProvider {
     pub fn new(
         slash_commands: Arc<SlashCommandWorkingSet>,
-        editor: Option<WeakEntity<ContextEditor>>,
+        editor: Option<WeakEntity<TextThreadEditor>>,
         workspace: Option<WeakEntity<Workspace>>,
     ) -> Self {
         Self {

crates/assistant_context_editor/src/slash_command_picker.rs → crates/agent_ui/src/slash_command_picker.rs 🔗

@@ -1,12 +1,10 @@
-use std::sync::Arc;
-
+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::*};
 
-use crate::context_editor::ContextEditor;
-
 #[derive(IntoElement)]
 pub(super) struct SlashCommandSelector<T, TT>
 where
@@ -14,7 +12,7 @@ where
     TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
 {
     working_set: Arc<SlashCommandWorkingSet>,
-    active_context_editor: WeakEntity<ContextEditor>,
+    active_context_editor: WeakEntity<TextThreadEditor>,
     trigger: T,
     tooltip: TT,
 }
@@ -49,7 +47,7 @@ impl AsRef<str> for SlashCommandEntry {
 pub(crate) struct SlashCommandDelegate {
     all_commands: Vec<SlashCommandEntry>,
     filtered_commands: Vec<SlashCommandEntry>,
-    active_context_editor: WeakEntity<ContextEditor>,
+    active_context_editor: WeakEntity<TextThreadEditor>,
     selected_index: usize,
 }
 
@@ -60,7 +58,7 @@ where
 {
     pub(crate) fn new(
         working_set: Arc<SlashCommandWorkingSet>,
-        active_context_editor: WeakEntity<ContextEditor>,
+        active_context_editor: WeakEntity<TextThreadEditor>,
         trigger: T,
         tooltip: TT,
     ) -> Self {

crates/assistant_context_editor/src/context_editor.rs → crates/agent_ui/src/text_thread_editor.rs 🔗

@@ -76,14 +76,11 @@ use workspace::{
     searchable::{SearchEvent, SearchableItem},
 };
 
-use crate::{
+use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};
+use assistant_context::{
     AssistantContext, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId,
     InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus,
-    ParsedSlashCommand, PendingSlashCommandStatus,
-};
-use crate::{
-    ThoughtProcessOutputSection, slash_command::SlashCommandCompletionProvider,
-    slash_command_picker,
+    ParsedSlashCommand, PendingSlashCommandStatus, ThoughtProcessOutputSection,
 };
 
 actions!(
@@ -131,7 +128,7 @@ pub trait AgentPanelDelegate {
         workspace: &mut Workspace,
         window: &mut Window,
         cx: &mut Context<Workspace>,
-    ) -> Option<Entity<ContextEditor>>;
+    ) -> Option<Entity<TextThreadEditor>>;
 
     fn open_saved_context(
         &self,
@@ -147,7 +144,7 @@ pub trait AgentPanelDelegate {
         context_id: ContextId,
         window: &mut Window,
         cx: &mut Context<Workspace>,
-    ) -> Task<Result<Entity<ContextEditor>>>;
+    ) -> Task<Result<Entity<TextThreadEditor>>>;
 
     fn quote_selection(
         &self,
@@ -176,7 +173,7 @@ struct GlobalAssistantPanelDelegate(Arc<dyn AgentPanelDelegate>);
 
 impl Global for GlobalAssistantPanelDelegate {}
 
-pub struct ContextEditor {
+pub struct TextThreadEditor {
     context: Entity<AssistantContext>,
     fs: Arc<dyn Fs>,
     slash_commands: Arc<SlashCommandWorkingSet>,
@@ -206,10 +203,24 @@ pub struct ContextEditor {
     language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
 }
 
-pub const DEFAULT_TAB_TITLE: &str = "New Chat";
 const MAX_TAB_TITLE_LEN: usize = 16;
 
-impl ContextEditor {
+impl TextThreadEditor {
+    pub fn init(cx: &mut App) {
+        workspace::FollowableViewRegistry::register::<TextThreadEditor>(cx);
+
+        cx.observe_new(
+            |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
+                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_context(
         context: Entity<AssistantContext>,
         fs: Arc<dyn Fs>,
@@ -1279,7 +1290,7 @@ impl ContextEditor {
     /// Returns either the selected text, or the content of the Markdown code
     /// block surrounding the cursor.
     fn get_selection_or_code_block(
-        context_editor_view: &Entity<ContextEditor>,
+        context_editor_view: &Entity<TextThreadEditor>,
         cx: &mut Context<Workspace>,
     ) -> Option<(String, bool)> {
         const CODE_FENCE_DELIMITER: &'static str = "```";
@@ -2029,7 +2040,7 @@ impl ContextEditor {
     /// Whether or not we should allow messages to be sent.
     /// Will return false if the selected provided has a configuration error or
     /// if the user has not accepted the terms of service for this provider.
-    fn sending_disabled(&self, cx: &mut Context<'_, ContextEditor>) -> bool {
+    fn sending_disabled(&self, cx: &mut Context<'_, TextThreadEditor>) -> bool {
         let model_registry = LanguageModelRegistry::read_global(cx);
         let Some(configuration_error) =
             model_registry.configuration_error(model_registry.default_model(), cx)
@@ -2546,10 +2557,10 @@ struct SelectedCreaseMetadata {
     crease: CreaseMetadata,
 }
 
-impl EventEmitter<EditorEvent> for ContextEditor {}
-impl EventEmitter<SearchEvent> for ContextEditor {}
+impl EventEmitter<EditorEvent> for TextThreadEditor {}
+impl EventEmitter<SearchEvent> for TextThreadEditor {}
 
-impl Render for ContextEditor {
+impl Render for TextThreadEditor {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let provider = LanguageModelRegistry::read_global(cx)
             .default_model()
@@ -2568,15 +2579,15 @@ impl Render for ContextEditor {
 
         v_flex()
             .key_context("ContextEditor")
-            .capture_action(cx.listener(ContextEditor::cancel))
-            .capture_action(cx.listener(ContextEditor::save))
-            .capture_action(cx.listener(ContextEditor::copy))
-            .capture_action(cx.listener(ContextEditor::cut))
-            .capture_action(cx.listener(ContextEditor::paste))
-            .capture_action(cx.listener(ContextEditor::cycle_message_role))
-            .capture_action(cx.listener(ContextEditor::confirm_command))
-            .on_action(cx.listener(ContextEditor::assist))
-            .on_action(cx.listener(ContextEditor::split))
+            .capture_action(cx.listener(TextThreadEditor::cancel))
+            .capture_action(cx.listener(TextThreadEditor::save))
+            .capture_action(cx.listener(TextThreadEditor::copy))
+            .capture_action(cx.listener(TextThreadEditor::cut))
+            .capture_action(cx.listener(TextThreadEditor::paste))
+            .capture_action(cx.listener(TextThreadEditor::cycle_message_role))
+            .capture_action(cx.listener(TextThreadEditor::confirm_command))
+            .on_action(cx.listener(TextThreadEditor::assist))
+            .on_action(cx.listener(TextThreadEditor::split))
             .on_action(move |_: &ToggleModelSelector, window, cx| {
                 language_model_selector.toggle(window, cx);
             })
@@ -2631,13 +2642,13 @@ impl Render for ContextEditor {
     }
 }
 
-impl Focusable for ContextEditor {
+impl Focusable for TextThreadEditor {
     fn focus_handle(&self, cx: &App) -> FocusHandle {
         self.editor.focus_handle(cx)
     }
 }
 
-impl Item for ContextEditor {
+impl Item for TextThreadEditor {
     type Event = editor::EditorEvent;
 
     fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {
@@ -2710,7 +2721,7 @@ impl Item for ContextEditor {
     }
 }
 
-impl SearchableItem for ContextEditor {
+impl SearchableItem for TextThreadEditor {
     type Match = <Editor as SearchableItem>::Match;
 
     fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@@ -2791,7 +2802,7 @@ impl SearchableItem for ContextEditor {
     }
 }
 
-impl FollowableItem for ContextEditor {
+impl FollowableItem for TextThreadEditor {
     fn remote_id(&self) -> Option<workspace::ViewId> {
         self.remote_id
     }
@@ -2914,21 +2925,14 @@ impl FollowableItem for ContextEditor {
 }
 
 pub struct ContextEditorToolbarItem {
-    active_context_editor: Option<WeakEntity<ContextEditor>>,
+    active_context_editor: Option<WeakEntity<TextThreadEditor>>,
     model_summary_editor: Entity<Editor>,
 }
 
-impl ContextEditorToolbarItem {
-    pub fn new(model_summary_editor: Entity<Editor>) -> Self {
-        Self {
-            active_context_editor: None,
-            model_summary_editor,
-        }
-    }
-}
+impl ContextEditorToolbarItem {}
 
 pub fn render_remaining_tokens(
-    context_editor: &Entity<ContextEditor>,
+    context_editor: &Entity<TextThreadEditor>,
     cx: &App,
 ) -> Option<impl IntoElement + use<>> {
     let context = &context_editor.read(cx).context;
@@ -3044,7 +3048,7 @@ impl ToolbarItemView for ContextEditorToolbarItem {
         cx: &mut Context<Self>,
     ) -> ToolbarItemLocation {
         self.active_context_editor = active_pane_item
-            .and_then(|item| item.act_as::<ContextEditor>(cx))
+            .and_then(|item| item.act_as::<TextThreadEditor>(cx))
             .map(|editor| editor.downgrade());
         cx.notify();
         if self.active_context_editor.is_none() {
@@ -3405,7 +3409,7 @@ mod tests {
         cx: &mut TestAppContext,
     ) -> (
         Entity<AssistantContext>,
-        Entity<ContextEditor>,
+        Entity<TextThreadEditor>,
         VisualTestContext,
     ) {
         cx.update(init_test);
@@ -3421,7 +3425,7 @@ mod tests {
         let context_editor = window
             .update(&mut cx, |_, window, cx| {
                 cx.new(|cx| {
-                    let editor = ContextEditor::for_context(
+                    let editor = TextThreadEditor::for_context(
                         context.clone(),
                         fs,
                         workspace.downgrade(),
@@ -3454,7 +3458,7 @@ mod tests {
     }
 
     fn assert_copy_paste_context_editor<T: editor::ToOffset>(
-        context_editor: &Entity<ContextEditor>,
+        context_editor: &Entity<TextThreadEditor>,
         range: Range<T>,
         expected_text: &str,
         cx: &mut VisualTestContext,

crates/assistant_context_editor/Cargo.toml → crates/assistant_context/Cargo.toml 🔗

@@ -1,5 +1,5 @@
 [package]
-name = "assistant_context_editor"
+name = "assistant_context"
 version = "0.1.0"
 edition.workspace = true
 publish.workspace = true
@@ -9,7 +9,7 @@ license = "GPL-3.0-or-later"
 workspace = true
 
 [lib]
-path = "src/assistant_context_editor.rs"
+path = "src/assistant_context.rs"
 
 [dependencies]
 agent_settings.workspace = true
@@ -21,27 +21,20 @@ client.workspace = true
 clock.workspace = true
 collections.workspace = true
 context_server.workspace = true
-editor.workspace = true
-feature_flags.workspace = true
 fs.workspace = true
 futures.workspace = true
 fuzzy.workspace = true
 gpui.workspace = true
-indexed_docs.workspace = true
 language.workspace = true
 language_model.workspace = true
 log.workspace = true
-multi_buffer.workspace = true
 open_ai.workspace = true
-ordered-float.workspace = true
 parking_lot.workspace = true
 paths.workspace = true
-picker.workspace = true
 project.workspace = true
 prompt_store.workspace = true
 proto.workspace = true
 regex.workspace = true
-rope.workspace = true
 rpc.workspace = true
 serde.workspace = true
 serde_json.workspace = true
@@ -50,21 +43,17 @@ smallvec.workspace = true
 smol.workspace = true
 telemetry_events.workspace = true
 text.workspace = true
-theme.workspace = true
 ui.workspace = true
 util.workspace = true
 uuid.workspace = true
 workspace-hack.workspace = true
 workspace.workspace = true
-zed_actions.workspace = true
 zed_llm_client.workspace = true
 
 [dev-dependencies]
 indoc.workspace = true
 language_model = { workspace = true, features = ["test-support"] }
-languages = { workspace = true, features = ["test-support"] }
 pretty_assertions.workspace = true
 rand.workspace = true
-tree-sitter-md.workspace = true
 unindent.workspace = true
 workspace = { workspace = true, features = ["test-support"] }

crates/assistant_context_editor/src/context.rs → crates/assistant_context/src/assistant_context.rs 🔗

@@ -1,5 +1,6 @@
 #[cfg(test)]
-mod context_tests;
+mod assistant_context_tests;
+mod context_store;
 
 use agent_settings::AgentSettings;
 use anyhow::{Context as _, Result, bail};
@@ -8,7 +9,7 @@ use assistant_slash_command::{
     SlashCommandResult, SlashCommandWorkingSet,
 };
 use assistant_slash_commands::FileCommandMetadata;
-use client::{self, proto, telemetry::Telemetry};
+use client::{self, Client, proto, telemetry::Telemetry};
 use clock::ReplicaId;
 use collections::{HashMap, HashSet};
 use fs::{Fs, RenameOptions};
@@ -47,6 +48,12 @@ use util::{ResultExt, TryFutureExt, post_inc};
 use uuid::Uuid;
 use zed_llm_client::CompletionIntent;
 
+pub use crate::context_store::*;
+
+pub fn init(client: Arc<Client>, _: &mut App) {
+    context_store::init(&client.into());
+}
+
 #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
 pub struct ContextId(String);
 

crates/assistant_context_editor/src/assistant_context_editor.rs 🔗

@@ -1,36 +0,0 @@
-mod context;
-mod context_editor;
-mod context_history;
-mod context_store;
-pub mod language_model_selector;
-mod max_mode_tooltip;
-mod slash_command;
-mod slash_command_picker;
-
-use std::sync::Arc;
-
-use client::Client;
-use gpui::{App, Context};
-use workspace::Workspace;
-
-pub use crate::context::*;
-pub use crate::context_editor::*;
-pub use crate::context_history::*;
-pub use crate::context_store::*;
-pub use crate::slash_command::*;
-
-pub fn init(client: Arc<Client>, cx: &mut App) {
-    context_store::init(&client.into());
-    workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
-
-    cx.observe_new(
-        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
-            workspace
-                .register_action(ContextEditor::quote_selection)
-                .register_action(ContextEditor::insert_selection)
-                .register_action(ContextEditor::copy_code)
-                .register_action(ContextEditor::handle_insert_dragged_files);
-        },
-    )
-    .detach();
-}

crates/assistant_context_editor/src/context_history.rs 🔗

@@ -1,271 +0,0 @@
-use std::sync::Arc;
-
-use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};
-use picker::{Picker, PickerDelegate};
-use project::Project;
-use ui::utils::{DateTimeType, format_distance_from_now};
-use ui::{Avatar, ListItem, ListItemSpacing, prelude::*};
-use workspace::{Item, Workspace};
-
-use crate::{
-    AgentPanelDelegate, ContextStore, DEFAULT_TAB_TITLE, RemoteContextMetadata,
-    SavedContextMetadata,
-};
-
-#[derive(Clone)]
-pub enum ContextMetadata {
-    Remote(RemoteContextMetadata),
-    Saved(SavedContextMetadata),
-}
-
-enum SavedContextPickerEvent {
-    Confirmed(ContextMetadata),
-}
-
-pub struct ContextHistory {
-    picker: Entity<Picker<SavedContextPickerDelegate>>,
-    _subscriptions: Vec<Subscription>,
-    workspace: WeakEntity<Workspace>,
-}
-
-impl ContextHistory {
-    pub fn new(
-        project: Entity<Project>,
-        context_store: Entity<ContextStore>,
-        workspace: WeakEntity<Workspace>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Self {
-        let picker = cx.new(|cx| {
-            Picker::uniform_list(
-                SavedContextPickerDelegate::new(project, context_store.clone()),
-                window,
-                cx,
-            )
-            .modal(false)
-            .max_height(None)
-        });
-
-        let subscriptions = vec![
-            cx.observe_in(&context_store, window, |this, _, window, cx| {
-                this.picker
-                    .update(cx, |picker, cx| picker.refresh(window, cx));
-            }),
-            cx.subscribe_in(&picker, window, Self::handle_picker_event),
-        ];
-
-        Self {
-            picker,
-            _subscriptions: subscriptions,
-            workspace,
-        }
-    }
-
-    fn handle_picker_event(
-        &mut self,
-        _: &Entity<Picker<SavedContextPickerDelegate>>,
-        event: &SavedContextPickerEvent,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let SavedContextPickerEvent::Confirmed(context) = event;
-
-        let Some(agent_panel_delegate) = <dyn AgentPanelDelegate>::try_global(cx) else {
-            return;
-        };
-
-        self.workspace
-            .update(cx, |workspace, cx| match context {
-                ContextMetadata::Remote(metadata) => {
-                    agent_panel_delegate
-                        .open_remote_context(workspace, metadata.id.clone(), window, cx)
-                        .detach_and_log_err(cx);
-                }
-                ContextMetadata::Saved(metadata) => {
-                    agent_panel_delegate
-                        .open_saved_context(workspace, metadata.path.clone(), window, cx)
-                        .detach_and_log_err(cx);
-                }
-            })
-            .ok();
-    }
-}
-
-impl Render for ContextHistory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-        div().size_full().child(self.picker.clone())
-    }
-}
-
-impl Focusable for ContextHistory {
-    fn focus_handle(&self, cx: &App) -> FocusHandle {
-        self.picker.focus_handle(cx)
-    }
-}
-
-impl EventEmitter<()> for ContextHistory {}
-
-impl Item for ContextHistory {
-    type Event = ();
-
-    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
-        "History".into()
-    }
-}
-
-struct SavedContextPickerDelegate {
-    store: Entity<ContextStore>,
-    project: Entity<Project>,
-    matches: Vec<ContextMetadata>,
-    selected_index: usize,
-}
-
-impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {}
-
-impl SavedContextPickerDelegate {
-    fn new(project: Entity<Project>, store: Entity<ContextStore>) -> Self {
-        Self {
-            project,
-            store,
-            matches: Vec::new(),
-            selected_index: 0,
-        }
-    }
-}
-
-impl PickerDelegate for SavedContextPickerDelegate {
-    type ListItem = ListItem;
-
-    fn match_count(&self) -> usize {
-        self.matches.len()
-    }
-
-    fn selected_index(&self) -> usize {
-        self.selected_index
-    }
-
-    fn set_selected_index(
-        &mut self,
-        ix: usize,
-        _window: &mut Window,
-        _cx: &mut Context<Picker<Self>>,
-    ) {
-        self.selected_index = ix;
-    }
-
-    fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
-        "Search...".into()
-    }
-
-    fn update_matches(
-        &mut self,
-        query: String,
-        _window: &mut Window,
-        cx: &mut Context<Picker<Self>>,
-    ) -> Task<()> {
-        let search = self.store.read(cx).search(query, cx);
-        cx.spawn(async move |this, cx| {
-            let matches = search.await;
-            this.update(cx, |this, cx| {
-                let host_contexts = this.delegate.store.read(cx).host_contexts();
-                this.delegate.matches = host_contexts
-                    .iter()
-                    .cloned()
-                    .map(ContextMetadata::Remote)
-                    .chain(matches.into_iter().map(ContextMetadata::Saved))
-                    .collect();
-                this.delegate.selected_index = 0;
-                cx.notify();
-            })
-            .ok();
-        })
-    }
-
-    fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
-        if let Some(metadata) = self.matches.get(self.selected_index) {
-            cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone()));
-        }
-    }
-
-    fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {}
-
-    fn render_match(
-        &self,
-        ix: usize,
-        selected: bool,
-        _window: &mut Window,
-        cx: &mut Context<Picker<Self>>,
-    ) -> Option<Self::ListItem> {
-        let context = self.matches.get(ix)?;
-        let item = match context {
-            ContextMetadata::Remote(context) => {
-                let host_user = self.project.read(cx).host().and_then(|collaborator| {
-                    self.project
-                        .read(cx)
-                        .user_store()
-                        .read(cx)
-                        .get_cached_user(collaborator.user_id)
-                });
-                div()
-                    .flex()
-                    .w_full()
-                    .justify_between()
-                    .gap_2()
-                    .child(
-                        h_flex().flex_1().overflow_x_hidden().child(
-                            Label::new(context.summary.clone().unwrap_or(DEFAULT_TAB_TITLE.into()))
-                                .size(LabelSize::Small),
-                        ),
-                    )
-                    .child(
-                        h_flex()
-                            .gap_2()
-                            .children(if let Some(host_user) = host_user {
-                                vec![
-                                    Avatar::new(host_user.avatar_uri.clone()).into_any_element(),
-                                    Label::new(format!("Shared by @{}", host_user.github_login))
-                                        .color(Color::Muted)
-                                        .size(LabelSize::Small)
-                                        .into_any_element(),
-                                ]
-                            } else {
-                                vec![
-                                    Label::new("Shared by host")
-                                        .color(Color::Muted)
-                                        .size(LabelSize::Small)
-                                        .into_any_element(),
-                                ]
-                            }),
-                    )
-            }
-            ContextMetadata::Saved(context) => div()
-                .flex()
-                .w_full()
-                .justify_between()
-                .gap_2()
-                .child(
-                    h_flex()
-                        .flex_1()
-                        .child(Label::new(context.title.clone()).size(LabelSize::Small))
-                        .overflow_x_hidden(),
-                )
-                .child(
-                    Label::new(format_distance_from_now(
-                        DateTimeType::Local(context.mtime),
-                        false,
-                        true,
-                        true,
-                    ))
-                    .color(Color::Muted)
-                    .size(LabelSize::Small),
-                ),
-        };
-        Some(
-            ListItem::new(ix)
-                .inset(true)
-                .spacing(ListItemSpacing::Sparse)
-                .toggle_state(selected)
-                .child(item),
-        )
-    }
-}

crates/collab/Cargo.toml 🔗

@@ -78,7 +78,7 @@ zed_llm_client.workspace = true
 
 [dev-dependencies]
 agent_settings.workspace = true
-assistant_context_editor.workspace = true
+assistant_context.workspace = true
 assistant_slash_command.workspace = true
 async-trait.workspace = true
 audio.workspace = true

crates/collab/src/tests/integration_tests.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
     },
 };
 use anyhow::{Result, anyhow};
-use assistant_context_editor::ContextStore;
+use assistant_context::ContextStore;
 use assistant_slash_command::SlashCommandWorkingSet;
 use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, assert_hunks};
 use call::{ActiveCall, ParticipantLocation, Room, room};

crates/collab/src/tests/test_server.rs 🔗

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

crates/zed/Cargo.toml 🔗

@@ -26,7 +26,6 @@ agent_settings.workspace = true
 anyhow.workspace = true
 askpass.workspace = true
 assets.workspace = true
-assistant_context_editor.workspace = true
 assistant_tool.workspace = true
 assistant_tools.workspace = true
 audio.workspace = true

crates/zed/src/zed.rs 🔗

@@ -9,11 +9,10 @@ mod quick_action_bar;
 #[cfg(target_os = "windows")]
 pub(crate) mod windows_only_instance;
 
-use agent_ui::AgentDiffToolbar;
+use agent_ui::{AgentDiffToolbar, AgentPanelDelegate};
 use anyhow::Context as _;
 pub use app_menus::*;
 use assets::Assets;
-use assistant_context_editor::AgentPanelDelegate;
 use breadcrumbs::Breadcrumbs;
 use client::zed_urls;
 use collections::VecDeque;