diff --git a/Cargo.lock b/Cargo.lock index 9eedaab7eca068c704b7c598c159bb66678fd7a4..4a65357efe6801549e6521a79651e1d7baf6978a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3743,6 +3743,7 @@ dependencies = [ "log", "lsp", "menu", + "project", "serde_json", "settings", "ui", diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 357b6786a7043bd0c8f3c335ab033925f4b36f5a..fbe51a297cf0b806acc1ce0887339c788c50301c 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -281,16 +281,25 @@ impl GlobalCopilotAuth { cx.try_global() } - pub fn get_or_init(app_state: Arc, cx: &mut App) -> GlobalCopilotAuth { - if let Some(copilot) = cx.try_global::() { - copilot.clone() - } else { - Self::set_global( + pub fn try_get_or_init(app_state: Arc, cx: &mut App) -> Option { + let ai_enabled = !DisableAiSettings::get(None, cx).disable_ai; + + if let Some(copilot) = cx.try_global::().cloned() { + if ai_enabled { + Some(copilot) + } else { + cx.remove_global::(); + None + } + } else if ai_enabled { + Some(Self::set_global( app_state.languages.next_language_server_id(), app_state.fs.clone(), app_state.node_runtime.clone(), cx, - ) + )) + } else { + None } } } diff --git a/crates/copilot_ui/Cargo.toml b/crates/copilot_ui/Cargo.toml index fbd16c5db0fa936cf39de8b61170f1631335873c..14b9fe436791eb2ad20a85c29686c35ae9804b77 100644 --- a/crates/copilot_ui/Cargo.toml +++ b/crates/copilot_ui/Cargo.toml @@ -27,6 +27,7 @@ language.workspace = true log.workspace = true lsp.workspace = true menu.workspace = true +project.workspace = true serde_json.workspace = true settings.workspace = true ui.workspace = true diff --git a/crates/copilot_ui/src/copilot_ui.rs b/crates/copilot_ui/src/copilot_ui.rs index f318acb5d547cd5652d6ad08acfad66a5056894b..506bf1fcf00ea3c771ceb9f03dad69ca83615288 100644 --- a/crates/copilot_ui/src/copilot_ui.rs +++ b/crates/copilot_ui/src/copilot_ui.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use copilot::GlobalCopilotAuth; use gpui::AppContext; use language::language_settings::AllLanguageSettings; +use project::DisableAiSettings; use settings::SettingsStore; pub use sign_in::{ ConfigurationMode, ConfigurationView, CopilotCodeVerification, initiate_sign_in, @@ -14,13 +15,16 @@ use ui::App; use workspace::AppState; pub fn init(app_state: &Arc, cx: &mut App) { + let disable_ai = cx.read_global(|settings: &SettingsStore, _| { + settings.get::(None).disable_ai + }); let provider = cx.read_global(|settings: &SettingsStore, _| { settings .get::(None) .edit_predictions .provider }); - if provider == settings::EditPredictionProvider::Copilot { + if !disable_ai && provider == settings::EditPredictionProvider::Copilot { GlobalCopilotAuth::set_global( app_state.languages.next_language_server_id(), app_state.fs.clone(), diff --git a/crates/copilot_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs index 2629ade34852e3b3555335afb2378eb8e1f40dd5..d5224280af8418c02c9bd46685f42f7a989236f8 100644 --- a/crates/copilot_ui/src/sign_in.rs +++ b/crates/copilot_ui/src/sign_in.rs @@ -475,7 +475,7 @@ impl ConfigurationView { ) -> Self { let copilot = AppState::try_global(cx) .and_then(|state| state.upgrade()) - .map(|state| GlobalCopilotAuth::get_or_init(state, cx)); + .and_then(|state| GlobalCopilotAuth::try_get_or_init(state, cx)); Self { copilot_status: copilot.as_ref().map(|copilot| copilot.0.read(cx).status()), @@ -569,8 +569,9 @@ impl ConfigurationView { .icon_position(IconPosition::Start) .icon_size(IconSize::Small) .on_click(|_, window, cx| { - if let Some(app_state) = AppState::global(cx).upgrade() { - let copilot = GlobalCopilotAuth::get_or_init(app_state, cx); + if let Some(app_state) = AppState::global(cx).upgrade() + && let Some(copilot) = GlobalCopilotAuth::try_get_or_init(app_state, cx) + { initiate_sign_in(copilot.0, window, cx) } }) @@ -597,8 +598,9 @@ impl ConfigurationView { .icon_position(IconPosition::Start) .icon_size(IconSize::Small) .on_click(|_, window, cx| { - if let Some(app_state) = AppState::global(cx).upgrade() { - let copilot = GlobalCopilotAuth::get_or_init(app_state, cx); + if let Some(app_state) = AppState::global(cx).upgrade() + && let Some(copilot) = GlobalCopilotAuth::try_get_or_init(app_state, cx) + { reinstall_and_sign_in(copilot.0, window, cx); } }) diff --git a/crates/edit_prediction/src/edit_prediction.rs b/crates/edit_prediction/src/edit_prediction.rs index 4adfa2f5f6201b03fe522b6a43963c00ba101cfc..74c749ac193a0077f64adbe3277a691f20499ab4 100644 --- a/crates/edit_prediction/src/edit_prediction.rs +++ b/crates/edit_prediction/src/edit_prediction.rs @@ -30,11 +30,11 @@ use language::language_settings::all_language_settings; use language::{Anchor, Buffer, File, Point, TextBufferSnapshot, ToOffset, ToPoint}; use language::{BufferSnapshot, OffsetRangeExt}; use language_model::{LlmApiToken, NeedsLlmTokenRefresh, RefreshLlmTokenListener}; -use project::{Project, ProjectPath, WorktreeId}; +use project::{DisableAiSettings, Project, ProjectPath, WorktreeId}; use release_channel::AppVersion; use semver::Version; use serde::de::DeserializeOwned; -use settings::{EditPredictionProvider, update_settings_file}; +use settings::{EditPredictionProvider, Settings as _, update_settings_file}; use std::collections::{VecDeque, hash_map}; use text::Edit; use workspace::Workspace; @@ -263,7 +263,7 @@ struct ProjectState { context: Entity, license_detection_watchers: HashMap>, user_actions: VecDeque, - _subscription: gpui::Subscription, + _subscriptions: [gpui::Subscription; 2], copilot: Option>, } @@ -725,6 +725,9 @@ impl EditPredictionStore { project: &Entity, cx: &mut Context, ) -> Option> { + if DisableAiSettings::get(None, cx).disable_ai { + return None; + } let state = self.get_or_init_project(project, cx); if state.copilot.is_some() { @@ -815,7 +818,13 @@ impl EditPredictionStore { last_prediction_refresh: None, license_detection_watchers: HashMap::default(), user_actions: VecDeque::with_capacity(USER_ACTION_HISTORY_SIZE), - _subscription: cx.subscribe(&project, Self::handle_project_event), + _subscriptions: [ + cx.subscribe(&project, Self::handle_project_event), + cx.observe_release(&project, move |this, _, cx| { + this.projects.remove(&entity_id); + cx.notify(); + }), + ], copilot: None, }) } diff --git a/crates/edit_prediction_ui/src/edit_prediction_button.rs b/crates/edit_prediction_ui/src/edit_prediction_button.rs index 94e408cf67e11e30190a82e41d4a7e403ea94f42..6668df82aea5ac8d7515dbdb94a4c40ddd0c95cf 100644 --- a/crates/edit_prediction_ui/src/edit_prediction_button.rs +++ b/crates/edit_prediction_ui/src/edit_prediction_button.rs @@ -1294,10 +1294,8 @@ pub fn get_available_providers(cx: &mut App) -> Vec { } if let Some(app_state) = workspace::AppState::global(cx).upgrade() - && copilot::GlobalCopilotAuth::get_or_init(app_state, cx) - .0 - .read(cx) - .is_authenticated() + && copilot::GlobalCopilotAuth::try_get_or_init(app_state, cx) + .is_some_and(|copilot| copilot.0.read(cx).is_authenticated()) { providers.push(EditPredictionProvider::Copilot); }; diff --git a/crates/settings_ui/src/pages/edit_prediction_provider_setup.rs b/crates/settings_ui/src/pages/edit_prediction_provider_setup.rs index d5e6688deeff35cecf545b249913549964765692..3c180d46cdcdaec45417a5b60a58c26e93bd3156 100644 --- a/crates/settings_ui/src/pages/edit_prediction_provider_setup.rs +++ b/crates/settings_ui/src/pages/edit_prediction_provider_setup.rs @@ -391,8 +391,8 @@ fn render_github_copilot_provider(window: &mut Window, cx: &mut App) -> Option