Remove Supermaven-related code from Zed (#50537)

Kirill Bulatov created

Follow-up of https://github.com/zed-industries/zed/pull/49317

See also https://supermaven.com/blog/sunsetting-supermaven

- N/A

Change summary

.github/CODEOWNERS.hold                                      |   2 
Cargo.lock                                                   |  45 
Cargo.toml                                                   |   5 
crates/agent_ui/src/agent_ui.rs                              |  10 
crates/edit_prediction/src/edit_prediction.rs                |   2 
crates/edit_prediction_ui/Cargo.toml                         |   1 
crates/edit_prediction_ui/src/edit_prediction_button.rs      | 153 -
crates/editor/src/editor.rs                                  |   2 
crates/icons/src/icons.rs                                    |   4 
crates/language/src/language_settings.rs                     |   3 
crates/paths/src/paths.rs                                    |   6 
crates/settings_content/src/language.rs                      |   5 
crates/supermaven/Cargo.toml                                 |  44 
crates/supermaven/LICENSE-GPL                                |   1 
crates/supermaven/src/messages.rs                            | 146 -
crates/supermaven/src/supermaven.rs                          | 485 ------
crates/supermaven/src/supermaven_edit_prediction_delegate.rs | 303 ---
crates/supermaven_api/Cargo.toml                             |  23 
crates/supermaven_api/LICENSE-GPL                            |   1 
crates/supermaven_api/src/supermaven_api.rs                  | 125 -
crates/zed/Cargo.toml                                        |   1 
crates/zed/src/main.rs                                       |   1 
crates/zed/src/zed.rs                                        |   1 
crates/zed/src/zed/edit_prediction_registry.rs               |  10 
docs/src/ai/overview.md                                      |   2 
docs/src/completions.md                                      |   2 
docs/src/reference/all-settings.md                           |  12 
27 files changed, 5 insertions(+), 1,390 deletions(-)

Detailed changes

.github/CODEOWNERS.hold 🔗

@@ -62,8 +62,6 @@
 /crates/rules_library/ @zed-industries/ai-team
 # SUGGESTED: Review needed - based on Richard Feldman (2 commits)
 /crates/shell_command_parser/ @zed-industries/ai-team
-/crates/supermaven/ @zed-industries/ai-team
-/crates/supermaven_api/ @zed-industries/ai-team
 /crates/vercel/ @zed-industries/ai-team
 /crates/x_ai/ @zed-industries/ai-team
 /crates/zeta_prompt/ @zed-industries/ai-team

Cargo.lock 🔗

@@ -5403,7 +5403,6 @@ dependencies = [
  "semver",
  "serde_json",
  "settings",
- "supermaven",
  "telemetry",
  "text",
  "theme",
@@ -16524,49 +16523,6 @@ dependencies = [
  "ztracing",
 ]
 
-[[package]]
-name = "supermaven"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "client",
- "collections",
- "edit_prediction_types",
- "editor",
- "env_logger 0.11.8",
- "futures 0.3.31",
- "gpui",
- "http_client",
- "language",
- "log",
- "postage",
- "project",
- "serde",
- "serde_json",
- "settings",
- "smol",
- "supermaven_api",
- "text",
- "theme",
- "ui",
- "unicode-segmentation",
- "util",
-]
-
-[[package]]
-name = "supermaven_api"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "futures 0.3.31",
- "http_client",
- "paths",
- "serde",
- "serde_json",
- "smol",
- "util",
-]
-
 [[package]]
 name = "sval"
 version = "2.15.0"
