Remove `assistant` crate (#30168)

Marshall Bowers created

This PR removes the `assistant` crate, as it is no longer used.

Release Notes:

- N/A

Change summary

Cargo.lock                                        |   66 
Cargo.toml                                        |    2 
assets/keymaps/default-linux.json                 |   19 
assets/keymaps/default-macos.json                 |   19 
crates/agent/Cargo.toml                           |    2 
crates/agent/src/assistant.rs                     |  129 
crates/agent/src/slash_command_settings.rs        |    0 
crates/assistant/Cargo.toml                       |   88 
crates/assistant/LICENSE-GPL                      |    1 
crates/assistant/src/assistant.rs                 |  260 -
crates/assistant/src/assistant_configuration.rs   |  199 
crates/assistant/src/assistant_panel.rs           | 1450 -----
crates/assistant/src/inline_assistant.rs          | 4130 -----------------
crates/assistant/src/terminal_inline_assistant.rs | 1275 -----
crates/collab/Cargo.toml                          |    1 
crates/zed/Cargo.toml                             |    1 
crates/zed/src/main.rs                            |    6 
crates/zed/src/zed.rs                             |   36 
18 files changed, 143 insertions(+), 7,541 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -56,6 +56,7 @@ dependencies = [
  "assistant_context_editor",
  "assistant_settings",
  "assistant_slash_command",
+ "assistant_slash_commands",
  "assistant_tool",
  "async-watch",
  "buffer_diff",
@@ -78,6 +79,7 @@ dependencies = [
  "heed",
  "html_to_markdown",
  "http_client",
+ "indexed_docs",
  "indoc",
  "itertools 0.14.0",
  "jsonschema",
@@ -470,68 +472,6 @@ dependencies = [
  "workspace-hack",
 ]
 
-[[package]]
-name = "assistant"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "assistant_context_editor",
- "assistant_settings",
- "assistant_slash_command",
- "assistant_slash_commands",
- "assistant_tool",
- "async-watch",
- "client",
- "collections",
- "command_palette_hooks",
- "ctor",
- "db",
- "editor",
- "env_logger 0.11.8",
- "feature_flags",
- "fs",
- "futures 0.3.31",
- "gpui",
- "indexed_docs",
- "indoc",
- "language",
- "language_model",
- "language_model_selector",
- "languages",
- "log",
- "lsp",
- "menu",
- "multi_buffer",
- "parking_lot",
- "pretty_assertions",
- "project",
- "prompt_store",
- "proto",
- "rand 0.8.5",
- "rope",
- "rules_library",
- "schemars",
- "search",
- "serde",
- "serde_json_lenient",
- "settings",
- "smol",
- "streaming_diff",
- "telemetry",
- "telemetry_events",
- "terminal",
- "terminal_view",
- "text",
- "theme",
- "tree-sitter-md",
- "ui",
- "unindent",
- "util",
- "workspace",
- "workspace-hack",
- "zed_actions",
-]
-
 [[package]]
 name = "assistant_context_editor"
 version = "0.1.0"
@@ -3002,7 +2942,6 @@ name = "collab"
 version = "0.44.0"
 dependencies = [
  "anyhow",
- "assistant",
  "assistant_context_editor",
  "assistant_settings",
  "assistant_slash_command",
@@ -18702,7 +18641,6 @@ dependencies = [
  "ashpd",
  "askpass",
  "assets",
- "assistant",
  "assistant_context_editor",
  "assistant_settings",
  "assistant_tools",

Cargo.toml πŸ”—

@@ -6,7 +6,6 @@ members = [
     "crates/anthropic",
     "crates/askpass",
     "crates/assets",
-    "crates/assistant",
     "crates/assistant_context_editor",
     "crates/assistant_settings",
     "crates/assistant_slash_command",
@@ -214,7 +213,6 @@ ai = { path = "crates/ai" }
 anthropic = { path = "crates/anthropic" }
 askpass = { path = "crates/askpass" }
 assets = { path = "crates/assets" }
-assistant = { path = "crates/assistant" }
 assistant_context_editor = { path = "crates/assistant_context_editor" }
 assistant_settings = { path = "crates/assistant_settings" }
 assistant_slash_command = { path = "crates/assistant_slash_command" }

assets/keymaps/default-linux.json πŸ”—

@@ -213,21 +213,6 @@
       "ctrl-shift-n": "agent::RejectAll"
     }
   },
-  {
-    "context": "AssistantPanel",
-    "bindings": {
-      "ctrl-k c": "assistant::CopyCode",
-      "ctrl-shift-e": "project_panel::ToggleFocus",
-      "ctrl-g": "search::SelectNextMatch",
-      "ctrl-shift-g": "search::SelectPreviousMatch",
-      "ctrl-alt-/": "agent::ToggleModelSelector",
-      "ctrl-k h": "assistant::DeployHistory",
-      "ctrl-k l": "agent::OpenRulesLibrary",
-      "new": "assistant::NewChat",
-      "ctrl-t": "assistant::NewChat",
-      "ctrl-n": "assistant::NewChat"
-    }
-  },
   {
     "context": "ContextEditor > Editor",
     "bindings": {
@@ -713,8 +698,8 @@
   {
     "context": "PromptEditor",
     "bindings": {
-      "ctrl-[": "assistant::CyclePreviousInlineAssist",
-      "ctrl-]": "assistant::CycleNextInlineAssist",
+      "ctrl-[": "agent::CyclePreviousInlineAssist",
+      "ctrl-]": "agent::CycleNextInlineAssist",
       "ctrl-alt-e": "agent::RemoveAllContext"
     }
   },

assets/keymaps/default-macos.json πŸ”—

@@ -258,21 +258,6 @@
       "shift-ctrl-r": "agent::OpenAgentDiff"
     }
   },
-  {
-    "context": "AssistantPanel",
-    "use_key_equivalents": true,
-    "bindings": {
-      "cmd-k c": "assistant::CopyCode",
-      "cmd-shift-e": "project_panel::ToggleFocus",
-      "cmd-g": "search::SelectNextMatch",
-      "cmd-shift-g": "search::SelectPreviousMatch",
-      "cmd-alt-/": "agent::ToggleModelSelector",
-      "cmd-k h": "assistant::DeployHistory",
-      "cmd-k l": "agent::OpenRulesLibrary",
-      "cmd-t": "assistant::NewChat",
-      "cmd-n": "assistant::NewChat"
-    }
-  },
   {
     "context": "ContextEditor > Editor",
     "use_key_equivalents": true,
@@ -780,8 +765,8 @@
       "cmd-shift-a": "agent::ToggleContextPicker",
       "cmd-alt-/": "agent::ToggleModelSelector",
       "cmd-alt-e": "agent::RemoveAllContext",
-      "ctrl-[": "assistant::CyclePreviousInlineAssist",
-      "ctrl-]": "assistant::CycleNextInlineAssist"
+      "ctrl-[": "agent::CyclePreviousInlineAssist",
+      "ctrl-]": "agent::CycleNextInlineAssist"
     }
   },
   {

crates/agent/Cargo.toml πŸ”—

@@ -23,6 +23,7 @@ anyhow.workspace = true
 assistant_context_editor.workspace = true
 assistant_settings.workspace = true
 assistant_slash_command.workspace = true
+assistant_slash_commands.workspace = true
 assistant_tool.workspace = true
 async-watch.workspace = true
 buffer_diff.workspace = true
@@ -45,6 +46,7 @@ gpui.workspace = true
 heed.workspace = true
 html_to_markdown.workspace = true
 http_client.workspace = true
+indexed_docs.workspace = true
 itertools.workspace = true
 jsonschema.workspace = true
 language.workspace = true

crates/agent/src/assistant.rs πŸ”—

@@ -16,6 +16,7 @@ mod inline_assistant;
 mod inline_prompt_editor;
 mod message_editor;
 mod profile_selector;
+mod slash_command_settings;
 mod terminal_codegen;
 mod terminal_inline_assistant;
 mod thread;
@@ -27,15 +28,18 @@ mod ui;
 
 use std::sync::Arc;
 
-use assistant_settings::{AgentProfileId, AssistantSettings};
+use assistant_settings::{AgentProfileId, AssistantSettings, LanguageModelSelection};
+use assistant_slash_command::SlashCommandRegistry;
 use client::Client;
+use feature_flags::FeatureFlagAppExt as _;
 use fs::Fs;
 use gpui::{App, actions, impl_actions};
 use language::LanguageRegistry;
+use language_model::{LanguageModelId, LanguageModelProviderId, LanguageModelRegistry};
 use prompt_store::PromptBuilder;
 use schemars::JsonSchema;
 use serde::Deserialize;
-use settings::Settings as _;
+use settings::{Settings as _, SettingsStore};
 use thread::ThreadId;
 
 pub use crate::active_thread::ActiveThread;
@@ -43,6 +47,7 @@ use crate::assistant_configuration::{AddContextServerModal, ManageProfilesModal}
 pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate};
 pub use crate::context::{ContextLoadResult, LoadedContext};
 pub use crate::inline_assistant::InlineAssistant;
+use crate::slash_command_settings::SlashCommandSettings;
 pub use crate::thread::{Message, MessageSegment, Thread, ThreadEvent};
 pub use crate::thread_store::{TextThreadStore, ThreadStore};
 pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
@@ -114,10 +119,17 @@ pub fn init(
     cx: &mut App,
 ) {
     AssistantSettings::register(cx);
+    SlashCommandSettings::register(cx);
+
+    assistant_context_editor::init(client.clone(), cx);
+    rules_library::init(cx);
+    init_language_model_settings(cx);
+    assistant_slash_command::init(cx);
     thread_store::init(cx);
     assistant_panel::init(cx);
     context_server_configuration::init(language_registry, cx);
 
+    register_slash_commands(cx);
     inline_assistant::init(
         fs.clone(),
         prompt_builder.clone(),
@@ -130,6 +142,119 @@ pub fn init(
         client.telemetry().clone(),
         cx,
     );
+    indexed_docs::init(cx);
     cx.observe_new(AddContextServerModal::register).detach();
     cx.observe_new(ManageProfilesModal::register).detach();
 }
+
+fn init_language_model_settings(cx: &mut App) {
+    update_active_language_model_from_settings(cx);
+
+    cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
+        .detach();
+    cx.subscribe(
+        &LanguageModelRegistry::global(cx),
+        |_, event: &language_model::Event, cx| match event {
+            language_model::Event::ProviderStateChanged
+            | language_model::Event::AddedProvider(_)
+            | language_model::Event::RemovedProvider(_) => {
+                update_active_language_model_from_settings(cx);
+            }
+            _ => {}
+        },
+    )
+    .detach();
+}
+
+fn update_active_language_model_from_settings(cx: &mut App) {
+    let settings = AssistantSettings::get_global(cx);
+
+    fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
+        language_model::SelectedModel {
+            provider: LanguageModelProviderId::from(selection.provider.0.clone()),
+            model: LanguageModelId::from(selection.model.clone()),
+        }
+    }
+
+    let default = to_selected_model(&settings.default_model);
+    let inline_assistant = settings
+        .inline_assistant_model
+        .as_ref()
+        .map(to_selected_model);
+    let commit_message = settings
+        .commit_message_model
+        .as_ref()
+        .map(to_selected_model);
+    let thread_summary = settings
+        .thread_summary_model
+        .as_ref()
+        .map(to_selected_model);
+    let inline_alternatives = settings
+        .inline_alternatives
+        .iter()
+        .map(to_selected_model)
+        .collect::<Vec<_>>();
+
+    LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
+        registry.select_default_model(Some(&default), cx);
+        registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
+        registry.select_commit_message_model(commit_message.as_ref(), cx);
+        registry.select_thread_summary_model(thread_summary.as_ref(), cx);
+        registry.select_inline_alternative_models(inline_alternatives, cx);
+    });
+}
+
+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::CargoWorkspaceSlashCommand, 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::TerminalSlashCommand, true);
+    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::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
+        let slash_command_registry = slash_command_registry.clone();
+        move |is_enabled, _cx| {
+            if is_enabled {
+                slash_command_registry.register_command(
+                    assistant_slash_commands::StreamingExampleSlashCommand,
+                    false,
+                );
+            }
+        }
+    })
+    .detach();
+
+    update_slash_commands_from_settings(cx);
+    cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
+        .detach();
+}
+
+fn update_slash_commands_from_settings(cx: &mut App) {
+    let slash_command_registry = SlashCommandRegistry::global(cx);
+    let settings = SlashCommandSettings::get_global(cx);
+
+    if settings.docs.enabled {
+        slash_command_registry.register_command(assistant_slash_commands::DocsSlashCommand, true);
+    } else {
+        slash_command_registry.unregister_command(assistant_slash_commands::DocsSlashCommand);
+    }
+
+    if settings.cargo_workspace.enabled {
+        slash_command_registry
+            .register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
+    } else {
+        slash_command_registry
+            .unregister_command(assistant_slash_commands::CargoWorkspaceSlashCommand);
+    }
+}

crates/assistant/Cargo.toml πŸ”—

@@ -1,88 +0,0 @@
-[package]
-name = "assistant"
-version = "0.1.0"
-edition.workspace = true
-publish.workspace = true
-license = "GPL-3.0-or-later"
-
-[lints]
-workspace = true
-
-[lib]
-path = "src/assistant.rs"
-doctest = false
-
-[features]
-test-support = [
-    "editor/test-support",
-    "language/test-support",
-    "project/test-support",
-    "text/test-support",
-]
-
-[dependencies]
-anyhow.workspace = true
-assistant_context_editor.workspace = true
-assistant_settings.workspace = true
-assistant_slash_command.workspace = true
-assistant_slash_commands.workspace = true
-assistant_tool.workspace = true
-async-watch.workspace = true
-client.workspace = true
-collections.workspace = true
-command_palette_hooks.workspace = true
-db.workspace = true
-editor.workspace = true
-feature_flags.workspace = true
-fs.workspace = true
-futures.workspace = true
-gpui.workspace = true
-indexed_docs.workspace = true
-indoc.workspace = true
-language.workspace = true
-language_model.workspace = true
-language_model_selector.workspace = true
-log.workspace = true
-lsp.workspace = true
-menu.workspace = true
-multi_buffer.workspace = true
-parking_lot.workspace = true
-project.workspace = true
-rules_library.workspace = true
-prompt_store.workspace = true
-proto.workspace = true
-rope.workspace = true
-schemars.workspace = true
-search.workspace = true
-serde.workspace = true
-settings.workspace = true
-smol.workspace = true
-streaming_diff.workspace = true
-telemetry.workspace = true
-telemetry_events.workspace = true
-terminal.workspace = true
-terminal_view.workspace = true
-text.workspace = true
-theme.workspace = true
-ui.workspace = true
-util.workspace = true
-workspace.workspace = true
-zed_actions.workspace = true
-workspace-hack.workspace = true
-
-[dev-dependencies]
-ctor.workspace = true
-editor = { workspace = true, features = ["test-support"] }
-env_logger.workspace = true
-language = { workspace = true, features = ["test-support"] }
-language_model = { workspace = true, features = ["test-support"] }
-languages = { workspace = true, features = ["test-support"] }
-log.workspace = true
-pretty_assertions.workspace = true
-project = { workspace = true, features = ["test-support"] }
-rand.workspace = true
-serde_json_lenient.workspace = true
-terminal_view = { workspace = true, features = ["test-support"] }
-text = { workspace = true, features = ["test-support"] }
-tree-sitter-md.workspace = true
-unindent.workspace = true

crates/assistant/src/assistant.rs πŸ”—

@@ -1,260 +0,0 @@
-#![cfg_attr(target_os = "windows", allow(unused, dead_code))]
-
-mod assistant_configuration;
-pub mod assistant_panel;
-mod inline_assistant;
-pub mod slash_command_settings;
-mod terminal_inline_assistant;
-
-use std::sync::Arc;
-
-use assistant_settings::{AssistantSettings, LanguageModelSelection};
-use assistant_slash_command::SlashCommandRegistry;
-use client::Client;
-use command_palette_hooks::CommandPaletteFilter;
-use feature_flags::FeatureFlagAppExt;
-use fs::Fs;
-use gpui::{App, Global, ReadGlobal, UpdateGlobal, actions};
-use language_model::{
-    LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
-};
-use prompt_store::PromptBuilder;
-use serde::Deserialize;
-use settings::{Settings, SettingsStore};
-
-pub use crate::assistant_panel::{AssistantPanel, AssistantPanelEvent};
-pub(crate) use crate::inline_assistant::*;
-use crate::slash_command_settings::SlashCommandSettings;
-
-actions!(
-    assistant,
-    [
-        InsertActivePrompt,
-        DeployHistory,
-        NewChat,
-        CycleNextInlineAssist,
-        CyclePreviousInlineAssist
-    ]
-);
-
-const DEFAULT_CONTEXT_LINES: usize = 50;
-
-#[derive(Deserialize, Debug)]
-pub struct LanguageModelUsage {
-    pub prompt_tokens: u32,
-    pub completion_tokens: u32,
-    pub total_tokens: u32,
-}
-
-#[derive(Deserialize, Debug)]
-pub struct LanguageModelChoiceDelta {
-    pub index: u32,
-    pub delta: LanguageModelResponseMessage,
-    pub finish_reason: Option<String>,
-}
-
-/// The state pertaining to the Assistant.
-#[derive(Default)]
-struct Assistant {
-    /// Whether the Assistant is enabled.
-    enabled: bool,
-}
-
-impl Global for Assistant {}
-
-impl Assistant {
-    const NAMESPACE: &'static str = "assistant";
-
-    fn set_enabled(&mut self, enabled: bool, cx: &mut App) {
-        if self.enabled == enabled {
-            return;
-        }
-
-        self.enabled = enabled;
-
-        if !enabled {
-            CommandPaletteFilter::update_global(cx, |filter, _cx| {
-                filter.hide_namespace(Self::NAMESPACE);
-            });
-
-            return;
-        }
-
-        CommandPaletteFilter::update_global(cx, |filter, _cx| {
-            filter.show_namespace(Self::NAMESPACE);
-        });
-    }
-
-    pub fn enabled(cx: &App) -> bool {
-        Self::global(cx).enabled
-    }
-}
-
-pub fn init(
-    fs: Arc<dyn Fs>,
-    client: Arc<Client>,
-    prompt_builder: Arc<PromptBuilder>,
-    cx: &mut App,
-) {
-    cx.set_global(Assistant::default());
-    AssistantSettings::register(cx);
-    SlashCommandSettings::register(cx);
-
-    assistant_context_editor::init(client.clone(), cx);
-    rules_library::init(cx);
-    init_language_model_settings(cx);
-    assistant_slash_command::init(cx);
-    assistant_tool::init(cx);
-    assistant_panel::init(cx);
-
-    register_slash_commands(cx);
-    inline_assistant::init(
-        fs.clone(),
-        prompt_builder.clone(),
-        client.telemetry().clone(),
-        cx,
-    );
-    terminal_inline_assistant::init(
-        fs.clone(),
-        prompt_builder.clone(),
-        client.telemetry().clone(),
-        cx,
-    );
-    indexed_docs::init(cx);
-
-    CommandPaletteFilter::update_global(cx, |filter, _cx| {
-        filter.hide_namespace(Assistant::NAMESPACE);
-    });
-    Assistant::update_global(cx, |assistant, cx| {
-        let settings = AssistantSettings::get_global(cx);
-
-        assistant.set_enabled(settings.enabled, cx);
-    });
-    cx.observe_global::<SettingsStore>(|cx| {
-        Assistant::update_global(cx, |assistant, cx| {
-            let settings = AssistantSettings::get_global(cx);
-            assistant.set_enabled(settings.enabled, cx);
-        });
-    })
-    .detach();
-}
-
-fn init_language_model_settings(cx: &mut App) {
-    update_active_language_model_from_settings(cx);
-
-    cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
-        .detach();
-    cx.subscribe(
-        &LanguageModelRegistry::global(cx),
-        |_, event: &language_model::Event, cx| match event {
-            language_model::Event::ProviderStateChanged
-            | language_model::Event::AddedProvider(_)
-            | language_model::Event::RemovedProvider(_) => {
-                update_active_language_model_from_settings(cx);
-            }
-            _ => {}
-        },
-    )
-    .detach();
-}
-
-fn update_active_language_model_from_settings(cx: &mut App) {
-    let settings = AssistantSettings::get_global(cx);
-
-    fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
-        language_model::SelectedModel {
-            provider: LanguageModelProviderId::from(selection.provider.0.clone()),
-            model: LanguageModelId::from(selection.model.clone()),
-        }
-    }
-
-    let default = to_selected_model(&settings.default_model);
-    let inline_assistant = settings
-        .inline_assistant_model
-        .as_ref()
-        .map(to_selected_model);
-    let commit_message = settings
-        .commit_message_model
-        .as_ref()
-        .map(to_selected_model);
-    let thread_summary = settings
-        .thread_summary_model
-        .as_ref()
-        .map(to_selected_model);
-    let inline_alternatives = settings
-        .inline_alternatives
-        .iter()
-        .map(to_selected_model)
-        .collect::<Vec<_>>();
-
-    LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
-        registry.select_default_model(Some(&default), cx);
-        registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
-        registry.select_commit_message_model(commit_message.as_ref(), cx);
-        registry.select_thread_summary_model(thread_summary.as_ref(), cx);
-        registry.select_inline_alternative_models(inline_alternatives, cx);
-    });
-}
-
-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::CargoWorkspaceSlashCommand, 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::TerminalSlashCommand, true);
-    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::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
-        let slash_command_registry = slash_command_registry.clone();
-        move |is_enabled, _cx| {
-            if is_enabled {
-                slash_command_registry.register_command(
-                    assistant_slash_commands::StreamingExampleSlashCommand,
-                    false,
-                );
-            }
-        }
-    })
-    .detach();
-
-    update_slash_commands_from_settings(cx);
-    cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
-        .detach();
-}
-
-fn update_slash_commands_from_settings(cx: &mut App) {
-    let slash_command_registry = SlashCommandRegistry::global(cx);
-    let settings = SlashCommandSettings::get_global(cx);
-
-    if settings.docs.enabled {
-        slash_command_registry.register_command(assistant_slash_commands::DocsSlashCommand, true);
-    } else {
-        slash_command_registry.unregister_command(assistant_slash_commands::DocsSlashCommand);
-    }
-
-    if settings.cargo_workspace.enabled {
-        slash_command_registry
-            .register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
-    } else {
-        slash_command_registry
-            .unregister_command(assistant_slash_commands::CargoWorkspaceSlashCommand);
-    }
-}
-
-#[cfg(test)]
-#[ctor::ctor]
-fn init_logger() {
-    if std::env::var("RUST_LOG").is_ok() {
-        env_logger::init();
-    }
-}

crates/assistant/src/assistant_configuration.rs πŸ”—

@@ -1,199 +0,0 @@
-use std::sync::Arc;
-
-use collections::HashMap;
-use gpui::{AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription, canvas};
-use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
-use ui::{ElevationIndex, prelude::*};
-use workspace::Item;
-
-pub struct ConfigurationView {
-    focus_handle: FocusHandle,
-    configuration_views: HashMap<LanguageModelProviderId, AnyView>,
-    _registry_subscription: Subscription,
-}
-
-impl ConfigurationView {
-    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
-        let focus_handle = cx.focus_handle();
-
-        let registry_subscription = cx.subscribe_in(
-            &LanguageModelRegistry::global(cx),
-            window,
-            |this, _, event: &language_model::Event, window, cx| match event {
-                language_model::Event::AddedProvider(provider_id) => {
-                    let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
-                    if let Some(provider) = provider {
-                        this.add_configuration_view(&provider, window, cx);
-                    }
-                }
-                language_model::Event::RemovedProvider(provider_id) => {
-                    this.remove_configuration_view(provider_id);
-                }
-                _ => {}
-            },
-        );
-
-        let mut this = Self {
-            focus_handle,
-            configuration_views: HashMap::default(),
-            _registry_subscription: registry_subscription,
-        };
-        this.build_configuration_views(window, cx);
-        this
-    }
-
-    fn build_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        let providers = LanguageModelRegistry::read_global(cx).providers();
-        for provider in providers {
-            self.add_configuration_view(&provider, window, cx);
-        }
-    }
-
-    fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
-        self.configuration_views.remove(provider_id);
-    }
-
-    fn add_configuration_view(
-        &mut self,
-        provider: &Arc<dyn LanguageModelProvider>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let configuration_view = provider.configuration_view(window, cx);
-        self.configuration_views
-            .insert(provider.id(), configuration_view);
-    }
-
-    fn render_provider_view(
-        &mut self,
-        provider: &Arc<dyn LanguageModelProvider>,
-        cx: &mut Context<Self>,
-    ) -> Div {
-        let provider_id = provider.id().0.clone();
-        let provider_name = provider.name().0.clone();
-        let configuration_view = self.configuration_views.get(&provider.id()).cloned();
-
-        let open_new_context = cx.listener({
-            let provider = provider.clone();
-            move |_, _, _window, cx| {
-                cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
-                    provider.clone(),
-                ))
-            }
-        });
-
-        v_flex()
-            .gap_2()
-            .child(
-                h_flex()
-                    .justify_between()
-                    .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
-                    .when(provider.is_authenticated(cx), move |this| {
-                        this.child(
-                            h_flex().justify_end().child(
-                                Button::new(
-                                    SharedString::from(format!("new-context-{provider_id}")),
-                                    "Open New Chat",
-                                )
-                                .icon_position(IconPosition::Start)
-                                .icon(IconName::Plus)
-                                .style(ButtonStyle::Filled)
-                                .layer(ElevationIndex::ModalSurface)
-                                .on_click(open_new_context),
-                            ),
-                        )
-                    }),
-            )
-            .child(
-                div()
-                    .p(DynamicSpacing::Base08.rems(cx))
-                    .bg(cx.theme().colors().surface_background)
-                    .border_1()
-                    .border_color(cx.theme().colors().border_variant)
-                    .rounded_sm()
-                    .when(configuration_view.is_none(), |this| {
-                        this.child(div().child(Label::new(format!(
-                            "No configuration view for {}",
-                            provider_name
-                        ))))
-                    })
-                    .when_some(configuration_view, |this, configuration_view| {
-                        this.child(configuration_view)
-                    }),
-            )
-    }
-}
-
-impl Render for ConfigurationView {
-    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let providers = LanguageModelRegistry::read_global(cx).providers();
-        let provider_views = providers
-            .into_iter()
-            .map(|provider| self.render_provider_view(&provider, cx))
-            .collect::<Vec<_>>();
-
-        let mut element = v_flex()
-            .id("assistant-configuration-view")
-            .track_focus(&self.focus_handle(cx))
-            .bg(cx.theme().colors().editor_background)
-            .size_full()
-            .overflow_y_scroll()
-            .child(
-                v_flex()
-                    .p(DynamicSpacing::Base16.rems(cx))
-                    .border_b_1()
-                    .border_color(cx.theme().colors().border)
-                    .gap_1()
-                    .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
-                    .child(
-                        Label::new(
-                            "At least one LLM provider must be configured to use the Assistant.",
-                        )
-                        .color(Color::Muted),
-                    ),
-            )
-            .child(
-                v_flex()
-                    .p(DynamicSpacing::Base16.rems(cx))
-                    .mt_1()
-                    .gap_6()
-                    .flex_1()
-                    .children(provider_views),
-            )
-            .into_any();
-
-        // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
-        // because we couldn't the element to take up the size of the parent.
-        canvas(
-            move |bounds, window, cx| {
-                element.prepaint_as_root(bounds.origin, bounds.size.into(), window, cx);
-                element
-            },
-            |_, mut element, window, cx| {
-                element.paint(window, cx);
-            },
-        )
-        .flex_1()
-        .w_full()
-    }
-}
-
-pub enum ConfigurationViewEvent {
-    NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
-}
-
-impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
-
-impl Focusable for ConfigurationView {
-    fn focus_handle(&self, _: &App) -> FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-impl Item for ConfigurationView {
-    type Event = ConfigurationViewEvent;
-
-    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
-        "Configuration".into()
-    }
-}

crates/assistant/src/assistant_panel.rs πŸ”—

