Remove Supermaven-related code from Zed (#50537)

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

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

- N/A
This commit is contained in:
Kirill Bulatov 2026-03-02 23:18:49 +01:00 committed by GitHub
parent 1f11592d93
commit 7ad524661d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 5 additions and 1390 deletions

View file

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

45
Cargo.lock generated
View file

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

View file

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

View file

@ -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());
}
}

View file

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

View file

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

View file

@ -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);
}

View file

@ -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();

View file

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

View file

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

View file

@ -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();

View file

@ -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"),

View file

@ -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"] }

View file

@ -1 +0,0 @@
../../LICENSE-GPL

View file

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

View file

@ -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"));
}
}

View file

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

View file

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

View file

@ -1 +0,0 @@
../../LICENSE-GPL

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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