@@ -21861,7 +21817,6 @@ dependencies = [
  "smol",
  "snippet_provider",
  "snippets_ui",
- "supermaven",
  "svg_preview",
  "sysinfo 0.37.2",
  "system_specs",

Cargo.toml 🔗

@@ -182,8 +182,6 @@ members = [
     "crates/storybook",
     "crates/streaming_diff",
     "crates/sum_tree",
-    "crates/supermaven",
-    "crates/supermaven_api",
     "crates/svg_preview",
     "crates/system_specs",
     "crates/tab_switcher",
@@ -427,8 +425,6 @@ sqlez_macros = { path = "crates/sqlez_macros" }
 story = { path = "crates/story" }
 streaming_diff = { path = "crates/streaming_diff" }
 sum_tree = { path = "crates/sum_tree" }
-supermaven = { path = "crates/supermaven" }
-supermaven_api = { path = "crates/supermaven_api" }
 codestral = { path = "crates/codestral" }
 system_specs = { path = "crates/system_specs" }
 tab_switcher = { path = "crates/tab_switcher" }
@@ -900,7 +896,6 @@ sidebar = { codegen-units = 1 }
 snippet = { codegen-units = 1 }
 snippets_ui = { codegen-units = 1 }
 story = { codegen-units = 1 }
-supermaven_api = { codegen-units = 1 }
 telemetry_events = { codegen-units = 1 }
 theme_selector = { codegen-units = 1 }
 time_format = { codegen-units = 1 }

crates/agent_ui/src/agent_ui.rs 🔗

@@ -371,7 +371,6 @@ fn update_command_palette_filter(cx: &mut App) {
             filter.hide_namespace("agents");
             filter.hide_namespace("assistant");
             filter.hide_namespace("copilot");
-            filter.hide_namespace("supermaven");
             filter.hide_namespace("zed_predict_onboarding");
             filter.hide_namespace("edit_prediction");
 
@@ -392,19 +391,11 @@ fn update_command_palette_filter(cx: &mut App) {
                 EditPredictionProvider::None => {
                     filter.hide_namespace("edit_prediction");
                     filter.hide_namespace("copilot");
-                    filter.hide_namespace("supermaven");
                     filter.hide_action_types(&edit_prediction_actions);
                 }
                 EditPredictionProvider::Copilot => {
                     filter.show_namespace("edit_prediction");
                     filter.show_namespace("copilot");
-                    filter.hide_namespace("supermaven");
-                    filter.show_action_types(edit_prediction_actions.iter());
-                }
-                EditPredictionProvider::Supermaven => {
-                    filter.show_namespace("edit_prediction");
-                    filter.hide_namespace("copilot");
-                    filter.show_namespace("supermaven");
                     filter.show_action_types(edit_prediction_actions.iter());
                 }
                 EditPredictionProvider::Zed
@@ -416,7 +407,6 @@ fn update_command_palette_filter(cx: &mut App) {
                 | EditPredictionProvider::Experimental(_) => {
                     filter.show_namespace("edit_prediction");
                     filter.hide_namespace("copilot");
-                    filter.hide_namespace("supermaven");
                     filter.show_action_types(edit_prediction_actions.iter());
                 }
             }

crates/edit_prediction/src/edit_prediction.rs 🔗

@@ -1836,7 +1836,6 @@ fn is_ep_store_provider(provider: EditPredictionProvider) -> bool {
         | EditPredictionProvider::Experimental(_) => true,
         EditPredictionProvider::None
         | EditPredictionProvider::Copilot
-        | EditPredictionProvider::Supermaven
         | EditPredictionProvider::Codestral => false,
     }
 }
@@ -1877,7 +1876,6 @@ impl EditPredictionStore {
                 EditPredictionProvider::OpenAiCompatibleApi => (false, 2),
                 EditPredictionProvider::None
                 | EditPredictionProvider::Copilot
-                | EditPredictionProvider::Supermaven
                 | EditPredictionProvider::Codestral => {
                     log::error!("queue_prediction_refresh called with non-store provider");
                     return;

crates/edit_prediction_ui/Cargo.toml 🔗

@@ -40,7 +40,6 @@ paths.workspace = true
 project.workspace = true
 regex.workspace = true
 settings.workspace = true
-supermaven.workspace = true
 telemetry.workspace = true
 text.workspace = true
 theme.workspace = true

crates/edit_prediction_ui/src/edit_prediction_button.rs 🔗

@@ -30,7 +30,6 @@ use std::{
     sync::{Arc, LazyLock},
     time::Duration,
 };
-use supermaven::{AccountStatus, Supermaven};
 use ui::{
     Clickable, ContextMenu, ContextMenuEntry, DocumentationSide, IconButton, IconButtonShape,
     Indicator, PopoverMenu, PopoverMenuHandle, ProgressBar, Tooltip, prelude::*,
@@ -75,13 +74,6 @@ pub struct EditPredictionButton {
     project: WeakEntity<Project>,
 }
 
-enum SupermavenButtonStatus {
-    Ready,
-    Errored(String),
-    NeedsActivation(String),
-    Initializing,
-}
-
 impl Render for EditPredictionButton {
     fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         // Return empty div if AI is disabled
@@ -188,101 +180,6 @@ impl Render for EditPredictionButton {
                         .with_handle(self.popover_menu_handle.clone()),
                 )
             }
-            EditPredictionProvider::Supermaven => {
-                let Some(supermaven) = Supermaven::global(cx) else {
-                    return div();
-                };
-
-                let supermaven = supermaven.read(cx);
-
-                let status = match supermaven {
-                    Supermaven::Starting => SupermavenButtonStatus::Initializing,
-                    Supermaven::FailedDownload { error } => {
-                        SupermavenButtonStatus::Errored(error.to_string())
-                    }
-                    Supermaven::Spawned(agent) => {
-                        let account_status = agent.account_status.clone();
-                        match account_status {
-                            AccountStatus::NeedsActivation { activate_url } => {
-                                SupermavenButtonStatus::NeedsActivation(activate_url)
-                            }
-                            AccountStatus::Unknown => SupermavenButtonStatus::Initializing,
-                            AccountStatus::Ready => SupermavenButtonStatus::Ready,
-                        }
-                    }
-                    Supermaven::Error { error } => {
-                        SupermavenButtonStatus::Errored(error.to_string())
-                    }
-                };
-
-                let icon = status.to_icon();
-                let tooltip_text = status.to_tooltip();
-                let has_menu = status.has_menu();
-                let this = cx.weak_entity();
-                let fs = self.fs.clone();
-                let file = self.file.clone();
-                let language = self.language.clone();
-                let project = self.project.clone();
-
-                div().child(
-                    PopoverMenu::new("supermaven")
-                        .on_open({
-                            let file = file.clone();
-                            let language = language;
-                            let project = project;
-                            Rc::new(move |_window, cx| {
-                                emit_edit_prediction_menu_opened(
-                                    "supermaven",
-                                    &file,
-                                    &language,
-                                    &project,
-                                    cx,
-                                );
-                            })
-                        })
-                        .menu(move |window, cx| match &status {
-                            SupermavenButtonStatus::NeedsActivation(activate_url) => {
-                                Some(ContextMenu::build(window, cx, |menu, _, _| {
-                                    let fs = fs.clone();
-                                    let activate_url = activate_url.clone();
-
-                                    menu.entry("Sign In", None, move |_, cx| {
-                                        cx.open_url(activate_url.as_str())
-                                    })
-                                    .entry(
-                                        "Use Zed AI",
-                                        None,
-                                        move |_, cx| {
-                                            set_completion_provider(
-                                                fs.clone(),
-                                                cx,
-                                                EditPredictionProvider::Zed,
-                                            )
-                                        },
-                                    )
-                                }))
-                            }
-                            SupermavenButtonStatus::Ready => this
-                                .update(cx, |this, cx| {
-                                    this.build_supermaven_context_menu(window, cx)
-                                })
-                                .ok(),
-                            _ => None,
-                        })
-                        .anchor(Corner::BottomRight)
-                        .trigger_with_tooltip(
-                            IconButton::new("supermaven-icon", icon),
-                            move |window, cx| {
-                                if has_menu {
-                                    Tooltip::for_action(tooltip_text.clone(), &ToggleMenu, cx)
-                                } else {
-                                    Tooltip::text(tooltip_text.clone())(window, cx)
-                                }
-                            },
-                        )
-                        .with_handle(self.popover_menu_handle.clone()),
-                )
-            }
             EditPredictionProvider::Codestral => {
                 let enabled = self.editor_enabled.unwrap_or(true);
                 let has_api_key = codestral::codestral_api_key(cx).is_some();
@@ -1120,21 +1017,6 @@ impl EditPredictionButton {
         })
     }
 
-    fn build_supermaven_context_menu(
-        &self,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Entity<ContextMenu> {
-        ContextMenu::build(window, cx, |menu, window, cx| {
-            let menu = self.build_language_settings_menu(menu, window, cx);
-            let menu =
-                self.add_provider_switching_section(menu, EditPredictionProvider::Supermaven, cx);
-
-            menu.separator()
-                .action("Sign Out", supermaven::SignOut.boxed_clone())
-        })
-    }
-
     fn build_codestral_context_menu(
         &self,
         window: &mut Window,
@@ -1384,33 +1266,6 @@ impl StatusItemView for EditPredictionButton {
     }
 }
 
-impl SupermavenButtonStatus {
-    fn to_icon(&self) -> IconName {
-        match self {
-            SupermavenButtonStatus::Ready => IconName::Supermaven,
-            SupermavenButtonStatus::Errored(_) => IconName::SupermavenError,
-            SupermavenButtonStatus::NeedsActivation(_) => IconName::SupermavenInit,
-            SupermavenButtonStatus::Initializing => IconName::SupermavenInit,
-        }
-    }
-
-    fn to_tooltip(&self) -> String {
-        match self {
-            SupermavenButtonStatus::Ready => "Supermaven is ready".to_string(),
-            SupermavenButtonStatus::Errored(error) => format!("Supermaven error: {}", error),
-            SupermavenButtonStatus::NeedsActivation(_) => "Supermaven needs activation".to_string(),
-            SupermavenButtonStatus::Initializing => "Supermaven initializing".to_string(),
-        }
-    }
-
-    fn has_menu(&self) -> bool {
-        match self {
-            SupermavenButtonStatus::Ready | SupermavenButtonStatus::NeedsActivation(_) => true,
-            SupermavenButtonStatus::Errored(_) | SupermavenButtonStatus::Initializing => false,
-        }
-    }
-}
-
 async fn open_disabled_globs_setting_in_editor(
     workspace: WeakEntity<Workspace>,
     cx: &mut AsyncWindowContext,
@@ -1507,14 +1362,6 @@ pub fn get_available_providers(cx: &mut App) -> Vec<EditPredictionProvider> {
         providers.push(EditPredictionProvider::Copilot);
     };
 
-    if let Some(supermaven) = Supermaven::global(cx) {
-        if let Supermaven::Spawned(agent) = supermaven.read(cx) {
-            if matches!(agent.account_status, AccountStatus::Ready) {
-                providers.push(EditPredictionProvider::Supermaven);
-            }
-        }
-    }
-
     if codestral::codestral_api_key(cx).is_some() {
         providers.push(EditPredictionProvider::Codestral);
     }

crates/editor/src/editor.rs 🔗

@@ -28579,7 +28579,7 @@ fn edit_prediction_edit_text(
 }
 
 fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
-    // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
+    // Fallback for providers that don't provide edit_preview (like Copilot)
     // Just show the raw edit text with basic styling
     let mut text = String::new();
     let mut highlights = Vec::new();

crates/icons/src/icons.rs 🔗

@@ -225,10 +225,6 @@ pub enum IconName {
     Star,
     StarFilled,
     Stop,
-    Supermaven,
-    SupermavenDisabled,
-    SupermavenError,
-    SupermavenInit,
     SwatchBook,
     SweepAi,
     SweepAiDisabled,

crates/language/src/language_settings.rs 🔗

@@ -396,8 +396,7 @@ impl InlayHintSettings {
     }
 }
 
-/// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot)
-/// or [Supermaven](https://supermaven.com).
+/// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot).
 #[derive(Clone, Debug, Default)]
 pub struct EditPredictionSettings {
     /// The provider that supplies edit predictions.

crates/paths/src/paths.rs 🔗

@@ -419,12 +419,6 @@ pub fn copilot_dir() -> &'static PathBuf {
     COPILOT_DIR.get_or_init(|| data_dir().join("copilot"))
 }
 
-/// Returns the path to the Supermaven directory.
-pub fn supermaven_dir() -> &'static PathBuf {
-    static SUPERMAVEN_DIR: OnceLock<PathBuf> = OnceLock::new();
-    SUPERMAVEN_DIR.get_or_init(|| data_dir().join("supermaven"))
-}
-
 /// Returns the path to the default Prettier directory.
 pub fn default_prettier_dir() -> &'static PathBuf {
     static DEFAULT_PRETTIER_DIR: OnceLock<PathBuf> = OnceLock::new();

crates/settings_content/src/language.rs 🔗

@@ -81,7 +81,6 @@ pub enum EditPredictionProvider {
     None,
     #[default]
     Copilot,
-    Supermaven,
     Zed,
     Codestral,
     Ollama,
@@ -103,7 +102,6 @@ impl<'de> Deserialize<'de> for EditPredictionProvider {
         pub enum Content {
             None,
             Copilot,
-            Supermaven,
             Zed,
             Codestral,
             Ollama,
@@ -116,7 +114,6 @@ impl<'de> Deserialize<'de> for EditPredictionProvider {
         Ok(match Content::deserialize(deserializer)? {
             Content::None => EditPredictionProvider::None,
             Content::Copilot => EditPredictionProvider::Copilot,
-            Content::Supermaven => EditPredictionProvider::Supermaven,
             Content::Zed => EditPredictionProvider::Zed,
             Content::Codestral => EditPredictionProvider::Codestral,
             Content::Ollama => EditPredictionProvider::Ollama,
@@ -146,7 +143,6 @@ impl EditPredictionProvider {
             EditPredictionProvider::Zed => true,
             EditPredictionProvider::None
             | EditPredictionProvider::Copilot
-            | EditPredictionProvider::Supermaven
             | EditPredictionProvider::Codestral
             | EditPredictionProvider::Ollama
             | EditPredictionProvider::OpenAiCompatibleApi
@@ -160,7 +156,6 @@ impl EditPredictionProvider {
         match self {
             EditPredictionProvider::Zed => Some("Zed AI"),
             EditPredictionProvider::Copilot => Some("GitHub Copilot"),
-            EditPredictionProvider::Supermaven => Some("Supermaven"),
             EditPredictionProvider::Codestral => Some("Codestral"),
             EditPredictionProvider::Sweep => Some("Sweep"),
             EditPredictionProvider::Mercury => Some("Mercury"),

crates/supermaven/Cargo.toml 🔗

@@ -1,44 +0,0 @@
-[package]
-name = "supermaven"
-version = "0.1.0"
-edition.workspace = true
-publish.workspace = true
-license = "GPL-3.0-or-later"
-
-[lints]
-workspace = true
-
-[lib]
-path = "src/supermaven.rs"
-doctest = false
-
-[dependencies]
-anyhow.workspace = true
-client.workspace = true
-collections.workspace = true
-edit_prediction_types.workspace = true
-futures.workspace = true
-gpui.workspace = true
-language.workspace = true
-log.workspace = true
-postage.workspace = true
-serde.workspace = true
-serde_json.workspace = true
-settings.workspace = true
-smol.workspace = true
-supermaven_api.workspace = true
-text.workspace = true
-ui.workspace = true
-unicode-segmentation.workspace = true
-util.workspace = true
-
-[dev-dependencies]
-editor = { workspace = true, features = ["test-support"] }
-env_logger.workspace = true
-gpui = { workspace = true, features = ["test-support"] }
-language = { workspace = true, features = ["test-support"] }
-project = { workspace = true, features = ["test-support"] }
-settings = { workspace = true, features = ["test-support"] }
-theme = { workspace = true, features = ["test-support"] }
-util = { workspace = true, features = ["test-support"] }
-http_client = { workspace = true, features = ["test-support"] }

crates/supermaven/src/messages.rs 🔗

@@ -1,146 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-// Outbound messages
-#[derive(Debug, Serialize)]
-#[serde(tag = "kind", rename_all = "snake_case")]
-pub enum OutboundMessage {
-    StateUpdate(StateUpdateMessage),
-    #[allow(dead_code)]
-    UseFreeVersion,
-    Logout,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct StateUpdateMessage {
-    pub new_id: String,
-    pub updates: Vec<StateUpdate>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(tag = "kind", rename_all = "snake_case")]
-pub enum StateUpdate {
-    FileUpdate(FileUpdateMessage),
-    CursorUpdate(CursorPositionUpdateMessage),
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub struct FileUpdateMessage {
-    pub path: String,
-    pub content: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub struct CursorPositionUpdateMessage {
-    pub path: String,
-    pub offset: usize,
-}
-
-// Inbound messages coming in on stdout
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(tag = "kind", rename_all = "snake_case")]
-pub enum ResponseItem {
-    // A completion
-    Text { text: String },
-    // Vestigial message type from old versions -- safe to ignore
-    Del { text: String },
-    // Be able to delete whitespace prior to the cursor, likely for the rest of the completion
-    Dedent { text: String },
-    // When the completion is over
-    End,
-    // Got the closing parentheses and shouldn't show any more after
-    Barrier,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct SupermavenResponse {
-    pub state_id: String,
-    pub items: Vec<ResponseItem>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct SupermavenMetadataMessage {
-    pub dust_strings: Option<Vec<String>>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct SupermavenTaskUpdateMessage {
-    pub task: String,
-    pub status: TaskStatus,
-    pub percent_complete: Option<f32>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub enum TaskStatus {
-    InProgress,
-    Complete,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct SupermavenActiveRepoMessage {
-    pub repo_simple_name: Option<String>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(tag = "kind", rename_all = "snake_case")]
-pub enum SupermavenPopupAction {
-    OpenUrl { label: String, url: String },
-    NoOp { label: String },
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub struct SupermavenPopupMessage {
-    pub message: String,
-    pub actions: Vec<SupermavenPopupAction>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(tag = "kind", rename_all = "camelCase")]
-pub struct ActivationRequest {
-    pub activate_url: Option<String>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct SupermavenSetMessage {
-    pub key: String,
-    pub value: serde_json::Value,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub enum ServiceTier {
-    FreeNoLicense,
-    #[serde(other)]
-    Unknown,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(tag = "kind", rename_all = "snake_case")]
-pub enum SupermavenMessage {
-    Response(SupermavenResponse),
-    Metadata(SupermavenMetadataMessage),
-    Apology {
-        message: Option<String>,
-    },
-    ActivationRequest(ActivationRequest),
-    ActivationSuccess,
-    Passthrough {
-        passthrough: Box<SupermavenMessage>,
-    },
-    Popup(SupermavenPopupMessage),
-    TaskStatus(SupermavenTaskUpdateMessage),
-    ActiveRepo(SupermavenActiveRepoMessage),
-    ServiceTier {
-        service_tier: ServiceTier,
-    },
-
-    Set(SupermavenSetMessage),
-    #[serde(other)]
-    Unknown,
-}

crates/supermaven/src/supermaven.rs 🔗

@@ -1,485 +0,0 @@
-mod messages;
-mod supermaven_edit_prediction_delegate;
-
-pub use supermaven_edit_prediction_delegate::*;
-
-use anyhow::{Context as _, Result};
-#[allow(unused_imports)]
-use client::{Client, proto};
-use collections::BTreeMap;
-
-use futures::{AsyncBufReadExt, StreamExt, channel::mpsc, io::BufReader};
-use gpui::{App, AsyncApp, Context, Entity, EntityId, Global, Task, WeakEntity, actions};
-use language::{
-    Anchor, Buffer, BufferSnapshot, ToOffset, language_settings::all_language_settings,
-};
-use messages::*;
-use postage::watch;
-use serde::{Deserialize, Serialize};
-use settings::SettingsStore;
-use smol::io::AsyncWriteExt;
-use std::{path::PathBuf, sync::Arc};
-use ui::prelude::*;
-use util::ResultExt;
-use util::command::Child;
-use util::command::Stdio;
-
-actions!(
-    supermaven,
-    [
-        /// Signs out of Supermaven.
-        SignOut
-    ]
-);
-
-pub fn init(client: Arc<Client>, cx: &mut App) {
-    let supermaven = cx.new(|_| Supermaven::Starting);
-    Supermaven::set_global(supermaven.clone(), cx);
-
-    let mut provider = all_language_settings(None, cx).edit_predictions.provider;
-    if provider == language::language_settings::EditPredictionProvider::Supermaven {
-        supermaven.update(cx, |supermaven, cx| supermaven.start(client.clone(), cx));
-    }
-
-    cx.observe_global::<SettingsStore>(move |cx| {
-        let new_provider = all_language_settings(None, cx).edit_predictions.provider;
-        if new_provider != provider {
-            provider = new_provider;
-            if provider == language::language_settings::EditPredictionProvider::Supermaven {
-                supermaven.update(cx, |supermaven, cx| supermaven.start(client.clone(), cx));
-            } else {
-                supermaven.update(cx, |supermaven, _cx| supermaven.stop());
-            }
-        }
-    })
-    .detach();
-
-    cx.on_action(|_: &SignOut, cx| {
-        if let Some(supermaven) = Supermaven::global(cx) {
-            supermaven.update(cx, |supermaven, _cx| supermaven.sign_out());
-        }
-    });
-}
-
-pub enum Supermaven {
-    Starting,
-    FailedDownload { error: anyhow::Error },
-    Spawned(SupermavenAgent),
-    Error { error: anyhow::Error },
-}
-
-#[derive(Clone)]
-pub enum AccountStatus {
-    Unknown,
-    NeedsActivation { activate_url: String },
-    Ready,
-}
-
-#[derive(Clone)]
-struct SupermavenGlobal(Entity<Supermaven>);
-
-impl Global for SupermavenGlobal {}
-
-impl Supermaven {
-    pub fn global(cx: &App) -> Option<Entity<Self>> {
-        cx.try_global::<SupermavenGlobal>()
-            .map(|model| model.0.clone())
-    }
-
-    pub fn set_global(supermaven: Entity<Self>, cx: &mut App) {
-        cx.set_global(SupermavenGlobal(supermaven));
-    }
-
-    pub fn start(&mut self, client: Arc<Client>, cx: &mut Context<Self>) {
-        if let Self::Starting = self {
-            cx.spawn(async move |this, cx| {
-                let binary_path =
-                    supermaven_api::get_supermaven_agent_path(client.http_client()).await?;
-
-                this.update(cx, |this, cx| {
-                    if let Self::Starting = this {
-                        *this =
-                            Self::Spawned(SupermavenAgent::new(binary_path, client.clone(), cx)?);
-                    }
-                    anyhow::Ok(())
-                })
-            })
-            .detach_and_log_err(cx)
-        }
-    }
-
-    pub fn stop(&mut self) {
-        *self = Self::Starting;
-    }
-
-    pub fn is_enabled(&self) -> bool {
-        matches!(self, Self::Spawned { .. })
-    }
-
-    pub fn complete(
-        &mut self,
-        buffer: &Entity<Buffer>,
-        cursor_position: Anchor,
-        cx: &App,
-    ) -> Option<SupermavenCompletion> {
-        if let Self::Spawned(agent) = self {
-            let buffer_id = buffer.entity_id();
-            let buffer = buffer.read(cx);
-            let path = buffer
-                .file()
-                .and_then(|file| Some(file.as_local()?.abs_path(cx)))
-                .unwrap_or_else(|| PathBuf::from("untitled"))
-                .to_string_lossy()
-                .to_string();
-            let content = buffer.text();
-            let offset = cursor_position.to_offset(buffer);
-            let state_id = agent.next_state_id;
-            agent.next_state_id.0 += 1;
-
-            let (updates_tx, mut updates_rx) = watch::channel();
-            postage::stream::Stream::try_recv(&mut updates_rx).unwrap();
-
-            agent.states.insert(
-                state_id,
-                SupermavenCompletionState {
-                    buffer_id,
-                    prefix_anchor: cursor_position,
-                    prefix_offset: offset,
-                    text: String::new(),
-                    dedent: String::new(),
-                    updates_tx,
-                },
-            );
-            // ensure the states map is max 1000 elements
-            if agent.states.len() > 1000 {
-                // state id is monotonic so it's sufficient to remove the first element
-                agent
-                    .states
-                    .remove(&agent.states.keys().next().unwrap().clone());
-            }
-
-            let _ = agent
-                .outgoing_tx
-                .unbounded_send(OutboundMessage::StateUpdate(StateUpdateMessage {
-                    new_id: state_id.0.to_string(),
-                    updates: vec![
-                        StateUpdate::FileUpdate(FileUpdateMessage {
-                            path: path.clone(),
-                            content,
-                        }),
-                        StateUpdate::CursorUpdate(CursorPositionUpdateMessage { path, offset }),
-                    ],
-                }));
-
-            Some(SupermavenCompletion {
-                id: state_id,
-                updates: updates_rx,
-            })
-        } else {
-            None
-        }
-    }
-
-    pub fn completion(
-        &self,
-        buffer: &Entity<Buffer>,
-        cursor_position: Anchor,
-        cx: &App,
-    ) -> Option<&str> {
-        if let Self::Spawned(agent) = self {
-            find_relevant_completion(
-                &agent.states,
-                buffer.entity_id(),
-                &buffer.read(cx).snapshot(),
-                cursor_position,
-            )
-        } else {
-            None
-        }
-    }
-
-    pub fn sign_out(&mut self) {
-        if let Self::Spawned(agent) = self {
-            agent
-                .outgoing_tx
-                .unbounded_send(OutboundMessage::Logout)
-                .ok();
-            // The account status will get set to RequiresActivation or Ready when the next
-            // message from the agent comes in. Until that happens, set the status to Unknown
-            // to disable the button.
-            agent.account_status = AccountStatus::Unknown;
-        }
-    }
-}
-
-fn find_relevant_completion<'a>(
-    states: &'a BTreeMap<SupermavenCompletionStateId, SupermavenCompletionState>,
-    buffer_id: EntityId,
-    buffer: &BufferSnapshot,
-    cursor_position: Anchor,
-) -> Option<&'a str> {
-    let mut best_completion: Option<&str> = None;
-    'completions: for state in states.values() {
-        if state.buffer_id != buffer_id {
-            continue;
-        }
-        let Some(state_completion) = state.text.strip_prefix(&state.dedent) else {
-            continue;
-        };
-
-        let current_cursor_offset = cursor_position.to_offset(buffer);
-        if current_cursor_offset < state.prefix_offset {
-            continue;
-        }
-
-        let original_cursor_offset = buffer.clip_offset(state.prefix_offset, text::Bias::Left);
-        let text_inserted_since_completion_request: String = buffer
-            .text_for_range(original_cursor_offset..current_cursor_offset)
-            .collect();
-        let trimmed_completion =
-            match state_completion.strip_prefix(&text_inserted_since_completion_request) {
-                Some(suffix) => suffix,
-                None => continue 'completions,
-            };
-
-        if best_completion.is_some_and(|best| best.len() > trimmed_completion.len()) {
-            continue;
-        }
-
-        best_completion = Some(trimmed_completion);
-    }
-    best_completion
-}
-
-pub struct SupermavenAgent {
-    _process: Child,
-    next_state_id: SupermavenCompletionStateId,
-    states: BTreeMap<SupermavenCompletionStateId, SupermavenCompletionState>,
-    outgoing_tx: mpsc::UnboundedSender<OutboundMessage>,
-    _handle_outgoing_messages: Task<Result<()>>,
-    _handle_incoming_messages: Task<Result<()>>,
-    pub account_status: AccountStatus,
-    service_tier: Option<ServiceTier>,
-    #[allow(dead_code)]
-    client: Arc<Client>,
-}
-
-impl SupermavenAgent {
-    fn new(
-        binary_path: PathBuf,
-        client: Arc<Client>,
-        cx: &mut Context<Supermaven>,
-    ) -> Result<Self> {
-        let mut process = util::command::new_command(&binary_path)
-            .arg("stdio")
-            .stdin(Stdio::piped())
-            .stdout(Stdio::piped())
-            .stderr(Stdio::piped())
-            .kill_on_drop(true)
-            .spawn()
-            .context("failed to start the binary")?;
-
-        let stdin = process
-            .stdin
-            .take()
-            .context("failed to get stdin for process")?;
-        let stdout = process
-            .stdout
-            .take()
-            .context("failed to get stdout for process")?;
-
-        let (outgoing_tx, outgoing_rx) = mpsc::unbounded();
-
-        Ok(Self {
-            _process: process,
-            next_state_id: SupermavenCompletionStateId::default(),
-            states: BTreeMap::default(),
-            outgoing_tx,
-            _handle_outgoing_messages: cx.spawn(async move |_, _cx| {
-                Self::handle_outgoing_messages(outgoing_rx, stdin).await
-            }),
-            _handle_incoming_messages: cx.spawn(async move |this, cx| {
-                Self::handle_incoming_messages(this, stdout, cx).await
-            }),
-            account_status: AccountStatus::Unknown,
-            service_tier: None,
-            client,
-        })
-    }
-
-    async fn handle_outgoing_messages<W: smol::io::AsyncWrite + Unpin>(
-        mut outgoing: mpsc::UnboundedReceiver<OutboundMessage>,
-        mut stdin: W,
-    ) -> Result<()> {
-        while let Some(message) = outgoing.next().await {
-            let bytes = serde_json::to_vec(&message)?;
-            stdin.write_all(&bytes).await?;
-            stdin.write_all(&[b'\n']).await?;
-        }
-        Ok(())
-    }
-
-    async fn handle_incoming_messages<R: smol::io::AsyncRead + Unpin>(
-        this: WeakEntity<Supermaven>,
-        stdout: R,
-        cx: &mut AsyncApp,
-    ) -> Result<()> {
-        const MESSAGE_PREFIX: &str = "SM-MESSAGE ";
-
-        let stdout = BufReader::new(stdout);
-        let mut lines = stdout.lines();
-        while let Some(line) = lines.next().await {
-            let Some(line) = line.context("failed to read line from stdout").log_err() else {
-                continue;
-            };
-            let Some(line) = line.strip_prefix(MESSAGE_PREFIX) else {
-                continue;
-            };
-            let Some(message) = serde_json::from_str::<SupermavenMessage>(line)
-                .with_context(|| format!("failed to deserialize line from stdout: {:?}", line))
-                .log_err()
-            else {
-                continue;
-            };
-
-            this.update(cx, |this, _cx| {
-                if let Supermaven::Spawned(this) = this {
-                    this.handle_message(message);
-                }
-                Task::ready(anyhow::Ok(()))
-            })?
-            .await?;
-        }
-
-        Ok(())
-    }
-
-    fn handle_message(&mut self, message: SupermavenMessage) {
-        match message {
-            SupermavenMessage::ActivationRequest(request) => {
-                self.account_status = match request.activate_url {
-                    Some(activate_url) => AccountStatus::NeedsActivation { activate_url },
-                    None => AccountStatus::Ready,
-                };
-            }
-            SupermavenMessage::ActivationSuccess => {
-                self.account_status = AccountStatus::Ready;
-            }
-            SupermavenMessage::ServiceTier { service_tier } => {
-                self.account_status = AccountStatus::Ready;
-                self.service_tier = Some(service_tier);
-            }
-            SupermavenMessage::Response(response) => {
-                let state_id = SupermavenCompletionStateId(response.state_id.parse().unwrap());
-                if let Some(state) = self.states.get_mut(&state_id) {
-                    for item in &response.items {
-                        match item {
-                            ResponseItem::Text { text } => state.text.push_str(text),
-                            ResponseItem::Dedent { text } => state.dedent.push_str(text),
-                            _ => {}
-                        }
-                    }
-                    *state.updates_tx.borrow_mut() = ();
-                }
-            }
-            SupermavenMessage::Passthrough { passthrough } => self.handle_message(*passthrough),
-            _ => {
-                log::warn!("unhandled message: {:?}", message);
-            }
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
-pub struct SupermavenCompletionStateId(usize);
-
-#[allow(dead_code)]
-pub struct SupermavenCompletionState {
-    buffer_id: EntityId,
-    prefix_anchor: Anchor,
-    // prefix_offset is tracked independently because the anchor biases left which
-    // doesn't allow us to determine if the prior text has been deleted.
-    prefix_offset: usize,
-    text: String,
-    dedent: String,
-    updates_tx: watch::Sender<()>,
-}
-
-pub struct SupermavenCompletion {
-    pub id: SupermavenCompletionStateId,
-    pub updates: watch::Receiver<()>,
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use collections::BTreeMap;
-    use gpui::TestAppContext;
-    use language::Buffer;
-
-    #[gpui::test]
-    async fn test_find_relevant_completion_no_first_letter_skip(cx: &mut TestAppContext) {
-        let buffer = cx.new(|cx| Buffer::local("hello world", cx));
-        let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
-
-        let mut states = BTreeMap::new();
-        let state_id = SupermavenCompletionStateId(1);
-        let (updates_tx, _) = watch::channel();
-
-        states.insert(
-            state_id,
-            SupermavenCompletionState {
-                buffer_id: buffer.entity_id(),
-                prefix_anchor: buffer_snapshot.anchor_before(0), // Start of buffer
-                prefix_offset: 0,
-                text: "hello".to_string(),
-                dedent: String::new(),
-                updates_tx,
-            },
-        );
-
-        let cursor_position = buffer_snapshot.anchor_after(1);
-
-        let result = find_relevant_completion(
-            &states,
-            buffer.entity_id(),
-            &buffer_snapshot,
-            cursor_position,
-        );
-
-        assert_eq!(result, Some("ello"));
-    }
-
-    #[gpui::test]
-    async fn test_find_relevant_completion_with_multiple_chars(cx: &mut TestAppContext) {
-        let buffer = cx.new(|cx| Buffer::local("hello world", cx));
-        let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
-
-        let mut states = BTreeMap::new();
-        let state_id = SupermavenCompletionStateId(1);
-        let (updates_tx, _) = watch::channel();
-
-        states.insert(
-            state_id,
-            SupermavenCompletionState {
-                buffer_id: buffer.entity_id(),
-                prefix_anchor: buffer_snapshot.anchor_before(0), // Start of buffer
-                prefix_offset: 0,
-                text: "hello".to_string(),
-                dedent: String::new(),
-                updates_tx,
-            },
-        );
-
-        let cursor_position = buffer_snapshot.anchor_after(3);
-
-        let result = find_relevant_completion(
-            &states,
-            buffer.entity_id(),
-            &buffer_snapshot,
-            cursor_position,
-        );
-
-        assert_eq!(result, Some("lo"));
-    }
-}

crates/supermaven/src/supermaven_edit_prediction_delegate.rs 🔗

@@ -1,303 +0,0 @@
-use crate::{Supermaven, SupermavenCompletionStateId};
-use anyhow::Result;
-use edit_prediction_types::{
-    EditPrediction, EditPredictionDelegate, EditPredictionDiscardReason, EditPredictionIconSet,
-};
-use futures::StreamExt as _;
-use gpui::{App, Context, Entity, EntityId, Task};
-use language::{Anchor, Buffer, BufferSnapshot};
-use std::{
-    ops::{AddAssign, Range},
-    path::Path,
-    sync::Arc,
-    time::Duration,
-};
-use text::{ToOffset, ToPoint};
-use ui::prelude::*;
-use unicode_segmentation::UnicodeSegmentation;
-
-pub const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
-
-pub struct SupermavenEditPredictionDelegate {
-    supermaven: Entity<Supermaven>,
-    buffer_id: Option<EntityId>,
-    completion_id: Option<SupermavenCompletionStateId>,
-    completion_text: Option<String>,
-    file_extension: Option<String>,
-    pending_refresh: Option<Task<Result<()>>>,
-    completion_position: Option<language::Anchor>,
-}
-
-impl SupermavenEditPredictionDelegate {
-    pub fn new(supermaven: Entity<Supermaven>) -> Self {
-        Self {
-            supermaven,
-            buffer_id: None,
-            completion_id: None,
-            completion_text: None,
-            file_extension: None,
-            pending_refresh: None,
-            completion_position: None,
-        }
-    }
-}
-
-// Computes the edit prediction from the difference between the completion text.
-// This is defined by greedily matching the buffer text against the completion text.
-// Inlays are inserted for parts of the completion text that are not present in the buffer text.
-// For example, given the completion text "axbyc" and the buffer text "xy", the rendered output in the editor would be "[a]x[b]y[c]".
-// The parts in brackets are the inlays.
-fn completion_from_diff(
-    snapshot: BufferSnapshot,
-    completion_text: &str,
-    position: Anchor,
-    delete_range: Range<Anchor>,
-) -> EditPrediction {
-    let buffer_text = snapshot.text_for_range(delete_range).collect::<String>();
-
-    let mut edits: Vec<(Range<language::Anchor>, Arc<str>)> = Vec::new();
-
-    let completion_graphemes: Vec<&str> = completion_text.graphemes(true).collect();
-    let buffer_graphemes: Vec<&str> = buffer_text.graphemes(true).collect();
-
-    let mut offset = position.to_offset(&snapshot);
-
-    let mut i = 0;
-    let mut j = 0;
-    while i < completion_graphemes.len() && j < buffer_graphemes.len() {
-        // find the next instance of the buffer text in the completion text.
-        let k = completion_graphemes[i..]
-            .iter()
-            .position(|c| *c == buffer_graphemes[j]);
-        match k {
-            Some(k) => {
-                if k != 0 {
-                    let offset = snapshot.anchor_after(offset);
-                    // the range from the current position to item is an inlay.
-                    let edit = (
-                        offset..offset,
-                        completion_graphemes[i..i + k].join("").into(),
-                    );
-                    edits.push(edit);
-                }
-                i += k + 1;
-                j += 1;
-                offset.add_assign(buffer_graphemes[j - 1].len());
-            }
-            None => {
-                // there are no more matching completions, so drop the remaining
-                // completion text as an inlay.
-                break;
-            }
-        }
-    }
-
-    if j == buffer_graphemes.len() && i < completion_graphemes.len() {
-        let offset = snapshot.anchor_after(offset);
-        // there is leftover completion text, so drop it as an inlay.
-        let edit_range = offset..offset;
-        let edit_text = completion_graphemes[i..].join("");
-        edits.push((edit_range, edit_text.into()));
-    }
-
-    EditPrediction::Local {
-        id: None,
-        edits,
-        cursor_position: None,
-        edit_preview: None,
-    }
-}
-
-impl EditPredictionDelegate for SupermavenEditPredictionDelegate {
-    fn name() -> &'static str {
-        "supermaven"
-    }
-
-    fn display_name() -> &'static str {
-        "Supermaven"
-    }
-
-    fn show_predictions_in_menu() -> bool {
-        true
-    }
-
-    fn show_tab_accept_marker() -> bool {
-        true
-    }
-
-    fn supports_jump_to_edit() -> bool {
-        false
-    }
-
-    fn icons(&self, _cx: &App) -> EditPredictionIconSet {
-        EditPredictionIconSet::new(IconName::Supermaven)
-            .with_disabled(IconName::SupermavenDisabled)
-            .with_error(IconName::SupermavenError)
-    }
-
-    fn is_enabled(&self, _buffer: &Entity<Buffer>, _cursor_position: Anchor, cx: &App) -> bool {
-        self.supermaven.read(cx).is_enabled()
-    }
-
-    fn is_refreshing(&self, _cx: &App) -> bool {
-        self.pending_refresh.is_some() && self.completion_id.is_none()
-    }
-
-    fn refresh(
-        &mut self,
-        buffer_handle: Entity<Buffer>,
-        cursor_position: Anchor,
-        debounce: bool,
-        cx: &mut Context<Self>,
-    ) {
-        // Only make new completion requests when debounce is true (i.e., when text is typed)
-        // When debounce is false (i.e., cursor movement), we should not make new requests
-        if !debounce {
-            return;
-        }
-
-        reset_completion_cache(self, cx);
-
-        let Some(mut completion) = self.supermaven.update(cx, |supermaven, cx| {
-            supermaven.complete(&buffer_handle, cursor_position, cx)
-        }) else {
-            return;
-        };
-
-        self.pending_refresh = Some(cx.spawn(async move |this, cx| {
-            if debounce {
-                cx.background_executor().timer(DEBOUNCE_TIMEOUT).await;
-            }
-
-            while let Some(()) = completion.updates.next().await {
-                this.update(cx, |this, cx| {
-                    // Get the completion text and cache it
-                    if let Some(text) =
-                        this.supermaven
-                            .read(cx)
-                            .completion(&buffer_handle, cursor_position, cx)
-                    {
-                        this.completion_text = Some(text.to_string());
-
-                        this.completion_position = Some(cursor_position);
-                    }
-
-                    this.completion_id = Some(completion.id);
-                    this.buffer_id = Some(buffer_handle.entity_id());
-                    this.file_extension = buffer_handle.read(cx).file().and_then(|file| {
-                        Some(
-                            Path::new(file.file_name(cx))
-                                .extension()?
-                                .to_str()?
-                                .to_string(),
-                        )
-                    });
-                    cx.notify();
-                })?;
-            }
-            Ok(())
-        }));
-    }
-
-    fn accept(&mut self, _cx: &mut Context<Self>) {
-        reset_completion_cache(self, _cx);
-    }
-
-    fn discard(&mut self, _reason: EditPredictionDiscardReason, _cx: &mut Context<Self>) {
-        reset_completion_cache(self, _cx);
-    }
-
-    fn suggest(
-        &mut self,
-        buffer: &Entity<Buffer>,
-        cursor_position: Anchor,
-        cx: &mut Context<Self>,
-    ) -> Option<EditPrediction> {
-        if self.buffer_id != Some(buffer.entity_id()) {
-            return None;
-        }
-
-        if self.completion_id.is_none() {
-            return None;
-        }
-
-        let completion_text = if let Some(cached_text) = &self.completion_text {
-            cached_text.as_str()
-        } else {
-            let text = self
-                .supermaven
-                .read(cx)
-                .completion(buffer, cursor_position, cx)?;
-            self.completion_text = Some(text.to_string());
-            text
-        };
-
-        // Check if the cursor is still at the same position as the completion request
-        // If we don't have a completion position stored, don't show the completion
-        if let Some(completion_position) = self.completion_position {
-            if cursor_position != completion_position {
-                return None;
-            }
-        } else {
-            return None;
-        }
-
-        let completion_text = trim_to_end_of_line_unless_leading_newline(completion_text);
-
-        let completion_text = completion_text.trim_end();
-
-        if !completion_text.trim().is_empty() {
-            let snapshot = buffer.read(cx).snapshot();
-
-            // Calculate the range from cursor to end of line correctly
-            let cursor_point = cursor_position.to_point(&snapshot);
-            let end_of_line = snapshot.anchor_after(language::Point::new(
-                cursor_point.row,
-                snapshot.line_len(cursor_point.row),
-            ));
-            let delete_range = cursor_position..end_of_line;
-
-            Some(completion_from_diff(
-                snapshot,
-                completion_text,
-                cursor_position,
-                delete_range,
-            ))
-        } else {
-            None
-        }
-    }
-}
-
-fn reset_completion_cache(
-    provider: &mut SupermavenEditPredictionDelegate,
-    _cx: &mut Context<SupermavenEditPredictionDelegate>,
-) {
-    provider.pending_refresh = None;
-    provider.completion_id = None;
-    provider.completion_text = None;
-    provider.completion_position = None;
-    provider.buffer_id = None;
-}
-
-fn trim_to_end_of_line_unless_leading_newline(text: &str) -> &str {
-    if has_leading_newline(text) {
-        text
-    } else if let Some(i) = text.find('\n') {
-        &text[..i]
-    } else {
-        text
-    }
-}
-
-fn has_leading_newline(text: &str) -> bool {
-    for c in text.chars() {
-        if c == '\n' {
-            return true;
-        }
-        if !c.is_whitespace() {
-            return false;
-        }
-    }
-    false
-}

crates/supermaven_api/Cargo.toml 🔗

@@ -1,23 +0,0 @@
-[package]
-name = "supermaven_api"
-version = "0.1.0"
-edition.workspace = true
-publish.workspace = true
-license = "GPL-3.0-or-later"
-
-[lints]
-workspace = true
-
-[lib]
-path = "src/supermaven_api.rs"
-doctest = false
-
-[dependencies]
-anyhow.workspace = true
-futures.workspace = true
-http_client.workspace = true
-paths.workspace = true
-serde.workspace = true
-serde_json.workspace = true
-smol.workspace = true
-util.workspace = true

crates/supermaven_api/src/supermaven_api.rs 🔗

@@ -1,125 +0,0 @@
-use anyhow::{Context as _, Result};
-use futures::AsyncReadExt;
-use futures::io::BufReader;
-use http_client::{AsyncBody, HttpClient, Request as HttpRequest};
-use paths::supermaven_dir;
-use serde::Deserialize;
-use smol::fs::{self, File};
-use std::path::{Path, PathBuf};
-use std::sync::Arc;
-
-use util::fs::{make_file_executable, remove_matching};
-
-#[derive(Deserialize)]
-pub struct SupermavenApiError {
-    pub message: String,
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct SupermavenDownloadResponse {
-    pub download_url: String,
-    pub version: u64,
-    pub sha256_hash: String,
-}
-
-pub async fn latest_release(
-    client: Arc<dyn HttpClient>,
-    platform: &str,
-    arch: &str,
-) -> Result<SupermavenDownloadResponse> {
-    let uri = format!(
-        "https://supermaven.com/api/download-path?platform={}&arch={}",
-        platform, arch
-    );
-
-    // Download is not authenticated
-    let request = HttpRequest::get(&uri);
-
-    let mut response = client
-        .send(request.body(AsyncBody::default())?)
-        .await
-        .with_context(|| "Unable to acquire Supermaven Agent".to_string())?;
-
-    let mut body = Vec::new();
-    response.body_mut().read_to_end(&mut body).await?;
-
-    if response.status().is_client_error() || response.status().is_server_error() {
-        let body_str = std::str::from_utf8(&body)?;
-        let error: SupermavenApiError = serde_json::from_str(body_str)?;
-        anyhow::bail!("Supermaven API error: {}", error.message);
-    }
-
-    serde_json::from_slice::<SupermavenDownloadResponse>(&body)
-        .with_context(|| "Unable to parse Supermaven Agent response".to_string())
-}
-
-pub fn version_path(version: u64) -> PathBuf {
-    supermaven_dir().join(format!(
-        "sm-agent-{}{}",
-        version,
-        std::env::consts::EXE_SUFFIX
-    ))
-}
-
-pub async fn has_version(version_path: &Path) -> bool {
-    fs::metadata(version_path).await.is_ok_and(|m| m.is_file())
-}
-
-pub async fn get_supermaven_agent_path(client: Arc<dyn HttpClient>) -> Result<PathBuf> {
-    fs::create_dir_all(supermaven_dir())
-        .await
-        .with_context(|| {
-            format!(
-                "Could not create Supermaven Agent Directory at {:?}",
-                supermaven_dir()
-            )
-        })?;
-
-    let platform = match std::env::consts::OS {
-        "macos" => "darwin",
-        "windows" => "windows",
-        "linux" => "linux",
-        unsupported => anyhow::bail!("unsupported platform {unsupported}"),
-    };
-
-    let arch = match std::env::consts::ARCH {
-        "x86_64" => "amd64",
-        "aarch64" => "arm64",
-        unsupported => anyhow::bail!("unsupported architecture {unsupported}"),
-    };
-
-    let download_info = latest_release(client.clone(), platform, arch).await?;
-
-    let binary_path = version_path(download_info.version);
-
-    if has_version(&binary_path).await {
-        // Due to an issue with the Supermaven binary not being made executable on
-        // earlier Zed versions and Supermaven releases not occurring that frequently,
-        // we ensure here that the found binary is actually executable.
-        make_file_executable(&binary_path).await?;
-
-        return Ok(binary_path);
-    }
-
-    let request = HttpRequest::get(&download_info.download_url);
-
-    let mut response = client
-        .send(request.body(AsyncBody::default())?)
-        .await
-        .with_context(|| "Unable to download Supermaven Agent".to_string())?;
-
-    let mut file = File::create(&binary_path)
-        .await
-        .with_context(|| format!("Unable to create file at {:?}", binary_path))?;
-
-    futures::io::copy(BufReader::new(response.body_mut()), &mut file)
-        .await
-        .with_context(|| format!("Unable to write binary to file at {:?}", binary_path))?;
-
-    make_file_executable(&binary_path).await?;
-
-    remove_matching(supermaven_dir(), |file| file != binary_path).await;
-
-    Ok(binary_path)
-}

crates/zed/Cargo.toml 🔗

@@ -189,7 +189,6 @@ sidebar.workspace = true
 smol.workspace = true
 snippet_provider.workspace = true
 snippets_ui.workspace = true
-supermaven.workspace = true
 svg_preview.workspace = true
 sysinfo.workspace = true
 tab_switcher.workspace = true

crates/zed/src/main.rs 🔗

@@ -638,7 +638,6 @@ fn main() {
         );
 
         copilot_ui::init(&app_state, cx);
-        supermaven::init(app_state.client.clone(), cx);
         language_model::init(app_state.client.clone(), cx);
         language_models::init(app_state.user_store.clone(), app_state.client.clone(), cx);
         acp_tools::init(cx);

crates/zed/src/zed.rs 🔗

@@ -4860,7 +4860,6 @@ mod tests {
                 "settings_profile_selector",
                 "snippets",
                 "stash_picker",
-                "supermaven",
                 "svg",
                 "syntax_tree_view",
                 "tab_switcher",

crates/zed/src/zed/edit_prediction_registry.rs 🔗

@@ -12,7 +12,6 @@ use settings::{
     EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME, EditPredictionPromptFormat, SettingsStore,
 };
 use std::{cell::RefCell, rc::Rc, sync::Arc};
-use supermaven::{Supermaven, SupermavenEditPredictionDelegate};
 use ui::Window;
 
 pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
@@ -132,7 +131,6 @@ fn edit_prediction_provider_config_for_settings(cx: &App) -> Option<EditPredicti
     match provider {
         EditPredictionProvider::None => None,
         EditPredictionProvider::Copilot => Some(EditPredictionProviderConfig::Copilot),
-        EditPredictionProvider::Supermaven => Some(EditPredictionProviderConfig::Supermaven),
         EditPredictionProvider::Zed => Some(EditPredictionProviderConfig::Zed(
             EditPredictionModel::Zeta1,
         )),
@@ -204,7 +202,6 @@ fn infer_prompt_format(model: &str) -> Option<EditPredictionPromptFormat> {
 #[derive(Copy, Clone, PartialEq, Eq)]
 enum EditPredictionProviderConfig {
     Copilot,
-    Supermaven,
     Codestral,
     Zed(EditPredictionModel),
 }
@@ -213,7 +210,6 @@ impl EditPredictionProviderConfig {
     fn name(&self) -> &'static str {
         match self {
             EditPredictionProviderConfig::Copilot => "Copilot",
-            EditPredictionProviderConfig::Supermaven => "Supermaven",
             EditPredictionProviderConfig::Codestral => "Codestral",
             EditPredictionProviderConfig::Zed(model) => match model {
                 EditPredictionModel::Zeta1 => "Zeta1",
@@ -306,12 +302,6 @@ fn assign_edit_prediction_provider(
                 editor.set_edit_prediction_provider(Some(provider), window, cx);
             }
         }
-        Some(EditPredictionProviderConfig::Supermaven) => {
-            if let Some(supermaven) = Supermaven::global(cx) {
-                let provider = cx.new(|_| SupermavenEditPredictionDelegate::new(supermaven));
-                editor.set_edit_prediction_provider(Some(provider), window, cx);
-            }
-        }
         Some(EditPredictionProviderConfig::Codestral) => {
             let http_client = client.http_client();
             let provider = cx.new(|_| CodestralEditPredictionDelegate::new(http_client));

docs/src/ai/overview.md 🔗

@@ -28,7 +28,7 @@ The [Inline Assistant](./inline-assistant.md) works differently: select code or
 
 [Edit Prediction](./edit-prediction.md) provides AI code completions on every keystroke. Each keypress sends a request to the prediction provider, which returns single or multi-line suggestions you accept with `tab`.
 
-The default provider is Zeta, Zed's open-source model trained on open data. You can also use GitHub Copilot, Supermaven, or Codestral.
+The default provider is Zeta, Zed's open-source model trained on open data. You can also use GitHub Copilot, or Codestral.
 
 ## Text threads
 

docs/src/completions.md 🔗

@@ -8,7 +8,7 @@ description: Zed's code completions from language servers and edit predictions.
 Zed supports two sources for completions:
 
 1. "Code Completions" provided by Language Servers (LSPs) automatically installed by Zed or via [Zed Language Extensions](languages.md).
-2. "Edit Predictions" provided by Zed's own Zeta model or by external providers like [GitHub Copilot](#github-copilot) or [Supermaven](#supermaven).
+2. "Edit Predictions" provided by Zed's own Zeta model or by external providers like [GitHub Copilot](#github-copilot).
 
 ## Language Server Code Completions {#code-completions}
 

docs/src/reference/all-settings.md 🔗

@@ -1800,17 +1800,7 @@ While other options may be changed at a runtime and should be placed under `sett
 }
 ```
 
-3. Use Supermaven as the edit prediction provider:
-
-```json [settings]
-{
-  "edit_predictions": {
-    "provider": "supermaven"
-  }
-}
-```
-
-4. Turn off edit predictions across all providers
+3. Turn off edit predictions across all providers
 
 ```json [settings]
 {