@@ -1,1450 +0,0 @@
-use crate::Assistant;
-use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent};
-use crate::{
-    DeployHistory, InlineAssistant, NewChat, terminal_inline_assistant::TerminalInlineAssistant,
-};
-use anyhow::{Result, anyhow};
-use assistant_context_editor::{
-    AssistantContext, AssistantPanelDelegate, ContextEditor, ContextEditorToolbarItem,
-    ContextEditorToolbarItemEvent, ContextHistory, ContextId, ContextStore, ContextStoreEvent,
-    DEFAULT_TAB_TITLE, InsertDraggedFiles, SlashCommandCompletionProvider,
-    make_lsp_adapter_delegate,
-};
-use assistant_settings::{AssistantDockPosition, AssistantSettings};
-use assistant_slash_command::SlashCommandWorkingSet;
-use client::{Client, Status, proto};
-use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
-use fs::Fs;
-use gpui::{
-    Action, App, AsyncWindowContext, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable,
-    InteractiveElement, IntoElement, ParentElement, Pixels, Render, Styled, Subscription, Task,
-    UpdateGlobal, WeakEntity, prelude::*,
-};
-use language::LanguageRegistry;
-use language_model::{
-    AuthenticateError, ConfiguredModel, LanguageModelProviderId, LanguageModelRegistry,
-};
-use project::Project;
-use prompt_store::{PromptBuilder, UserPromptId};
-use rules_library::{RulesLibrary, open_rules_library};
-
-use search::{BufferSearchBar, buffer_search::DivRegistrar};
-use settings::{Settings, update_settings_file};
-use smol::stream::StreamExt;
-
-use std::ops::Range;
-use std::path::Path;
-use std::{ops::ControlFlow, sync::Arc};
-use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
-use ui::{ContextMenu, PopoverMenu, Tooltip, prelude::*};
-use util::{ResultExt, maybe};
-use workspace::DraggedTab;
-use workspace::{
-    DraggedSelection, Pane, ToggleZoom, Workspace,
-    dock::{DockPosition, Panel, PanelEvent},
-    pane,
-};
-use zed_actions::assistant::{InlineAssist, OpenRulesLibrary, ShowConfiguration, ToggleFocus};
-
-pub fn init(cx: &mut App) {
-    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)
-                .register_action(AssistantPanel::show_configuration)
-                .register_action(AssistantPanel::create_new_context)
-                .register_action(AssistantPanel::restart_context_servers)
-                .register_action(|workspace, action: &OpenRulesLibrary, window, cx| {
-                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
-                        workspace.focus_panel::<AssistantPanel>(window, cx);
-                        panel.update(cx, |panel, cx| {
-                            panel.deploy_rules_library(action, window, cx)
-                        });
-                    }
-                });
-        },
-    )
-    .detach();
-
-    cx.observe_new(
-        |terminal_panel: &mut TerminalPanel, _, cx: &mut Context<TerminalPanel>| {
-            terminal_panel.set_assistant_enabled(Assistant::enabled(cx), cx);
-        },
-    )
-    .detach();
-}
-
-pub enum AssistantPanelEvent {
-    ContextEdited,
-}
-
-pub struct AssistantPanel {
-    pane: Entity<Pane>,
-    workspace: WeakEntity<Workspace>,
-    width: Option<Pixels>,
-    height: Option<Pixels>,
-    project: Entity<Project>,
-    context_store: Entity<ContextStore>,
-    languages: Arc<LanguageRegistry>,
-    fs: Arc<dyn Fs>,
-    subscriptions: Vec<Subscription>,
-    model_summary_editor: Entity<Editor>,
-    authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
-    configuration_subscription: Option<Subscription>,
-    client_status: Option<client::Status>,
-    watch_client_status: Option<Task<()>>,
-    pub(crate) show_zed_ai_notice: bool,
-}
-
-enum InlineAssistTarget {
-    Editor(Entity<Editor>, bool),
-    Terminal(Entity<TerminalView>),
-}
-
-impl AssistantPanel {
-    pub fn load(
-        workspace: WeakEntity<Workspace>,
-        prompt_builder: Arc<PromptBuilder>,
-        cx: AsyncWindowContext,
-    ) -> Task<Result<Entity<Self>>> {
-        cx.spawn(async move |cx| {
-            let slash_commands = Arc::new(SlashCommandWorkingSet::default());
-            let context_store = workspace
-                .update(cx, |workspace, cx| {
-                    let project = workspace.project().clone();
-                    ContextStore::new(project, prompt_builder.clone(), slash_commands, cx)
-                })?
-                .await?;
-
-            workspace.update_in(cx, |workspace, window, cx| {
-                // TODO: deserialize state.
-                cx.new(|cx| Self::new(workspace, context_store, window, cx))
-            })
-        })
-    }
-
-    fn new(
-        workspace: &Workspace,
-        context_store: Entity<ContextStore>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Self {
-        let model_summary_editor = cx.new(|cx| Editor::single_line(window, cx));
-        let context_editor_toolbar =
-            cx.new(|_| ContextEditorToolbarItem::new(model_summary_editor.clone()));
-
-        let pane = cx.new(|cx| {
-            let mut pane = Pane::new(
-                workspace.weak_handle(),
-                workspace.project().clone(),
-                Default::default(),
-                None,
-                NewChat.boxed_clone(),
-                window,
-                cx,
-            );
-
-            let project = workspace.project().clone();
-            pane.set_custom_drop_handle(cx, move |_, dropped_item, window, cx| {
-                let action = maybe!({
-                    if project.read(cx).is_local() {
-                        if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() {
-                            return Some(InsertDraggedFiles::ExternalFiles(paths.paths().to_vec()));
-                        }
-                    }
-
-                    let project_paths = if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>()
-                    {
-                        if tab.pane == cx.entity() {
-                            return None;
-                        }
-                        let item = tab.pane.read(cx).item_for_index(tab.ix);
-                        Some(
-                            item.and_then(|item| item.project_path(cx))
-                                .into_iter()
-                                .collect::<Vec<_>>(),
-                        )
-                    } else if let Some(selection) = dropped_item.downcast_ref::<DraggedSelection>()
-                    {
-                        Some(
-                            selection
-                                .items()
-                                .filter_map(|item| {
-                                    project.read(cx).path_for_entry(item.entry_id, cx)
-                                })
-                                .collect::<Vec<_>>(),
-                        )
-                    } else {
-                        None
-                    }?;
-
-                    Some(InsertDraggedFiles::ProjectPaths(project_paths))
-                });
-
-                if let Some(action) = action {
-                    window.dispatch_action(action.boxed_clone(), cx);
-                }
-
-                ControlFlow::Break(())
-            });
-
-            pane.set_can_navigate(true, cx);
-            pane.display_nav_history_buttons(None);
-            pane.set_should_display_tab_bar(|_, _| true);
-            pane.set_render_tab_bar_buttons(cx, move |pane, _window, cx| {
-                let focus_handle = pane.focus_handle(cx);
-                let left_children = IconButton::new("history", IconName::HistoryRerun)
-                    .icon_size(IconSize::Small)
-                    .on_click(cx.listener({
-                        let focus_handle = focus_handle.clone();
-                        move |_, _, window, cx| {
-                            focus_handle.focus(window);
-                            window.dispatch_action(DeployHistory.boxed_clone(), cx)
-                        }
-                    }))
-                    .tooltip({
-                        let focus_handle = focus_handle.clone();
-                        move |window, cx| {
-                            Tooltip::for_action_in(
-                                "Open History",
-                                &DeployHistory,
-                                &focus_handle,
-                                window,
-                                cx,
-                            )
-                        }
-                    })
-                    .toggle_state(
-                        pane.active_item()
-                            .map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
-                    );
-                let _pane = cx.entity().clone();
-                let right_children = h_flex()
-                    .gap(DynamicSpacing::Base02.rems(cx))
-                    .child(
-                        IconButton::new("new-chat", IconName::Plus)
-                            .icon_size(IconSize::Small)
-                            .on_click(cx.listener(|_, _, window, cx| {
-                                window.dispatch_action(NewChat.boxed_clone(), cx)
-                            }))
-                            .tooltip(move |window, cx| {
-                                Tooltip::for_action_in(
-                                    "New Chat",
-                                    &NewChat,
-                                    &focus_handle,
-                                    window,
-                                    cx,
-                                )
-                            }),
-                    )
-                    .child(
-                        PopoverMenu::new("assistant-panel-popover-menu")
-                            .trigger_with_tooltip(
-                                IconButton::new("menu", IconName::EllipsisVertical)
-                                    .icon_size(IconSize::Small),
-                                Tooltip::text("Toggle Assistant Menu"),
-                            )
-                            .menu(move |window, cx| {
-                                let zoom_label = if _pane.read(cx).is_zoomed() {
-                                    "Zoom Out"
-                                } else {
-                                    "Zoom In"
-                                };
-                                let focus_handle = _pane.focus_handle(cx);
-                                Some(ContextMenu::build(window, cx, move |menu, _, _| {
-                                    menu.context(focus_handle.clone())
-                                        .action("New Chat", Box::new(NewChat))
-                                        .action("History", Box::new(DeployHistory))
-                                        .action(
-                                            "Rules Library",
-                                            Box::new(OpenRulesLibrary::default()),
-                                        )
-                                        .action("Configure", Box::new(ShowConfiguration))
-                                        .action(zoom_label, Box::new(ToggleZoom))
-                                }))
-                            }),
-                    )
-                    .into_any_element()
-                    .into();
-
-                (Some(left_children.into_any_element()), right_children)
-            });
-            pane.toolbar().update(cx, |toolbar, cx| {
-                toolbar.add_item(context_editor_toolbar.clone(), window, cx);
-                toolbar.add_item(
-                    cx.new(|cx| {
-                        BufferSearchBar::new(
-                            Some(workspace.project().read(cx).languages().clone()),
-                            window,
-                            cx,
-                        )
-                    }),
-                    window,
-                    cx,
-                )
-            });
-            pane
-        });
-
-        let subscriptions = vec![
-            cx.observe(&pane, |_, _, cx| cx.notify()),
-            cx.subscribe_in(&pane, window, Self::handle_pane_event),
-            cx.subscribe(&context_editor_toolbar, Self::handle_toolbar_event),
-            cx.subscribe(&model_summary_editor, Self::handle_summary_editor_event),
-            cx.subscribe_in(&context_store, window, Self::handle_context_store_event),
-            cx.subscribe_in(
-                &LanguageModelRegistry::global(cx),
-                window,
-                |this, _, event: &language_model::Event, window, cx| match event {
-                    language_model::Event::DefaultModelChanged
-                    | language_model::Event::InlineAssistantModelChanged
-                    | language_model::Event::CommitMessageModelChanged
-                    | language_model::Event::ThreadSummaryModelChanged => {
-                        this.completion_provider_changed(window, cx);
-                    }
-                    language_model::Event::ProviderStateChanged => {
-                        this.ensure_authenticated(window, cx);
-                        cx.notify()
-                    }
-                    language_model::Event::AddedProvider(_)
-                    | language_model::Event::RemovedProvider(_) => {
-                        this.ensure_authenticated(window, cx);
-                    }
-                },
-            ),
-        ];
-
-        let watch_client_status = Self::watch_client_status(workspace.client().clone(), window, cx);
-
-        Self {
-            pane,
-            workspace: workspace.weak_handle(),
-            width: None,
-            height: None,
-            project: workspace.project().clone(),
-            context_store,
-            languages: workspace.app_state().languages.clone(),
-            fs: workspace.app_state().fs.clone(),
-            subscriptions,
-            model_summary_editor,
-            authenticate_provider_task: None,
-            configuration_subscription: None,
-            client_status: None,
-            watch_client_status: Some(watch_client_status),
-            show_zed_ai_notice: false,
-        }
-    }
-
-    pub fn toggle_focus(
-        workspace: &mut Workspace,
-        _: &ToggleFocus,
-        window: &mut Window,
-        cx: &mut Context<Workspace>,
-    ) {
-        if workspace
-            .panel::<Self>(cx)
-            .is_some_and(|panel| panel.read(cx).enabled(cx))
-        {
-            workspace.toggle_panel_focus::<Self>(window, cx);
-        }
-    }
-
-    fn watch_client_status(
-        client: Arc<Client>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Task<()> {
-        let mut status_rx = client.status();
-
-        cx.spawn_in(window, async move |this, cx| {
-            while let Some(status) = status_rx.next().await {
-                this.update(cx, |this, cx| {
-                    if this.client_status.is_none()
-                        || this
-                            .client_status
-                            .map_or(false, |old_status| old_status != status)
-                    {
-                        this.update_zed_ai_notice_visibility(status, cx);
-                    }
-                    this.client_status = Some(status);
-                })
-                .log_err();
-            }
-            this.update(cx, |this, _cx| this.watch_client_status = None)
-                .log_err();
-        })
-    }
-
-    fn handle_pane_event(
-        &mut self,
-        pane: &Entity<Pane>,
-        event: &pane::Event,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let update_model_summary = match event {
-            pane::Event::Remove { .. } => {
-                cx.emit(PanelEvent::Close);
-                false
-            }
-            pane::Event::ZoomIn => {
-                cx.emit(PanelEvent::ZoomIn);
-                false
-            }
-            pane::Event::ZoomOut => {
-                cx.emit(PanelEvent::ZoomOut);
-                false
-            }
-
-            pane::Event::AddItem { item } => {
-                self.workspace
-                    .update(cx, |workspace, cx| {
-                        item.added_to_pane(workspace, self.pane.clone(), window, cx)
-                    })
-                    .ok();
-                true
-            }
-
-            pane::Event::ActivateItem { local, .. } => {
-                if *local {
-                    self.workspace
-                        .update(cx, |workspace, cx| {
-                            workspace.unfollow_in_pane(&pane, window, cx);
-                        })
-                        .ok();
-                }
-                cx.emit(AssistantPanelEvent::ContextEdited);
-                true
-            }
-            pane::Event::RemovedItem { .. } => {
-                let has_configuration_view = self
-                    .pane
-                    .read(cx)
-                    .items_of_type::<ConfigurationView>()
-                    .next()
-                    .is_some();
-
-                if !has_configuration_view {
-                    self.configuration_subscription = None;
-                }
-
-                cx.emit(AssistantPanelEvent::ContextEdited);
-                true
-            }
-
-            _ => false,
-        };
-
-        if update_model_summary {
-            if let Some(editor) = self.active_context_editor(cx) {
-                self.show_updated_summary(&editor, window, cx)
-            }
-        }
-    }
-
-    fn handle_summary_editor_event(
-        &mut self,
-        model_summary_editor: Entity<Editor>,
-        event: &EditorEvent,
-        cx: &mut Context<Self>,
-    ) {
-        if matches!(event, EditorEvent::Edited { .. }) {
-            if let Some(context_editor) = self.active_context_editor(cx) {
-                let new_summary = model_summary_editor.read(cx).text(cx);
-                context_editor.update(cx, |context_editor, cx| {
-                    context_editor.context().update(cx, |context, cx| {
-                        if context.summary().is_none()
-                            && (new_summary == DEFAULT_TAB_TITLE || new_summary.trim().is_empty())
-                        {
-                            return;
-                        }
-                        context.set_custom_summary(new_summary, cx)
-                    });
-                });
-            }
-        }
-    }
-
-    fn update_zed_ai_notice_visibility(&mut self, client_status: Status, cx: &mut Context<Self>) {
-        let model = LanguageModelRegistry::read_global(cx).default_model();
-
-        // If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is
-        // the provider, we want to show a nudge to sign in.
-        let show_zed_ai_notice =
-            client_status.is_signed_out() && model.map_or(true, |model| model.is_provided_by_zed());
-
-        self.show_zed_ai_notice = show_zed_ai_notice;
-        cx.notify();
-    }
-
-    fn handle_toolbar_event(
-        &mut self,
-        _: Entity<ContextEditorToolbarItem>,
-        _: &ContextEditorToolbarItemEvent,
-        cx: &mut Context<Self>,
-    ) {
-        if let Some(context_editor) = self.active_context_editor(cx) {
-            context_editor.update(cx, |context_editor, cx| {
-                context_editor.context().update(cx, |context, cx| {
-                    context.summarize(true, cx);
-                })
-            })
-        }
-    }
-
-    fn handle_context_store_event(
-        &mut self,
-        _context_store: &Entity<ContextStore>,
-        event: &ContextStoreEvent,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let ContextStoreEvent::ContextCreated(context_id) = event;
-        let Some(context) = self
-            .context_store
-            .read(cx)
-            .loaded_context_for_id(&context_id, cx)
-        else {
-            log::error!("no context found with ID: {}", context_id.to_proto());
-            return;
-        };
-        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
-            .log_err()
-            .flatten();
-
-        let editor = cx.new(|cx| {
-            let mut editor = ContextEditor::for_context(
-                context,
-                self.fs.clone(),
-                self.workspace.clone(),
-                self.project.clone(),
-                lsp_adapter_delegate,
-                window,
-                cx,
-            );
-            editor.insert_default_prompt(window, cx);
-            editor
-        });
-
-        self.show_context(editor.clone(), window, cx);
-    }
-
-    fn completion_provider_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        if let Some(editor) = self.active_context_editor(cx) {
-            editor.update(cx, |active_context, cx| {
-                active_context
-                    .context()
-                    .update(cx, |context, cx| context.completion_provider_changed(cx))
-            })
-        }
-
-        let Some(new_provider_id) = LanguageModelRegistry::read_global(cx)
-            .default_model()
-            .map(|default| default.provider.id())
-        else {
-            return;
-        };
-
-        if self
-            .authenticate_provider_task
-            .as_ref()
-            .map_or(true, |(old_provider_id, _)| {
-                *old_provider_id != new_provider_id
-            })
-        {
-            self.authenticate_provider_task = None;
-            self.ensure_authenticated(window, cx);
-        }
-
-        if let Some(status) = self.client_status {
-            self.update_zed_ai_notice_visibility(status, cx);
-        }
-    }
-
-    fn ensure_authenticated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        if self.is_authenticated(cx) {
-            return;
-        }
-
-        let Some(ConfiguredModel { provider, .. }) =
-            LanguageModelRegistry::read_global(cx).default_model()
-        else {
-            return;
-        };
-
-        let load_credentials = self.authenticate(cx);
-
-        if self.authenticate_provider_task.is_none() {
-            self.authenticate_provider_task = Some((
-                provider.id(),
-                cx.spawn_in(window, async move |this, cx| {
-                    if let Some(future) = load_credentials {
-                        let _ = future.await;
-                    }
-                    this.update(cx, |this, _cx| {
-                        this.authenticate_provider_task = None;
-                    })
-                    .log_err();
-                }),
-            ));
-        }
-    }
-
-    pub fn inline_assist(
-        workspace: &mut Workspace,
-        action: &InlineAssist,
-        window: &mut Window,
-        cx: &mut Context<Workspace>,
-    ) {
-        let Some(assistant_panel) = workspace
-            .panel::<AssistantPanel>(cx)
-            .filter(|panel| panel.read(cx).enabled(cx))
-        else {
-            return;
-        };
-
-        let Some(inline_assist_target) =
-            Self::resolve_inline_assist_target(workspace, &assistant_panel, window, cx)
-        else {
-            return;
-        };
-
-        let initial_prompt = action.prompt.clone();
-
-        if assistant_panel.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
-            match inline_assist_target {
-                InlineAssistTarget::Editor(active_editor, include_context) => {
-                    InlineAssistant::update_global(cx, |assistant, cx| {
-                        assistant.assist(
-                            &active_editor,
-                            Some(cx.entity().downgrade()),
-                            include_context.then_some(&assistant_panel),
-                            initial_prompt,
-                            window,
-                            cx,
-                        )
-                    })
-                }
-                InlineAssistTarget::Terminal(active_terminal) => {
-                    TerminalInlineAssistant::update_global(cx, |assistant, cx| {
-                        assistant.assist(
-                            &active_terminal,
-                            Some(cx.entity().downgrade()),
-                            Some(&assistant_panel),
-                            initial_prompt,
-                            window,
-                            cx,
-                        )
-                    })
-                }
-            }
-        } else {
-            let assistant_panel = assistant_panel.downgrade();
-            cx.spawn_in(window, async move |workspace, cx| {
-                let Some(task) =
-                    assistant_panel.update(cx, |assistant, cx| assistant.authenticate(cx))?
-                else {
-                    let answer = cx
-                        .prompt(
-                            gpui::PromptLevel::Warning,
-                            "No language model provider configured",
-                            None,
-                            &["Configure", "Cancel"],
-                        )
-                        .await
-                        .ok();
-                    if let Some(answer) = answer {
-                        if answer == 0 {
-                            cx.update(|window, cx| {
-                                window.dispatch_action(Box::new(ShowConfiguration), cx)
-                            })
-                            .ok();
-                        }
-                    }
-                    return Ok(());
-                };
-                task.await?;
-                if assistant_panel.update(cx, |panel, cx| panel.is_authenticated(cx))? {
-                    cx.update(|window, cx| match inline_assist_target {
-                        InlineAssistTarget::Editor(active_editor, include_context) => {
-                            let assistant_panel = if include_context {
-                                assistant_panel.upgrade()
-                            } else {
-                                None
-                            };
-                            InlineAssistant::update_global(cx, |assistant, cx| {
-                                assistant.assist(
-                                    &active_editor,
-                                    Some(workspace),
-                                    assistant_panel.as_ref(),
-                                    initial_prompt,
-                                    window,
-                                    cx,
-                                )
-                            })
-                        }
-                        InlineAssistTarget::Terminal(active_terminal) => {
-                            TerminalInlineAssistant::update_global(cx, |assistant, cx| {
-                                assistant.assist(
-                                    &active_terminal,
-                                    Some(workspace),
-                                    assistant_panel.upgrade().as_ref(),
-                                    initial_prompt,
-                                    window,
-                                    cx,
-                                )
-                            })
-                        }
-                    })?
-                } else {
-                    workspace.update_in(cx, |workspace, window, cx| {
-                        workspace.focus_panel::<AssistantPanel>(window, cx)
-                    })?;
-                }
-
-                anyhow::Ok(())
-            })
-            .detach_and_log_err(cx)
-        }
-    }
-
-    fn resolve_inline_assist_target(
-        workspace: &mut Workspace,
-        assistant_panel: &Entity<AssistantPanel>,
-        window: &mut Window,
-        cx: &mut App,
-    ) -> Option<InlineAssistTarget> {
-        if let Some(terminal_panel) = workspace.panel::<TerminalPanel>(cx) {
-            if terminal_panel
-                .read(cx)
-                .focus_handle(cx)
-                .contains_focused(window, cx)
-            {
-                if let Some(terminal_view) = terminal_panel.read(cx).pane().and_then(|pane| {
-                    pane.read(cx)
-                        .active_item()
-                        .and_then(|t| t.downcast::<TerminalView>())
-                }) {
-                    return Some(InlineAssistTarget::Terminal(terminal_view));
-                }
-            }
-        }
-        let context_editor =
-            assistant_panel
-                .read(cx)
-                .active_context_editor(cx)
-                .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(context_editor) = context_editor {
-            Some(InlineAssistTarget::Editor(context_editor, false))
-        } else if let Some(workspace_editor) = workspace
-            .active_item(cx)
-            .and_then(|item| item.act_as::<Editor>(cx))
-        {
-            Some(InlineAssistTarget::Editor(workspace_editor, true))
-        } else if let Some(terminal_view) = workspace
-            .active_item(cx)
-            .and_then(|item| item.act_as::<TerminalView>(cx))
-        {
-            Some(InlineAssistTarget::Terminal(terminal_view))
-        } else {
-            None
-        }
-    }
-
-    pub fn create_new_context(
-        workspace: &mut Workspace,
-        _: &NewChat,
-        window: &mut Window,
-        cx: &mut Context<Workspace>,
-    ) {
-        if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
-            let did_create_context = panel
-                .update(cx, |panel, cx| {
-                    panel.new_context(window, cx)?;
-
-                    Some(())
-                })
-                .is_some();
-            if did_create_context {
-                ContextEditor::quote_selection(workspace, &Default::default(), window, cx);
-            }
-        }
-    }
-
-    pub fn new_context(
-        &mut self,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Option<Entity<ContextEditor>> {
-        let project = self.project.read(cx);
-        if project.is_via_collab() {
-            let task = self
-                .context_store
-                .update(cx, |store, cx| store.create_remote_context(cx));
-
-            cx.spawn_in(window, async move |this, cx| {
-                let context = task.await?;
-
-                this.update_in(cx, |this, window, cx| {
-                    let workspace = this.workspace.clone();
-                    let project = this.project.clone();
-                    let lsp_adapter_delegate =
-                        make_lsp_adapter_delegate(&project, cx).log_err().flatten();
-
-                    let fs = this.fs.clone();
-                    let project = this.project.clone();
-
-                    let editor = cx.new(|cx| {
-                        ContextEditor::for_context(
-                            context,
-                            fs,
-                            workspace,
-                            project,
-                            lsp_adapter_delegate,
-                            window,
-                            cx,
-                        )
-                    });
-
-                    this.show_context(editor, window, cx);
-
-                    anyhow::Ok(())
-                })??;
-
-                anyhow::Ok(())
-            })
-            .detach_and_log_err(cx);
-
-            None
-        } else {
-            let context = self.context_store.update(cx, |store, cx| store.create(cx));
-            let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
-                .log_err()
-                .flatten();
-
-            let editor = cx.new(|cx| {
-                let mut editor = ContextEditor::for_context(
-                    context,
-                    self.fs.clone(),
-                    self.workspace.clone(),
-                    self.project.clone(),
-                    lsp_adapter_delegate,
-                    window,
-                    cx,
-                );
-                editor.insert_default_prompt(window, cx);
-                editor
-            });
-
-            self.show_context(editor.clone(), window, cx);
-            let workspace = self.workspace.clone();
-            cx.spawn_in(window, async move |_, cx| {
-                workspace
-                    .update_in(cx, |workspace, window, cx| {
-                        workspace.focus_panel::<AssistantPanel>(window, cx);
-                    })
-                    .ok();
-            })
-            .detach();
-            Some(editor)
-        }
-    }
-
-    fn show_context(
-        &mut self,
-        context_editor: Entity<ContextEditor>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let focus = self.focus_handle(cx).contains_focused(window, cx);
-        let prev_len = self.pane.read(cx).items_len();
-        self.pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(context_editor.clone()),
-                focus,
-                focus,
-                None,
-                window,
-                cx,
-            )
-        });
-
-        if prev_len != self.pane.read(cx).items_len() {
-            self.subscriptions.push(cx.subscribe_in(
-                &context_editor,
-                window,
-                Self::handle_context_editor_event,
-            ));
-        }
-
-        self.show_updated_summary(&context_editor, window, cx);
-
-        cx.emit(AssistantPanelEvent::ContextEdited);
-        cx.notify();
-    }
-
-    fn show_updated_summary(
-        &self,
-        context_editor: &Entity<ContextEditor>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        context_editor.update(cx, |context_editor, cx| {
-            let new_summary = context_editor.title(cx).to_string();
-            self.model_summary_editor.update(cx, |summary_editor, cx| {
-                if summary_editor.text(cx) != new_summary {
-                    summary_editor.set_text(new_summary, window, cx);
-                }
-            });
-        });
-    }
-
-    fn handle_context_editor_event(
-        &mut self,
-        context_editor: &Entity<ContextEditor>,
-        event: &EditorEvent,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        match event {
-            EditorEvent::TitleChanged => {
-                self.show_updated_summary(&context_editor, window, cx);
-                cx.notify()
-            }
-            EditorEvent::Edited { .. } => {
-                self.workspace
-                    .update(cx, |workspace, cx| {
-                        let is_via_ssh = workspace
-                            .project()
-                            .update(cx, |project, _| project.is_via_ssh());
-
-                        workspace
-                            .client()
-                            .telemetry()
-                            .log_edit_event("assistant panel", is_via_ssh);
-                    })
-                    .log_err();
-                cx.emit(AssistantPanelEvent::ContextEdited)
-            }
-            _ => {}
-        }
-    }
-
-    fn show_configuration(
-        workspace: &mut Workspace,
-        _: &ShowConfiguration,
-        window: &mut Window,
-        cx: &mut Context<Workspace>,
-    ) {
-        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
-            return;
-        };
-
-        if !panel.focus_handle(cx).contains_focused(window, cx) {
-            workspace.toggle_panel_focus::<AssistantPanel>(window, cx);
-        }
-
-        panel.update(cx, |this, cx| {
-            this.show_configuration_tab(window, cx);
-        })
-    }
-
-    fn show_configuration_tab(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        let configuration_item_ix = self
-            .pane
-            .read(cx)
-            .items()
-            .position(|item| item.downcast::<ConfigurationView>().is_some());
-
-        if let Some(configuration_item_ix) = configuration_item_ix {
-            self.pane.update(cx, |pane, cx| {
-                pane.activate_item(configuration_item_ix, true, true, window, cx);
-            });
-        } else {
-            let configuration = cx.new(|cx| ConfigurationView::new(window, cx));
-            self.configuration_subscription = Some(cx.subscribe_in(
-                &configuration,
-                window,
-                |this, _, event: &ConfigurationViewEvent, window, cx| match event {
-                    ConfigurationViewEvent::NewProviderContextEditor(provider) => {
-                        if LanguageModelRegistry::read_global(cx)
-                            .default_model()
-                            .map_or(true, |default| default.provider.id() != provider.id())
-                        {
-                            if let Some(model) = provider.default_model(cx) {
-                                update_settings_file::<AssistantSettings>(
-                                    this.fs.clone(),
-                                    cx,
-                                    move |settings, _| settings.set_model(model),
-                                );
-                            }
-                        }
-
-                        this.new_context(window, cx);
-                    }
-                },
-            ));
-            self.pane.update(cx, |pane, cx| {
-                pane.add_item(Box::new(configuration), true, true, None, window, cx);
-            });
-        }
-    }
-
-    fn deploy_history(&mut self, _: &DeployHistory, window: &mut Window, cx: &mut Context<Self>) {
-        let history_item_ix = self
-            .pane
-            .read(cx)
-            .items()
-            .position(|item| item.downcast::<ContextHistory>().is_some());
-
-        if let Some(history_item_ix) = history_item_ix {
-            self.pane.update(cx, |pane, cx| {
-                pane.activate_item(history_item_ix, true, true, window, cx);
-            });
-        } else {
-            let history = cx.new(|cx| {
-                ContextHistory::new(
-                    self.project.clone(),
-                    self.context_store.clone(),
-                    self.workspace.clone(),
-                    window,
-                    cx,
-                )
-            });
-            self.pane.update(cx, |pane, cx| {
-                pane.add_item(Box::new(history), true, true, None, window, cx);
-            });
-        }
-    }
-
-    fn deploy_rules_library(
-        &mut self,
-        action: &OpenRulesLibrary,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        open_rules_library(
-            self.languages.clone(),
-            Box::new(PromptLibraryInlineAssist),
-            Arc::new(|| {
-                Box::new(SlashCommandCompletionProvider::new(
-                    Arc::new(SlashCommandWorkingSet::default()),
-                    None,
-                    None,
-                ))
-            }),
-            action
-                .prompt_to_select
-                .map(|uuid| UserPromptId(uuid).into()),
-            cx,
-        )
-        .detach_and_log_err(cx);
-    }
-
-    pub(crate) fn active_context_editor(&self, cx: &App) -> Option<Entity<ContextEditor>> {
-        self.pane
-            .read(cx)
-            .active_item()?
-            .downcast::<ContextEditor>()
-    }
-
-    pub fn active_context(&self, cx: &App) -> Option<Entity<AssistantContext>> {
-        Some(self.active_context_editor(cx)?.read(cx).context().clone())
-    }
-
-    pub fn open_saved_context(
-        &mut self,
-        path: Arc<Path>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Task<Result<()>> {
-        let existing_context = self.pane.read(cx).items().find_map(|item| {
-            item.downcast::<ContextEditor>()
-                .filter(|editor| editor.read(cx).context().read(cx).path() == Some(&path))
-        });
-        if let Some(existing_context) = existing_context {
-            return cx.spawn_in(window, async move |this, cx| {
-                this.update_in(cx, |this, window, cx| {
-                    this.show_context(existing_context, window, cx)
-                })
-            });
-        }
-
-        let context = self
-            .context_store
-            .update(cx, |store, cx| store.open_local_context(path.clone(), cx));
-        let fs = self.fs.clone();
-        let project = self.project.clone();
-        let workspace = self.workspace.clone();
-
-        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
-
-        cx.spawn_in(window, async move |this, cx| {
-            let context = context.await?;
-            this.update_in(cx, |this, window, cx| {
-                let editor = cx.new(|cx| {
-                    ContextEditor::for_context(
-                        context,
-                        fs,
-                        workspace,
-                        project,
-                        lsp_adapter_delegate,
-                        window,
-                        cx,
-                    )
-                });
-                this.show_context(editor, window, cx);
-                anyhow::Ok(())
-            })??;
-            Ok(())
-        })
-    }
-
-    pub fn open_remote_context(
-        &mut self,
-        id: ContextId,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Task<Result<Entity<ContextEditor>>> {
-        let existing_context = self.pane.read(cx).items().find_map(|item| {
-            item.downcast::<ContextEditor>()
-                .filter(|editor| *editor.read(cx).context().read(cx).id() == id)
-        });
-        if let Some(existing_context) = existing_context {
-            return cx.spawn_in(window, async move |this, cx| {
-                this.update_in(cx, |this, window, cx| {
-                    this.show_context(existing_context.clone(), window, cx)
-                })?;
-                Ok(existing_context)
-            });
-        }
-
-        let context = self
-            .context_store
-            .update(cx, |store, cx| store.open_remote_context(id, cx));
-        let fs = self.fs.clone();
-        let workspace = self.workspace.clone();
-        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
-            .log_err()
-            .flatten();
-
-        cx.spawn_in(window, async move |this, cx| {
-            let context = context.await?;
-            this.update_in(cx, |this, window, cx| {
-                let editor = cx.new(|cx| {
-                    ContextEditor::for_context(
-                        context,
-                        fs,
-                        workspace,
-                        this.project.clone(),
-                        lsp_adapter_delegate,
-                        window,
-                        cx,
-                    )
-                });
-                this.show_context(editor.clone(), window, cx);
-                anyhow::Ok(editor)
-            })?
-        })
-    }
-
-    fn is_authenticated(&mut self, cx: &mut Context<Self>) -> bool {
-        LanguageModelRegistry::read_global(cx)
-            .default_model()
-            .map_or(false, |default| default.provider.is_authenticated(cx))
-    }
-
-    fn authenticate(
-        &mut self,
-        cx: &mut Context<Self>,
-    ) -> Option<Task<Result<(), AuthenticateError>>> {
-        LanguageModelRegistry::read_global(cx)
-            .default_model()
-            .map_or(None, |default| Some(default.provider.authenticate(cx)))
-    }
-
-    fn restart_context_servers(
-        workspace: &mut Workspace,
-        _action: &project::context_server_store::Restart,
-        _: &mut Window,
-        cx: &mut Context<Workspace>,
-    ) {
-        workspace
-            .project()
-            .read(cx)
-            .context_server_store()
-            .update(cx, |store, cx| {
-                for server in store.running_servers() {
-                    store.restart_server(&server.id(), cx).log_err();
-                }
-            });
-    }
-}
-
-impl Render for AssistantPanel {
-    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let mut registrar = DivRegistrar::new(
-            |panel, _, cx| {
-                panel
-                    .pane
-                    .read(cx)
-                    .toolbar()
-                    .read(cx)
-                    .item_of_type::<BufferSearchBar>()
-            },
-            cx,
-        );
-        BufferSearchBar::register(&mut registrar);
-        let registrar = registrar.into_div();
-
-        v_flex()
-            .key_context("AssistantPanel")
-            .size_full()
-            .on_action(cx.listener(|this, _: &NewChat, window, cx| {
-                this.new_context(window, cx);
-            }))
-            .on_action(cx.listener(|this, _: &ShowConfiguration, window, cx| {
-                this.show_configuration_tab(window, cx)
-            }))
-            .on_action(cx.listener(AssistantPanel::deploy_history))
-            .on_action(cx.listener(AssistantPanel::deploy_rules_library))
-            .child(registrar.size_full().child(self.pane.clone()))
-            .into_any_element()
-    }
-}
-
-impl Panel for AssistantPanel {
-    fn persistent_name() -> &'static str {
-        "AssistantPanel"
-    }
-
-    fn position(&self, _: &Window, cx: &App) -> DockPosition {
-        match AssistantSettings::get_global(cx).dock {
-            AssistantDockPosition::Left => DockPosition::Left,
-            AssistantDockPosition::Bottom => DockPosition::Bottom,
-            AssistantDockPosition::Right => DockPosition::Right,
-        }
-    }
-
-    fn position_is_valid(&self, _: DockPosition) -> bool {
-        true
-    }
-
-    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
-        settings::update_settings_file::<AssistantSettings>(
-            self.fs.clone(),
-            cx,
-            move |settings, _| {
-                let dock = match position {
-                    DockPosition::Left => AssistantDockPosition::Left,
-                    DockPosition::Bottom => AssistantDockPosition::Bottom,
-                    DockPosition::Right => AssistantDockPosition::Right,
-                };
-                settings.set_dock(dock);
-            },
-        );
-    }
-
-    fn size(&self, window: &Window, cx: &App) -> Pixels {
-        let settings = AssistantSettings::get_global(cx);
-        match self.position(window, cx) {
-            DockPosition::Left | DockPosition::Right => {
-                self.width.unwrap_or(settings.default_width)
-            }
-            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
-        }
-    }
-
-    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
-        match self.position(window, cx) {
-            DockPosition::Left | DockPosition::Right => self.width = size,
-            DockPosition::Bottom => self.height = size,
-        }
-        cx.notify();
-    }
-
-    fn is_zoomed(&self, _: &Window, cx: &App) -> bool {
-        self.pane.read(cx).is_zoomed()
-    }
-
-    fn set_zoomed(&mut self, zoomed: bool, _: &mut Window, cx: &mut Context<Self>) {
-        self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
-    }
-
-    fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {
-        if active {
-            if self.pane.read(cx).items_len() == 0 {
-                self.new_context(window, cx);
-            }
-
-            self.ensure_authenticated(window, cx);
-        }
-    }
-
-    fn pane(&self) -> Option<Entity<Pane>> {
-        Some(self.pane.clone())
-    }
-
-    fn remote_id() -> Option<proto::PanelId> {
-        Some(proto::PanelId::AssistantPanel)
-    }
-
-    fn icon(&self, _: &Window, cx: &App) -> Option<IconName> {
-        (self.enabled(cx) && AssistantSettings::get_global(cx).button)
-            .then_some(IconName::ZedAssistant)
-    }
-
-    fn icon_tooltip(&self, _: &Window, _: &App) -> Option<&'static str> {
-        Some("Assistant Panel")
-    }
-
-    fn toggle_action(&self) -> Box<dyn Action> {
-        Box::new(ToggleFocus)
-    }
-
-    fn activation_priority(&self) -> u32 {
-        4
-    }
-
-    fn enabled(&self, cx: &App) -> bool {
-        Assistant::enabled(cx)
-    }
-}
-
-impl EventEmitter<PanelEvent> for AssistantPanel {}
-impl EventEmitter<AssistantPanelEvent> for AssistantPanel {}
-
-impl Focusable for AssistantPanel {
-    fn focus_handle(&self, cx: &App) -> FocusHandle {
-        self.pane.focus_handle(cx)
-    }
-}
-
-struct PromptLibraryInlineAssist;
-
-impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
-    fn assist(
-        &self,
-        prompt_editor: &Entity<Editor>,
-        initial_prompt: Option<String>,
-        window: &mut Window,
-        cx: &mut Context<RulesLibrary>,
-    ) {
-        InlineAssistant::update_global(cx, |assistant, cx| {
-            assistant.assist(&prompt_editor, None, None, initial_prompt, window, cx)
-        })
-    }
-
-    fn focus_assistant_panel(
-        &self,
-        workspace: &mut Workspace,
-        window: &mut Window,
-        cx: &mut Context<Workspace>,
-    ) -> bool {
-        workspace
-            .focus_panel::<AssistantPanel>(window, cx)
-            .is_some()
-    }
-}
-
-pub struct ConcreteAssistantPanelDelegate;
-
-impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
-    fn active_context_editor(
-        &self,
-        workspace: &mut Workspace,
-        _window: &mut Window,
-        cx: &mut Context<Workspace>,
-    ) -> Option<Entity<ContextEditor>> {
-        let panel = workspace.panel::<AssistantPanel>(cx)?;
-        panel.read(cx).active_context_editor(cx)
-    }
-
-    fn open_saved_context(
-        &self,
-        workspace: &mut Workspace,
-        path: Arc<Path>,
-        window: &mut Window,
-        cx: &mut Context<Workspace>,
-    ) -> Task<Result<()>> {
-        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
-            return Task::ready(Err(anyhow!("no Assistant panel found")));
-        };
-
-        panel.update(cx, |panel, cx| panel.open_saved_context(path, window, cx))
-    }
-
-    fn open_remote_context(
-        &self,
-        workspace: &mut Workspace,
-        context_id: ContextId,
-        window: &mut Window,
-        cx: &mut Context<Workspace>,
-    ) -> Task<Result<Entity<ContextEditor>>> {
-        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
-            return Task::ready(Err(anyhow!("no Assistant panel found")));
-        };
-
-        panel.update(cx, |panel, cx| {
-            panel.open_remote_context(context_id, window, cx)
-        })
-    }
-
-    fn quote_selection(
-        &self,
-        workspace: &mut Workspace,
-        selection_ranges: Vec<Range<Anchor>>,
-        buffer: Entity<MultiBuffer>,
-        window: &mut Window,
-        cx: &mut Context<Workspace>,
-    ) {
-        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
-            return;
-        };
-
-        if !panel.focus_handle(cx).contains_focused(window, cx) {
-            workspace.toggle_panel_focus::<AssistantPanel>(window, cx);
-        }
-
-        let snapshot = buffer.read(cx).snapshot(cx);
-        let selection_ranges = selection_ranges
-            .into_iter()
-            .map(|range| range.to_point(&snapshot))
-            .collect::<Vec<_>>();
-
-        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(context) = panel
-                    .active_context_editor(cx)
-                    .or_else(|| panel.new_context(window, cx))
-                {
-                    context.update(cx, |context, cx| {
-                        context.quote_ranges(selection_ranges, snapshot, window, cx)
-                    });
-                };
-            });
-        });
-    }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum WorkflowAssistStatus {
-    Pending,
-    Confirmed,
-    Done,
-    Idle,
-}

