diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 98107ec8cf..3da2e1de71 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -3,8 +3,11 @@ mod sign_in; use anyhow::{anyhow, Result}; use client::Client; -use futures::{future::Shared, FutureExt, TryFutureExt}; -use gpui::{actions, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task}; +use futures::{future::Shared, Future, FutureExt, TryFutureExt}; +use gpui::{ + actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, + Task, +}; use language::{point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, ToPointUtf16}; use lsp::LanguageServer; use node_runtime::NodeRuntime; @@ -17,6 +20,7 @@ use std::{ }; use util::{fs::remove_matching, http::HttpClient, paths, ResultExt}; +const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth"; actions!(copilot_auth, [SignIn, SignOut]); const COPILOT_NAMESPACE: &'static str = "copilot"; @@ -42,8 +46,18 @@ pub fn init(client: Arc, node_runtime: Arc, cx: &mut Mutabl let status = handle.read(cx).status(); cx.update_global::( move |filter, _cx| match status { - Status::Authorized => filter.filtered_namespaces.remove(COPILOT_NAMESPACE), - _ => filter.filtered_namespaces.insert(COPILOT_NAMESPACE), + Status::Disabled => { + filter.filtered_namespaces.insert(COPILOT_NAMESPACE); + filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE); + } + Status::Authorized => { + filter.filtered_namespaces.remove(COPILOT_NAMESPACE); + filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE); + } + _ => { + filter.filtered_namespaces.insert(COPILOT_NAMESPACE); + filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE); + } }, ); }) @@ -55,6 +69,7 @@ pub fn init(client: Arc, node_runtime: Arc, cx: &mut Mutabl enum CopilotServer { Downloading, Error(Arc), + Disabled, Started { server: Arc, status: SignInStatus, @@ -80,6 +95,7 @@ enum SignInStatus { pub enum Status { Downloading, Error(Arc), + Disabled, SignedOut, SigningIn { prompt: Option, @@ -122,8 +138,55 @@ impl Copilot { node_runtime: Arc, cx: &mut ModelContext, ) -> Self { - // TODO: Don't eagerly download the LSP - cx.spawn(|this, mut cx| async move { + // TODO: Make this task resilient to users thrashing the copilot setting + cx.observe_global::({ + let http = http.clone(); + let node_runtime = node_runtime.clone(); + move |this, cx| { + if cx.global::().copilot.as_bool() { + if matches!(this.server, CopilotServer::Disabled) { + cx.spawn({ + let http = http.clone(); + let node_runtime = node_runtime.clone(); + move |this, cx| { + Self::start_language_server(http, node_runtime, this, cx) + } + }) + .detach(); + } + } else { + // TODO: What else needs to be turned off here? + this.server = CopilotServer::Disabled + } + } + }) + .detach(); + + if !cx.global::().copilot.as_bool() { + return Self { + server: CopilotServer::Disabled, + }; + } + + cx.spawn({ + let http = http.clone(); + let node_runtime = node_runtime.clone(); + move |this, cx| Self::start_language_server(http, node_runtime, this, cx) + }) + .detach(); + + Self { + server: CopilotServer::Downloading, + } + } + + fn start_language_server( + http: Arc, + node_runtime: Arc, + this: ModelHandle, + mut cx: AsyncAppContext, + ) -> impl Future { + async move { let start_language_server = async { let server_path = get_copilot_lsp(http, node_runtime.clone()).await?; let node_path = node_runtime.binary_path().await?; @@ -156,11 +219,6 @@ impl Copilot { } } }) - }) - .detach(); - - Self { - server: CopilotServer::Downloading, } } @@ -324,6 +382,7 @@ impl Copilot { pub fn status(&self) -> Status { match &self.server { CopilotServer::Downloading => Status::Downloading, + CopilotServer::Disabled => Status::Disabled, CopilotServer::Error(error) => Status::Error(error.clone()), CopilotServer::Started { status, .. } => match status { SignInStatus::Authorized { .. } => Status::Authorized, @@ -358,6 +417,7 @@ impl Copilot { fn authorized_server(&self) -> Result> { match &self.server { CopilotServer::Downloading => Err(anyhow!("copilot is still downloading")), + CopilotServer::Disabled => Err(anyhow!("copilot is disabled")), CopilotServer::Error(error) => Err(anyhow!( "copilot was not started because of an error: {}", error diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 4566776a34..f56364cfa8 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -58,6 +58,29 @@ pub struct Settings { pub telemetry_overrides: TelemetrySettings, pub auto_update: bool, pub base_keymap: BaseKeymap, + pub copilot: CopilotSettings, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] +pub enum CopilotSettings { + #[default] + On, + Off, +} + +impl From for bool { + fn from(value: CopilotSettings) -> Self { + match value { + CopilotSettings::On => true, + CopilotSettings::Off => false, + } + } +} + +impl CopilotSettings { + pub fn as_bool(&self) -> bool { + >::into(*self) + } } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] @@ -375,6 +398,8 @@ pub struct SettingsFileContent { pub auto_update: Option, #[serde(default)] pub base_keymap: Option, + #[serde(default)] + pub copilot: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] @@ -452,6 +477,7 @@ impl Settings { telemetry_overrides: Default::default(), auto_update: defaults.auto_update.unwrap(), base_keymap: Default::default(), + copilot: Default::default(), } } @@ -503,6 +529,7 @@ impl Settings { merge(&mut self.autosave, data.autosave); merge(&mut self.default_dock_anchor, data.default_dock_anchor); merge(&mut self.base_keymap, data.base_keymap); + merge(&mut self.copilot, data.copilot); self.editor_overrides = data.editor; self.git_overrides = data.git.unwrap_or_default(); @@ -681,6 +708,7 @@ impl Settings { telemetry_overrides: Default::default(), auto_update: true, base_keymap: Default::default(), + copilot: Default::default(), } }