1740 lines · 71.2 KB · Rust
RawDownload
use anyhow::Result;use client::{Client, UserStore, zed_urls};use cloud_llm_client::UsageLimit;use codestral::{self, CodestralEditPredictionDelegate};use copilot::Status;use edit_prediction::EditPredictionStore;use edit_prediction_types::EditPredictionDelegateHandle;use editor::{    Editor, MultiBufferOffset, SelectionEffects, actions::ShowEditPrediction, scroll::Autoscroll,};use feature_flags::FeatureFlagAppExt;use fs::Fs;use gpui::{    Action, Anchor, Animation, AnimationExt, App, AsyncWindowContext, Entity, FocusHandle,    Focusable, IntoElement, ParentElement, Render, Subscription, TaskExt, WeakEntity, actions, div,    ease_in_out, pulsating_between,};use indoc::indoc;use language::{    EditPredictionsMode, File, Language,    language_settings::{        AllLanguageSettings, EditPredictionProvider, LanguageSettings, all_language_settings,    },};use project::{DisableAiSettings, Project};use regex::Regex;use settings::{Settings, SettingsStore, update_settings_file};use std::{    rc::Rc,    sync::{Arc, LazyLock},    time::Duration,};use ui::{    Clickable, ContextMenu, ContextMenuEntry, DocumentationSide, IconButton, IconButtonShape,    Indicator, PopoverMenu, PopoverMenuHandle, ProgressBar, Tooltip, prelude::*,};use util::ResultExt as _; use workspace::{    HideStatusItem, StatusItemView, Toast, Workspace, create_and_open_local_file, item::ItemHandle,    notifications::NotificationId,};use zed_actions::{OpenBrowser, OpenSettingsAt}; use crate::{RatePredictions, rate_prediction_modal::PredictEditsRatePredictionsFeatureFlag}; actions!(    edit_prediction,    [        /// Toggles the edit prediction menu.        ToggleMenu    ]); const COPILOT_SETTINGS_PATH: &str = "/settings/copilot";const COPILOT_SETTINGS_URL: &str = concat!("https://github.com", "/settings/copilot");const PRIVACY_DOCS: &str = "https://zed.dev/docs/ai/privacy-and-security"; struct CopilotErrorToast; pub struct EditPredictionButton {    editor_subscription: Option<(Subscription, usize)>,    editor_enabled: Option<bool>,    editor_show_predictions: bool,    editor_focus_handle: Option<FocusHandle>,    language: Option<Arc<Language>>,    file: Option<Arc<dyn File>>,    edit_prediction_provider: Option<Arc<dyn EditPredictionDelegateHandle>>,    fs: Arc<dyn Fs>,    user_store: Entity<UserStore>,    popover_menu_handle: PopoverMenuHandle<ContextMenu>,    project: WeakEntity<Project>,} impl Render for EditPredictionButton {    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {        // Return empty div if AI is disabled        if DisableAiSettings::get_global(cx).disable_ai {            return div().hidden();        }         let language_settings = all_language_settings(None, cx);         match language_settings.edit_predictions.provider {            EditPredictionProvider::Copilot => {                let Some(copilot) = EditPredictionStore::try_global(cx)                    .and_then(|store| store.read(cx).copilot_for_project(&self.project.upgrade()?))                else {                    return div().hidden();                };                let status = copilot.read(cx).status();                 let enabled = self.editor_enabled.unwrap_or(false);                 let icon = match status {                    Status::Error(_) => IconName::CopilotError,                    Status::Authorized => {                        if enabled {                            IconName::Copilot                        } else {                            IconName::CopilotDisabled                        }                    }                    _ => IconName::CopilotInit,                };                 if let Status::Error(e) = status {                    return div().child(                        IconButton::new("copilot-error", icon)                            .icon_size(IconSize::Small)                            .tab_index(0isize)                            .aria_label("GitHub Copilot")                            .on_click(cx.listener(move |_, _, window, cx| {                                if let Some(workspace) = Workspace::for_window(window, cx) {                                    workspace.update(cx, |workspace, cx| {                                        let copilot = copilot.clone();                                        workspace.show_toast(                                            Toast::new(                                                NotificationId::unique::<CopilotErrorToast>(),                                                format!("Copilot can't be started: {}", e),                                            )                                            .on_click(                                                "Reinstall Copilot",                                                move |window, cx| {                                                    copilot_ui::reinstall_and_sign_in(                                                        copilot.clone(),                                                        window,                                                        cx,                                                    )                                                },                                            ),                                            cx,                                        );                                    });                                }                            }))                            .tooltip(|_window, cx| {                                Tooltip::for_action("GitHub Copilot", &ToggleMenu, cx)                            }),                    );                }                let this = cx.weak_entity();                let project = self.project.clone();                let file = self.file.clone();                let language = self.language.clone();                div().child(                    PopoverMenu::new("copilot")                        .on_open({                            let file = file.clone();                            let language = language;                            let project = project.clone();                            Rc::new(move |_window, cx| {                                emit_edit_prediction_menu_opened(                                    "copilot", &file, &language, &project, cx,                                );                            })                        })                        .menu(move |window, cx| {                            let current_status = EditPredictionStore::try_global(cx)                                .and_then(|store| {                                    store.read(cx).copilot_for_project(&project.upgrade()?)                                })?                                .read(cx)                                .status();                            match current_status {                                Status::Authorized => this.update(cx, |this, cx| {                                    this.build_copilot_context_menu(window, cx)                                }),                                _ => this.update(cx, |this, cx| {                                    this.build_copilot_start_menu(window, cx)                                }),                            }                            .ok()                        })                        .anchor(Anchor::BottomRight)                        .trigger_with_tooltip(                            IconButton::new("copilot-icon", icon)                                .tab_index(0isize)                                .aria_label("GitHub Copilot"),                            |_window, cx| Tooltip::for_action("GitHub Copilot", &ToggleMenu, 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();                let this = cx.weak_entity();                let file = self.file.clone();                let language = self.language.clone();                let project = self.project.clone();                 let tooltip_meta = if has_api_key {                    "Powered by Codestral"                } else {                    "Missing API key for Codestral"                };                 div().child(                    PopoverMenu::new("codestral")                        .on_open({                            let file = file.clone();                            let language = language;                            let project = project;                            Rc::new(move |_window, cx| {                                emit_edit_prediction_menu_opened(                                    "codestral",                                    &file,                                    &language,                                    &project,                                    cx,                                );                            })                        })                        .menu(move |window, cx| {                            this.update(cx, |this, cx| {                                this.build_codestral_context_menu(window, cx)                            })                            .ok()                        })                        .anchor(Anchor::BottomRight)                        .trigger_with_tooltip(                            IconButton::new("codestral-icon", IconName::AiMistral)                                .shape(IconButtonShape::Square)                                .tab_index(0isize)                                .aria_label("Edit Prediction")                                .when(!has_api_key, |this| {                                    this.indicator(Indicator::dot().color(Color::Error))                                        .indicator_border_color(Some(                                            cx.theme().colors().status_bar_background,                                        ))                                })                                .when(has_api_key && !enabled, |this| {                                    this.indicator(Indicator::dot().color(Color::Ignored))                                        .indicator_border_color(Some(                                            cx.theme().colors().status_bar_background,                                        ))                                }),                            move |_window, cx| {                                Tooltip::with_meta(                                    "Edit Prediction",                                    Some(&ToggleMenu),                                    tooltip_meta,                                    cx,                                )                            },                        )                        .with_handle(self.popover_menu_handle.clone()),                )            }            EditPredictionProvider::OpenAiCompatibleApi => {                let enabled = self.editor_enabled.unwrap_or(true);                let this = cx.weak_entity();                 div().child(                    PopoverMenu::new("openai-compatible-api")                        .menu(move |window, cx| {                            this.update(cx, |this, cx| {                                this.build_edit_prediction_context_menu(                                    EditPredictionProvider::OpenAiCompatibleApi,                                    window,                                    cx,                                )                            })                            .ok()                        })                        .anchor(Anchor::BottomRight)                        .trigger(                            IconButton::new("openai-compatible-api-icon", IconName::AiOpenAiCompat)                                .shape(IconButtonShape::Square)                                .tab_index(0isize)                                .aria_label("Edit Prediction")                                .when(!enabled, |this| {                                    this.indicator(Indicator::dot().color(Color::Ignored))                                        .indicator_border_color(Some(                                            cx.theme().colors().status_bar_background,                                        ))                                }),                        )                        .with_handle(self.popover_menu_handle.clone()),                )            }            EditPredictionProvider::Ollama => {                let enabled = self.editor_enabled.unwrap_or(true);                let this = cx.weak_entity();                 div().child(                    PopoverMenu::new("ollama")                        .menu(move |window, cx| {                            this.update(cx, |this, cx| {                                this.build_edit_prediction_context_menu(                                    EditPredictionProvider::Ollama,                                    window,                                    cx,                                )                            })                            .ok()                        })                        .anchor(Anchor::BottomRight)                        .trigger_with_tooltip(                            IconButton::new("ollama-icon", IconName::AiOllama)                                .shape(IconButtonShape::Square)                                .tab_index(0isize)                                .aria_label("Edit Prediction")                                .when(!enabled, |this| {                                    this.indicator(Indicator::dot().color(Color::Ignored))                                        .indicator_border_color(Some(                                            cx.theme().colors().status_bar_background,                                        ))                                }),                            move |_window, cx| {                                let settings = all_language_settings(None, cx);                                let tooltip_meta = match settings.edit_predictions.ollama.as_ref() {                                    Some(settings) if !settings.model.trim().is_empty() => {                                        format!("Powered by Ollama ({})", settings.model)                                    }                                    _ => {                                        "Ollama model not configured — configure a model before use"                                            .to_string()                                    }                                };                                 Tooltip::with_meta(                                    "Edit Prediction",                                    Some(&ToggleMenu),                                    tooltip_meta,                                    cx,                                )                            },                        )                        .with_handle(self.popover_menu_handle.clone()),                )            }            provider @ (EditPredictionProvider::Zed | EditPredictionProvider::Mercury) => {                let enabled = self.editor_enabled.unwrap_or(true);                let file = self.file.clone();                let language = self.language.clone();                let project = self.project.clone();                let provider_name: &'static str = match provider {                    EditPredictionProvider::Zed => "zed",                    _ => "unknown",                };                let icons = self                    .edit_prediction_provider                    .as_ref()                    .map(|p| p.icons(cx))                    .unwrap_or_else(|| {                        edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict)                    });                 let ep_icon;                let tooltip_meta;                let mut missing_token = false;                 match provider {                    EditPredictionProvider::Mercury => {                        ep_icon = if enabled { icons.base } else { icons.disabled };                        let mercury_has_error =                            edit_prediction::EditPredictionStore::try_global(cx).is_some_and(                                |ep_store| ep_store.read(cx).mercury_has_payment_required_error(),                            );                        missing_token = edit_prediction::EditPredictionStore::try_global(cx)                            .is_some_and(|ep_store| !ep_store.read(cx).has_mercury_api_token(cx));                        tooltip_meta = if missing_token {                            "Missing API key for Mercury"                        } else if mercury_has_error {                            "Mercury free tier limit reached"                        } else {                            "Powered by Mercury"                        };                    }                    _ => {                        ep_icon = if enabled { icons.base } else { icons.disabled };                        tooltip_meta = "Powered by Zeta"                    }                };                 if edit_prediction::should_show_upsell_modal(cx) {                    let tooltip_meta = if self.user_store.read(cx).current_user().is_some() {                        "Choose a Plan"                    } else {                        "Configure a Provider"                    };                     return div().child(                        IconButton::new("zed-predict-pending-button", ep_icon)                            .shape(IconButtonShape::Square)                            .tab_index(0isize)                            .aria_label("Edit Predictions")                            .indicator(Indicator::dot().color(Color::Muted))                            .indicator_border_color(Some(cx.theme().colors().status_bar_background))                            .tooltip(move |_window, cx| {                                Tooltip::with_meta("Edit Predictions", None, tooltip_meta, cx)                            })                            .on_click(cx.listener(move |_, _, window, cx| {                                telemetry::event!(                                    "Pending ToS Clicked",                                    source = "Edit Prediction Status Button"                                );                                window.dispatch_action(                                    zed_actions::OpenZedPredictOnboarding.boxed_clone(),                                    cx,                                );                            })),                    );                }                 let mut over_limit = false;                 if let Some(usage) = self                    .edit_prediction_provider                    .as_ref()                    .and_then(|provider| provider.usage(cx))                {                    over_limit = usage.over_limit()                }                 let show_editor_predictions = self.editor_show_predictions;                let user = self.user_store.read(cx).current_user();                 let mercury_has_error = matches!(provider, EditPredictionProvider::Mercury)                    && edit_prediction::EditPredictionStore::try_global(cx).is_some_and(                        |ep_store| ep_store.read(cx).mercury_has_payment_required_error(),                    );                 let indicator_color = if missing_token || mercury_has_error {                    Some(Color::Error)                } else if enabled && (!show_editor_predictions || over_limit) {                    Some(if over_limit {                        Color::Error                    } else {                        Color::Muted                    })                } else {                    None                };                 let zed_cloud_needs_sign_in =                    matches!(provider, EditPredictionProvider::Zed) && user.is_none();                let provider_unavailable =                    missing_token || mercury_has_error || zed_cloud_needs_sign_in;                 let icon_button = IconButton::new("zed-predict-pending-button", ep_icon)                    .shape(IconButtonShape::Square)                    .tab_index(0isize)                    .aria_label("Edit Prediction")                    .when_some(indicator_color, |this, color| {                        this.indicator(Indicator::dot().color(color))                            .indicator_border_color(Some(cx.theme().colors().status_bar_background))                    })                    .when(!self.popover_menu_handle.is_deployed(), |element| {                        element.tooltip(move |_window, cx| {                            let description = if !enabled {                                "Disabled For This File"                            } else if zed_cloud_needs_sign_in {                                "Sign In Or Configure a Provider"                            } else if provider_unavailable || show_editor_predictions {                                tooltip_meta                            } else {                                "Enable to Use"                            };                             Tooltip::with_meta(                                "Edit Prediction",                                Some(&ToggleMenu),                                description,                                cx,                            )                        })                    });                 let this = cx.weak_entity();                 let mut popover_menu = PopoverMenu::new("edit-prediction")                    .on_open({                        let file = file.clone();                        let language = language;                        let project = project;                        Rc::new(move |_window, cx| {                            emit_edit_prediction_menu_opened(                                provider_name,                                &file,                                &language,                                &project,                                cx,                            );                        })                    })                    .map(|popover_menu| {                        let this = this.clone();                        popover_menu.menu(move |window, cx| {                            this.update(cx, |this, cx| {                                this.build_edit_prediction_context_menu(provider, window, cx)                            })                            .ok()                        })                    })                    .anchor(Anchor::BottomRight)                    .with_handle(self.popover_menu_handle.clone()); 

Lines 1–500 of 1740.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t