crates/assistant/src/inline_assistant.rs πŸ”—

@@ -1,4130 +0,0 @@
-use crate::{
-    Assistant, AssistantPanel, AssistantPanelEvent, CycleNextInlineAssist,
-    CyclePreviousInlineAssist,
-};
-use anyhow::{Context as _, Result, anyhow};
-use assistant_context_editor::{RequestType, humanize_token_count};
-use assistant_settings::AssistantSettings;
-use client::{ErrorExt, telemetry::Telemetry};
-use collections::{HashMap, HashSet, VecDeque, hash_map};
-use editor::{
-    Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode,
-    EditorStyle, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
-    actions::{MoveDown, MoveUp, SelectAll},
-    display_map::{
-        BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, EditorMargins,
-        RenderBlock, ToDisplayPoint,
-    },
-};
-use feature_flags::{FeatureFlagAppExt as _, ZedProFeatureFlag};
-use fs::Fs;
-use futures::{
-    SinkExt, Stream, StreamExt, TryStreamExt as _,
-    channel::mpsc,
-    future::{BoxFuture, LocalBoxFuture},
-    join,
-};
-use gpui::{
-    AnyElement, App, ClickEvent, Context, CursorStyle, Entity, EventEmitter, FocusHandle,
-    Focusable, FontWeight, Global, HighlightStyle, Subscription, Task, TextStyle, UpdateGlobal,
-    WeakEntity, Window, anchored, deferred, point,
-};
-use language::{Buffer, IndentKind, Point, Selection, TransactionId, line_diff};
-use language_model::{
-    ConfiguredModel, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
-    LanguageModelRequestMessage, LanguageModelTextStream, Role, report_assistant_event,
-};
-use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
-use multi_buffer::MultiBufferRow;
-use parking_lot::Mutex;
-use project::{CodeAction, LspAction, ProjectTransaction};
-use prompt_store::PromptBuilder;
-use rope::Rope;
-use settings::{Settings, SettingsStore, update_settings_file};
-use smol::future::FutureExt;
-use std::{
-    cmp,
-    future::{self, Future},
-    iter, mem,
-    ops::{Range, RangeInclusive},
-    pin::Pin,
-    rc::Rc,
-    sync::Arc,
-    task::{self, Poll},
-    time::{Duration, Instant},
-};
-use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
-use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
-use terminal_view::terminal_panel::TerminalPanel;
-use text::{OffsetRangeExt, ToPoint as _};
-use theme::ThemeSettings;
-use ui::{
-    CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip, prelude::*, text_for_action,
-};
-use util::{RangeExt, ResultExt};
-use workspace::{ItemHandle, Toast, Workspace, notifications::NotificationId};
-
-pub fn init(
-    fs: Arc<dyn Fs>,
-    prompt_builder: Arc<PromptBuilder>,
-    telemetry: Arc<Telemetry>,
-    cx: &mut App,
-) {
-    cx.set_global(InlineAssistant::new(fs, prompt_builder, telemetry));
-    // Don't register now that the Agent is released.
-    if false {
-        cx.observe_new(|_, window, cx| {
-            let Some(window) = window else {
-                return;
-            };
-            let workspace = cx.entity().clone();
-            InlineAssistant::update_global(cx, |inline_assistant, cx| {
-                inline_assistant.register_workspace(&workspace, window, cx)
-            });
-        })
-        .detach();
-    }
-}
-
-const PROMPT_HISTORY_MAX_LEN: usize = 20;
-
-pub struct InlineAssistant {
-    next_assist_id: InlineAssistId,
-    next_assist_group_id: InlineAssistGroupId,
-    assists: HashMap<InlineAssistId, InlineAssist>,
-    assists_by_editor: HashMap<WeakEntity<Editor>, EditorInlineAssists>,
-    assist_groups: HashMap<InlineAssistGroupId, InlineAssistGroup>,
-    confirmed_assists: HashMap<InlineAssistId, Entity<CodegenAlternative>>,
-    prompt_history: VecDeque<String>,
-    prompt_builder: Arc<PromptBuilder>,
-    telemetry: Arc<Telemetry>,
-    fs: Arc<dyn Fs>,
-}
-
-impl Global for InlineAssistant {}
-
-impl InlineAssistant {
-    pub fn new(
-        fs: Arc<dyn Fs>,
-        prompt_builder: Arc<PromptBuilder>,
-        telemetry: Arc<Telemetry>,
-    ) -> Self {
-        Self {
-            next_assist_id: InlineAssistId::default(),
-            next_assist_group_id: InlineAssistGroupId::default(),
-            assists: HashMap::default(),
-            assists_by_editor: HashMap::default(),
-            assist_groups: HashMap::default(),
-            confirmed_assists: HashMap::default(),
-            prompt_history: VecDeque::default(),
-            prompt_builder,
-            telemetry,
-            fs,
-        }
-    }
-
-    pub fn register_workspace(
-        &mut self,
-        workspace: &Entity<Workspace>,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        window
-            .subscribe(workspace, cx, |workspace, event, window, cx| {
-                Self::update_global(cx, |this, cx| {
-                    this.handle_workspace_event(workspace, event, window, cx)
-                });
-            })
-            .detach();
-
-        let workspace = workspace.downgrade();
-        cx.observe_global::<SettingsStore>(move |cx| {
-            let Some(workspace) = workspace.upgrade() else {
-                return;
-            };
-            let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
-                return;
-            };
-            let enabled = AssistantSettings::get_global(cx).enabled;
-            terminal_panel.update(cx, |terminal_panel, cx| {
-                terminal_panel.set_assistant_enabled(enabled, cx)
-            });
-        })
-        .detach();
-    }
-
-    fn handle_workspace_event(
-        &mut self,
-        workspace: Entity<Workspace>,
-        event: &workspace::Event,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        match event {
-            workspace::Event::UserSavedItem { item, .. } => {
-                // When the user manually saves an editor, automatically accepts all finished transformations.
-                if let Some(editor) = item.upgrade().and_then(|item| item.act_as::<Editor>(cx)) {
-                    if let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) {
-                        for assist_id in editor_assists.assist_ids.clone() {
-                            let assist = &self.assists[&assist_id];
-                            if let CodegenStatus::Done = assist.codegen.read(cx).status(cx) {
-                                self.finish_assist(assist_id, false, window, cx)
-                            }
-                        }
-                    }
-                }
-            }
-            workspace::Event::ItemAdded { item } => {
-                self.register_workspace_item(&workspace, item.as_ref(), window, cx);
-            }
-            _ => (),
-        }
-    }
-
-    fn register_workspace_item(
-        &mut self,
-        workspace: &Entity<Workspace>,
-        item: &dyn ItemHandle,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        let is_assistant2_enabled = true;
-
-        if let Some(editor) = item.act_as::<Editor>(cx) {
-            editor.update(cx, |editor, cx| {
-                if is_assistant2_enabled {
-                    editor.remove_code_action_provider(
-                        ASSISTANT_CODE_ACTION_PROVIDER_ID.into(),
-                        window,
-                        cx,
-                    );
-                } else {
-                    editor.add_code_action_provider(
-                        Rc::new(AssistantCodeActionProvider {
-                            editor: cx.entity().downgrade(),
-                            workspace: workspace.downgrade(),
-                        }),
-                        window,
-                        cx,
-                    );
-                }
-            });
-        }
-    }
-
-    pub fn assist(
-        &mut self,
-        editor: &Entity<Editor>,
-        workspace: Option<WeakEntity<Workspace>>,
-        assistant_panel: Option<&Entity<AssistantPanel>>,
-        initial_prompt: Option<String>,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        let (snapshot, initial_selections) = editor.update(cx, |editor, cx| {
-            (
-                editor.snapshot(window, cx),
-                editor.selections.all::<Point>(cx),
-            )
-        });
-
-        let mut selections = Vec::<Selection<Point>>::new();
-        let mut newest_selection = None;
-        for mut selection in initial_selections {
-            if selection.end > selection.start {
-                selection.start.column = 0;
-                // If the selection ends at the start of the line, we don't want to include it.
-                if selection.end.column == 0 {
-                    selection.end.row -= 1;
-                }
-                selection.end.column = snapshot
-                    .buffer_snapshot
-                    .line_len(MultiBufferRow(selection.end.row));
-            } else if let Some(fold) =
-                snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))
-            {
-                selection.start = fold.range().start;
-                selection.end = fold.range().end;
-                if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() {
-                    let chars = snapshot
-                        .buffer_snapshot
-                        .chars_at(Point::new(selection.end.row + 1, 0));
-
-                    for c in chars {
-                        if c == '\n' {
-                            break;
-                        }
-                        if c.is_whitespace() {
-                            continue;
-                        }
-                        if snapshot
-                            .language_at(selection.end)
-                            .is_some_and(|language| language.config().brackets.is_closing_brace(c))
-                        {
-                            selection.end.row += 1;
-                            selection.end.column = snapshot
-                                .buffer_snapshot
-                                .line_len(MultiBufferRow(selection.end.row));
-                        }
-                    }
-                }
-            }
-
-            if let Some(prev_selection) = selections.last_mut() {
-                if selection.start <= prev_selection.end {
-                    prev_selection.end = selection.end;
-                    continue;
-                }
-            }
-
-            let latest_selection = newest_selection.get_or_insert_with(|| selection.clone());
-            if selection.id > latest_selection.id {
-                *latest_selection = selection.clone();
-            }
-            selections.push(selection);
-        }
-        let snapshot = &snapshot.buffer_snapshot;
-        let newest_selection = newest_selection.unwrap();
-
-        let mut codegen_ranges = Vec::new();
-        for (buffer, buffer_range, excerpt_id) in
-            snapshot.ranges_to_buffer_ranges(selections.iter().map(|selection| {
-                snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end)
-            }))
-        {
-            let start = buffer.anchor_before(buffer_range.start);
-            let end = buffer.anchor_after(buffer_range.end);
-
-            codegen_ranges.push(Anchor::range_in_buffer(
-                excerpt_id,
-                buffer.remote_id(),
-                start..end,
-            ));
-
-            if let Some(ConfiguredModel { model, .. }) =
-                LanguageModelRegistry::read_global(cx).default_model()
-            {
-                self.telemetry.report_assistant_event(AssistantEventData {
-                    conversation_id: None,
-                    kind: AssistantKind::Inline,
-                    phase: AssistantPhase::Invoked,
-                    message_id: None,
-                    model: model.telemetry_id(),
-                    model_provider: model.provider_id().to_string(),
-                    response_latency: None,
-                    error_message: None,
-                    language_name: buffer.language().map(|language| language.name().to_proto()),
-                });
-            }
-        }
-
-        let assist_group_id = self.next_assist_group_id.post_inc();
-        let prompt_buffer = cx.new(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx));
-        let prompt_buffer = cx.new(|cx| MultiBuffer::singleton(prompt_buffer, cx));
-
-        let mut assists = Vec::new();
-        let mut assist_to_focus = None;
-        for range in codegen_ranges {
-            let assist_id = self.next_assist_id.post_inc();
-            let codegen = cx.new(|cx| {
-                Codegen::new(
-                    editor.read(cx).buffer().clone(),
-                    range.clone(),
-                    None,
-                    self.telemetry.clone(),
-                    self.prompt_builder.clone(),
-                    cx,
-                )
-            });
-
-            let editor_margins = Arc::new(Mutex::new(EditorMargins::default()));
-            let prompt_editor = cx.new(|cx| {
-                PromptEditor::new(
-                    assist_id,
-                    editor_margins,
-                    self.prompt_history.clone(),
-                    prompt_buffer.clone(),
-                    codegen.clone(),
-                    editor,
-                    assistant_panel,
-                    workspace.clone(),
-                    self.fs.clone(),
-                    window,
-                    cx,
-                )
-            });
-
-            if assist_to_focus.is_none() {
-                let focus_assist = if newest_selection.reversed {
-                    range.start.to_point(&snapshot) == newest_selection.start
-                } else {
-                    range.end.to_point(&snapshot) == newest_selection.end
-                };
-                if focus_assist {
-                    assist_to_focus = Some(assist_id);
-                }
-            }
-
-            let [prompt_block_id, end_block_id] =
-                self.insert_assist_blocks(editor, &range, &prompt_editor, cx);
-
-            assists.push((
-                assist_id,
-                range,
-                prompt_editor,
-                prompt_block_id,
-                end_block_id,
-            ));
-        }
-
-        let editor_assists = self
-            .assists_by_editor
-            .entry(editor.downgrade())
-            .or_insert_with(|| EditorInlineAssists::new(&editor, window, cx));
-        let mut assist_group = InlineAssistGroup::new();
-        for (assist_id, range, prompt_editor, prompt_block_id, end_block_id) in assists {
-            self.assists.insert(
-                assist_id,
-                InlineAssist::new(
-                    assist_id,
-                    assist_group_id,
-                    assistant_panel.is_some(),
-                    editor,
-                    &prompt_editor,
-                    prompt_block_id,
-                    end_block_id,
-                    range,
-                    prompt_editor.read(cx).codegen.clone(),
-                    workspace.clone(),
-                    window,
-                    cx,
-                ),
-            );
-            assist_group.assist_ids.push(assist_id);
-            editor_assists.assist_ids.push(assist_id);
-        }
-        self.assist_groups.insert(assist_group_id, assist_group);
-
-        if let Some(assist_id) = assist_to_focus {
-            self.focus_assist(assist_id, window, cx);
-        }
-    }
-
-    pub fn suggest_assist(
-        &mut self,
-        editor: &Entity<Editor>,
-        mut range: Range<Anchor>,
-        initial_prompt: String,
-        initial_transaction_id: Option<TransactionId>,
-        focus: bool,
-        workspace: Option<WeakEntity<Workspace>>,
-        assistant_panel: Option<&Entity<AssistantPanel>>,
-        window: &mut Window,
-        cx: &mut App,
-    ) -> InlineAssistId {
-        let assist_group_id = self.next_assist_group_id.post_inc();
-        let prompt_buffer = cx.new(|cx| Buffer::local(&initial_prompt, cx));
-        let prompt_buffer = cx.new(|cx| MultiBuffer::singleton(prompt_buffer, cx));
-
-        let assist_id = self.next_assist_id.post_inc();
-
-        let buffer = editor.read(cx).buffer().clone();
-        {
-            let snapshot = buffer.read(cx).read(cx);
-            range.start = range.start.bias_left(&snapshot);
-            range.end = range.end.bias_right(&snapshot);
-        }
-
-        let codegen = cx.new(|cx| {
-            Codegen::new(
-                editor.read(cx).buffer().clone(),
-                range.clone(),
-                initial_transaction_id,
-                self.telemetry.clone(),
-                self.prompt_builder.clone(),
-                cx,
-            )
-        });
-
-        let editor_margins = Arc::new(Mutex::new(EditorMargins::default()));
-        let prompt_editor = cx.new(|cx| {
-            PromptEditor::new(
-                assist_id,
-                editor_margins,
-                self.prompt_history.clone(),
-                prompt_buffer.clone(),
-                codegen.clone(),
-                editor,
-                assistant_panel,
-                workspace.clone(),
-                self.fs.clone(),
-                window,
-                cx,
-            )
-        });
-
-        let [prompt_block_id, end_block_id] =
-            self.insert_assist_blocks(editor, &range, &prompt_editor, cx);
-
-        let editor_assists = self
-            .assists_by_editor
-            .entry(editor.downgrade())
-            .or_insert_with(|| EditorInlineAssists::new(&editor, window, cx));
-
-        let mut assist_group = InlineAssistGroup::new();
-        self.assists.insert(
-            assist_id,
-            InlineAssist::new(
-                assist_id,
-                assist_group_id,
-                assistant_panel.is_some(),
-                editor,
-                &prompt_editor,
-                prompt_block_id,
-                end_block_id,
-                range,
-                prompt_editor.read(cx).codegen.clone(),
-                workspace.clone(),
-                window,
-                cx,
-            ),
-        );
-        assist_group.assist_ids.push(assist_id);
-        editor_assists.assist_ids.push(assist_id);
-        self.assist_groups.insert(assist_group_id, assist_group);
-
-        if focus {
-            self.focus_assist(assist_id, window, cx);
-        }
-
-        assist_id
-    }
-
-    fn insert_assist_blocks(
-        &self,
-        editor: &Entity<Editor>,
-        range: &Range<Anchor>,
-        prompt_editor: &Entity<PromptEditor>,
-        cx: &mut App,
-    ) -> [CustomBlockId; 2] {
-        let prompt_editor_height = prompt_editor.update(cx, |prompt_editor, cx| {
-            prompt_editor
-                .editor
-                .update(cx, |editor, cx| editor.max_point(cx).row().0 + 1 + 2)
-        });
-        let assist_blocks = vec![
-            BlockProperties {
-                style: BlockStyle::Sticky,
-                placement: BlockPlacement::Above(range.start),
-                height: Some(prompt_editor_height),
-                render: build_assist_editor_renderer(prompt_editor),
-                priority: 0,
-                render_in_minimap: false,
-            },
-            BlockProperties {
-                style: BlockStyle::Sticky,
-                placement: BlockPlacement::Below(range.end),
-                height: None,
-                render: Arc::new(|cx| {
-                    v_flex()
-                        .h_full()
-                        .w_full()
-                        .border_t_1()
-                        .border_color(cx.theme().status().info_border)
-                        .into_any_element()
-                }),
-                priority: 0,
-                render_in_minimap: false,
-            },
-        ];
-
-        editor.update(cx, |editor, cx| {
-            let block_ids = editor.insert_blocks(assist_blocks, None, cx);
-            [block_ids[0], block_ids[1]]
-        })
-    }
-
-    fn handle_prompt_editor_focus_in(&mut self, assist_id: InlineAssistId, cx: &mut App) {
-        let assist = &self.assists[&assist_id];
-        let Some(decorations) = assist.decorations.as_ref() else {
-            return;
-        };
-        let assist_group = self.assist_groups.get_mut(&assist.group_id).unwrap();
-        let editor_assists = self.assists_by_editor.get_mut(&assist.editor).unwrap();
-
-        assist_group.active_assist_id = Some(assist_id);
-        if assist_group.linked {
-            for assist_id in &assist_group.assist_ids {
-                if let Some(decorations) = self.assists[assist_id].decorations.as_ref() {
-                    decorations.prompt_editor.update(cx, |prompt_editor, cx| {
-                        prompt_editor.set_show_cursor_when_unfocused(true, cx)
-                    });
-                }
-            }
-        }
-
-        assist
-            .editor
-            .update(cx, |editor, cx| {
-                let scroll_top = editor.scroll_position(cx).y;
-                let scroll_bottom = scroll_top + editor.visible_line_count().unwrap_or(0.);
-                let prompt_row = editor
-                    .row_for_block(decorations.prompt_block_id, cx)
-                    .unwrap()
-                    .0 as f32;
-
-                if (scroll_top..scroll_bottom).contains(&prompt_row) {
-                    editor_assists.scroll_lock = Some(InlineAssistScrollLock {
-                        assist_id,
-                        distance_from_top: prompt_row - scroll_top,
-                    });
-                } else {
-                    editor_assists.scroll_lock = None;
-                }
-            })
-            .ok();
-    }
-
-    fn handle_prompt_editor_focus_out(&mut self, assist_id: InlineAssistId, cx: &mut App) {
-        let assist = &self.assists[&assist_id];
-        let assist_group = self.assist_groups.get_mut(&assist.group_id).unwrap();
-        if assist_group.active_assist_id == Some(assist_id) {
-            assist_group.active_assist_id = None;
-            if assist_group.linked {
-                for assist_id in &assist_group.assist_ids {
-                    if let Some(decorations) = self.assists[assist_id].decorations.as_ref() {
-                        decorations.prompt_editor.update(cx, |prompt_editor, cx| {
-                            prompt_editor.set_show_cursor_when_unfocused(false, cx)
-                        });
-                    }
-                }
-            }
-        }
-    }
-
-    fn handle_prompt_editor_event(
-        &mut self,
-        prompt_editor: Entity<PromptEditor>,
-        event: &PromptEditorEvent,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        let assist_id = prompt_editor.read(cx).id;
-        match event {
-            PromptEditorEvent::StartRequested => {
-                self.start_assist(assist_id, window, cx);
-            }
-            PromptEditorEvent::StopRequested => {
-                self.stop_assist(assist_id, cx);
-            }
-            PromptEditorEvent::ConfirmRequested => {
-                self.finish_assist(assist_id, false, window, cx);
-            }
-            PromptEditorEvent::CancelRequested => {
-                self.finish_assist(assist_id, true, window, cx);
-            }
-            PromptEditorEvent::DismissRequested => {
-                self.dismiss_assist(assist_id, window, cx);
-            }
-        }
-    }
-
-    fn handle_editor_newline(&mut self, editor: Entity<Editor>, window: &mut Window, cx: &mut App) {
-        let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else {
-            return;
-        };
-
-        if editor.read(cx).selections.count() == 1 {
-            let (selection, buffer) = editor.update(cx, |editor, cx| {
-                (
-                    editor.selections.newest::<usize>(cx),
-                    editor.buffer().read(cx).snapshot(cx),
-                )
-            });
-            for assist_id in &editor_assists.assist_ids {
-                let assist = &self.assists[assist_id];
-                let assist_range = assist.range.to_offset(&buffer);
-                if assist_range.contains(&selection.start) && assist_range.contains(&selection.end)
-                {
-                    if matches!(assist.codegen.read(cx).status(cx), CodegenStatus::Pending) {
-                        self.dismiss_assist(*assist_id, window, cx);
-                    } else {
-                        self.finish_assist(*assist_id, false, window, cx);
-                    }
-
-                    return;
-                }
-            }
-        }
-
-        cx.propagate();
-    }
-
-    fn handle_editor_cancel(&mut self, editor: Entity<Editor>, window: &mut Window, cx: &mut App) {
-        let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else {
-            return;
-        };
-
-        if editor.read(cx).selections.count() == 1 {
-            let (selection, buffer) = editor.update(cx, |editor, cx| {
-                (
-                    editor.selections.newest::<usize>(cx),
-                    editor.buffer().read(cx).snapshot(cx),
-                )
-            });
-            let mut closest_assist_fallback = None;
-            for assist_id in &editor_assists.assist_ids {
-                let assist = &self.assists[assist_id];
-                let assist_range = assist.range.to_offset(&buffer);
-                if assist.decorations.is_some() {
-                    if assist_range.contains(&selection.start)
-                        && assist_range.contains(&selection.end)
-                    {
-                        self.focus_assist(*assist_id, window, cx);
-                        return;
-                    } else {
-                        let distance_from_selection = assist_range
-                            .start
-                            .abs_diff(selection.start)
-                            .min(assist_range.start.abs_diff(selection.end))
-                            + assist_range
-                                .end
-                                .abs_diff(selection.start)
-                                .min(assist_range.end.abs_diff(selection.end));
-                        match closest_assist_fallback {
-                            Some((_, old_distance)) => {
-                                if distance_from_selection < old_distance {
-                                    closest_assist_fallback =
-                                        Some((assist_id, distance_from_selection));
-                                }
-                            }
-                            None => {
-                                closest_assist_fallback = Some((assist_id, distance_from_selection))
-                            }
-                        }
-                    }
-                }
-            }
-
-            if let Some((&assist_id, _)) = closest_assist_fallback {
-                self.focus_assist(assist_id, window, cx);
-            }
-        }
-
-        cx.propagate();
-    }
-
-    fn handle_editor_release(
-        &mut self,
-        editor: WeakEntity<Editor>,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        if let Some(editor_assists) = self.assists_by_editor.get_mut(&editor) {
-            for assist_id in editor_assists.assist_ids.clone() {
-                self.finish_assist(assist_id, true, window, cx);
-            }
-        }
-    }
-
-    fn handle_editor_change(&mut self, editor: Entity<Editor>, window: &mut Window, cx: &mut App) {
-        let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else {
-            return;
-        };
-        let Some(scroll_lock) = editor_assists.scroll_lock.as_ref() else {
-            return;
-        };
-        let assist = &self.assists[&scroll_lock.assist_id];
-        let Some(decorations) = assist.decorations.as_ref() else {
-            return;
-        };
-
-        editor.update(cx, |editor, cx| {
-            let scroll_position = editor.scroll_position(cx);
-            let target_scroll_top = editor
-                .row_for_block(decorations.prompt_block_id, cx)
-                .unwrap()
-                .0 as f32
-                - scroll_lock.distance_from_top;
-            if target_scroll_top != scroll_position.y {
-                editor.set_scroll_position(point(scroll_position.x, target_scroll_top), window, cx);
-            }
-        });
-    }
-
-    fn handle_editor_event(
-        &mut self,
-        editor: Entity<Editor>,
-        event: &EditorEvent,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        let Some(editor_assists) = self.assists_by_editor.get_mut(&editor.downgrade()) else {
-            return;
-        };
-
-        match event {
-            EditorEvent::Edited { transaction_id } => {
-                let buffer = editor.read(cx).buffer().read(cx);
-                let edited_ranges =
-                    buffer.edited_ranges_for_transaction::<usize>(*transaction_id, cx);
-                let snapshot = buffer.snapshot(cx);
-
-                for assist_id in editor_assists.assist_ids.clone() {
-                    let assist = &self.assists[&assist_id];
-                    if matches!(
-                        assist.codegen.read(cx).status(cx),
-                        CodegenStatus::Error(_) | CodegenStatus::Done
-                    ) {
-                        let assist_range = assist.range.to_offset(&snapshot);
-                        if edited_ranges
-                            .iter()
-                            .any(|range| range.overlaps(&assist_range))
-                        {
-                            self.finish_assist(assist_id, false, window, cx);
-                        }
-                    }
-                }
-            }
-            EditorEvent::ScrollPositionChanged { .. } => {
-                if let Some(scroll_lock) = editor_assists.scroll_lock.as_ref() {
-                    let assist = &self.assists[&scroll_lock.assist_id];
-                    if let Some(decorations) = assist.decorations.as_ref() {
-                        let distance_from_top = editor.update(cx, |editor, cx| {
-                            let scroll_top = editor.scroll_position(cx).y;
-                            let prompt_row = editor
-                                .row_for_block(decorations.prompt_block_id, cx)
-                                .unwrap()
-                                .0 as f32;
-                            prompt_row - scroll_top
-                        });
-
-                        if distance_from_top != scroll_lock.distance_from_top {
-                            editor_assists.scroll_lock = None;
-                        }
-                    }
-                }
-            }
-            EditorEvent::SelectionsChanged { .. } => {
-                for assist_id in editor_assists.assist_ids.clone() {
-                    let assist = &self.assists[&assist_id];
-                    if let Some(decorations) = assist.decorations.as_ref() {
-                        if decorations
-                            .prompt_editor
-                            .focus_handle(cx)
-                            .is_focused(window)
-                        {
-                            return;
-                        }
-                    }
-                }
-
-                editor_assists.scroll_lock = None;
-            }
-            _ => {}
-        }
-    }
-
-    pub fn finish_assist(
-        &mut self,
-        assist_id: InlineAssistId,
-        undo: bool,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        if let Some(assist) = self.assists.get(&assist_id) {
-            let assist_group_id = assist.group_id;
-            if self.assist_groups[&assist_group_id].linked {
-                for assist_id in self.unlink_assist_group(assist_group_id, window, cx) {
-                    self.finish_assist(assist_id, undo, window, cx);
-                }
-                return;
-            }
-        }
-
-        self.dismiss_assist(assist_id, window, cx);
-
-        if let Some(assist) = self.assists.remove(&assist_id) {
-            if let hash_map::Entry::Occupied(mut entry) = self.assist_groups.entry(assist.group_id)
-            {
-                entry.get_mut().assist_ids.retain(|id| *id != assist_id);
-                if entry.get().assist_ids.is_empty() {
-                    entry.remove();
-                }
-            }
-
-            if let hash_map::Entry::Occupied(mut entry) =
-                self.assists_by_editor.entry(assist.editor.clone())
-            {
-                entry.get_mut().assist_ids.retain(|id| *id != assist_id);
-                if entry.get().assist_ids.is_empty() {
-                    entry.remove();
-                    if let Some(editor) = assist.editor.upgrade() {
-                        self.update_editor_highlights(&editor, cx);
-                    }
-                } else {
-                    entry.get().highlight_updates.send(()).ok();
-                }
-            }
-
-            let active_alternative = assist.codegen.read(cx).active_alternative().clone();
-            let message_id = active_alternative.read(cx).message_id.clone();
-
-            if let Some(ConfiguredModel { model, .. }) =
-                LanguageModelRegistry::read_global(cx).default_model()
-            {
-                let language_name = assist.editor.upgrade().and_then(|editor| {
-                    let multibuffer = editor.read(cx).buffer().read(cx);
-                    let multibuffer_snapshot = multibuffer.snapshot(cx);
-                    let ranges = multibuffer_snapshot.range_to_buffer_ranges(assist.range.clone());
-                    ranges
-                        .first()
-                        .and_then(|(buffer, _, _)| buffer.language())
-                        .map(|language| language.name())
-                });
-                report_assistant_event(
-                    AssistantEventData {
-                        conversation_id: None,
-                        kind: AssistantKind::Inline,
-                        message_id,
-                        phase: if undo {
-                            AssistantPhase::Rejected
-                        } else {
-                            AssistantPhase::Accepted
-                        },
-                        model: model.telemetry_id(),
-                        model_provider: model.provider_id().to_string(),
-                        response_latency: None,
-                        error_message: None,
-                        language_name: language_name.map(|name| name.to_proto()),
-                    },
-                    Some(self.telemetry.clone()),
-                    cx.http_client(),
-                    model.api_key(cx),
-                    cx.background_executor(),
-                );
-            }
-
-            if undo {
-                assist.codegen.update(cx, |codegen, cx| codegen.undo(cx));
-            } else {
-                self.confirmed_assists.insert(assist_id, active_alternative);
-            }
-        }
-    }
-
-    fn dismiss_assist(
-        &mut self,
-        assist_id: InlineAssistId,
-        window: &mut Window,
-        cx: &mut App,
-    ) -> bool {
-        let Some(assist) = self.assists.get_mut(&assist_id) else {
-            return false;
-        };
-        let Some(editor) = assist.editor.upgrade() else {
-            return false;
-        };
-        let Some(decorations) = assist.decorations.take() else {
-            return false;
-        };
-
-        editor.update(cx, |editor, cx| {
-            let mut to_remove = decorations.removed_line_block_ids;
-            to_remove.insert(decorations.prompt_block_id);
-            to_remove.insert(decorations.end_block_id);
-            editor.remove_blocks(to_remove, None, cx);
-        });
-
-        if decorations
-            .prompt_editor
-            .focus_handle(cx)
-            .contains_focused(window, cx)
-        {
-            self.focus_next_assist(assist_id, window, cx);
-        }
-
-        if let Some(editor_assists) = self.assists_by_editor.get_mut(&editor.downgrade()) {
-            if editor_assists
-                .scroll_lock
-                .as_ref()
-                .map_or(false, |lock| lock.assist_id == assist_id)
-            {
-                editor_assists.scroll_lock = None;
-            }
-            editor_assists.highlight_updates.send(()).ok();
-        }
-
-        true
-    }
-
-    fn focus_next_assist(&mut self, assist_id: InlineAssistId, window: &mut Window, cx: &mut App) {
-        let Some(assist) = self.assists.get(&assist_id) else {
-            return;
-        };
-
-        let assist_group = &self.assist_groups[&assist.group_id];
-        let assist_ix = assist_group
-            .assist_ids
-            .iter()
-            .position(|id| *id == assist_id)
-            .unwrap();
-        let assist_ids = assist_group
-            .assist_ids
-            .iter()
-            .skip(assist_ix + 1)
-            .chain(assist_group.assist_ids.iter().take(assist_ix));
-
-        for assist_id in assist_ids {
-            let assist = &self.assists[assist_id];
-            if assist.decorations.is_some() {
-                self.focus_assist(*assist_id, window, cx);
-                return;
-            }
-        }
-
-        assist
-            .editor
-            .update(cx, |editor, cx| window.focus(&editor.focus_handle(cx)))
-            .ok();
-    }
-
-    fn focus_assist(&mut self, assist_id: InlineAssistId, window: &mut Window, cx: &mut App) {
-        let Some(assist) = self.assists.get(&assist_id) else {
-            return;
-        };
-
-        if let Some(decorations) = assist.decorations.as_ref() {
-            decorations.prompt_editor.update(cx, |prompt_editor, cx| {
-                prompt_editor.editor.update(cx, |editor, cx| {
-                    window.focus(&editor.focus_handle(cx));
-                    editor.select_all(&SelectAll, window, cx);
-                })
-            });
-        }
-
-        self.scroll_to_assist(assist_id, window, cx);
-    }
-
-    pub fn scroll_to_assist(
-        &mut self,
-        assist_id: InlineAssistId,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        let Some(assist) = self.assists.get(&assist_id) else {
-            return;
-        };
-        let Some(editor) = assist.editor.upgrade() else {
-            return;
-        };
-
-        let position = assist.range.start;
-        editor.update(cx, |editor, cx| {
-            editor.change_selections(None, window, cx, |selections| {
-                selections.select_anchor_ranges([position..position])
-            });
-
-            let mut scroll_target_top;
-            let mut scroll_target_bottom;
-            if let Some(decorations) = assist.decorations.as_ref() {
-                scroll_target_top = editor
-                    .row_for_block(decorations.prompt_block_id, cx)
-                    .unwrap()
-                    .0 as f32;
-                scroll_target_bottom = editor
-                    .row_for_block(decorations.end_block_id, cx)
-                    .unwrap()
-                    .0 as f32;
-            } else {
-                let snapshot = editor.snapshot(window, cx);
-                let start_row = assist
-                    .range
-                    .start
-                    .to_display_point(&snapshot.display_snapshot)
-                    .row();
-                scroll_target_top = start_row.0 as f32;
-                scroll_target_bottom = scroll_target_top + 1.;
-            }
-            scroll_target_top -= editor.vertical_scroll_margin() as f32;
-            scroll_target_bottom += editor.vertical_scroll_margin() as f32;
-
-            let height_in_lines = editor.visible_line_count().unwrap_or(0.);
-            let scroll_top = editor.scroll_position(cx).y;
-            let scroll_bottom = scroll_top + height_in_lines;
-
-            if scroll_target_top < scroll_top {
-                editor.set_scroll_position(point(0., scroll_target_top), window, cx);
-            } else if scroll_target_bottom > scroll_bottom {
-                if (scroll_target_bottom - scroll_target_top) <= height_in_lines {
-                    editor.set_scroll_position(
-                        point(0., scroll_target_bottom - height_in_lines),
-                        window,
-                        cx,
-                    );
-                } else {
-                    editor.set_scroll_position(point(0., scroll_target_top), window, cx);
-                }
-            }
-        });
-    }
-
-    fn unlink_assist_group(
-        &mut self,
-        assist_group_id: InlineAssistGroupId,
-        window: &mut Window,
-        cx: &mut App,
-    ) -> Vec<InlineAssistId> {
-        let assist_group = self.assist_groups.get_mut(&assist_group_id).unwrap();
-        assist_group.linked = false;
-        for assist_id in &assist_group.assist_ids {
-            let assist = self.assists.get_mut(assist_id).unwrap();
-            if let Some(editor_decorations) = assist.decorations.as_ref() {
-                editor_decorations
-                    .prompt_editor
-                    .update(cx, |prompt_editor, cx| prompt_editor.unlink(window, cx));
-            }
-        }
-        assist_group.assist_ids.clone()
-    }
-
-    pub fn start_assist(&mut self, assist_id: InlineAssistId, window: &mut Window, cx: &mut App) {
-        let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
-            assist
-        } else {
-            return;
-        };
-
-        let assist_group_id = assist.group_id;
-        if self.assist_groups[&assist_group_id].linked {
-            for assist_id in self.unlink_assist_group(assist_group_id, window, cx) {
-                self.start_assist(assist_id, window, cx);
-            }
-            return;
-        }
-
-        let Some(user_prompt) = assist.user_prompt(cx) else {
-            return;
-        };
-
-        self.prompt_history.retain(|prompt| *prompt != user_prompt);
-        self.prompt_history.push_back(user_prompt.clone());
-        if self.prompt_history.len() > PROMPT_HISTORY_MAX_LEN {
-            self.prompt_history.pop_front();
-        }
-
-        let assistant_panel_context = assist.assistant_panel_context(cx);
-
-        assist
-            .codegen
-            .update(cx, |codegen, cx| {
-                codegen.start(user_prompt, assistant_panel_context, cx)
-            })
-            .log_err();
-    }
-
-    pub fn stop_assist(&mut self, assist_id: InlineAssistId, cx: &mut App) {
-        let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
-            assist
-        } else {
-            return;
-        };
-
-        assist.codegen.update(cx, |codegen, cx| codegen.stop(cx));
-    }
-
-    fn update_editor_highlights(&self, editor: &Entity<Editor>, cx: &mut App) {
-        let mut gutter_pending_ranges = Vec::new();
-        let mut gutter_transformed_ranges = Vec::new();
-        let mut foreground_ranges = Vec::new();
-        let mut inserted_row_ranges = Vec::new();
-        let empty_assist_ids = Vec::new();
-        let assist_ids = self
-            .assists_by_editor
-            .get(&editor.downgrade())
-            .map_or(&empty_assist_ids, |editor_assists| {
-                &editor_assists.assist_ids
-            });
-
-        for assist_id in assist_ids {
-            if let Some(assist) = self.assists.get(assist_id) {
-                let codegen = assist.codegen.read(cx);
-                let buffer = codegen.buffer(cx).read(cx).read(cx);
-                foreground_ranges.extend(codegen.last_equal_ranges(cx).iter().cloned());
-
-                let pending_range =
-                    codegen.edit_position(cx).unwrap_or(assist.range.start)..assist.range.end;
-                if pending_range.end.to_offset(&buffer) > pending_range.start.to_offset(&buffer) {
-                    gutter_pending_ranges.push(pending_range);
-                }
-
-                if let Some(edit_position) = codegen.edit_position(cx) {
-                    let edited_range = assist.range.start..edit_position;
-                    if edited_range.end.to_offset(&buffer) > edited_range.start.to_offset(&buffer) {
-                        gutter_transformed_ranges.push(edited_range);
-                    }
-                }
-
-                if assist.decorations.is_some() {
-                    inserted_row_ranges
-                        .extend(codegen.diff(cx).inserted_row_ranges.iter().cloned());
-                }
-            }
-        }
-
-        let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
-        merge_ranges(&mut foreground_ranges, &snapshot);
-        merge_ranges(&mut gutter_pending_ranges, &snapshot);
-        merge_ranges(&mut gutter_transformed_ranges, &snapshot);
-        editor.update(cx, |editor, cx| {
-            enum GutterPendingRange {}
-            if gutter_pending_ranges.is_empty() {
-                editor.clear_gutter_highlights::<GutterPendingRange>(cx);
-            } else {
-                editor.highlight_gutter::<GutterPendingRange>(
-                    &gutter_pending_ranges,
-                    |cx| cx.theme().status().info_background,
-                    cx,
-                )
-            }
-
-            enum GutterTransformedRange {}
-            if gutter_transformed_ranges.is_empty() {
-                editor.clear_gutter_highlights::<GutterTransformedRange>(cx);
-            } else {
-                editor.highlight_gutter::<GutterTransformedRange>(
-                    &gutter_transformed_ranges,
-                    |cx| cx.theme().status().info,
-                    cx,
-                )
-            }
-
-            if foreground_ranges.is_empty() {
-                editor.clear_highlights::<InlineAssist>(cx);
-            } else {
-                editor.highlight_text::<InlineAssist>(
-                    foreground_ranges,
-                    HighlightStyle {
-                        fade_out: Some(0.6),
-                        ..Default::default()
-                    },
-                    cx,
-                );
-            }
-
-            editor.clear_row_highlights::<InlineAssist>();
-            for row_range in inserted_row_ranges {
-                editor.highlight_rows::<InlineAssist>(
-                    row_range,
-                    cx.theme().status().info_background,
-                    Default::default(),
-                    cx,
-                );
-            }
-        });
-    }
-
-    fn update_editor_blocks(
-        &mut self,
-        editor: &Entity<Editor>,
-        assist_id: InlineAssistId,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        let Some(assist) = self.assists.get_mut(&assist_id) else {
-            return;
-        };
-        let Some(decorations) = assist.decorations.as_mut() else {
-            return;
-        };
-
-        let codegen = assist.codegen.read(cx);
-        let old_snapshot = codegen.snapshot(cx);
-        let old_buffer = codegen.old_buffer(cx);
-        let deleted_row_ranges = codegen.diff(cx).deleted_row_ranges.clone();
-
-        editor.update(cx, |editor, cx| {
-            let old_blocks = mem::take(&mut decorations.removed_line_block_ids);
-            editor.remove_blocks(old_blocks, None, cx);
-
-            let mut new_blocks = Vec::new();
-            for (new_row, old_row_range) in deleted_row_ranges {
-                let (_, buffer_start) = old_snapshot
-                    .point_to_buffer_offset(Point::new(*old_row_range.start(), 0))
-                    .unwrap();
-                let (_, buffer_end) = old_snapshot
-                    .point_to_buffer_offset(Point::new(
-                        *old_row_range.end(),
-                        old_snapshot.line_len(MultiBufferRow(*old_row_range.end())),
-                    ))
-                    .unwrap();
-
-                let deleted_lines_editor = cx.new(|cx| {
-                    let multi_buffer =
-                        cx.new(|_| MultiBuffer::without_headers(language::Capability::ReadOnly));
-                    multi_buffer.update(cx, |multi_buffer, cx| {
-                        multi_buffer.push_excerpts(
-                            old_buffer.clone(),
-                            Some(ExcerptRange::new(buffer_start..buffer_end)),
-                            cx,
-                        );
-                    });
-
-                    enum DeletedLines {}
-                    let mut editor = Editor::for_multibuffer(multi_buffer, None, window, cx);
-                    editor.disable_scrollbars_and_minimap(cx);
-                    editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
-                    editor.set_show_wrap_guides(false, cx);
-                    editor.set_show_gutter(false, cx);
-                    editor.scroll_manager.set_forbid_vertical_scroll(true);
-                    editor.set_read_only(true);
-                    editor.set_show_edit_predictions(Some(false), window, cx);
-                    editor.highlight_rows::<DeletedLines>(
-                        Anchor::min()..Anchor::max(),
-                        cx.theme().status().deleted_background,
-                        Default::default(),
-                        cx,
-                    );
-                    editor
-                });
-
-                let height =
-                    deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1);
-                new_blocks.push(BlockProperties {
-                    placement: BlockPlacement::Above(new_row),
-                    height: Some(height),
-                    style: BlockStyle::Flex,
-                    render: Arc::new(move |cx| {
-                        div()
-                            .block_mouse_down()
-                            .bg(cx.theme().status().deleted_background)
-                            .size_full()
-                            .h(height as f32 * cx.window.line_height())
-                            .pl(cx.margins.gutter.full_width())
-                            .child(deleted_lines_editor.clone())
-                            .into_any_element()
-                    }),
-                    priority: 0,
-                    render_in_minimap: false,
-                });
-            }
-
-            decorations.removed_line_block_ids = editor
-                .insert_blocks(new_blocks, None, cx)
-                .into_iter()
-                .collect();
-        })
-    }
-}
-
-struct EditorInlineAssists {
-    assist_ids: Vec<InlineAssistId>,
-    scroll_lock: Option<InlineAssistScrollLock>,
-    highlight_updates: async_watch::Sender<()>,
-    _update_highlights: Task<Result<()>>,
-    _subscriptions: Vec<gpui::Subscription>,
-}
-
-struct InlineAssistScrollLock {
-    assist_id: InlineAssistId,
-    distance_from_top: f32,
-}
-
-impl EditorInlineAssists {
-    fn new(editor: &Entity<Editor>, window: &mut Window, cx: &mut App) -> Self {
-        let (highlight_updates_tx, mut highlight_updates_rx) = async_watch::channel(());
-        Self {
-            assist_ids: Vec::new(),
-            scroll_lock: None,
-            highlight_updates: highlight_updates_tx,
-            _update_highlights: cx.spawn({
-                let editor = editor.downgrade();
-                async move |cx| {
-                    while let Ok(()) = highlight_updates_rx.changed().await {
-                        let editor = editor.upgrade().context("editor was dropped")?;
-                        cx.update_global(|assistant: &mut InlineAssistant, cx| {
-                            assistant.update_editor_highlights(&editor, cx);
-                        })?;
-                    }
-                    Ok(())
-                }
-            }),
-            _subscriptions: vec![
-                cx.observe_release_in(editor, window, {
-                    let editor = editor.downgrade();
-                    |_, window, cx| {
-                        InlineAssistant::update_global(cx, |this, cx| {
-                            this.handle_editor_release(editor, window, cx);
-                        })
-                    }
-                }),
-                window.observe(editor, cx, move |editor, window, cx| {
-                    InlineAssistant::update_global(cx, |this, cx| {
-                        this.handle_editor_change(editor, window, cx)
-                    })
-                }),
-                window.subscribe(editor, cx, move |editor, event, window, cx| {
-                    InlineAssistant::update_global(cx, |this, cx| {
-                        this.handle_editor_event(editor, event, window, cx)
-                    })
-                }),
-                editor.update(cx, |editor, cx| {
-                    let editor_handle = cx.entity().downgrade();
-                    editor.register_action(move |_: &editor::actions::Newline, window, cx| {
-                        InlineAssistant::update_global(cx, |this, cx| {
-                            if let Some(editor) = editor_handle.upgrade() {
-                                this.handle_editor_newline(editor, window, cx)
-                            }
-                        })
-                    })
-                }),
-                editor.update(cx, |editor, cx| {
-                    let editor_handle = cx.entity().downgrade();
-                    editor.register_action(move |_: &editor::actions::Cancel, window, cx| {
-                        InlineAssistant::update_global(cx, |this, cx| {
-                            if let Some(editor) = editor_handle.upgrade() {
-                                this.handle_editor_cancel(editor, window, cx)
-                            }
-                        })
-                    })
-                }),
-            ],
-        }
-    }
-}
-
-struct InlineAssistGroup {
-    assist_ids: Vec<InlineAssistId>,
-    linked: bool,
-    active_assist_id: Option<InlineAssistId>,
-}
-
-impl InlineAssistGroup {
-    fn new() -> Self {
-        Self {
-            assist_ids: Vec::new(),
-            linked: true,
-            active_assist_id: None,
-        }
-    }
-}
-
-fn build_assist_editor_renderer(editor: &Entity<PromptEditor>) -> RenderBlock {
-    let editor = editor.clone();
-    Arc::new(move |cx: &mut BlockContext| {
-        *editor.read(cx).editor_margins.lock() = *cx.margins;
-        editor.clone().into_any_element()
-    })
-}
-
-#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
-pub struct InlineAssistId(usize);
-
-impl InlineAssistId {
-    fn post_inc(&mut self) -> InlineAssistId {
-        let id = *self;
-        self.0 += 1;
-        id
-    }
-}
-
-#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
-struct InlineAssistGroupId(usize);
-
-impl InlineAssistGroupId {
-    fn post_inc(&mut self) -> InlineAssistGroupId {
-        let id = *self;
-        self.0 += 1;
-        id
-    }
-}
-
-enum PromptEditorEvent {
-    StartRequested,
-    StopRequested,
-    ConfirmRequested,
-    CancelRequested,
-    DismissRequested,
-}
-
-struct PromptEditor {
-    id: InlineAssistId,
-    editor: Entity<Editor>,
-    language_model_selector: Entity<LanguageModelSelector>,
-    edited_since_done: bool,
-    editor_margins: Arc<Mutex<EditorMargins>>,
-    prompt_history: VecDeque<String>,
-    prompt_history_ix: Option<usize>,
-    pending_prompt: String,
-    codegen: Entity<Codegen>,
-    _codegen_subscription: Subscription,
-    editor_subscriptions: Vec<Subscription>,
-    pending_token_count: Task<Result<()>>,
-    token_counts: Option<TokenCounts>,
-    _token_count_subscriptions: Vec<Subscription>,
-    workspace: Option<WeakEntity<Workspace>>,
-    show_rate_limit_notice: bool,
-}
-
-#[derive(Copy, Clone)]
-pub struct TokenCounts {
-    total: usize,
-    assistant_panel: usize,
-}
-
-impl EventEmitter<PromptEditorEvent> for PromptEditor {}
-
-impl Render for PromptEditor {
-    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let editor_margins = *self.editor_margins.lock();
-        let gutter_dimensions = editor_margins.gutter;
-        let codegen = self.codegen.read(cx);
-
-        let mut buttons = Vec::new();
-        if codegen.alternative_count(cx) > 1 {
-            buttons.push(self.render_cycle_controls(cx));
-        }
-
-        let status = codegen.status(cx);
-        buttons.extend(match status {
-            CodegenStatus::Idle => {
-                vec![
-                    IconButton::new("cancel", IconName::Close)
-                        .icon_color(Color::Muted)
-                        .shape(IconButtonShape::Square)
-                        .tooltip(|window, cx| {
-                            Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
-                        })
-                        .on_click(
-                            cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
-                        )
-                        .into_any_element(),
-                    IconButton::new("start", IconName::SparkleAlt)
-                        .icon_color(Color::Muted)
-                        .shape(IconButtonShape::Square)
-                        .tooltip(|window, cx| {
-                            Tooltip::for_action("Transform", &menu::Confirm, window, cx)
-                        })
-                        .on_click(
-                            cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
-                        )
-                        .into_any_element(),
-                ]
-            }
-            CodegenStatus::Pending => {
-                vec![
-                    IconButton::new("cancel", IconName::Close)
-                        .icon_color(Color::Muted)
-                        .shape(IconButtonShape::Square)
-                        .tooltip(Tooltip::text("Cancel Assist"))
-                        .on_click(
-                            cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
-                        )
-                        .into_any_element(),
-                    IconButton::new("stop", IconName::Stop)
-                        .icon_color(Color::Error)
-                        .shape(IconButtonShape::Square)
-                        .tooltip(|window, cx| {
-                            Tooltip::with_meta(
-                                "Interrupt Transformation",
-                                Some(&menu::Cancel),
-                                "Changes won't be discarded",
-                                window,
-                                cx,
-                            )
-                        })
-                        .on_click(
-                            cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StopRequested)),
-                        )
-                        .into_any_element(),
-                ]
-            }
-            CodegenStatus::Error(_) | CodegenStatus::Done => {
-                let must_rerun =
-                    self.edited_since_done || matches!(status, CodegenStatus::Error(_));
-                // when accept button isn't visible, then restart maps to confirm
-                // when accept button is visible, then restart must be mapped to an alternate keyboard shortcut
-                let restart_key: &dyn gpui::Action = if must_rerun {
-                    &menu::Confirm
-                } else {
-                    &menu::Restart
-                };
-                vec![
-                    IconButton::new("cancel", IconName::Close)
-                        .icon_color(Color::Muted)
-                        .shape(IconButtonShape::Square)
-                        .tooltip(|window, cx| {
-                            Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
-                        })
-                        .on_click(
-                            cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
-                        )
-                        .into_any_element(),
-                    IconButton::new("restart", IconName::RotateCw)
-                        .icon_color(Color::Muted)
-                        .shape(IconButtonShape::Square)
-                        .tooltip(|window, cx| {
-                            Tooltip::with_meta(
-                                "Regenerate Transformation",
-                                Some(restart_key),
-                                "Current change will be discarded",
-                                window,
-                                cx,
-                            )
-                        })
-                        .on_click(cx.listener(|_, _, _, cx| {
-                            cx.emit(PromptEditorEvent::StartRequested);
-                        }))
-                        .into_any_element(),
-                    if !must_rerun {
-                        IconButton::new("confirm", IconName::Check)
-                            .icon_color(Color::Info)
-                            .shape(IconButtonShape::Square)
-                            .tooltip(|window, cx| {
-                                Tooltip::for_action("Confirm Assist", &menu::Confirm, window, cx)
-                            })
-                            .on_click(cx.listener(|_, _, _, cx| {
-                                cx.emit(PromptEditorEvent::ConfirmRequested);
-                            }))
-                            .into_any_element()
-                    } else {
-                        div().into_any_element()
-                    },
-                ]
-            }
-        });
-
-        h_flex()
-            .key_context("PromptEditor")
-            .bg(cx.theme().colors().editor_background)
-            .block_mouse_down()
-            .cursor(CursorStyle::Arrow)
-            .border_y_1()
-            .border_color(cx.theme().status().info_border)
-            .size_full()
-            .pr(editor_margins.right)
-            .py(window.line_height() / 2.5)
-            .on_action(cx.listener(Self::confirm))
-            .on_action(cx.listener(Self::cancel))
-            .on_action(cx.listener(Self::restart))
-            .on_action(cx.listener(Self::move_up))
-            .on_action(cx.listener(Self::move_down))
-            .capture_action(cx.listener(Self::cycle_prev))
-            .capture_action(cx.listener(Self::cycle_next))
-            .child(
-                h_flex()
-                    .w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
-                    .justify_center()
-                    .gap_2()
-                    .child(LanguageModelSelectorPopoverMenu::new(
-                        self.language_model_selector.clone(),
-                        IconButton::new("context", IconName::SettingsAlt)
-                            .shape(IconButtonShape::Square)
-                            .icon_size(IconSize::Small)
-                            .icon_color(Color::Muted),
-                        move |window, cx| {
-                            Tooltip::with_meta(
-                                format!(
-                                    "Using {}",
-                                    LanguageModelRegistry::read_global(cx)
-                                        .default_model()
-                                        .map(|default| default.model.name().0)
-                                        .unwrap_or_else(|| "No model selected".into()),
-                                ),
-                                None,
-                                "Change Model",
-                                window,
-                                cx,
-                            )
-                        },
-                        gpui::Corner::TopRight,
-                    ))
-                    .map(|el| {
-                        let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
-                            return el;
-                        };
-
-                        let error_message = SharedString::from(error.to_string());
-                        if error.error_code() == proto::ErrorCode::RateLimitExceeded
-                            && cx.has_flag::<ZedProFeatureFlag>()
-                        {
-                            el.child(
-                                v_flex()
-                                    .child(
-                                        IconButton::new("rate-limit-error", IconName::XCircle)
-                                            .toggle_state(self.show_rate_limit_notice)
-                                            .shape(IconButtonShape::Square)
-                                            .icon_size(IconSize::Small)
-                                            .on_click(cx.listener(Self::toggle_rate_limit_notice)),
-                                    )
-                                    .children(self.show_rate_limit_notice.then(|| {
-                                        deferred(
-                                            anchored()
-                                                .position_mode(gpui::AnchoredPositionMode::Local)
-                                                .position(point(px(0.), px(24.)))
-                                                .anchor(gpui::Corner::TopLeft)
-                                                .child(self.render_rate_limit_notice(cx)),
-                                        )
-                                    })),
-                            )
-                        } else {
-                            el.child(
-                                div()
-                                    .id("error")
-                                    .tooltip(Tooltip::text(error_message))
-                                    .child(
-                                        Icon::new(IconName::XCircle)
-                                            .size(IconSize::Small)
-                                            .color(Color::Error),
-                                    ),
-                            )
-                        }
-                    }),
-            )
-            .child(div().flex_1().child(self.render_prompt_editor(cx)))
-            .child(
-                h_flex()
-                    .gap_2()
-                    .pr(px(9.))
-                    .children(self.render_token_count(cx))
-                    .children(buttons),
-            )
-    }
-}
-
-impl Focusable for PromptEditor {
-    fn focus_handle(&self, cx: &App) -> FocusHandle {
-        self.editor.focus_handle(cx)
-    }
-}
-
-impl PromptEditor {
-    const MAX_LINES: u8 = 8;
-
-    fn new(
-        id: InlineAssistId,
-        editor_margins: Arc<Mutex<EditorMargins>>,
-        prompt_history: VecDeque<String>,
-        prompt_buffer: Entity<MultiBuffer>,
-        codegen: Entity<Codegen>,
-        parent_editor: &Entity<Editor>,
-        assistant_panel: Option<&Entity<AssistantPanel>>,
-        workspace: Option<WeakEntity<Workspace>>,
-        fs: Arc<dyn Fs>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Self {
-        let prompt_editor = cx.new(|cx| {
-            let mut editor = Editor::new(
-                EditorMode::AutoHeight {
-                    max_lines: Self::MAX_LINES as usize,
-                },
-                prompt_buffer,
-                None,
-                window,
-                cx,
-            );
-            editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
-            // Since the prompt editors for all inline assistants are linked,
-            // always show the cursor (even when it isn't focused) because
-            // typing in one will make what you typed appear in all of them.
-            editor.set_show_cursor_when_unfocused(true, cx);
-            editor.set_placeholder_text(Self::placeholder_text(codegen.read(cx), window, cx), cx);
-            editor
-        });
-
-        let mut token_count_subscriptions = Vec::new();
-        token_count_subscriptions.push(cx.subscribe_in(
-            parent_editor,
-            window,
-            Self::handle_parent_editor_event,
-        ));
-        if let Some(assistant_panel) = assistant_panel {
-            token_count_subscriptions.push(cx.subscribe_in(
-                assistant_panel,
-                window,
-                Self::handle_assistant_panel_event,
-            ));
-        }
-
-        let mut this = Self {
-            id,
-            editor: prompt_editor,
-            language_model_selector: cx.new(|cx| {
-                let fs = fs.clone();
-                LanguageModelSelector::new(
-                    |cx| LanguageModelRegistry::read_global(cx).default_model(),
-                    move |model, cx| {
-                        update_settings_file::<AssistantSettings>(
-                            fs.clone(),
-                            cx,
-                            move |settings, _| settings.set_model(model.clone()),
-                        );
-                    },
-                    window,
-                    cx,
-                )
-            }),
-            edited_since_done: false,
-            editor_margins,
-            prompt_history,
-            prompt_history_ix: None,
-            pending_prompt: String::new(),
-            _codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
-            editor_subscriptions: Vec::new(),
-            codegen,
-            pending_token_count: Task::ready(Ok(())),
-            token_counts: None,
-            _token_count_subscriptions: token_count_subscriptions,
-            workspace,
-            show_rate_limit_notice: false,
-        };
-        this.count_tokens(cx);
-        this.subscribe_to_editor(window, cx);
-        this
-    }
-
-    fn subscribe_to_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        self.editor_subscriptions.clear();
-        self.editor_subscriptions.push(cx.subscribe_in(
-            &self.editor,
-            window,
-            Self::handle_prompt_editor_events,
-        ));
-    }
-
-    fn set_show_cursor_when_unfocused(
-        &mut self,
-        show_cursor_when_unfocused: bool,
-        cx: &mut Context<Self>,
-    ) {
-        self.editor.update(cx, |editor, cx| {
-            editor.set_show_cursor_when_unfocused(show_cursor_when_unfocused, cx)
-        });
-    }
-
-    fn unlink(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        let prompt = self.prompt(cx);
-        let focus = self.editor.focus_handle(cx).contains_focused(window, cx);
-        self.editor = cx.new(|cx| {
-            let mut editor = Editor::auto_height(Self::MAX_LINES as usize, window, cx);
-            editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
-            editor.set_placeholder_text("Add a prompt…", cx);
-            editor.set_text(prompt, window, cx);
-            if focus {
-                window.focus(&editor.focus_handle(cx));
-            }
-            editor
-        });
-        self.subscribe_to_editor(window, cx);
-    }
-
-    fn placeholder_text(codegen: &Codegen, window: &Window, cx: &App) -> String {
-        let context_keybinding = text_for_action(&zed_actions::assistant::ToggleFocus, window, cx)
-            .map(|keybinding| format!(" β€’ {keybinding} for context"))
-            .unwrap_or_default();
-
-        let action = if codegen.is_insertion {
-            "Generate"
-        } else {
-            "Transform"
-        };
-
-        format!("{action}…{context_keybinding} β€’ ↓↑ for history")
-    }
-
-    fn prompt(&self, cx: &App) -> String {
-        self.editor.read(cx).text(cx)
-    }
-
-    fn toggle_rate_limit_notice(
-        &mut self,
-        _: &ClickEvent,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.show_rate_limit_notice = !self.show_rate_limit_notice;
-        if self.show_rate_limit_notice {
-            window.focus(&self.editor.focus_handle(cx));
-        }
-        cx.notify();
-    }
-
-    fn handle_parent_editor_event(
-        &mut self,
-        _: &Entity<Editor>,
-        event: &EditorEvent,
-        _: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        if let EditorEvent::BufferEdited { .. } = event {
-            self.count_tokens(cx);
-        }
-    }
-
-    fn handle_assistant_panel_event(
-        &mut self,
-        _: &Entity<AssistantPanel>,
-        event: &AssistantPanelEvent,
-        _: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let AssistantPanelEvent::ContextEdited { .. } = event;
-        self.count_tokens(cx);
-    }
-
-    fn count_tokens(&mut self, cx: &mut Context<Self>) {
-        let assist_id = self.id;
-        self.pending_token_count = cx.spawn(async move |this, cx| {
-            cx.background_executor().timer(Duration::from_secs(1)).await;
-            let token_count = cx
-                .update_global(|inline_assistant: &mut InlineAssistant, cx| {
-                    let assist = inline_assistant
-                        .assists
-                        .get(&assist_id)
-                        .context("assist not found")?;
-                    anyhow::Ok(assist.count_tokens(cx))
-                })??
-                .await?;
-
-            this.update(cx, |this, cx| {
-                this.token_counts = Some(token_count);
-                cx.notify();
-            })
-        })
-    }
-
-    fn handle_prompt_editor_events(
-        &mut self,
-        _: &Entity<Editor>,
-        event: &EditorEvent,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        match event {
-            EditorEvent::Edited { .. } => {
-                if let Some(workspace) = window.root::<Workspace>().flatten() {
-                    workspace.update(cx, |workspace, cx| {
-                        let is_via_ssh = workspace
-                            .project()
-                            .update(cx, |project, _| project.is_via_ssh());
-
-                        workspace
-                            .client()
-                            .telemetry()
-                            .log_edit_event("inline assist", is_via_ssh);
-                    });
-                }
-                let prompt = self.editor.read(cx).text(cx);
-                if self
-                    .prompt_history_ix
-                    .map_or(true, |ix| self.prompt_history[ix] != prompt)
-                {
-                    self.prompt_history_ix.take();
-                    self.pending_prompt = prompt;
-                }
-
-                self.edited_since_done = true;
-                cx.notify();
-            }
-            EditorEvent::BufferEdited => {
-                self.count_tokens(cx);
-            }
-            EditorEvent::Blurred => {
-                if self.show_rate_limit_notice {
-                    self.show_rate_limit_notice = false;
-                    cx.notify();
-                }
-            }
-            _ => {}
-        }
-    }
-
-    fn handle_codegen_changed(&mut self, _: Entity<Codegen>, cx: &mut Context<Self>) {
-        match self.codegen.read(cx).status(cx) {
-            CodegenStatus::Idle => {
-                self.editor
-                    .update(cx, |editor, _| editor.set_read_only(false));
-            }
-            CodegenStatus::Pending => {
-                self.editor
-                    .update(cx, |editor, _| editor.set_read_only(true));
-            }
-            CodegenStatus::Done => {
-                self.edited_since_done = false;
-                self.editor
-                    .update(cx, |editor, _| editor.set_read_only(false));
-            }
-            CodegenStatus::Error(error) => {
-                if cx.has_flag::<ZedProFeatureFlag>()
-                    && error.error_code() == proto::ErrorCode::RateLimitExceeded
-                    && !dismissed_rate_limit_notice()
-                {
-                    self.show_rate_limit_notice = true;
-                    cx.notify();
-                }
-
-                self.edited_since_done = false;
-                self.editor
-                    .update(cx, |editor, _| editor.set_read_only(false));
-            }
-        }
-    }
-
-    fn restart(&mut self, _: &menu::Restart, _window: &mut Window, cx: &mut Context<Self>) {
-        cx.emit(PromptEditorEvent::StartRequested);
-    }
-
-    fn cancel(
-        &mut self,
-        _: &editor::actions::Cancel,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        match self.codegen.read(cx).status(cx) {
-            CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
-                cx.emit(PromptEditorEvent::CancelRequested);
-            }
-            CodegenStatus::Pending => {
-                cx.emit(PromptEditorEvent::StopRequested);
-            }
-        }
-    }
-
-    fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
-        match self.codegen.read(cx).status(cx) {
-            CodegenStatus::Idle => {
-                cx.emit(PromptEditorEvent::StartRequested);
-            }
-            CodegenStatus::Pending => {
-                cx.emit(PromptEditorEvent::DismissRequested);
-            }
-            CodegenStatus::Done => {
-                if self.edited_since_done {
-                    cx.emit(PromptEditorEvent::StartRequested);
-                } else {
-                    cx.emit(PromptEditorEvent::ConfirmRequested);
-                }
-            }
-            CodegenStatus::Error(_) => {
-                cx.emit(PromptEditorEvent::StartRequested);
-            }
-        }
-    }
-
-    fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
-        if let Some(ix) = self.prompt_history_ix {
-            if ix > 0 {
-                self.prompt_history_ix = Some(ix - 1);
-                let prompt = self.prompt_history[ix - 1].as_str();
-                self.editor.update(cx, |editor, cx| {
-                    editor.set_text(prompt, window, cx);
-                    editor.move_to_beginning(&Default::default(), window, cx);
-                });
-            }
-        } else if !self.prompt_history.is_empty() {
-            self.prompt_history_ix = Some(self.prompt_history.len() - 1);
-            let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
-            self.editor.update(cx, |editor, cx| {
-                editor.set_text(prompt, window, cx);
-                editor.move_to_beginning(&Default::default(), window, cx);
-            });
-        }
-    }
-
-    fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
-        if let Some(ix) = self.prompt_history_ix {
-            if ix < self.prompt_history.len() - 1 {
-                self.prompt_history_ix = Some(ix + 1);
-                let prompt = self.prompt_history[ix + 1].as_str();
-                self.editor.update(cx, |editor, cx| {
-                    editor.set_text(prompt, window, cx);
-                    editor.move_to_end(&Default::default(), window, cx)
-                });
-            } else {
-                self.prompt_history_ix = None;
-                let prompt = self.pending_prompt.as_str();
-                self.editor.update(cx, |editor, cx| {
-                    editor.set_text(prompt, window, cx);
-                    editor.move_to_end(&Default::default(), window, cx)
-                });
-            }
-        }
-    }
-
-    fn cycle_prev(
-        &mut self,
-        _: &CyclePreviousInlineAssist,
-        _: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.codegen
-            .update(cx, |codegen, cx| codegen.cycle_prev(cx));
-    }
-
-    fn cycle_next(&mut self, _: &CycleNextInlineAssist, _: &mut Window, cx: &mut Context<Self>) {
-        self.codegen
-            .update(cx, |codegen, cx| codegen.cycle_next(cx));
-    }
-
-    fn render_cycle_controls(&self, cx: &Context<Self>) -> AnyElement {
-        let codegen = self.codegen.read(cx);
-        let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
-
-        let model_registry = LanguageModelRegistry::read_global(cx);
-        let default_model = model_registry.default_model().map(|default| default.model);
-        let alternative_models = model_registry.inline_alternative_models();
-
-        let get_model_name = |index: usize| -> String {
-            let name = |model: &Arc<dyn LanguageModel>| model.name().0.to_string();
-
-            match index {
-                0 => default_model.as_ref().map_or_else(String::new, name),
-                index if index <= alternative_models.len() => alternative_models
-                    .get(index - 1)
-                    .map_or_else(String::new, name),
-                _ => String::new(),
-            }
-        };
-
-        let total_models = alternative_models.len() + 1;
-
-        if total_models <= 1 {
-            return div().into_any_element();
-        }
-
-        let current_index = codegen.active_alternative;
-        let prev_index = (current_index + total_models - 1) % total_models;
-        let next_index = (current_index + 1) % total_models;
-
-        let prev_model_name = get_model_name(prev_index);
-        let next_model_name = get_model_name(next_index);
-
-        h_flex()
-            .child(
-                IconButton::new("previous", IconName::ChevronLeft)
-                    .icon_color(Color::Muted)
-                    .disabled(disabled || current_index == 0)
-                    .shape(IconButtonShape::Square)
-                    .tooltip({
-                        let focus_handle = self.editor.focus_handle(cx);
-                        move |window, cx| {
-                            cx.new(|cx| {
-                                let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
-                                    KeyBinding::for_action_in(
-                                        &CyclePreviousInlineAssist,
-                                        &focus_handle,
-                                        window,
-                                        cx,
-                                    ),
-                                );
-                                if !disabled && current_index != 0 {
-                                    tooltip = tooltip.meta(prev_model_name.clone());
-                                }
-                                tooltip
-                            })
-                            .into()
-                        }
-                    })
-                    .on_click(cx.listener(|this, _, _, cx| {
-                        this.codegen
-                            .update(cx, |codegen, cx| codegen.cycle_prev(cx))
-                    })),
-            )
-            .child(
-                Label::new(format!(
-                    "{}/{}",
-                    codegen.active_alternative + 1,
-                    codegen.alternative_count(cx)
-                ))
-                .size(LabelSize::Small)
-                .color(if disabled {
-                    Color::Disabled
-                } else {
-                    Color::Muted
-                }),
-            )
-            .child(
-                IconButton::new("next", IconName::ChevronRight)
-                    .icon_color(Color::Muted)
-                    .disabled(disabled || current_index == total_models - 1)
-                    .shape(IconButtonShape::Square)
-                    .tooltip({
-                        let focus_handle = self.editor.focus_handle(cx);
-                        move |window, cx| {
-                            cx.new(|cx| {
-                                let mut tooltip = Tooltip::new("Next Alternative").key_binding(
-                                    KeyBinding::for_action_in(
-                                        &CycleNextInlineAssist,
-                                        &focus_handle,
-                                        window,
-                                        cx,
-                                    ),
-                                );
-                                if !disabled && current_index != total_models - 1 {
-                                    tooltip = tooltip.meta(next_model_name.clone());
-                                }
-                                tooltip
-                            })
-                            .into()
-                        }
-                    })
-                    .on_click(cx.listener(|this, _, _, cx| {
-                        this.codegen
-                            .update(cx, |codegen, cx| codegen.cycle_next(cx))
-                    })),
-            )
-            .into_any_element()
-    }
-
-    fn render_token_count(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
-        let model = LanguageModelRegistry::read_global(cx)
-            .default_model()?
-            .model;
-        let token_counts = self.token_counts?;
-        let max_token_count = model.max_token_count();
-
-        let remaining_tokens = max_token_count as isize - token_counts.total as isize;
-        let token_count_color = if remaining_tokens <= 0 {
-            Color::Error
-        } else if token_counts.total as f32 / max_token_count as f32 >= 0.8 {
-            Color::Warning
-        } else {
-            Color::Muted
-        };
-
-        let mut token_count = h_flex()
-            .id("token_count")
-            .gap_0p5()
-            .child(
-                Label::new(humanize_token_count(token_counts.total))
-                    .size(LabelSize::Small)
-                    .color(token_count_color),
-            )
-            .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
-            .child(
-                Label::new(humanize_token_count(max_token_count))
-                    .size(LabelSize::Small)
-                    .color(Color::Muted),
-            );
-        if let Some(workspace) = self.workspace.clone() {
-            token_count = token_count
-                .tooltip(move |window, cx| {
-                    Tooltip::with_meta(
-                        format!(
-                            "Tokens Used ({} from the Assistant Panel)",
-                            humanize_token_count(token_counts.assistant_panel)
-                        ),
-                        None,
-                        "Click to open the Assistant Panel",
-                        window,
-                        cx,
-                    )
-                })
-                .cursor_pointer()
-                .on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation())
-                .on_click(move |_, window, cx| {
-                    cx.stop_propagation();
-                    workspace
-                        .update(cx, |workspace, cx| {
-                            workspace.focus_panel::<AssistantPanel>(window, cx)
-                        })
-                        .ok();
-                });
-        } else {
-            token_count = token_count
-                .cursor_default()
-                .tooltip(Tooltip::text("Tokens used"));
-        }
-
-        Some(token_count)
-    }
-
-    fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
-        let settings = ThemeSettings::get_global(cx);
-        let text_style = TextStyle {
-            color: if self.editor.read(cx).read_only(cx) {
-                cx.theme().colors().text_disabled
-            } else {
-                cx.theme().colors().text
-            },
-            font_family: settings.buffer_font.family.clone(),
-            font_fallbacks: settings.buffer_font.fallbacks.clone(),
-            font_size: settings.buffer_font_size(cx).into(),
-            font_weight: settings.buffer_font.weight,
-            line_height: relative(settings.buffer_line_height.value()),
-            ..Default::default()
-        };
-        EditorElement::new(
-            &self.editor,
-            EditorStyle {
-                background: cx.theme().colors().editor_background,
-                local_player: cx.theme().players().local(),
-                text: text_style,
-                ..Default::default()
-            },
-        )
-    }
-
-    fn render_rate_limit_notice(&self, cx: &mut Context<Self>) -> impl IntoElement {
-        Popover::new().child(
-            v_flex()
-                .occlude()
-                .p_2()
-                .child(
-                    Label::new("Out of Tokens")
-                        .size(LabelSize::Small)
-                        .weight(FontWeight::BOLD),
-                )
-                .child(Label::new(
-                    "Try Zed Pro for higher limits, a wider range of models, and more.",
-                ))
-                .child(
-                    h_flex()
-                        .justify_between()
-                        .child(CheckboxWithLabel::new(
-                            "dont-show-again",
-                            Label::new("Don't show again"),
-                            if dismissed_rate_limit_notice() {
-                                ui::ToggleState::Selected
-                            } else {
-                                ui::ToggleState::Unselected
-                            },
-                            |selection, _, cx| {
-                                let is_dismissed = match selection {
-                                    ui::ToggleState::Unselected => false,
-                                    ui::ToggleState::Indeterminate => return,
-                                    ui::ToggleState::Selected => true,
-                                };
-
-                                set_rate_limit_notice_dismissed(is_dismissed, cx)
-                            },
-                        ))
-                        .child(
-                            h_flex()
-                                .gap_2()
-                                .child(
-                                    Button::new("dismiss", "Dismiss")
-                                        .style(ButtonStyle::Transparent)
-                                        .on_click(cx.listener(Self::toggle_rate_limit_notice)),
-                                )
-                                .child(Button::new("more-info", "More Info").on_click(
-                                    |_event, window, cx| {
-                                        window.dispatch_action(
-                                            Box::new(zed_actions::OpenAccountSettings),
-                                            cx,
-                                        )
-                                    },
-                                )),
-                        ),
-                ),
-        )
-    }
-}
-
-const DISMISSED_RATE_LIMIT_NOTICE_KEY: &str = "dismissed-rate-limit-notice";
-
-fn dismissed_rate_limit_notice() -> bool {
-    db::kvp::KEY_VALUE_STORE
-        .read_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY)
-        .log_err()
-        .map_or(false, |s| s.is_some())
-}
-
-fn set_rate_limit_notice_dismissed(is_dismissed: bool, cx: &mut App) {
-    db::write_and_log(cx, move || async move {
-        if is_dismissed {
-            db::kvp::KEY_VALUE_STORE
-                .write_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY.into(), "1".into())
-                .await
-        } else {
-            db::kvp::KEY_VALUE_STORE
-                .delete_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY.into())
-                .await
-        }
-    })
-}
-
-struct InlineAssist {
-    group_id: InlineAssistGroupId,
-    range: Range<Anchor>,
-    editor: WeakEntity<Editor>,
-    decorations: Option<InlineAssistDecorations>,
-    codegen: Entity<Codegen>,
-    _subscriptions: Vec<Subscription>,
-    workspace: Option<WeakEntity<Workspace>>,
-    include_context: bool,
-}
-
-impl InlineAssist {
-    fn new(
-        assist_id: InlineAssistId,
-        group_id: InlineAssistGroupId,
-        include_context: bool,
-        editor: &Entity<Editor>,
-        prompt_editor: &Entity<PromptEditor>,
-        prompt_block_id: CustomBlockId,
-        end_block_id: CustomBlockId,
-        range: Range<Anchor>,
-        codegen: Entity<Codegen>,
-        workspace: Option<WeakEntity<Workspace>>,
-        window: &mut Window,
-        cx: &mut App,
-    ) -> Self {
-        let prompt_editor_focus_handle = prompt_editor.focus_handle(cx);
-        InlineAssist {
-            group_id,
-            include_context,
-            editor: editor.downgrade(),
-            decorations: Some(InlineAssistDecorations {
-                prompt_block_id,
-                prompt_editor: prompt_editor.clone(),
-                removed_line_block_ids: HashSet::default(),
-                end_block_id,
-            }),
-            range,
-            codegen: codegen.clone(),
-            workspace: workspace.clone(),
-            _subscriptions: vec![
-                window.on_focus_in(&prompt_editor_focus_handle, cx, move |_, cx| {
-                    InlineAssistant::update_global(cx, |this, cx| {
-                        this.handle_prompt_editor_focus_in(assist_id, cx)
-                    })
-                }),
-                window.on_focus_out(&prompt_editor_focus_handle, cx, move |_, _, cx| {
-                    InlineAssistant::update_global(cx, |this, cx| {
-                        this.handle_prompt_editor_focus_out(assist_id, cx)
-                    })
-                }),
-                window.subscribe(
-                    prompt_editor,
-                    cx,
-                    move |prompt_editor, event, window, cx| {
-                        InlineAssistant::update_global(cx, |this, cx| {
-                            this.handle_prompt_editor_event(prompt_editor, event, window, cx)
-                        })
-                    },
-                ),
-                window.observe(&codegen, cx, {
-                    let editor = editor.downgrade();
-                    move |_, window, cx| {
-                        if let Some(editor) = editor.upgrade() {
-                            InlineAssistant::update_global(cx, |this, cx| {
-                                if let Some(editor_assists) =
-                                    this.assists_by_editor.get(&editor.downgrade())
-                                {
-                                    editor_assists.highlight_updates.send(()).ok();
-                                }
-
-                                this.update_editor_blocks(&editor, assist_id, window, cx);
-                            })
-                        }
-                    }
-                }),
-                window.subscribe(&codegen, cx, move |codegen, event, window, cx| {
-                    InlineAssistant::update_global(cx, |this, cx| match event {
-                        CodegenEvent::Undone => this.finish_assist(assist_id, false, window, cx),
-                        CodegenEvent::Finished => {
-                            let assist = if let Some(assist) = this.assists.get(&assist_id) {
-                                assist
-                            } else {
-                                return;
-                            };
-
-                            if let CodegenStatus::Error(error) = codegen.read(cx).status(cx) {
-                                if assist.decorations.is_none() {
-                                    if let Some(workspace) = assist
-                                        .workspace
-                                        .as_ref()
-                                        .and_then(|workspace| workspace.upgrade())
-                                    {
-                                        let error = format!("Inline assistant error: {}", error);
-                                        workspace.update(cx, |workspace, cx| {
-                                            struct InlineAssistantError;
-
-                                            let id =
-                                                NotificationId::composite::<InlineAssistantError>(
-                                                    assist_id.0,
-                                                );
-
-                                            workspace.show_toast(Toast::new(id, error), cx);
-                                        })
-                                    }
-                                }
-                            }
-
-                            if assist.decorations.is_none() {
-                                this.finish_assist(assist_id, false, window, cx);
-                            }
-                        }
-                    })
-                }),
-            ],
-        }
-    }
-
-    fn user_prompt(&self, cx: &App) -> Option<String> {
-        let decorations = self.decorations.as_ref()?;
-        Some(decorations.prompt_editor.read(cx).prompt(cx))
-    }
-
-    fn assistant_panel_context(&self, cx: &mut App) -> Option<LanguageModelRequest> {
-        if self.include_context {
-            let workspace = self.workspace.as_ref()?;
-            let workspace = workspace.upgrade()?.read(cx);
-            let assistant_panel = workspace.panel::<AssistantPanel>(cx)?;
-            Some(
-                assistant_panel
-                    .read(cx)
-                    .active_context(cx)?
-                    .read(cx)
-                    .to_completion_request(None, RequestType::Chat, cx),
-            )
-        } else {
-            None
-        }
-    }
-
-    pub fn count_tokens(&self, cx: &mut App) -> BoxFuture<'static, Result<TokenCounts>> {
-        let Some(user_prompt) = self.user_prompt(cx) else {
-            return future::ready(Err(anyhow!("no user prompt"))).boxed();
-        };
-        let assistant_panel_context = self.assistant_panel_context(cx);
-        self.codegen
-            .read(cx)
-            .count_tokens(user_prompt, assistant_panel_context, cx)
-    }
-}
-
-struct InlineAssistDecorations {
-    prompt_block_id: CustomBlockId,
-    prompt_editor: Entity<PromptEditor>,
-    removed_line_block_ids: HashSet<CustomBlockId>,
-    end_block_id: CustomBlockId,
-}
-
-#[derive(Copy, Clone, Debug)]
-pub enum CodegenEvent {
-    Finished,
-    Undone,
-}
-
-pub struct Codegen {
-    alternatives: Vec<Entity<CodegenAlternative>>,
-    active_alternative: usize,
-    seen_alternatives: HashSet<usize>,
-    subscriptions: Vec<Subscription>,
-    buffer: Entity<MultiBuffer>,
-    range: Range<Anchor>,
-    initial_transaction_id: Option<TransactionId>,
-    telemetry: Arc<Telemetry>,
-    builder: Arc<PromptBuilder>,
-    is_insertion: bool,
-}
-
-impl Codegen {
-    pub fn new(
-        buffer: Entity<MultiBuffer>,
-        range: Range<Anchor>,
-        initial_transaction_id: Option<TransactionId>,
-        telemetry: Arc<Telemetry>,
-        builder: Arc<PromptBuilder>,
-        cx: &mut Context<Self>,
-    ) -> Self {
-        let codegen = cx.new(|cx| {
-            CodegenAlternative::new(
-                buffer.clone(),
-                range.clone(),
-                false,
-                Some(telemetry.clone()),
-                builder.clone(),
-                cx,
-            )
-        });
-        let mut this = Self {
-            is_insertion: range.to_offset(&buffer.read(cx).snapshot(cx)).is_empty(),
-            alternatives: vec![codegen],
-            active_alternative: 0,
-            seen_alternatives: HashSet::default(),
-            subscriptions: Vec::new(),
-            buffer,
-            range,
-            initial_transaction_id,
-            telemetry,
-            builder,
-        };
-        this.activate(0, cx);
-        this
-    }
-
-    fn subscribe_to_alternative(&mut self, cx: &mut Context<Self>) {
-        let codegen = self.active_alternative().clone();
-        self.subscriptions.clear();
-        self.subscriptions
-            .push(cx.observe(&codegen, |_, _, cx| cx.notify()));
-        self.subscriptions
-            .push(cx.subscribe(&codegen, |_, _, event, cx| cx.emit(*event)));
-    }
-
-    fn active_alternative(&self) -> &Entity<CodegenAlternative> {
-        &self.alternatives[self.active_alternative]
-    }
-
-    fn status<'a>(&self, cx: &'a App) -> &'a CodegenStatus {
-        &self.active_alternative().read(cx).status
-    }
-
-    fn alternative_count(&self, cx: &App) -> usize {
-        LanguageModelRegistry::read_global(cx)
-            .inline_alternative_models()
-            .len()
-            + 1
-    }
-
-    pub fn cycle_prev(&mut self, cx: &mut Context<Self>) {
-        let next_active_ix = if self.active_alternative == 0 {
-            self.alternatives.len() - 1
-        } else {
-            self.active_alternative - 1
-        };
-        self.activate(next_active_ix, cx);
-    }
-
-    pub fn cycle_next(&mut self, cx: &mut Context<Self>) {
-        let next_active_ix = (self.active_alternative + 1) % self.alternatives.len();
-        self.activate(next_active_ix, cx);
-    }
-
-    fn activate(&mut self, index: usize, cx: &mut Context<Self>) {
-        self.active_alternative()
-            .update(cx, |codegen, cx| codegen.set_active(false, cx));
-        self.seen_alternatives.insert(index);
-        self.active_alternative = index;
-        self.active_alternative()
-            .update(cx, |codegen, cx| codegen.set_active(true, cx));
-        self.subscribe_to_alternative(cx);
-        cx.notify();
-    }
-
-    pub fn start(
-        &mut self,
-        user_prompt: String,
-        assistant_panel_context: Option<LanguageModelRequest>,
-        cx: &mut Context<Self>,
-    ) -> Result<()> {
-        let alternative_models = LanguageModelRegistry::read_global(cx)
-            .inline_alternative_models()
-            .to_vec();
-
-        self.active_alternative()
-            .update(cx, |alternative, cx| alternative.undo(cx));
-        self.activate(0, cx);
-        self.alternatives.truncate(1);
-
-        for _ in 0..alternative_models.len() {
-            self.alternatives.push(cx.new(|cx| {
-                CodegenAlternative::new(
-                    self.buffer.clone(),
-                    self.range.clone(),
-                    false,
-                    Some(self.telemetry.clone()),
-                    self.builder.clone(),
-                    cx,
-                )
-            }));
-        }
-
-        let primary_model = LanguageModelRegistry::read_global(cx)
-            .default_model()
-            .context("no active model")?
-            .model;
-
-        for (model, alternative) in iter::once(primary_model)
-            .chain(alternative_models)
-            .zip(&self.alternatives)
-        {
-            alternative.update(cx, |alternative, cx| {
-                alternative.start(
-                    user_prompt.clone(),
-                    assistant_panel_context.clone(),
-                    model.clone(),
-                    cx,
-                )
-            })?;
-        }
-
-        Ok(())
-    }
-
-    pub fn stop(&mut self, cx: &mut Context<Self>) {
-        for codegen in &self.alternatives {
-            codegen.update(cx, |codegen, cx| codegen.stop(cx));
-        }
-    }
-
-    pub fn undo(&mut self, cx: &mut Context<Self>) {
-        self.active_alternative()
-            .update(cx, |codegen, cx| codegen.undo(cx));
-
-        self.buffer.update(cx, |buffer, cx| {
-            if let Some(transaction_id) = self.initial_transaction_id.take() {
-                buffer.undo_transaction(transaction_id, cx);
-                buffer.refresh_preview(cx);
-            }
-        });
-    }
-
-    pub fn count_tokens(
-        &self,
-        user_prompt: String,
-        assistant_panel_context: Option<LanguageModelRequest>,
-        cx: &App,
-    ) -> BoxFuture<'static, Result<TokenCounts>> {
-        self.active_alternative()
-            .read(cx)
-            .count_tokens(user_prompt, assistant_panel_context, cx)
-    }
-
-    pub fn buffer(&self, cx: &App) -> Entity<MultiBuffer> {
-        self.active_alternative().read(cx).buffer.clone()
-    }
-
-    pub fn old_buffer(&self, cx: &App) -> Entity<Buffer> {
-        self.active_alternative().read(cx).old_buffer.clone()
-    }
-
-    pub fn snapshot(&self, cx: &App) -> MultiBufferSnapshot {
-        self.active_alternative().read(cx).snapshot.clone()
-    }
-
-    pub fn edit_position(&self, cx: &App) -> Option<Anchor> {
-        self.active_alternative().read(cx).edit_position
-    }
-
-    fn diff<'a>(&self, cx: &'a App) -> &'a Diff {
-        &self.active_alternative().read(cx).diff
-    }
-
-    pub fn last_equal_ranges<'a>(&self, cx: &'a App) -> &'a [Range<Anchor>] {
-        self.active_alternative().read(cx).last_equal_ranges()
-    }
-}
-
-impl EventEmitter<CodegenEvent> for Codegen {}
-
-pub struct CodegenAlternative {
-    buffer: Entity<MultiBuffer>,
-    old_buffer: Entity<Buffer>,
-    snapshot: MultiBufferSnapshot,
-    edit_position: Option<Anchor>,
-    range: Range<Anchor>,
-    last_equal_ranges: Vec<Range<Anchor>>,
-    transformation_transaction_id: Option<TransactionId>,
-    status: CodegenStatus,
-    generation: Task<()>,
-    diff: Diff,
-    telemetry: Option<Arc<Telemetry>>,
-    _subscription: gpui::Subscription,
-    builder: Arc<PromptBuilder>,
-    active: bool,
-    edits: Vec<(Range<Anchor>, String)>,
-    line_operations: Vec<LineOperation>,
-    request: Option<LanguageModelRequest>,
-    elapsed_time: Option<f64>,
-    completion: Option<String>,
-    message_id: Option<String>,
-}
-
-enum CodegenStatus {
-    Idle,
-    Pending,
-    Done,
-    Error(anyhow::Error),
-}
-
-#[derive(Default)]
-struct Diff {
-    deleted_row_ranges: Vec<(Anchor, RangeInclusive<u32>)>,
-    inserted_row_ranges: Vec<Range<Anchor>>,
-}
-
-impl Diff {
-    fn is_empty(&self) -> bool {
-        self.deleted_row_ranges.is_empty() && self.inserted_row_ranges.is_empty()
-    }
-}
-
-impl EventEmitter<CodegenEvent> for CodegenAlternative {}
-
-impl CodegenAlternative {
-    pub fn new(
-        multi_buffer: Entity<MultiBuffer>,
-        range: Range<Anchor>,
-        active: bool,
-        telemetry: Option<Arc<Telemetry>>,
-        builder: Arc<PromptBuilder>,
-        cx: &mut Context<Self>,
-    ) -> Self {
-        let snapshot = multi_buffer.read(cx).snapshot(cx);
-
-        let (buffer, _, _) = snapshot
-            .range_to_buffer_ranges(range.clone())
-            .pop()
-            .unwrap();
-        let old_buffer = cx.new(|cx| {
-            let text = buffer.as_rope().clone();
-            let line_ending = buffer.line_ending();
-            let language = buffer.language().cloned();
-            let language_registry = multi_buffer
-                .read(cx)
-                .buffer(buffer.remote_id())
-                .unwrap()
-                .read(cx)
-                .language_registry();
-
-            let mut buffer = Buffer::local_normalized(text, line_ending, cx);
-            buffer.set_language(language, cx);
-            if let Some(language_registry) = language_registry {
-                buffer.set_language_registry(language_registry)
-            }
-            buffer
-        });
-
-        Self {
-            buffer: multi_buffer.clone(),
-            old_buffer,
-            edit_position: None,
-            message_id: None,
-            snapshot,
-            last_equal_ranges: Default::default(),
-            transformation_transaction_id: None,
-            status: CodegenStatus::Idle,
-            generation: Task::ready(()),
-            diff: Diff::default(),
-            telemetry,
-            _subscription: cx.subscribe(&multi_buffer, Self::handle_buffer_event),
-            builder,
-            active,
-            edits: Vec::new(),
-            line_operations: Vec::new(),
-            range,
-            request: None,
-            elapsed_time: None,
-            completion: None,
-        }
-    }
-
-    fn set_active(&mut self, active: bool, cx: &mut Context<Self>) {
-        if active != self.active {
-            self.active = active;
-
-            if self.active {
-                let edits = self.edits.clone();
-                self.apply_edits(edits, cx);
-                if matches!(self.status, CodegenStatus::Pending) {
-                    let line_operations = self.line_operations.clone();
-                    self.reapply_line_based_diff(line_operations, cx);
-                } else {
-                    self.reapply_batch_diff(cx).detach();
-                }
-            } else if let Some(transaction_id) = self.transformation_transaction_id.take() {
-                self.buffer.update(cx, |buffer, cx| {
-                    buffer.undo_transaction(transaction_id, cx);
-                    buffer.forget_transaction(transaction_id, cx);
-                });
-            }
-        }
-    }
-
-    fn handle_buffer_event(
-        &mut self,
-        _buffer: Entity<MultiBuffer>,
-        event: &multi_buffer::Event,
-        cx: &mut Context<Self>,
-    ) {
-        if let multi_buffer::Event::TransactionUndone { transaction_id } = event {
-            if self.transformation_transaction_id == Some(*transaction_id) {
-                self.transformation_transaction_id = None;
-                self.generation = Task::ready(());
-                cx.emit(CodegenEvent::Undone);
-            }
-        }
-    }
-
-    pub fn last_equal_ranges(&self) -> &[Range<Anchor>] {
-        &self.last_equal_ranges
-    }
-
-    pub fn count_tokens(
-        &self,
-        user_prompt: String,
-        assistant_panel_context: Option<LanguageModelRequest>,
-        cx: &App,
-    ) -> BoxFuture<'static, Result<TokenCounts>> {
-        if let Some(ConfiguredModel { model, .. }) =
-            LanguageModelRegistry::read_global(cx).inline_assistant_model()
-        {
-            let request =
-                self.build_request(&model, user_prompt, assistant_panel_context.clone(), cx);
-            match request {
-                Ok(request) => {
-                    let total_count = model.count_tokens(request.clone(), cx);
-                    let assistant_panel_count = assistant_panel_context
-                        .map(|context| model.count_tokens(context, cx))
-                        .unwrap_or_else(|| future::ready(Ok(0)).boxed());
-
-                    async move {
-                        Ok(TokenCounts {
-                            total: total_count.await?,
-                            assistant_panel: assistant_panel_count.await?,
-                        })
-                    }
-                    .boxed()
-                }
-                Err(error) => futures::future::ready(Err(error)).boxed(),
-            }
-        } else {
-            future::ready(Err(anyhow!("no active model"))).boxed()
-        }
-    }
-
-    pub fn start(
-        &mut self,
-        user_prompt: String,
-        assistant_panel_context: Option<LanguageModelRequest>,
-        model: Arc<dyn LanguageModel>,
-        cx: &mut Context<Self>,
-    ) -> Result<()> {
-        if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() {
-            self.buffer.update(cx, |buffer, cx| {
-                buffer.undo_transaction(transformation_transaction_id, cx);
-            });
-        }
-
-        self.edit_position = Some(self.range.start.bias_right(&self.snapshot));
-
-        let api_key = model.api_key(cx);
-        let telemetry_id = model.telemetry_id();
-        let provider_id = model.provider_id();
-        let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
-            if user_prompt.trim().to_lowercase() == "delete" {
-                async { Ok(LanguageModelTextStream::default()) }.boxed_local()
-            } else {
-                let request =
-                    self.build_request(&model, user_prompt, assistant_panel_context, cx)?;
-                self.request = Some(request.clone());
-
-                cx.spawn(async move |_, cx| model.stream_completion_text(request, &cx).await)
-                    .boxed_local()
-            };
-        self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
-        Ok(())
-    }
-
-    fn build_request(
-        &self,
-        model: &Arc<dyn LanguageModel>,
-        user_prompt: String,
-        assistant_panel_context: Option<LanguageModelRequest>,
-        cx: &App,
-    ) -> Result<LanguageModelRequest> {
-        let buffer = self.buffer.read(cx).snapshot(cx);
-        let language = buffer.language_at(self.range.start);
-        let language_name = if let Some(language) = language.as_ref() {
-            if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
-                None
-            } else {
-                Some(language.name())
-            }
-        } else {
-            None
-        };
-
-        let language_name = language_name.as_ref();
-        let start = buffer.point_to_buffer_offset(self.range.start);
-        let end = buffer.point_to_buffer_offset(self.range.end);
-        let (buffer, range) = if let Some((start, end)) = start.zip(end) {
-            let (start_buffer, start_buffer_offset) = start;
-            let (end_buffer, end_buffer_offset) = end;
-            if start_buffer.remote_id() == end_buffer.remote_id() {
-                (start_buffer.clone(), start_buffer_offset..end_buffer_offset)
-            } else {
-                return Err(anyhow::anyhow!("invalid transformation range"));
-            }
-        } else {
-            return Err(anyhow::anyhow!("invalid transformation range"));
-        };
-
-        let prompt = self
-            .builder
-            .generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
-            .map_err(|e| anyhow::anyhow!("Failed to generate content prompt: {}", e))?;
-
-        let mut messages = Vec::new();
-        if let Some(context_request) = assistant_panel_context {
-            messages = context_request.messages;
-        }
-
-        messages.push(LanguageModelRequestMessage {
-            role: Role::User,
-            content: vec![prompt.into()],
-            cache: false,
-        });
-
-        Ok(LanguageModelRequest {
-            thread_id: None,
-            prompt_id: None,
-            mode: None,
-            messages,
-            tools: Vec::new(),
-            stop: Vec::new(),
-            temperature: AssistantSettings::temperature_for_model(&model, cx),
-        })
-    }
-
-    pub fn handle_stream(
-        &mut self,
-        model_telemetry_id: String,
-        model_provider_id: String,
-        model_api_key: Option<String>,
-        stream: impl 'static + Future<Output = Result<LanguageModelTextStream>>,
-        cx: &mut Context<Self>,
-    ) {
-        let start_time = Instant::now();
-        let snapshot = self.snapshot.clone();
-        let selected_text = snapshot
-            .text_for_range(self.range.start..self.range.end)
-            .collect::<Rope>();
-
-        let selection_start = self.range.start.to_point(&snapshot);
-
-        // Start with the indentation of the first line in the selection
-        let mut suggested_line_indent = snapshot
-            .suggested_indents(selection_start.row..=selection_start.row, cx)
-            .into_values()
-            .next()
-            .unwrap_or_else(|| snapshot.indent_size_for_line(MultiBufferRow(selection_start.row)));
-
-        // If the first line in the selection does not have indentation, check the following lines
-        if suggested_line_indent.len == 0 && suggested_line_indent.kind == IndentKind::Space {
-            for row in selection_start.row..=self.range.end.to_point(&snapshot).row {
-                let line_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
-                // Prefer tabs if a line in the selection uses tabs as indentation
-                if line_indent.kind == IndentKind::Tab {
-                    suggested_line_indent.kind = IndentKind::Tab;
-                    break;
-                }
-            }
-        }
-
-        let http_client = cx.http_client();
-        let telemetry = self.telemetry.clone();
-        let language_name = {
-            let multibuffer = self.buffer.read(cx);
-            let snapshot = multibuffer.snapshot(cx);
-            let ranges = snapshot.range_to_buffer_ranges(self.range.clone());
-            ranges
-                .first()
-                .and_then(|(buffer, _, _)| buffer.language())
-                .map(|language| language.name())
-        };
-
-        self.diff = Diff::default();
-        self.status = CodegenStatus::Pending;
-        let mut edit_start = self.range.start.to_offset(&snapshot);
-        let completion = Arc::new(Mutex::new(String::new()));
-        let completion_clone = completion.clone();
-
-        self.generation = cx.spawn(async move |codegen, cx| {
-            let stream = stream.await;
-            let message_id = stream
-                .as_ref()
-                .ok()
-                .and_then(|stream| stream.message_id.clone());
-            let generate = async {
-                let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
-                let executor = cx.background_executor().clone();
-                let message_id = message_id.clone();
-                let line_based_stream_diff: Task<anyhow::Result<()>> =
-                    cx.background_spawn(async move {
-                        let mut response_latency = None;
-                        let request_start = Instant::now();
-                        let diff = async {
-                            let chunks =
-                                StripInvalidSpans::new(stream?.stream.map_err(|e| e.into()));
-                            futures::pin_mut!(chunks);
-                            let mut diff = StreamingDiff::new(selected_text.to_string());
-                            let mut line_diff = LineDiff::default();
-
-                            let mut new_text = String::new();
-                            let mut base_indent = None;
-                            let mut line_indent = None;
-                            let mut first_line = true;
-
-                            while let Some(chunk) = chunks.next().await {
-                                if response_latency.is_none() {
-                                    response_latency = Some(request_start.elapsed());
-                                }
-                                let chunk = chunk?;
-                                completion_clone.lock().push_str(&chunk);
-
-                                let mut lines = chunk.split('\n').peekable();
-                                while let Some(line) = lines.next() {
-                                    new_text.push_str(line);
-                                    if line_indent.is_none() {
-                                        if let Some(non_whitespace_ch_ix) =
-                                            new_text.find(|ch: char| !ch.is_whitespace())
-                                        {
-                                            line_indent = Some(non_whitespace_ch_ix);
-                                            base_indent = base_indent.or(line_indent);
-
-                                            let line_indent = line_indent.unwrap();
-                                            let base_indent = base_indent.unwrap();
-                                            let indent_delta =
-                                                line_indent as i32 - base_indent as i32;
-                                            let mut corrected_indent_len = cmp::max(
-                                                0,
-                                                suggested_line_indent.len as i32 + indent_delta,
-                                            )
-                                                as usize;
-                                            if first_line {
-                                                corrected_indent_len = corrected_indent_len
-                                                    .saturating_sub(
-                                                        selection_start.column as usize,
-                                                    );
-                                            }
-
-                                            let indent_char = suggested_line_indent.char();
-                                            let mut indent_buffer = [0; 4];
-                                            let indent_str =
-                                                indent_char.encode_utf8(&mut indent_buffer);
-                                            new_text.replace_range(
-                                                ..line_indent,
-                                                &indent_str.repeat(corrected_indent_len),
-                                            );
-                                        }
-                                    }
-
-                                    if line_indent.is_some() {
-                                        let char_ops = diff.push_new(&new_text);
-                                        line_diff.push_char_operations(&char_ops, &selected_text);
-                                        diff_tx
-                                            .send((char_ops, line_diff.line_operations()))
-                                            .await?;
-                                        new_text.clear();
-                                    }
-
-                                    if lines.peek().is_some() {
-                                        let char_ops = diff.push_new("\n");
-                                        line_diff.push_char_operations(&char_ops, &selected_text);
-                                        diff_tx
-                                            .send((char_ops, line_diff.line_operations()))
-                                            .await?;
-                                        if line_indent.is_none() {
-                                            // Don't write out the leading indentation in empty lines on the next line
-                                            // This is the case where the above if statement didn't clear the buffer
-                                            new_text.clear();
-                                        }
-                                        line_indent = None;
-                                        first_line = false;
-                                    }
-                                }
-                            }
-
-                            let mut char_ops = diff.push_new(&new_text);
-                            char_ops.extend(diff.finish());
-                            line_diff.push_char_operations(&char_ops, &selected_text);
-                            line_diff.finish(&selected_text);
-                            diff_tx
-                                .send((char_ops, line_diff.line_operations()))
-                                .await?;
-
-                            anyhow::Ok(())
-                        };
-
-                        let result = diff.await;
-
-                        let error_message = result.as_ref().err().map(|error| error.to_string());
-                        report_assistant_event(
-                            AssistantEventData {
-                                conversation_id: None,
-                                message_id,
-                                kind: AssistantKind::Inline,
-                                phase: AssistantPhase::Response,
-                                model: model_telemetry_id,
-                                model_provider: model_provider_id.to_string(),
-                                response_latency,
-                                error_message,
-                                language_name: language_name.map(|name| name.to_proto()),
-                            },
-                            telemetry,
-                            http_client,
-                            model_api_key,
-                            &executor,
-                        );
-
-                        result?;
-                        Ok(())
-                    });
-
-                while let Some((char_ops, line_ops)) = diff_rx.next().await {
-                    codegen.update(cx, |codegen, cx| {
-                        codegen.last_equal_ranges.clear();
-
-                        let edits = char_ops
-                            .into_iter()
-                            .filter_map(|operation| match operation {
-                                CharOperation::Insert { text } => {
-                                    let edit_start = snapshot.anchor_after(edit_start);
-                                    Some((edit_start..edit_start, text))
-                                }
-                                CharOperation::Delete { bytes } => {
-                                    let edit_end = edit_start + bytes;
-                                    let edit_range = snapshot.anchor_after(edit_start)
-                                        ..snapshot.anchor_before(edit_end);
-                                    edit_start = edit_end;
-                                    Some((edit_range, String::new()))
-                                }
-                                CharOperation::Keep { bytes } => {
-                                    let edit_end = edit_start + bytes;
-                                    let edit_range = snapshot.anchor_after(edit_start)
-                                        ..snapshot.anchor_before(edit_end);
-                                    edit_start = edit_end;
-                                    codegen.last_equal_ranges.push(edit_range);
-                                    None
-                                }
-                            })
-                            .collect::<Vec<_>>();
-
-                        if codegen.active {
-                            codegen.apply_edits(edits.iter().cloned(), cx);
-                            codegen.reapply_line_based_diff(line_ops.iter().cloned(), cx);
-                        }
-                        codegen.edits.extend(edits);
-                        codegen.line_operations = line_ops;
-                        codegen.edit_position = Some(snapshot.anchor_after(edit_start));
-
-                        cx.notify();
-                    })?;
-                }
-
-                // Streaming stopped and we have the new text in the buffer, and a line-based diff applied for the whole new buffer.
-                // That diff is not what a regular diff is and might look unexpected, ergo apply a regular diff.
-                // It's fine to apply even if the rest of the line diffing fails, as no more hunks are coming through `diff_rx`.
-                let batch_diff_task =
-                    codegen.update(cx, |codegen, cx| codegen.reapply_batch_diff(cx))?;
-                let (line_based_stream_diff, ()) = join!(line_based_stream_diff, batch_diff_task);
-                line_based_stream_diff?;
-
-                anyhow::Ok(())
-            };
-
-            let result = generate.await;
-            let elapsed_time = start_time.elapsed().as_secs_f64();
-
-            codegen
-                .update(cx, |this, cx| {
-                    this.message_id = message_id;
-                    this.last_equal_ranges.clear();
-                    if let Err(error) = result {
-                        this.status = CodegenStatus::Error(error);
-                    } else {
-                        this.status = CodegenStatus::Done;
-                    }
-                    this.elapsed_time = Some(elapsed_time);
-                    this.completion = Some(completion.lock().clone());
-                    cx.emit(CodegenEvent::Finished);
-                    cx.notify();
-                })
-                .ok();
-        });
-        cx.notify();
-    }
-
-    pub fn stop(&mut self, cx: &mut Context<Self>) {
-        self.last_equal_ranges.clear();
-        if self.diff.is_empty() {
-            self.status = CodegenStatus::Idle;
-        } else {
-            self.status = CodegenStatus::Done;
-        }
-        self.generation = Task::ready(());
-        cx.emit(CodegenEvent::Finished);
-        cx.notify();
-    }
-
-    pub fn undo(&mut self, cx: &mut Context<Self>) {
-        self.buffer.update(cx, |buffer, cx| {
-            if let Some(transaction_id) = self.transformation_transaction_id.take() {
-                buffer.undo_transaction(transaction_id, cx);
-                buffer.refresh_preview(cx);
-            }
-        });
-    }
-
-    fn apply_edits(
-        &mut self,
-        edits: impl IntoIterator<Item = (Range<Anchor>, String)>,
-        cx: &mut Context<CodegenAlternative>,
-    ) {
-        let transaction = self.buffer.update(cx, |buffer, cx| {
-            // Avoid grouping assistant edits with user edits.
-            buffer.finalize_last_transaction(cx);
-            buffer.start_transaction(cx);
-            buffer.edit(edits, None, cx);
-            buffer.end_transaction(cx)
-        });
-
-        if let Some(transaction) = transaction {
-            if let Some(first_transaction) = self.transformation_transaction_id {
-                // Group all assistant edits into the first transaction.
-                self.buffer.update(cx, |buffer, cx| {
-                    buffer.merge_transactions(transaction, first_transaction, cx)
-                });
-            } else {
-                self.transformation_transaction_id = Some(transaction);
-                self.buffer
-                    .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
-            }
-        }
-    }
-
-    fn reapply_line_based_diff(
-        &mut self,
-        line_operations: impl IntoIterator<Item = LineOperation>,
-        cx: &mut Context<Self>,
-    ) {
-        let old_snapshot = self.snapshot.clone();
-        let old_range = self.range.to_point(&old_snapshot);
-        let new_snapshot = self.buffer.read(cx).snapshot(cx);
-        let new_range = self.range.to_point(&new_snapshot);
-
-        let mut old_row = old_range.start.row;
-        let mut new_row = new_range.start.row;
-
-        self.diff.deleted_row_ranges.clear();
-        self.diff.inserted_row_ranges.clear();
-        for operation in line_operations {
-            match operation {
-                LineOperation::Keep { lines } => {
-                    old_row += lines;
-                    new_row += lines;
-                }
-                LineOperation::Delete { lines } => {
-                    let old_end_row = old_row + lines - 1;
-                    let new_row = new_snapshot.anchor_before(Point::new(new_row, 0));
-
-                    if let Some((_, last_deleted_row_range)) =
-                        self.diff.deleted_row_ranges.last_mut()
-                    {
-                        if *last_deleted_row_range.end() + 1 == old_row {
-                            *last_deleted_row_range = *last_deleted_row_range.start()..=old_end_row;
-                        } else {
-                            self.diff
-                                .deleted_row_ranges
-                                .push((new_row, old_row..=old_end_row));
-                        }
-                    } else {
-                        self.diff
-                            .deleted_row_ranges
-                            .push((new_row, old_row..=old_end_row));
-                    }
-
-                    old_row += lines;
-                }
-                LineOperation::Insert { lines } => {
-                    let new_end_row = new_row + lines - 1;
-                    let start = new_snapshot.anchor_before(Point::new(new_row, 0));
-                    let end = new_snapshot.anchor_before(Point::new(
-                        new_end_row,
-                        new_snapshot.line_len(MultiBufferRow(new_end_row)),
-                    ));
-                    self.diff.inserted_row_ranges.push(start..end);
-                    new_row += lines;
-                }
-            }
-
-            cx.notify();
-        }
-    }
-
-    fn reapply_batch_diff(&mut self, cx: &mut Context<Self>) -> Task<()> {
-        let old_snapshot = self.snapshot.clone();
-        let old_range = self.range.to_point(&old_snapshot);
-        let new_snapshot = self.buffer.read(cx).snapshot(cx);
-        let new_range = self.range.to_point(&new_snapshot);
-
-        cx.spawn(async move |codegen, cx| {
-            let (deleted_row_ranges, inserted_row_ranges) = cx
-                .background_spawn(async move {
-                    let old_text = old_snapshot
-                        .text_for_range(
-                            Point::new(old_range.start.row, 0)
-                                ..Point::new(
-                                    old_range.end.row,
-                                    old_snapshot.line_len(MultiBufferRow(old_range.end.row)),
-                                ),
-                        )
-                        .collect::<String>();
-                    let new_text = new_snapshot
-                        .text_for_range(
-                            Point::new(new_range.start.row, 0)
-                                ..Point::new(
-                                    new_range.end.row,
-                                    new_snapshot.line_len(MultiBufferRow(new_range.end.row)),
-                                ),
-                        )
-                        .collect::<String>();
-
-                    let old_start_row = old_range.start.row;
-                    let new_start_row = new_range.start.row;
-                    let mut deleted_row_ranges: Vec<(Anchor, RangeInclusive<u32>)> = Vec::new();
-                    let mut inserted_row_ranges = Vec::new();
-                    for (old_rows, new_rows) in line_diff(&old_text, &new_text) {
-                        let old_rows = old_start_row + old_rows.start..old_start_row + old_rows.end;
-                        let new_rows = new_start_row + new_rows.start..new_start_row + new_rows.end;
-                        if !old_rows.is_empty() {
-                            deleted_row_ranges.push((
-                                new_snapshot.anchor_before(Point::new(new_rows.start, 0)),
-                                old_rows.start..=old_rows.end - 1,
-                            ));
-                        }
-                        if !new_rows.is_empty() {
-                            let start = new_snapshot.anchor_before(Point::new(new_rows.start, 0));
-                            let new_end_row = new_rows.end - 1;
-                            let end = new_snapshot.anchor_before(Point::new(
-                                new_end_row,
-                                new_snapshot.line_len(MultiBufferRow(new_end_row)),
-                            ));
-                            inserted_row_ranges.push(start..end);
-                        }
-                    }
-                    (deleted_row_ranges, inserted_row_ranges)
-                })
-                .await;
-
-            codegen
-                .update(cx, |codegen, cx| {
-                    codegen.diff.deleted_row_ranges = deleted_row_ranges;
-                    codegen.diff.inserted_row_ranges = inserted_row_ranges;
-                    cx.notify();
-                })
-                .ok();
-        })
-    }
-}
-
-struct StripInvalidSpans<T> {
-    stream: T,
-    stream_done: bool,
-    buffer: String,
-    first_line: bool,
-    line_end: bool,
-    starts_with_code_block: bool,
-}
-
-impl<T> StripInvalidSpans<T>
-where
-    T: Stream<Item = Result<String>>,
-{
-    fn new(stream: T) -> Self {
-        Self {
-            stream,
-            stream_done: false,
-            buffer: String::new(),
-            first_line: true,
-            line_end: false,
-            starts_with_code_block: false,
-        }
-    }
-}
-
-impl<T> Stream for StripInvalidSpans<T>
-where
-    T: Stream<Item = Result<String>>,
-{
-    type Item = Result<String>;
-
-    fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Option<Self::Item>> {
-        const CODE_BLOCK_DELIMITER: &str = "```";
-        const CURSOR_SPAN: &str = "<|CURSOR|>";
-
-        let this = unsafe { self.get_unchecked_mut() };
-        loop {
-            if !this.stream_done {
-                let mut stream = unsafe { Pin::new_unchecked(&mut this.stream) };
-                match stream.as_mut().poll_next(cx) {
-                    Poll::Ready(Some(Ok(chunk))) => {
-                        this.buffer.push_str(&chunk);
-                    }
-                    Poll::Ready(Some(Err(error))) => return Poll::Ready(Some(Err(error))),
-                    Poll::Ready(None) => {
-                        this.stream_done = true;
-                    }
-                    Poll::Pending => return Poll::Pending,
-                }
-            }
-
-            let mut chunk = String::new();
-            let mut consumed = 0;
-            if !this.buffer.is_empty() {
-                let mut lines = this.buffer.split('\n').enumerate().peekable();
-                while let Some((line_ix, line)) = lines.next() {
-                    if line_ix > 0 {
-                        this.first_line = false;
-                    }
-
-                    if this.first_line {
-                        let trimmed_line = line.trim();
-                        if lines.peek().is_some() {
-                            if trimmed_line.starts_with(CODE_BLOCK_DELIMITER) {
-                                consumed += line.len() + 1;
-                                this.starts_with_code_block = true;
-                                continue;
-                            }
-                        } else if trimmed_line.is_empty()
-                            || prefixes(CODE_BLOCK_DELIMITER)
-                                .any(|prefix| trimmed_line.starts_with(prefix))
-                        {
-                            break;
-                        }
-                    }
-
-                    let line_without_cursor = line.replace(CURSOR_SPAN, "");
-                    if lines.peek().is_some() {
-                        if this.line_end {
-                            chunk.push('\n');
-                        }
-
-                        chunk.push_str(&line_without_cursor);
-                        this.line_end = true;
-                        consumed += line.len() + 1;
-                    } else if this.stream_done {
-                        if !this.starts_with_code_block
-                            || !line_without_cursor.trim().ends_with(CODE_BLOCK_DELIMITER)
-                        {
-                            if this.line_end {
-                                chunk.push('\n');
-                            }
-
-                            chunk.push_str(&line);
-                        }
-
-                        consumed += line.len();
-                    } else {
-                        let trimmed_line = line.trim();
-                        if trimmed_line.is_empty()
-                            || prefixes(CURSOR_SPAN).any(|prefix| trimmed_line.ends_with(prefix))
-                            || prefixes(CODE_BLOCK_DELIMITER)
-                                .any(|prefix| trimmed_line.ends_with(prefix))
-                        {
-                            break;
-                        } else {
-                            if this.line_end {
-                                chunk.push('\n');
-                                this.line_end = false;
-                            }
-
-                            chunk.push_str(&line_without_cursor);
-                            consumed += line.len();
-                        }
-                    }
-                }
-            }
-
-            this.buffer = this.buffer.split_off(consumed);
-            if !chunk.is_empty() {
-                return Poll::Ready(Some(Ok(chunk)));
-            } else if this.stream_done {
-                return Poll::Ready(None);
-            }
-        }
-    }
-}
-
-struct AssistantCodeActionProvider {
-    editor: WeakEntity<Editor>,
-    workspace: WeakEntity<Workspace>,
-}
-
-const ASSISTANT_CODE_ACTION_PROVIDER_ID: &str = "assistant";
-
-impl CodeActionProvider for AssistantCodeActionProvider {
-    fn id(&self) -> Arc<str> {
-        ASSISTANT_CODE_ACTION_PROVIDER_ID.into()
-    }
-
-    fn code_actions(
-        &self,
-        buffer: &Entity<Buffer>,
-        range: Range<text::Anchor>,
-        _: &mut Window,
-        cx: &mut App,
-    ) -> Task<Result<Vec<CodeAction>>> {
-        if !Assistant::enabled(cx) {
-            return Task::ready(Ok(Vec::new()));
-        }
-
-        let snapshot = buffer.read(cx).snapshot();
-        let mut range = range.to_point(&snapshot);
-
-        // Expand the range to line boundaries.
-        range.start.column = 0;
-        range.end.column = snapshot.line_len(range.end.row);
-
-        let mut has_diagnostics = false;
-        for diagnostic in snapshot.diagnostics_in_range::<_, Point>(range.clone(), false) {
-            range.start = cmp::min(range.start, diagnostic.range.start);
-            range.end = cmp::max(range.end, diagnostic.range.end);
-            has_diagnostics = true;
-        }
-        if has_diagnostics {
-            if let Some(symbols_containing_start) = snapshot.symbols_containing(range.start, None) {
-                if let Some(symbol) = symbols_containing_start.last() {
-                    range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot));
-                    range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot));
-                }
-            }
-
-            if let Some(symbols_containing_end) = snapshot.symbols_containing(range.end, None) {
-                if let Some(symbol) = symbols_containing_end.last() {
-                    range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot));
-                    range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot));
-                }
-            }
-
-            Task::ready(Ok(vec![CodeAction {
-                server_id: language::LanguageServerId(0),
-                range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
-                lsp_action: LspAction::Action(Box::new(lsp::CodeAction {
-                    title: "Fix with Assistant".into(),
-                    ..Default::default()
-                })),
-                resolved: true,
-            }]))
-        } else {
-            Task::ready(Ok(Vec::new()))
-        }
-    }
-
-    fn apply_code_action(
-        &self,
-        buffer: Entity<Buffer>,
-        action: CodeAction,
-        excerpt_id: ExcerptId,
-        _push_to_history: bool,
-        window: &mut Window,
-        cx: &mut App,
-    ) -> Task<Result<ProjectTransaction>> {
-        let editor = self.editor.clone();
-        let workspace = self.workspace.clone();
-        window.spawn(cx, async move |cx| {
-            let editor = editor.upgrade().context("editor was released")?;
-            let range = editor
-                .update(cx, |editor, cx| {
-                    editor.buffer().update(cx, |multibuffer, cx| {
-                        let buffer = buffer.read(cx);
-                        let multibuffer_snapshot = multibuffer.read(cx);
-
-                        let old_context_range =
-                            multibuffer_snapshot.context_range_for_excerpt(excerpt_id)?;
-                        let mut new_context_range = old_context_range.clone();
-                        if action
-                            .range
-                            .start
-                            .cmp(&old_context_range.start, buffer)
-                            .is_lt()
-                        {
-                            new_context_range.start = action.range.start;
-                        }
-                        if action.range.end.cmp(&old_context_range.end, buffer).is_gt() {
-                            new_context_range.end = action.range.end;
-                        }
-                        drop(multibuffer_snapshot);
-
-                        if new_context_range != old_context_range {
-                            multibuffer.resize_excerpt(excerpt_id, new_context_range, cx);
-                        }
-
-                        let multibuffer_snapshot = multibuffer.read(cx);
-                        Some(
-                            multibuffer_snapshot
-                                .anchor_in_excerpt(excerpt_id, action.range.start)?
-                                ..multibuffer_snapshot
-                                    .anchor_in_excerpt(excerpt_id, action.range.end)?,
-                        )
-                    })
-                })?
-                .context("invalid range")?;
-            let assistant_panel = workspace.update(cx, |workspace, cx| {
-                workspace
-                    .panel::<AssistantPanel>(cx)
-                    .context("assistant panel was released")
-            })??;
-
-            cx.update_global(|assistant: &mut InlineAssistant, window, cx| {
-                let assist_id = assistant.suggest_assist(
-                    &editor,
-                    range,
-                    "Fix Diagnostics".into(),
-                    None,
-                    true,
-                    Some(workspace),
-                    Some(&assistant_panel),
-                    window,
-                    cx,
-                );
-                assistant.start_assist(assist_id, window, cx);
-            })?;
-
-            Ok(ProjectTransaction::default())
-        })
-    }
-}
-
-fn prefixes(text: &str) -> impl Iterator<Item = &str> {
-    (0..text.len() - 1).map(|ix| &text[..ix + 1])
-}
-
-fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
-    ranges.sort_unstable_by(|a, b| {
-        a.start
-            .cmp(&b.start, buffer)
-            .then_with(|| b.end.cmp(&a.end, buffer))
-    });
-
-    let mut ix = 0;
-    while ix + 1 < ranges.len() {
-        let b = ranges[ix + 1].clone();
-        let a = &mut ranges[ix];
-        if a.end.cmp(&b.start, buffer).is_gt() {
-            if a.end.cmp(&b.end, buffer).is_lt() {
-                a.end = b.end;
-            }
-            ranges.remove(ix + 1);
-        } else {
-            ix += 1;
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use futures::stream::{self};
-    use gpui::TestAppContext;
-    use indoc::indoc;
-    use language::{
-        Buffer, Language, LanguageConfig, LanguageMatcher, Point, language_settings,
-        tree_sitter_rust,
-    };
-    use language_model::{LanguageModelRegistry, TokenUsage};
-    use rand::prelude::*;
-    use serde::Serialize;
-    use settings::SettingsStore;
-    use std::{future, sync::Arc};
-
-    #[derive(Serialize)]
-    pub struct DummyCompletionRequest {
-        pub name: String,
-    }
-
-    #[gpui::test(iterations = 10)]
-    async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
-        cx.set_global(cx.update(SettingsStore::test));
-        cx.update(language_model::LanguageModelRegistry::test);
-        cx.update(language_settings::init);
-
-        let text = indoc! {"
-            fn main() {
-                let x = 0;
-                for _ in 0..10 {
-                    x += 1;
-                }
-            }
-        "};
-        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
-        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
-        let range = buffer.read_with(cx, |buffer, cx| {
-            let snapshot = buffer.snapshot(cx);
-            snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
-        });
-        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
-        let codegen = cx.new(|cx| {
-            CodegenAlternative::new(
-                buffer.clone(),
-                range.clone(),
-                true,
-                None,
-                prompt_builder,
-                cx,
-            )
-        });
-
-        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
-
-        let mut new_text = concat!(
-            "       let mut x = 0;\n",
-            "       while x < 10 {\n",
-            "           x += 1;\n",
-            "       }",
-        );
-        while !new_text.is_empty() {
-            let max_len = cmp::min(new_text.len(), 10);
-            let len = rng.gen_range(1..=max_len);
-            let (chunk, suffix) = new_text.split_at(len);
-            chunks_tx.unbounded_send(chunk.to_string()).unwrap();
-            new_text = suffix;
-            cx.background_executor.run_until_parked();
-        }
-        drop(chunks_tx);
-        cx.background_executor.run_until_parked();
-
-        assert_eq!(
-            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
-            indoc! {"
-                fn main() {
-                    let mut x = 0;
-                    while x < 10 {
-                        x += 1;
-                    }
-                }
-            "}
-        );
-    }
-
-    #[gpui::test(iterations = 10)]
-    async fn test_autoindent_when_generating_past_indentation(
-        cx: &mut TestAppContext,
-        mut rng: StdRng,
-    ) {
-        cx.set_global(cx.update(SettingsStore::test));
-        cx.update(language_settings::init);
-
-        let text = indoc! {"
-            fn main() {
-                le
-            }
-        "};
-        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
-        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
-        let range = buffer.read_with(cx, |buffer, cx| {
-            let snapshot = buffer.snapshot(cx);
-            snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6))
-        });
-        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
-        let codegen = cx.new(|cx| {
-            CodegenAlternative::new(
-                buffer.clone(),
-                range.clone(),
-                true,
-                None,
-                prompt_builder,
-                cx,
-            )
-        });
-
-        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
-
-        cx.background_executor.run_until_parked();
-
-        let mut new_text = concat!(
-            "t mut x = 0;\n",
-            "while x < 10 {\n",
-            "    x += 1;\n",
-            "}", //
-        );
-        while !new_text.is_empty() {
-            let max_len = cmp::min(new_text.len(), 10);
-            let len = rng.gen_range(1..=max_len);
-            let (chunk, suffix) = new_text.split_at(len);
-            chunks_tx.unbounded_send(chunk.to_string()).unwrap();
-            new_text = suffix;
-            cx.background_executor.run_until_parked();
-        }
-        drop(chunks_tx);
-        cx.background_executor.run_until_parked();
-
-        assert_eq!(
-            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
-            indoc! {"
-                fn main() {
-                    let mut x = 0;
-                    while x < 10 {
-                        x += 1;
-                    }
-                }
-            "}
-        );
-    }
-
-    #[gpui::test(iterations = 10)]
-    async fn test_autoindent_when_generating_before_indentation(
-        cx: &mut TestAppContext,
-        mut rng: StdRng,
-    ) {
-        cx.update(LanguageModelRegistry::test);
-        cx.set_global(cx.update(SettingsStore::test));
-        cx.update(language_settings::init);
-
-        let text = concat!(
-            "fn main() {\n",
-            "  \n",
-            "}\n" //
-        );
-        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
-        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
-        let range = buffer.read_with(cx, |buffer, cx| {
-            let snapshot = buffer.snapshot(cx);
-            snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2))
-        });
-        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
-        let codegen = cx.new(|cx| {
-            CodegenAlternative::new(
-                buffer.clone(),
-                range.clone(),
-                true,
-                None,
-                prompt_builder,
-                cx,
-            )
-        });
-
-        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
-
-        cx.background_executor.run_until_parked();
-
-        let mut new_text = concat!(
-            "let mut x = 0;\n",
-            "while x < 10 {\n",
-            "    x += 1;\n",
-            "}", //
-        );
-        while !new_text.is_empty() {
-            let max_len = cmp::min(new_text.len(), 10);
-            let len = rng.gen_range(1..=max_len);
-            let (chunk, suffix) = new_text.split_at(len);
-            chunks_tx.unbounded_send(chunk.to_string()).unwrap();
-            new_text = suffix;
-            cx.background_executor.run_until_parked();
-        }
-        drop(chunks_tx);
-        cx.background_executor.run_until_parked();
-
-        assert_eq!(
-            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
-            indoc! {"
-                fn main() {
-                    let mut x = 0;
-                    while x < 10 {
-                        x += 1;
-                    }
-                }
-            "}
-        );
-    }
-
-    #[gpui::test(iterations = 10)]
-    async fn test_autoindent_respects_tabs_in_selection(cx: &mut TestAppContext) {
-        cx.update(LanguageModelRegistry::test);
-        cx.set_global(cx.update(SettingsStore::test));
-        cx.update(language_settings::init);
-
-        let text = indoc! {"
-            func main() {
-            \tx := 0
-            \tfor i := 0; i < 10; i++ {
-            \t\tx++
-            \t}
-            }
-        "};
-        let buffer = cx.new(|cx| Buffer::local(text, cx));
-        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
-        let range = buffer.read_with(cx, |buffer, cx| {
-            let snapshot = buffer.snapshot(cx);
-            snapshot.anchor_before(Point::new(0, 0))..snapshot.anchor_after(Point::new(4, 2))
-        });
-        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
-        let codegen = cx.new(|cx| {
-            CodegenAlternative::new(
-                buffer.clone(),
-                range.clone(),
-                true,
-                None,
-                prompt_builder,
-                cx,
-            )
-        });
-
-        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
-        let new_text = concat!(
-            "func main() {\n",
-            "\tx := 0\n",
-            "\tfor x < 10 {\n",
-            "\t\tx++\n",
-            "\t}", //
-        );
-        chunks_tx.unbounded_send(new_text.to_string()).unwrap();
-        drop(chunks_tx);
-        cx.background_executor.run_until_parked();
-
-        assert_eq!(
-            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
-            indoc! {"
-                func main() {
-                \tx := 0
-                \tfor x < 10 {
-                \t\tx++
-                \t}
-                }
-            "}
-        );
-    }
-
-    #[gpui::test]
-    async fn test_inactive_codegen_alternative(cx: &mut TestAppContext) {
-        cx.update(LanguageModelRegistry::test);
-        cx.set_global(cx.update(SettingsStore::test));
-        cx.update(language_settings::init);
-
-        let text = indoc! {"
-            fn main() {
-                let x = 0;
-            }
-        "};
-        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
-        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
-        let range = buffer.read_with(cx, |buffer, cx| {
-            let snapshot = buffer.snapshot(cx);
-            snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 14))
-        });
-        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
-        let codegen = cx.new(|cx| {
-            CodegenAlternative::new(
-                buffer.clone(),
-                range.clone(),
-                false,
-                None,
-                prompt_builder,
-                cx,
-            )
-        });
-
-        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
-        chunks_tx
-            .unbounded_send("let mut x = 0;\nx += 1;".to_string())
-            .unwrap();
-        drop(chunks_tx);
-        cx.run_until_parked();
-
-        // The codegen is inactive, so the buffer doesn't get modified.
-        assert_eq!(
-            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
-            text
-        );
-
-        // Activating the codegen applies the changes.
-        codegen.update(cx, |codegen, cx| codegen.set_active(true, cx));
-        assert_eq!(
-            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
-            indoc! {"
-                fn main() {
-                    let mut x = 0;
-                    x += 1;
-                }
-            "}
-        );
-
-        // Deactivating the codegen undoes the changes.
-        codegen.update(cx, |codegen, cx| codegen.set_active(false, cx));
-        cx.run_until_parked();
-        assert_eq!(
-            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
-            text
-        );
-    }
-
-    #[gpui::test]
-    async fn test_strip_invalid_spans_from_codeblock() {
-        assert_chunks("Lorem ipsum dolor", "Lorem ipsum dolor").await;
-        assert_chunks("```\nLorem ipsum dolor", "Lorem ipsum dolor").await;
-        assert_chunks("```\nLorem ipsum dolor\n```", "Lorem ipsum dolor").await;
-        assert_chunks(
-            "```html\n```js\nLorem ipsum dolor\n```\n```",
-            "```js\nLorem ipsum dolor\n```",
-        )
-        .await;
-        assert_chunks("``\nLorem ipsum dolor\n```", "``\nLorem ipsum dolor\n```").await;
-        assert_chunks("Lorem<|CURSOR|> ipsum", "Lorem ipsum").await;
-        assert_chunks("Lorem ipsum", "Lorem ipsum").await;
-        assert_chunks("```\n<|CURSOR|>Lorem ipsum\n```", "Lorem ipsum").await;
-
-        async fn assert_chunks(text: &str, expected_text: &str) {
-            for chunk_size in 1..=text.len() {
-                let actual_text = StripInvalidSpans::new(chunks(text, chunk_size))
-                    .map(|chunk| chunk.unwrap())
-                    .collect::<String>()
-                    .await;
-                assert_eq!(
-                    actual_text, expected_text,
-                    "failed to strip invalid spans, chunk size: {}",
-                    chunk_size
-                );
-            }
-        }
-
-        fn chunks(text: &str, size: usize) -> impl Stream<Item = Result<String>> {
-            stream::iter(
-                text.chars()
-                    .collect::<Vec<_>>()
-                    .chunks(size)
-                    .map(|chunk| Ok(chunk.iter().collect::<String>()))
-                    .collect::<Vec<_>>(),
-            )
-        }
-    }
-
-    fn simulate_response_stream(
-        codegen: Entity<CodegenAlternative>,
-        cx: &mut TestAppContext,
-    ) -> mpsc::UnboundedSender<String> {
-        let (chunks_tx, chunks_rx) = mpsc::unbounded();
-        codegen.update(cx, |codegen, cx| {
-            codegen.handle_stream(
-                String::new(),
-                String::new(),
-                None,
-                future::ready(Ok(LanguageModelTextStream {
-                    message_id: None,
-                    stream: chunks_rx.map(Ok).boxed(),
-                    last_token_usage: Arc::new(Mutex::new(TokenUsage::default())),
-                })),
-                cx,
-            );
-        });
-        chunks_tx
-    }
-
-    fn rust_lang() -> Language {
-        Language::new(
-            LanguageConfig {
-                name: "Rust".into(),
-                matcher: LanguageMatcher {
-                    path_suffixes: vec!["rs".to_string()],
-                    ..Default::default()
-                },
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::LANGUAGE.into()),
-        )
-        .with_indents_query(
-            r#"
-            (call_expression) @indent
-            (field_expression) @indent
-            (_ "(" ")" @end) @indent
-            (_ "{" "}" @end) @indent
-            "#,
-        )
-        .unwrap()
-    }
-}

crates/assistant/src/terminal_inline_assistant.rs πŸ”—

@@ -1,1275 +0,0 @@
-use crate::{AssistantPanel, AssistantPanelEvent, DEFAULT_CONTEXT_LINES};
-use anyhow::{Context as _, Result};
-use assistant_context_editor::{RequestType, humanize_token_count};
-use assistant_settings::AssistantSettings;
-use client::telemetry::Telemetry;
-use collections::{HashMap, VecDeque};
-use editor::{
-    ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
-    actions::{MoveDown, MoveUp, SelectAll},
-};
-use fs::Fs;
-use futures::{SinkExt, StreamExt, channel::mpsc};
-use gpui::{
-    App, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, Subscription, Task,
-    TextStyle, UpdateGlobal, WeakEntity,
-};
-use language::Buffer;
-use language_model::{
-    ConfiguredModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
-    Role, report_assistant_event,
-};
-use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
-use prompt_store::PromptBuilder;
-use settings::{Settings, update_settings_file};
-use std::{
-    cmp,
-    sync::Arc,
-    time::{Duration, Instant},
-};
-use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
-use terminal::Terminal;
-use terminal_view::TerminalView;
-use theme::ThemeSettings;
-use ui::{IconButtonShape, Tooltip, prelude::*, text_for_action};
-use util::ResultExt;
-use workspace::{Toast, Workspace, notifications::NotificationId};
-
-pub fn init(
-    fs: Arc<dyn Fs>,
-    prompt_builder: Arc<PromptBuilder>,
-    telemetry: Arc<Telemetry>,
-    cx: &mut App,
-) {
-    cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry));
-}
-
-const PROMPT_HISTORY_MAX_LEN: usize = 20;
-
-#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
-struct TerminalInlineAssistId(usize);
-
-impl TerminalInlineAssistId {
-    fn post_inc(&mut self) -> TerminalInlineAssistId {
-        let id = *self;
-        self.0 += 1;
-        id
-    }
-}
-
-pub struct TerminalInlineAssistant {
-    next_assist_id: TerminalInlineAssistId,
-    assists: HashMap<TerminalInlineAssistId, TerminalInlineAssist>,
-    prompt_history: VecDeque<String>,
-    telemetry: Option<Arc<Telemetry>>,
-    fs: Arc<dyn Fs>,
-    prompt_builder: Arc<PromptBuilder>,
-}
-
-impl Global for TerminalInlineAssistant {}
-
-impl TerminalInlineAssistant {
-    pub fn new(
-        fs: Arc<dyn Fs>,
-        prompt_builder: Arc<PromptBuilder>,
-        telemetry: Arc<Telemetry>,
-    ) -> Self {
-        Self {
-            next_assist_id: TerminalInlineAssistId::default(),
-            assists: HashMap::default(),
-            prompt_history: VecDeque::default(),
-            telemetry: Some(telemetry),
-            fs,
-            prompt_builder,
-        }
-    }
-
-    pub fn assist(
-        &mut self,
-        terminal_view: &Entity<TerminalView>,
-        workspace: Option<WeakEntity<Workspace>>,
-        assistant_panel: Option<&Entity<AssistantPanel>>,
-        initial_prompt: Option<String>,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        let terminal = terminal_view.read(cx).terminal().clone();
-        let assist_id = self.next_assist_id.post_inc();
-        let prompt_buffer = cx.new(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx));
-        let prompt_buffer = cx.new(|cx| MultiBuffer::singleton(prompt_buffer, cx));
-        let codegen = cx.new(|_| Codegen::new(terminal, self.telemetry.clone()));
-
-        let prompt_editor = cx.new(|cx| {
-            PromptEditor::new(
-                assist_id,
-                self.prompt_history.clone(),
-                prompt_buffer.clone(),
-                codegen,
-                assistant_panel,
-                workspace.clone(),
-                self.fs.clone(),
-                window,
-                cx,
-            )
-        });
-        let prompt_editor_render = prompt_editor.clone();
-        let block = terminal_view::BlockProperties {
-            height: 2,
-            render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
-        };
-        terminal_view.update(cx, |terminal_view, cx| {
-            terminal_view.set_block_below_cursor(block, window, cx);
-        });
-
-        let terminal_assistant = TerminalInlineAssist::new(
-            assist_id,
-            terminal_view,
-            assistant_panel.is_some(),
-            prompt_editor,
-            workspace.clone(),
-            window,
-            cx,
-        );
-
-        self.assists.insert(assist_id, terminal_assistant);
-
-        self.focus_assist(assist_id, window, cx);
-    }
-
-    fn focus_assist(
-        &mut self,
-        assist_id: TerminalInlineAssistId,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        let assist = &self.assists[&assist_id];
-        if let Some(prompt_editor) = assist.prompt_editor.as_ref() {
-            prompt_editor.update(cx, |this, cx| {
-                this.editor.update(cx, |editor, cx| {
-                    window.focus(&editor.focus_handle(cx));
-                    editor.select_all(&SelectAll, window, cx);
-                });
-            });
-        }
-    }
-
-    fn handle_prompt_editor_event(
-        &mut self,
-        prompt_editor: Entity<PromptEditor>,
-        event: &PromptEditorEvent,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        let assist_id = prompt_editor.read(cx).id;
-        match event {
-            PromptEditorEvent::StartRequested => {
-                self.start_assist(assist_id, cx);
-            }
-            PromptEditorEvent::StopRequested => {
-                self.stop_assist(assist_id, cx);
-            }
-            PromptEditorEvent::ConfirmRequested { execute } => {
-                self.finish_assist(assist_id, false, *execute, window, cx);
-            }
-            PromptEditorEvent::CancelRequested => {
-                self.finish_assist(assist_id, true, false, window, cx);
-            }
-            PromptEditorEvent::DismissRequested => {
-                self.dismiss_assist(assist_id, window, cx);
-            }
-            PromptEditorEvent::Resized { height_in_lines } => {
-                self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, window, cx);
-            }
-        }
-    }
-
-    fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
-        let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
-            assist
-        } else {
-            return;
-        };
-
-        let Some(user_prompt) = assist
-            .prompt_editor
-            .as_ref()
-            .map(|editor| editor.read(cx).prompt(cx))
-        else {
-            return;
-        };
-
-        self.prompt_history.retain(|prompt| *prompt != user_prompt);
-        self.prompt_history.push_back(user_prompt.clone());
-        if self.prompt_history.len() > PROMPT_HISTORY_MAX_LEN {
-            self.prompt_history.pop_front();
-        }
-
-        assist
-            .terminal
-            .update(cx, |terminal, cx| {
-                terminal
-                    .terminal()
-                    .update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
-            })
-            .log_err();
-
-        let codegen = assist.codegen.clone();
-        let Some(request) = self.request_for_inline_assist(assist_id, cx).log_err() else {
-            return;
-        };
-
-        codegen.update(cx, |codegen, cx| codegen.start(request, cx));
-    }
-
-    fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
-        let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
-            assist
-        } else {
-            return;
-        };
-
-        assist.codegen.update(cx, |codegen, cx| codegen.stop(cx));
-    }
-
-    fn request_for_inline_assist(
-        &self,
-        assist_id: TerminalInlineAssistId,
-        cx: &mut App,
-    ) -> Result<LanguageModelRequest> {
-        let assist = self.assists.get(&assist_id).context("invalid assist")?;
-
-        let shell = std::env::var("SHELL").ok();
-        let (latest_output, working_directory) = assist
-            .terminal
-            .update(cx, |terminal, cx| {
-                let terminal = terminal.entity().read(cx);
-                let latest_output = terminal.last_n_non_empty_lines(DEFAULT_CONTEXT_LINES);
-                let working_directory = terminal
-                    .working_directory()
-                    .map(|path| path.to_string_lossy().to_string());
-                (latest_output, working_directory)
-            })
-            .ok()
-            .unwrap_or_default();
-
-        let context_request = if assist.include_context {
-            assist.workspace.as_ref().and_then(|workspace| {
-                let workspace = workspace.upgrade()?.read(cx);
-                let assistant_panel = workspace.panel::<AssistantPanel>(cx)?;
-                Some(
-                    assistant_panel
-                        .read(cx)
-                        .active_context(cx)?
-                        .read(cx)
-                        .to_completion_request(None, RequestType::Chat, cx),
-                )
-            })
-        } else {
-            None
-        };
-
-        let prompt = self.prompt_builder.generate_terminal_assistant_prompt(
-            &assist
-                .prompt_editor
-                .clone()
-                .context("invalid assist")?
-                .read(cx)
-                .prompt(cx),
-            shell.as_deref(),
-            working_directory.as_deref(),
-            &latest_output,
-        )?;
-
-        let mut messages = Vec::new();
-        if let Some(context_request) = context_request {
-            messages = context_request.messages;
-        }
-
-        messages.push(LanguageModelRequestMessage {
-            role: Role::User,
-            content: vec![prompt.into()],
-            cache: false,
-        });
-
-        Ok(LanguageModelRequest {
-            thread_id: None,
-            prompt_id: None,
-            mode: None,
-            messages,
-            tools: Vec::new(),
-            stop: Vec::new(),
-            temperature: None,
-        })
-    }
-
-    fn finish_assist(
-        &mut self,
-        assist_id: TerminalInlineAssistId,
-        undo: bool,
-        execute: bool,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        self.dismiss_assist(assist_id, window, cx);
-
-        if let Some(assist) = self.assists.remove(&assist_id) {
-            assist
-                .terminal
-                .update(cx, |this, cx| {
-                    this.clear_block_below_cursor(cx);
-                    this.focus_handle(cx).focus(window);
-                })
-                .log_err();
-
-            if let Some(ConfiguredModel { model, .. }) =
-                LanguageModelRegistry::read_global(cx).inline_assistant_model()
-            {
-                let codegen = assist.codegen.read(cx);
-                let executor = cx.background_executor().clone();
-                report_assistant_event(
-                    AssistantEventData {
-                        conversation_id: None,
-                        kind: AssistantKind::InlineTerminal,
-                        message_id: codegen.message_id.clone(),
-                        phase: if undo {
-                            AssistantPhase::Rejected
-                        } else {
-                            AssistantPhase::Accepted
-                        },
-                        model: model.telemetry_id(),
-                        model_provider: model.provider_id().to_string(),
-                        response_latency: None,
-                        error_message: None,
-                        language_name: None,
-                    },
-                    codegen.telemetry.clone(),
-                    cx.http_client(),
-                    model.api_key(cx),
-                    &executor,
-                );
-            }
-
-            assist.codegen.update(cx, |codegen, cx| {
-                if undo {
-                    codegen.undo(cx);
-                } else if execute {
-                    codegen.complete(cx);
-                }
-            });
-        }
-    }
-
-    fn dismiss_assist(
-        &mut self,
-        assist_id: TerminalInlineAssistId,
-        window: &mut Window,
-        cx: &mut App,
-    ) -> bool {
-        let Some(assist) = self.assists.get_mut(&assist_id) else {
-            return false;
-        };
-        if assist.prompt_editor.is_none() {
-            return false;
-        }
-        assist.prompt_editor = None;
-        assist
-            .terminal
-            .update(cx, |this, cx| {
-                this.clear_block_below_cursor(cx);
-                this.focus_handle(cx).focus(window);
-            })
-            .is_ok()
-    }
-
-    fn insert_prompt_editor_into_terminal(
-        &mut self,
-        assist_id: TerminalInlineAssistId,
-        height: u8,
-        window: &mut Window,
-        cx: &mut App,
-    ) {
-        if let Some(assist) = self.assists.get_mut(&assist_id) {
-            if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
-                assist
-                    .terminal
-                    .update(cx, |terminal, cx| {
-                        terminal.clear_block_below_cursor(cx);
-                        let block = terminal_view::BlockProperties {
-                            height,
-                            render: Box::new(move |_| prompt_editor.clone().into_any_element()),
-                        };
-                        terminal.set_block_below_cursor(block, window, cx);
-                    })
-                    .log_err();
-            }
-        }
-    }
-}
-
-struct TerminalInlineAssist {
-    terminal: WeakEntity<TerminalView>,
-    prompt_editor: Option<Entity<PromptEditor>>,
-    codegen: Entity<Codegen>,
-    workspace: Option<WeakEntity<Workspace>>,
-    include_context: bool,
-    _subscriptions: Vec<Subscription>,
-}
-
-impl TerminalInlineAssist {
-    pub fn new(
-        assist_id: TerminalInlineAssistId,
-        terminal: &Entity<TerminalView>,
-        include_context: bool,
-        prompt_editor: Entity<PromptEditor>,
-        workspace: Option<WeakEntity<Workspace>>,
-        window: &mut Window,
-        cx: &mut App,
-    ) -> Self {
-        let codegen = prompt_editor.read(cx).codegen.clone();
-        Self {
-            terminal: terminal.downgrade(),
-            prompt_editor: Some(prompt_editor.clone()),
-            codegen: codegen.clone(),
-            workspace: workspace.clone(),
-            include_context,
-            _subscriptions: vec![
-                window.subscribe(&prompt_editor, cx, |prompt_editor, event, window, cx| {
-                    TerminalInlineAssistant::update_global(cx, |this, cx| {
-                        this.handle_prompt_editor_event(prompt_editor, event, window, cx)
-                    })
-                }),
-                window.subscribe(&codegen, cx, move |codegen, event, window, cx| {
-                    TerminalInlineAssistant::update_global(cx, |this, cx| match event {
-                        CodegenEvent::Finished => {
-                            let assist = if let Some(assist) = this.assists.get(&assist_id) {
-                                assist
-                            } else {
-                                return;
-                            };
-
-                            if let CodegenStatus::Error(error) = &codegen.read(cx).status {
-                                if assist.prompt_editor.is_none() {
-                                    if let Some(workspace) = assist
-                                        .workspace
-                                        .as_ref()
-                                        .and_then(|workspace| workspace.upgrade())
-                                    {
-                                        let error =
-                                            format!("Terminal inline assistant error: {}", error);
-                                        workspace.update(cx, |workspace, cx| {
-                                            struct InlineAssistantError;
-
-                                            let id =
-                                                NotificationId::composite::<InlineAssistantError>(
-                                                    assist_id.0,
-                                                );
-
-                                            workspace.show_toast(Toast::new(id, error), cx);
-                                        })
-                                    }
-                                }
-                            }
-
-                            if assist.prompt_editor.is_none() {
-                                this.finish_assist(assist_id, false, false, window, cx);
-                            }
-                        }
-                    })
-                }),
-            ],
-        }
-    }
-}
-
-enum PromptEditorEvent {
-    StartRequested,
-    StopRequested,
-    ConfirmRequested { execute: bool },
-    CancelRequested,
-    DismissRequested,
-    Resized { height_in_lines: u8 },
-}
-
-struct PromptEditor {
-    id: TerminalInlineAssistId,
-    height_in_lines: u8,
-    editor: Entity<Editor>,
-    language_model_selector: Entity<LanguageModelSelector>,
-    edited_since_done: bool,
-    prompt_history: VecDeque<String>,
-    prompt_history_ix: Option<usize>,
-    pending_prompt: String,
-    codegen: Entity<Codegen>,
-    _codegen_subscription: Subscription,
-    editor_subscriptions: Vec<Subscription>,
-    pending_token_count: Task<Result<()>>,
-    token_count: Option<usize>,
-    _token_count_subscriptions: Vec<Subscription>,
-    workspace: Option<WeakEntity<Workspace>>,
-}
-
-impl EventEmitter<PromptEditorEvent> for PromptEditor {}
-
-impl Render for PromptEditor {
-    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let status = &self.codegen.read(cx).status;
-        let buttons = match status {
-            CodegenStatus::Idle => {
-                vec![
-                    IconButton::new("cancel", IconName::Close)
-                        .icon_color(Color::Muted)
-                        .shape(IconButtonShape::Square)
-                        .tooltip(|window, cx| {
-                            Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
-                        })
-                        .on_click(
-                            cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
-                        ),
-                    IconButton::new("start", IconName::SparkleAlt)
-                        .icon_color(Color::Muted)
-                        .shape(IconButtonShape::Square)
-                        .tooltip(|window, cx| {
-                            Tooltip::for_action("Generate", &menu::Confirm, window, cx)
-                        })
-                        .on_click(
-                            cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
-                        ),
-                ]
-            }
-            CodegenStatus::Pending => {
-                vec![
-                    IconButton::new("cancel", IconName::Close)
-                        .icon_color(Color::Muted)
-                        .shape(IconButtonShape::Square)
-                        .tooltip(Tooltip::text("Cancel Assist"))
-                        .on_click(
-                            cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
-                        ),
-                    IconButton::new("stop", IconName::Stop)
-                        .icon_color(Color::Error)
-                        .shape(IconButtonShape::Square)
-                        .tooltip(|window, cx| {
-                            Tooltip::with_meta(
-                                "Interrupt Generation",
-                                Some(&menu::Cancel),
-                                "Changes won't be discarded",
-                                window,
-                                cx,
-                            )
-                        })
-                        .on_click(
-                            cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StopRequested)),
-                        ),
-                ]
-            }
-            CodegenStatus::Error(_) | CodegenStatus::Done => {
-                let cancel = IconButton::new("cancel", IconName::Close)
-                    .icon_color(Color::Muted)
-                    .shape(IconButtonShape::Square)
-                    .tooltip(|window, cx| {
-                        Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
-                    })
-                    .on_click(
-                        cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
-                    );
-
-                let has_error = matches!(status, CodegenStatus::Error(_));
-                if has_error || self.edited_since_done {
-                    vec![
-                        cancel,
-                        IconButton::new("restart", IconName::RotateCw)
-                            .icon_color(Color::Info)
-                            .shape(IconButtonShape::Square)
-                            .tooltip(|window, cx| {
-                                Tooltip::with_meta(
-                                    "Restart Generation",
-                                    Some(&menu::Confirm),
-                                    "Changes will be discarded",
-                                    window,
-                                    cx,
-                                )
-                            })
-                            .on_click(cx.listener(|_, _, _, cx| {
-                                cx.emit(PromptEditorEvent::StartRequested);
-                            })),
-                    ]
-                } else {
-                    vec![
-                        cancel,
-                        IconButton::new("accept", IconName::Check)
-                            .icon_color(Color::Info)
-                            .shape(IconButtonShape::Square)
-                            .tooltip(|window, cx| {
-                                Tooltip::for_action(
-                                    "Accept Generated Command",
-                                    &menu::Confirm,
-                                    window,
-                                    cx,
-                                )
-                            })
-                            .on_click(cx.listener(|_, _, _, cx| {
-                                cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
-                            })),
-                        IconButton::new("confirm", IconName::Play)
-                            .icon_color(Color::Info)
-                            .shape(IconButtonShape::Square)
-                            .tooltip(|window, cx| {
-                                Tooltip::for_action(
-                                    "Execute Generated Command",
-                                    &menu::SecondaryConfirm,
-                                    window,
-                                    cx,
-                                )
-                            })
-                            .on_click(cx.listener(|_, _, _, cx| {
-                                cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
-                            })),
-                    ]
-                }
-            }
-        };
-
-        h_flex()
-            .bg(cx.theme().colors().editor_background)
-            .border_y_1()
-            .border_color(cx.theme().status().info_border)
-            .py_2()
-            .h_full()
-            .w_full()
-            .on_action(cx.listener(Self::confirm))
-            .on_action(cx.listener(Self::secondary_confirm))
-            .on_action(cx.listener(Self::cancel))
-            .on_action(cx.listener(Self::move_up))
-            .on_action(cx.listener(Self::move_down))
-            .child(
-                h_flex()
-                    .w_12()
-                    .justify_center()
-                    .gap_2()
-                    .child(LanguageModelSelectorPopoverMenu::new(
-                        self.language_model_selector.clone(),
-                        IconButton::new("change-model", IconName::SettingsAlt)
-                            .shape(IconButtonShape::Square)
-                            .icon_size(IconSize::Small)
-                            .icon_color(Color::Muted),
-                        move |window, cx| {
-                            Tooltip::with_meta(
-                                format!(
-                                    "Using {}",
-                                    LanguageModelRegistry::read_global(cx)
-                                        .inline_assistant_model()
-                                        .map(|inline_assistant| inline_assistant.model.name().0)
-                                        .unwrap_or_else(|| "No model selected".into()),
-                                ),
-                                None,
-                                "Change Model",
-                                window,
-                                cx,
-                            )
-                        },
-                        gpui::Corner::TopRight,
-                    ))
-                    .children(
-                        if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
-                            let error_message = SharedString::from(error.to_string());
-                            Some(
-                                div()
-                                    .id("error")
-                                    .tooltip(Tooltip::text(error_message))
-                                    .child(
-                                        Icon::new(IconName::XCircle)
-                                            .size(IconSize::Small)
-                                            .color(Color::Error),
-                                    ),
-                            )
-                        } else {
-                            None
-                        },
-                    ),
-            )
-            .child(div().flex_1().child(self.render_prompt_editor(cx)))
-            .child(
-                h_flex()
-                    .gap_1()
-                    .pr_4()
-                    .children(self.render_token_count(cx))
-                    .children(buttons),
-            )
-    }
-}
-
-impl Focusable for PromptEditor {
-    fn focus_handle(&self, cx: &App) -> FocusHandle {
-        self.editor.focus_handle(cx)
-    }
-}
-
-impl PromptEditor {
-    const MAX_LINES: u8 = 8;
-
-    fn new(
-        id: TerminalInlineAssistId,
-        prompt_history: VecDeque<String>,
-        prompt_buffer: Entity<MultiBuffer>,
-        codegen: Entity<Codegen>,
-        assistant_panel: Option<&Entity<AssistantPanel>>,
-        workspace: Option<WeakEntity<Workspace>>,
-        fs: Arc<dyn Fs>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Self {
-        let prompt_editor = cx.new(|cx| {
-            let mut editor = Editor::new(
-                EditorMode::AutoHeight {
-                    max_lines: Self::MAX_LINES as usize,
-                },
-                prompt_buffer,
-                None,
-                window,
-                cx,
-            );
-            editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
-            editor.set_placeholder_text(Self::placeholder_text(window, cx), cx);
-            editor.set_context_menu_options(ContextMenuOptions {
-                min_entries_visible: 12,
-                max_entries_visible: 12,
-                placement: None,
-            });
-            editor
-        });
-
-        let mut token_count_subscriptions = Vec::new();
-        if let Some(assistant_panel) = assistant_panel {
-            token_count_subscriptions.push(cx.subscribe_in(
-                assistant_panel,
-                window,
-                Self::handle_assistant_panel_event,
-            ));
-        }
-
-        let mut this = Self {
-            id,
-            height_in_lines: 1,
-            editor: prompt_editor,
-            language_model_selector: cx.new(|cx| {
-                let fs = fs.clone();
-                LanguageModelSelector::new(
-                    |cx| LanguageModelRegistry::read_global(cx).default_model(),
-                    move |model, cx| {
-                        update_settings_file::<AssistantSettings>(
-                            fs.clone(),
-                            cx,
-                            move |settings, _| settings.set_model(model.clone()),
-                        );
-                    },
-                    window,
-                    cx,
-                )
-            }),
-            edited_since_done: false,
-            prompt_history,
-            prompt_history_ix: None,
-            pending_prompt: String::new(),
-            _codegen_subscription: cx.observe_in(&codegen, window, Self::handle_codegen_changed),
-            editor_subscriptions: Vec::new(),
-            codegen,
-            pending_token_count: Task::ready(Ok(())),
-            token_count: None,
-            _token_count_subscriptions: token_count_subscriptions,
-            workspace,
-        };
-        this.count_lines(cx);
-        this.count_tokens(cx);
-        this.subscribe_to_editor(cx);
-        this
-    }
-
-    fn placeholder_text(window: &Window, cx: &App) -> String {
-        let context_keybinding = text_for_action(&zed_actions::assistant::ToggleFocus, window, cx)
-            .map(|keybinding| format!(" β€’ {keybinding} for context"))
-            .unwrap_or_default();
-
-        format!("Generate…{context_keybinding} β€’ ↓↑ for history")
-    }
-
-    fn subscribe_to_editor(&mut self, cx: &mut Context<Self>) {
-        self.editor_subscriptions.clear();
-        self.editor_subscriptions
-            .push(cx.observe(&self.editor, Self::handle_prompt_editor_changed));
-        self.editor_subscriptions
-            .push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events));
-    }
-
-    fn prompt(&self, cx: &App) -> String {
-        self.editor.read(cx).text(cx)
-    }
-
-    fn count_lines(&mut self, cx: &mut Context<Self>) {
-        let height_in_lines = cmp::max(
-            2, // Make the editor at least two lines tall, to account for padding and buttons.
-            cmp::min(
-                self.editor
-                    .update(cx, |editor, cx| editor.max_point(cx).row().0 + 1),
-                Self::MAX_LINES as u32,
-            ),
-        ) as u8;
-
-        if height_in_lines != self.height_in_lines {
-            self.height_in_lines = height_in_lines;
-            cx.emit(PromptEditorEvent::Resized { height_in_lines });
-        }
-    }
-
-    fn handle_assistant_panel_event(
-        &mut self,
-        _: &Entity<AssistantPanel>,
-        event: &AssistantPanelEvent,
-        _: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let AssistantPanelEvent::ContextEdited { .. } = event;
-        self.count_tokens(cx);
-    }
-
-    fn count_tokens(&mut self, cx: &mut Context<Self>) {
-        let assist_id = self.id;
-        let Some(ConfiguredModel { model, .. }) =
-            LanguageModelRegistry::read_global(cx).inline_assistant_model()
-        else {
-            return;
-        };
-        self.pending_token_count = cx.spawn(async move |this, cx| {
-            cx.background_executor().timer(Duration::from_secs(1)).await;
-            let request =
-                cx.update_global(|inline_assistant: &mut TerminalInlineAssistant, cx| {
-                    inline_assistant.request_for_inline_assist(assist_id, cx)
-                })??;
-
-            let token_count = cx.update(|cx| model.count_tokens(request, cx))?.await?;
-            this.update(cx, |this, cx| {
-                this.token_count = Some(token_count);
-                cx.notify();
-            })
-        })
-    }
-
-    fn handle_prompt_editor_changed(&mut self, _: Entity<Editor>, cx: &mut Context<Self>) {
-        self.count_lines(cx);
-    }
-
-    fn handle_prompt_editor_events(
-        &mut self,
-        _: Entity<Editor>,
-        event: &EditorEvent,
-        cx: &mut Context<Self>,
-    ) {
-        match event {
-            EditorEvent::Edited { .. } => {
-                let prompt = self.editor.read(cx).text(cx);
-                if self
-                    .prompt_history_ix
-                    .map_or(true, |ix| self.prompt_history[ix] != prompt)
-                {
-                    self.prompt_history_ix.take();
-                    self.pending_prompt = prompt;
-                }
-
-                self.edited_since_done = true;
-                cx.notify();
-            }
-            EditorEvent::BufferEdited => {
-                self.count_tokens(cx);
-            }
-            _ => {}
-        }
-    }
-
-    fn handle_codegen_changed(
-        &mut self,
-        _: Entity<Codegen>,
-        _: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        match &self.codegen.read(cx).status {
-            CodegenStatus::Idle => {
-                self.editor
-                    .update(cx, |editor, _| editor.set_read_only(false));
-            }
-            CodegenStatus::Pending => {
-                self.editor
-                    .update(cx, |editor, _| editor.set_read_only(true));
-            }
-            CodegenStatus::Done | CodegenStatus::Error(_) => {
-                self.edited_since_done = false;
-                self.editor
-                    .update(cx, |editor, _| editor.set_read_only(false));
-            }
-        }
-    }
-
-    fn cancel(&mut self, _: &editor::actions::Cancel, _: &mut Window, cx: &mut Context<Self>) {
-        match &self.codegen.read(cx).status {
-            CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
-                cx.emit(PromptEditorEvent::CancelRequested);
-            }
-            CodegenStatus::Pending => {
-                cx.emit(PromptEditorEvent::StopRequested);
-            }
-        }
-    }
-
-    fn confirm(&mut self, _: &menu::Confirm, _: &mut Window, cx: &mut Context<Self>) {
-        match &self.codegen.read(cx).status {
-            CodegenStatus::Idle => {
-                if !self.editor.read(cx).text(cx).trim().is_empty() {
-                    cx.emit(PromptEditorEvent::StartRequested);
-                }
-            }
-            CodegenStatus::Pending => {
-                cx.emit(PromptEditorEvent::DismissRequested);
-            }
-            CodegenStatus::Done => {
-                if self.edited_since_done {
-                    cx.emit(PromptEditorEvent::StartRequested);
-                } else {
-                    cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
-                }
-            }
-            CodegenStatus::Error(_) => {
-                cx.emit(PromptEditorEvent::StartRequested);
-            }
-        }
-    }
-
-    fn secondary_confirm(
-        &mut self,
-        _: &menu::SecondaryConfirm,
-        _: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        if matches!(self.codegen.read(cx).status, CodegenStatus::Done) {
-            cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
-        }
-    }
-
-    fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
-        if let Some(ix) = self.prompt_history_ix {
-            if ix > 0 {
-                self.prompt_history_ix = Some(ix - 1);
-                let prompt = self.prompt_history[ix - 1].as_str();
-                self.editor.update(cx, |editor, cx| {
-                    editor.set_text(prompt, window, cx);
-                    editor.move_to_beginning(&Default::default(), window, cx);
-                });
-            }
-        } else if !self.prompt_history.is_empty() {
-            self.prompt_history_ix = Some(self.prompt_history.len() - 1);
-            let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
-            self.editor.update(cx, |editor, cx| {
-                editor.set_text(prompt, window, cx);
-                editor.move_to_beginning(&Default::default(), window, cx);
-            });
-        }
-    }
-
-    fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
-        if let Some(ix) = self.prompt_history_ix {
-            if ix < self.prompt_history.len() - 1 {
-                self.prompt_history_ix = Some(ix + 1);
-                let prompt = self.prompt_history[ix + 1].as_str();
-                self.editor.update(cx, |editor, cx| {
-                    editor.set_text(prompt, window, cx);
-                    editor.move_to_end(&Default::default(), window, cx)
-                });
-            } else {
-                self.prompt_history_ix = None;
-                let prompt = self.pending_prompt.as_str();
-                self.editor.update(cx, |editor, cx| {
-                    editor.set_text(prompt, window, cx);
-                    editor.move_to_end(&Default::default(), window, cx)
-                });
-            }
-        }
-    }
-
-    fn render_token_count(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
-        let model = LanguageModelRegistry::read_global(cx)
-            .inline_assistant_model()?
-            .model;
-        let token_count = self.token_count?;
-        let max_token_count = model.max_token_count();
-
-        let remaining_tokens = max_token_count as isize - token_count as isize;
-        let token_count_color = if remaining_tokens <= 0 {
-            Color::Error
-        } else if token_count as f32 / max_token_count as f32 >= 0.8 {
-            Color::Warning
-        } else {
-            Color::Muted
-        };
-
-        let mut token_count = h_flex()
-            .id("token_count")
-            .gap_0p5()
-            .child(
-                Label::new(humanize_token_count(token_count))
-                    .size(LabelSize::Small)
-                    .color(token_count_color),
-            )
-            .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
-            .child(
-                Label::new(humanize_token_count(max_token_count))
-                    .size(LabelSize::Small)
-                    .color(Color::Muted),
-            );
-        if let Some(workspace) = self.workspace.clone() {
-            token_count = token_count
-                .tooltip(|window, cx| {
-                    Tooltip::with_meta(
-                        "Tokens Used by Inline Assistant",
-                        None,
-                        "Click to Open Assistant Panel",
-                        window,
-                        cx,
-                    )
-                })
-                .cursor_pointer()
-                .on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation())
-                .on_click(move |_, window, cx| {
-                    cx.stop_propagation();
-                    workspace
-                        .update(cx, |workspace, cx| {
-                            workspace.focus_panel::<AssistantPanel>(window, cx)
-                        })
-                        .ok();
-                });
-        } else {
-            token_count = token_count
-                .cursor_default()
-                .tooltip(Tooltip::text("Tokens Used by Inline Assistant"));
-        }
-
-        Some(token_count)
-    }
-
-    fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
-        let settings = ThemeSettings::get_global(cx);
-        let text_style = TextStyle {
-            color: if self.editor.read(cx).read_only(cx) {
-                cx.theme().colors().text_disabled
-            } else {
-                cx.theme().colors().text
-            },
-            font_family: settings.buffer_font.family.clone(),
-            font_fallbacks: settings.buffer_font.fallbacks.clone(),
-            font_size: settings.buffer_font_size(cx).into(),
-            font_weight: settings.buffer_font.weight,
-            line_height: relative(settings.buffer_line_height.value()),
-            ..Default::default()
-        };
-        EditorElement::new(
-            &self.editor,
-            EditorStyle {
-                background: cx.theme().colors().editor_background,
-                local_player: cx.theme().players().local(),
-                text: text_style,
-                ..Default::default()
-            },
-        )
-    }
-}
-
-#[derive(Debug)]
-pub enum CodegenEvent {
-    Finished,
-}
-
-impl EventEmitter<CodegenEvent> for Codegen {}
-
-#[cfg(not(target_os = "windows"))]
-const CLEAR_INPUT: &str = "\x15";
-#[cfg(target_os = "windows")]
-const CLEAR_INPUT: &str = "\x03";
-const CARRIAGE_RETURN: &str = "\x0d";
-
-struct TerminalTransaction {
-    terminal: Entity<Terminal>,
-}
-
-impl TerminalTransaction {
-    pub fn start(terminal: Entity<Terminal>) -> Self {
-        Self { terminal }
-    }
-
-    pub fn push(&mut self, hunk: String, cx: &mut App) {
-        // Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
-        let input = Self::sanitize_input(hunk);
-        self.terminal
-            .update(cx, |terminal, _| terminal.input(input));
-    }
-
-    pub fn undo(&self, cx: &mut App) {
-        self.terminal
-            .update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
-    }
-
-    pub fn complete(&self, cx: &mut App) {
-        self.terminal.update(cx, |terminal, _| {
-            terminal.input(CARRIAGE_RETURN.to_string())
-        });
-    }
-
-    fn sanitize_input(input: String) -> String {
-        input.replace(['\r', '\n'], "")
-    }
-}
-
-pub struct Codegen {
-    status: CodegenStatus,
-    telemetry: Option<Arc<Telemetry>>,
-    terminal: Entity<Terminal>,
-    generation: Task<()>,
-    message_id: Option<String>,
-    transaction: Option<TerminalTransaction>,
-}
-
-impl Codegen {
-    pub fn new(terminal: Entity<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
-        Self {
-            terminal,
-            telemetry,
-            status: CodegenStatus::Idle,
-            generation: Task::ready(()),
-            message_id: None,
-            transaction: None,
-        }
-    }
-
-    pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut Context<Self>) {
-        let Some(ConfiguredModel { model, .. }) =
-            LanguageModelRegistry::read_global(cx).inline_assistant_model()
-        else {
-            return;
-        };
-
-        let model_api_key = model.api_key(cx);
-        let http_client = cx.http_client();
-        let telemetry = self.telemetry.clone();
-        self.status = CodegenStatus::Pending;
-        self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
-        self.generation = cx.spawn(async move |this, cx| {
-            let model_telemetry_id = model.telemetry_id();
-            let model_provider_id = model.provider_id();
-            let response = model.stream_completion_text(prompt, &cx).await;
-            let generate = async {
-                let message_id = response
-                    .as_ref()
-                    .ok()
-                    .and_then(|response| response.message_id.clone());
-
-                let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
-
-                let task = cx.background_spawn({
-                    let message_id = message_id.clone();
-                    let executor = cx.background_executor().clone();
-                    async move {
-                        let mut response_latency = None;
-                        let request_start = Instant::now();
-                        let task = async {
-                            let mut chunks = response?.stream;
-                            while let Some(chunk) = chunks.next().await {
-                                if response_latency.is_none() {
-                                    response_latency = Some(request_start.elapsed());
-                                }
-                                let chunk = chunk?;
-                                hunks_tx.send(chunk).await?;
-                            }
-
-                            anyhow::Ok(())
-                        };
-
-                        let result = task.await;
-
-                        let error_message = result.as_ref().err().map(|error| error.to_string());
-                        report_assistant_event(
-                            AssistantEventData {
-                                conversation_id: None,
-                                kind: AssistantKind::InlineTerminal,
-                                message_id,
-                                phase: AssistantPhase::Response,
-                                model: model_telemetry_id,
-                                model_provider: model_provider_id.to_string(),
-                                response_latency,
-                                error_message,
-                                language_name: None,
-                            },
-                            telemetry,
-                            http_client,
-                            model_api_key,
-                            &executor,
-                        );
-
-                        result?;
-                        anyhow::Ok(())
-                    }
-                });
-
-                this.update(cx, |this, _| {
-                    this.message_id = message_id;
-                })?;
-
-                while let Some(hunk) = hunks_rx.next().await {
-                    this.update(cx, |this, cx| {
-                        if let Some(transaction) = &mut this.transaction {
-                            transaction.push(hunk, cx);
-                            cx.notify();
-                        }
-                    })?;
-                }
-
-                task.await?;
-                anyhow::Ok(())
-            };
-
-            let result = generate.await;
-
-            this.update(cx, |this, cx| {
-                if let Err(error) = result {
-                    this.status = CodegenStatus::Error(error);
-                } else {
-                    this.status = CodegenStatus::Done;
-                }
-                cx.emit(CodegenEvent::Finished);
-                cx.notify();
-            })
-            .ok();
-        });
-        cx.notify();
-    }
-
-    pub fn stop(&mut self, cx: &mut Context<Self>) {
-        self.status = CodegenStatus::Done;
-        self.generation = Task::ready(());
-        cx.emit(CodegenEvent::Finished);
-        cx.notify();
-    }
-
-    pub fn complete(&mut self, cx: &mut Context<Self>) {
-        if let Some(transaction) = self.transaction.take() {
-            transaction.complete(cx);
-        }
-    }
-
-    pub fn undo(&mut self, cx: &mut Context<Self>) {
-        if let Some(transaction) = self.transaction.take() {
-            transaction.undo(cx);
-        }
-    }
-}
-
-enum CodegenStatus {
-    Idle,
-    Pending,
-    Done,
-    Error(anyhow::Error),
-}

crates/collab/Cargo.toml πŸ”—

@@ -76,7 +76,6 @@ workspace-hack.workspace = true
 zed_llm_client.workspace = true
 
 [dev-dependencies]
-assistant = { workspace = true, features = ["test-support"] }
 assistant_context_editor.workspace = true
 assistant_settings.workspace = true
 assistant_slash_command.workspace = true

crates/zed/Cargo.toml πŸ”—

@@ -20,7 +20,6 @@ agent.workspace = true
 anyhow.workspace = true
 askpass.workspace = true
 assets.workspace = true
-assistant.workspace = true
 assistant_context_editor.workspace = true
 assistant_settings.workspace = true
 assistant_tools.workspace = true

crates/zed/src/main.rs πŸ”—

@@ -503,12 +503,6 @@ fn main() {
             cx,
         );
         let prompt_builder = PromptBuilder::load(app_state.fs.clone(), stdout_is_a_pty(), cx);
-        assistant::init(
-            app_state.fs.clone(),
-            app_state.client.clone(),
-            prompt_builder.clone(),
-            cx,
-        );
         agent::init(
             app_state.fs.clone(),
             app_state.client.clone(),

crates/zed/src/zed.rs πŸ”—

@@ -433,31 +433,19 @@ fn initialize_panels(
         })?;
 
         let is_assistant2_enabled = !cfg!(test);
-
-        let (assistant_panel, assistant2_panel) = if is_assistant2_enabled {
-            let assistant2_panel =
+        let agent_panel = if is_assistant2_enabled {
+            let agent_panel =
                 agent::AssistantPanel::load(workspace_handle.clone(), prompt_builder, cx.clone())
                     .await?;
 
-            (None, Some(assistant2_panel))
+            Some(agent_panel)
         } else {
-            let assistant_panel = assistant::AssistantPanel::load(
-                workspace_handle.clone(),
-                prompt_builder.clone(),
-                cx.clone(),
-            )
-            .await?;
-
-            (Some(assistant_panel), None)
+            None
         };
 
         workspace_handle.update_in(cx, |workspace, window, cx| {
-            if let Some(assistant2_panel) = assistant2_panel {
-                workspace.add_panel(assistant2_panel, window, cx);
-            }
-
-            if let Some(assistant_panel) = assistant_panel {
-                workspace.add_panel(assistant_panel, window, cx);
+            if let Some(agent_panel) = agent_panel {
+                workspace.add_panel(agent_panel, window, cx);
             }
 
             // Register the actions that are shared between `assistant` and `assistant2`.
@@ -475,15 +463,6 @@ fn initialize_panels(
                 workspace
                     .register_action(agent::AssistantPanel::toggle_focus)
                     .register_action(agent::InlineAssistant::inline_assist);
-            } else {
-                <dyn AssistantPanelDelegate>::set_global(
-                    Arc::new(assistant::assistant_panel::ConcreteAssistantPanelDelegate),
-                    cx,
-                );
-
-                workspace
-                    .register_action(assistant::AssistantPanel::toggle_focus)
-                    .register_action(assistant::AssistantPanel::inline_assist);
             }
         })?;
 
@@ -4241,10 +4220,11 @@ mod tests {
             web_search::init(cx);
             web_search_providers::init(app_state.client.clone(), cx);
             let prompt_builder = PromptBuilder::load(app_state.fs.clone(), false, cx);
-            assistant::init(
+            agent::init(
                 app_state.fs.clone(),
                 app_state.client.clone(),
                 prompt_builder.clone(),
+                app_state.languages.clone(),
                 cx,
             );
             repl::init(app_state.fs.clone(), cx);